Créer des listes déroulantes liées entre elles sans utiliser Ajax

Nous allons voir comment lier des listes déroulantes, <select>, entre elles sans faire appel à des requêtes serveur type Ajax, tout se passe donc côté client.

L'approche que je vous propose est une approche s'appuyant sur une pseudobase de données facile à maintenir associée à une fonction de recherche.

14 commentaires Donner une note à l'article (5)

Article lu   fois.

L'auteur

Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Il nous arrive d'avoir besoin de lier deux, voire plus, listes <select> entre elles et lorsque les données sont peu nombreuses, il est souvent pratique de les intégrer directement dans la fonction appelée sur l'événement onchange de celles-ci.

Lorsque les données deviennent plus importantes ou complexes, on a recours à une base de données côté serveur que l'on interroge via Ajax. Cette méthode requiert de pouvoir disposer d'un système de gestion de base de données et d'un langage serveur. Il est à noter que dans le cas de données volumineuses cette méthode reste optimum.

Nous allons voir comment mettre en place un système hybride situé à mi-chemin entre ces deux méthodes et fonctionnant côté client.

II. Le résultat en action

Dans l'exemple choisi, il s'agit à partir des nouvelles régions françaises de retrouver les anciennes régions qui les constituaient et par suite en sélectionnant un département afficher la préfecture de celui-ci.

Dans la mesure où un seul choix est possible, il est sélectionné automatiquement et la liaison continue, c'est le cas pour les régions n'ayant pas changé.

Même si cet exemple n'est pas forcément des plus judicieux, il permet de voir la mise en œuvre de la méthode décrite ci-dessous.

Les données sont issues de l'INSEEInstitut national de la statistique et des études économiques.

III. Structure HTML utilisée

La structure HTML utilisée pour cet exemple est la suivante.

 
Sélectionnez
<div id="liste">
  <p>
    <label>Région 2016</label>
    <select id="new_region"></select>
    <span class="nombre"></span>
  </p>
  <p>
    <label>Ancienne région</label>
    <select id="old_region"></select>
    <span class="nombre"></span>
  </p>
  <p>
    <label>Département</label>
    <select id="departement"></select>
    <span class="nombre"></span>
  </p>
  <p>
    <label>Préfecture</label>
    <input id="prefecture" readonly>
  </p>
</div>    

Les éléments <span class="nombre"> ne sont là que pour indiquer le nombre d'<option> disponibles dans les <select>.

IV. Structure des données

Nous allons utiliser une pseudobase de données construite sur base de trois tableaux (Array) dont chaque élément sera constitué par un objet (Object) au format JSON contenant les propriétés/valeurs à exploiter.

Ces trois « tables » sont définies ci-dessous.

IV-A. Table nouvelles régions

 
Sélectionnez
var tbl_region_2016 = [
  {
    "reg_2016_code" : "NR_84",              // code INSEE de la nouvelle région
    "reg_2016_nom" : "Auvergne-Rhône-Alpes" // nom de la nouvelle région
  },
  // la suite des données
];

Au jour de la publication, le nom de toutes les régions n'est pas encore définitif.

La table complète
CacherSélectionnez

IV-B. Table anciennes régions

 
Sélectionnez
var tbl_old_region = [
  {
    "reg_code" : "R_82",                    // code INSEE de la région
    "reg_nom" : "Rhône-Alpes",              // nom de la région
    "reg_2016_code" : "NR_84"               // code INSEE de la région d'attachement
  },
  // la suite des données
];
La table complète
CacherSélectionnez

IV-C. Table départements

 
Sélectionnez
var tbl_departement = [
  {
    "dep_code" : "D_38",                    // code INSEE du département
    "dep_nom" : "Isère",                    // nom du département
    "dep_prefecture" : "Grenoble",          // nom de la préfecture
    "reg_code" : "R_82"                     // code INSEE de la région d'attachement
  },
  // la suite des données
];
La table complète
CacherSélectionnez

IV-D. Relations intertables

Relations entre tables

La liaison entre la « table » tbl_old_region et la « table » tbl_region_2016 se fait grâce à la clé reg_2016_code.

La liaison entre la « table » tbl_departement et la « table » tbl_old_region se fait grâce à la clé reg_code.

V. Fonction de recherche

Une fois les « tables » définies et renseignées, il nous faut une fonction de recherche qui nous permette d'extraire d'une « table » les données répondant à un critère précis, par exemple recherche des départements appartenant à une ancienne région.

V-A. Description

Nous nommerons cette fonction getDataFromTable pour laquelle nous passerons en paramètres :

  • la condition à respecter sous forme d'une chaîne, exemple 'reg_code=R_82' ;
  • la référence à la table dans laquelle la recherche sera effectuée.

En retour cette fonction renvoie un Array contenant les objets répondant au critère, unique, passé en paramètre.

Exemple d'appel :

Rechercher dans la « table » tbl_departement tous les départements appartenant à la région "R_82".

 
Sélectionnez
var liste = getDataFromTable( 'reg_code=R_82', tbl_departement);

Dans ce cas nous aurons le retour suivant :

 
Sélectionnez
[
  {"dep_code":"D_01","reg_code":"R_82","dep_nom":"Ain","dep_prefecture":"Bourg-en-Bresse"},
  {"dep_code":"D_07","reg_code":"R_82","dep_nom":"Ardèche","dep_prefecture":"Privas"},
  {"dep_code":"D_26","reg_code":"R_82","dep_nom":"Drôme","dep_prefecture":"Valence"},
  {"dep_code":"D_38","reg_code":"R_82","dep_nom":"Isère","dep_prefecture":"Grenoble"},
  {"dep_code":"D_42","reg_code":"R_82","dep_nom":"Loire","dep_prefecture":"Saint-Étienne"},
  {"dep_code":"D_69","reg_code":"R_82","dep_nom":"Rhône","dep_prefecture":"Lyon"},
  {"dep_code":"D_73","reg_code":"R_82","dep_nom":"Savoie","dep_prefecture":"Chambéry"},
  {"dep_code":"D_74","reg_code":"R_82","dep_nom":"Haute Savoie","dep_prefecture":"Annecy"}
]

V-B. Code de la fonction

 
Sélectionnez
/**
* Fonction de récupération des données correspondant au critère de recherche
* @param   {String} condition - Chaine indiquant la condition à remplir
* @param   {Array}  table - Tableau contenant les données à extraire
* @returns {Array}  result - Tableau contenant les données extraites
*/
function getDataFromTable( condition, table) {
  // récupération de la clé et de la valeur
  var cde = condition.replace(/\s/g, '').split('='),
      key = cde[0],
      value = cde[1],
      result = [];
  
  // retour direct si *
  if (condition === '*') {
    return table.slice();
  }
  // retourne les éléments répondant à la condition
  result = table.filter( function(obj){
       return obj[key] === value;
    });
  return result;
}

VI. Fonction d'update des <select>

Maintenant que l'on a des données, il nous faut les afficher à l'écran. Dans notre cas cela consiste à mettre ces données dans les <option> d'un <select>.

Nous allons donc créer une fonction de remplissage de <select>.

VI-A. Description

Nous nommerons cette fonction updateSelect pour laquelle nous passerons en paramètres :

  • l'id du <select> devant être mis à jour ;
  • la référence à l'Array dans lequel se trouvent les données à exploiter ;
  • le champ devant être mis dans la value de l'<option> ;
  • le champ devant être mis dans le text de l'<option>.

En retour cette fonction renvoie une String contenant la valeur sélectionnée du <select>.

Exemple d'appel :

 
Sélectionnez
liste  = getDataFromTable( 'reg_2016_code=NR_84', tbl_old_region);
valeur = updateSelect( 'old_region', liste, 'reg_code', 'reg_nom');

En sortie on obtiendra le code HTML suivant :

 
Sélectionnez
<select id="old_region">
    <option>Choisir</option>
    <option value="R_83">Auvergne</option>
    <option value="R_82">Rhône-Alpes</option>
</select>

VI-B. Code de la fonction

 
Sélectionnez
/**
* Fonction d'ajout des <option> à un <select>
* @param   {String} id_select - ID du <select> à mettre à jour
* @param   {Array}  liste - Tableau contenant les données à ajouter
* @param   {String} valeur - Champ pris en compte pour la value de l'<option>
* @param   {String} texte - Champ pris en compte pour le texte affiché de l'<option>
* @returns {String} Valeur sélectionnée du <select> pour chainage
*/
function updateSelect( id_select, liste, valeur, texte){
  var oOption,
      oSelect = document.getElementById( id_select),
      i, nb = liste.length;
  // vide le select
  oSelect.options.length = 0;
  // désactive si aucune option disponible
  oSelect.disabled = nb ? false : true;
  // affiche info nombre options, facultatif
  setNombre( oSelect, nb);
  // ajoute 1st option
  if( nb){
    oSelect.add( new Option( 'Choisir', ''));
    // focus sur le select
    oSelect.focus();
  }
  // création des options d'après la liste
  for (i = 0; i < nb; i += 1) {
    // création option
    oOption = new Option( liste[i][texte], liste[i][valeur]);
    // ajout de l'option en fin
    oSelect.add( oOption);
  }
  // si une seule option on la sélectionne
  oSelect.selectedIndex = nb === 1 ? 1 : 0;
  // on retourne la valeur pour le select suivant
  return oSelect.value;
}

Le code de la fonction setNombre, facultative et utilisée dans l'exemple, est présentée ci-dessous :

 
Sélectionnez
/**
* Affichage du nombre d'<option> présentes dans le <select>
* @param {Object} obj - <select> parent
* @param {Number} nb - nombre à afficher
*/
function setNombre( obj, nb){
  var oElem = obj.parentNode.querySelector('.nombre');
  if( oElem){
    oElem.innerHTML = nb ? '(' +nb +')' :'';
  }
}

VII. Fonction de liaison des <select>

Pour finir, il nous faut créer une fonction qui va mettre à jour les différents <select> fonction de la valeur de celui dont il dépend.

VII-A. Description

Cette fonction que l'on nommera chainSelect, sera appelée sur l'événement onchange du <select> en passant en paramètre l'id du <select> ou la référence à lui-même via l'opérateur this.

Les différents cas sont gérés à travers une instruction switch où l'expression évaluée n'est autre que l'id du <select>, celui-ci doit donc avoir une id.

Après chargement du DOM il convient d'initialiser le premier <select> en passant la chaîne 'init' en paramètre :

 
Sélectionnez
// init du 1st select
chainSelect('init');

Aucune valeur n'est retournée.

VII-B. Code de la fonction

 
Sélectionnez
/**
* fonction de chainage des <select>
* @param {String|Object} ID du <select> à traiter ou le <select> lui-même
*/
function chainSelect( param){
  // affectation par défaut
  param = param || 'init';
  var liste,
      id     = param.id || param,
      valeur = param.value || '';
      
  // test à faire pour récupération de la value
  if( typeof id === 'string'){
     param = document.getElementById( id);
     valeur = param ? param.value : '';
  }

  switch (id){
    case 'init':
      // récup. des données
      liste = getDataFromTable( '*', tbl_region_2016);
      // mise à jour du select
      valeur = updateSelect( 'new_region', liste, 'reg_2016_code', 'reg_2016_nom');
      // chainage sur le select lié
      chainSelect('new_region');
      break;
    case 'new_region':
      // récup. des données
      liste = getDataFromTable( 'reg_2016_code=' +valeur, tbl_old_region);
      // mise à jour du select
      valeur = updateSelect( 'old_region', liste, 'reg_code', 'reg_nom');
      // chainage sur le select lié
      chainSelect('old_region');
      break;
    case 'old_region':
      // récup. des données
      liste = getDataFromTable( 'reg_code=' +valeur, tbl_departement);
      // mise à jour du select
      valeur= updateSelect( 'departement', liste, 'dep_prefecture', 'dep_nom');
      // chainage sur le select lié
      chainSelect('departement');
      break;
    case 'departement':
      // affichage final
      document.getElementById('prefecture').value = valeur;
      break;
  }
}

VIII. Initialisation

Il ne reste plus qu'à initialiser les <select> une fois le DOM chargé.

 
Sélectionnez
// Initialisation après chargement du DOM
document.addEventListener("DOMContentLoaded", function() {
  var oSelects = document.querySelectorAll('#liste select'),
      i, nb = oSelects.length;
  // affectation de la fonction sur le onchange
  for( i = 0; i < nb; i += 1) {
    oSelects[i].onchange = function() {
        chainSelect(this);
      };
  }
  // init du 1st select
  if( nb){
    chainSelect('init');
  }
});

IX. Conclusion

Pour peu que les données soient bien structurées, ce qui devrait toujours être le cas, cette méthode simple à mettre en place, permet de lier deux, trois ou plus, listes entre elles.

Cette méthode ne convient néanmoins que pour des requêtes simples et une quantité de données raisonnable.

X. Remerciements

Je tiens à remercier Claude Leloup pour sa relecture orthographique ainsi que SylvainPV et vermine pour leur relecture technique.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2016 NoSmoking. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.