[JS]Listes déroulantes liées sans Ajax
Dans cette page nous allons voir comment réaliser des listes déroulantes, <select>
, liées entre elles et sans faire appel à des requêtes serveur, tout ce 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.
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.
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'INSEE.
Structure HTML utilisée
La structure HTML utilisée pour cet exemple est la suivante.
<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>
.
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.
Table nouvelles régions
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
];
Table anciennes régions
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
];
Table départements
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
];
Relations intertables
La liaison entre la « table » tbl_region_2016
et la « table » tbl_old_region
se fait grâce à la clé reg_2016_code
.
La liaison entre la « table » tbl_old_region
et la « table » tbl_departement
se fait grâce à la clé reg_code
.
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.
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"
.
var liste = getDataFromTable( 'reg_code=R_82', tbl_departement);
Dans ce cas nous aurons le retour suivant :
[
{"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"}
]
Code de la fonction
/**
* 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;
}
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>
.
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 renverra une String
contenant la valeur sélectionnée du <select>
.
Exemple d'appel :
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 :
<select id="old_region">
<option>Choisir</option>
<option value="R_83">Auvergne</option>
<option value="R_82">Rhône-Alpes</option>
</select>
Code de la fonction
/**
* 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 :
/**
* 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 +')' :'';
}
}
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.
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 :
// init du 1st select
chainSelect('init');
Aucune valeur n'est retournée.
Code de la fonction
/**
* 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;
}
}
Initialisation
Il ne reste plus qu'à initialiser les <select>
une fois le DOM chargé.
// 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');
}
});
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.