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.
<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▲
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.
IV-B. 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
];
IV-C. 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
];
IV-D. Relations intertables▲
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".
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"
}
]
V-B. 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;
}
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 :
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 :
VI-B. 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 :
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 :
// init du 1st select
chainSelect
(
'init'
);
Aucune valeur n'est retournée.
VII-B. 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;
}
}
VIII. 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'
);
}
}
);
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.