Bon... finalement c'est devenu un walkthrough + tutoriel javascript
S'pas grave, je le poste quand même, mais je vous préviens c'est long
Dans le cas précis, il faut tout d'abord une bonne base de travail (HTML).
D'après ce que je vois, tu as deux tables de DB "REGIONS" et "DEPARTEMENTS".
Je vais considérer que les PK sont les noms des régions et les noms des départements (ils sont uniques, donc ils rempliront très bien le rôle), et que les départements ont le nom de la région qui les contient en FK.
Donc avec "SELECT nom FROM regions" et "SELECT nom, region FROM departements" on récupère la liste des régions et la liste des départements.
Ensuite, il faut construire les dropdowns: un avec l'id "regions" pour les régions et un avec l'id "departements" pour les départements, avec les noms respectifs en value.
Ensuite il faut pouvoir associer l'un à l'autre. Ici j'utiliserais une méthode qui peut être décriée mais qui est propre, simple, rapide, logique et sémantique (hé oui, toussa): étendre mes balises HTML avec un attribut non standard.
À chaque <option> du select#departements on va donner un attribut "region" avec comme valeur le nom de la région associée.
Donc par exemple pour la Corrèze on va passer de
<option value="(19) Corrèze">(19) Corrèze</option> |
à
<option value="(19) Corrèze" region="Limousin">(19) Corrèze</option> |
Il y a sûrement des gens qui vont gueuler mais on s'en fout, et le validateur va gueuler mais on sait pourquoi et on peut l'ignorer.
Dans les autres solutions qui auraient pu jouer, la plus simple est un hack bien infâme en collant le nom de la région dans l'attribut "class" des <option> avec un préfixe spécifique, c'est crade et c'est lourd à gérer (il faut parser la chaîne de caractères contenue dans "class" et la manipuler pour en extraire nos infos) donc niet
À ce stade, si on crée un fichier HTML basique avec tout ça on va avoir un truc du style
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>blank</title>
</head>
<body>
<form action="machin" method="GET">
<fieldset>
<legend>Sélectionner un département</legend>
<select id="regions" name="regions">
<option value=""></option>
<option value="Alsace">Alsace</option>
<option value="Aquitaine">Aquitaine</option>
<option value="Auvergne">Auvergne</option>
<option value="Basse-Normandie">Basse-Normandie</option>
<option value="Bourgogne">Bourgogne</option>
<option value="Bretagne">Bretagne</option>
<option value="Centre">Centre</option>
<option value="Champagne-Ardenne">Champagne-Ardenne</option>
<option value="Corse">Corse</option>
<option value="Départements d'Outre-Mer">Départements d'Outre-Mer</option>
<option value="Franche-Comté">Franche-Comté</option>
<option value="Haute-Normandie">Haute-Normandie</option>
<option value="Ile-de-France">Ile-de-France</option>
<option value="Languedoc-Roussillon">Languedoc-Roussillon</option>
<option value="Limousin">Limousin</option>
<option value="Lorraine">Lorraine</option>
<option value="Midi-Pyrénées">Midi-Pyrénées</option>
<option value="Nord-Pas-de-Calais">Nord-Pas-de-Calais</option>
<option value="Pays de la Loire">Pays de la Loire</option>
<option value="Picardie">Picardie</option>
<option value="Poitou-Charentes">Poitou-Charentes</option>
<option value="Provence-Alpes-Côte-d'Azur">Provence-Alpes-Côte-d'Azur</option>
<option value="Rhône-Alpes">Rhône-Alpes</option>
<option value="Territoires d'Outre-Mer">Territoires d'Outre-Mer</option>
</select>
<select id="departements" name="departements">
<option value="(01) Ain" region="Rhône-Alpes">(01) Ain</option>
<option value="(02) Aisne" region="Picardie">(02) Aisne</option>
<option value="(03) Allier" region="Auvergne">(03) Allier</option>
<option value="(04) Alpes-de-Haute-Provence" region="Provence-Alpes-Côte-d'Azur">(04) Alpes-de-Haute-Provence</option>
<option value="(05) Hautes-Alpes" region="Provence-Alpes-Côte-d'Azur">(05) Hautes-Alpes</option>
<option value="(06) Alpes-Maritimes" region="Provence-Alpes-Côte-d'Azur">(06) Alpes-Maritimes</option>
<option value="(07) Ardèche" region="Rhône-Alpes">(07) Ardèche</option>
<option value="(08) Ardennes" region="Champagne-Ardenne">(08) Ardennes</option>
<option value="(09) Ariège" region="Midi-Pyrénées">(09) Ariège</option>
<option value="(10) Aube" region="Champagne-Ardenne">(10) Aube</option>
<option value="(11) Aude" region="Languedoc-Roussillon">(11) Aude</option>
<option value="(12) Aveyron" region="Midi-Pyrénées">(12) Aveyron</option>
<option value="(13) Bouches-du-Rhône" region="Provence-Alpes-Côte-d'Azur">(13) Bouches-du-Rhône</option>
<option value="(14) Calvados" region="Basse-Normandie">(14) Calvados</option>
<option value="(15) Cantal" region="Auvergne">(15) Cantal</option>
<option value="(16) Charente" region="Poitou-Charentes">(16) Charente</option>
<option value="(17) Charente-Maritime" region="Poitou-Charentes">(17) Charente-Maritime</option>
<option value="(18) Cher" region="Centre">(18) Cher</option>
<option value="(19) Corrèze" region="Limousin">(19) Corrèze</option>
<option value="(21) Côte-d'Or" region="Bourgogne">(21) Côte-d'Or</option>
<option value="(22) Côtes-d'Armor" region="Bretagne">(22) Côtes-d'Armor</option>
<option value="(23) Creuse" region="Limousin">(23) Creuse</option>
<option value="(24) Dordogne" region="Aquitaine">(24) Dordogne</option>
<option value="(25) Doubs" region="Franche-Comté">(25) Doubs</option>
<option value="(26) Drôme" region="Rhône-Alpes">(26) Drôme</option>
<option value="(27) Eure" region="Haute-Normandie">(27) Eure</option>
<option value="(28) Eure-et-Loir" region="Centre">(28) Eure-et-Loir</option>
<option value="(29) Finistère" region="Bretagne">(29) Finistère</option>
<option value="(2A) Corse-du-Sud" region="Corse">(2A) Corse-du-Sud</option>
<option value="(2B) Haute-Corse" region="Corse">(2B) Haute-Corse</option>
<option value="(30) Gard" region="Languedoc-Roussillon">(30) Gard</option>
<option value="(31) Haute-Garonne" region="Midi-Pyrénées">(31) Haute-Garonne</option>
<option value="(32) Gers" region="Midi-Pyrénées">(32) Gers</option>
<option value="(33) Gironde" region="Aquitaine">(33) Gironde</option>
<option value="(34) Hérault" region="Languedoc-Roussillon">(34) Hérault</option>
<option value="(35) Ille-et-Vilaine" region="Bretagne">(35) Ille-et-Vilaine</option>
<option value="(36) Indre" region="Centre">(36) Indre</option>
<option value="(37) Indre-et-Loire" region="Centre">(37) Indre-et-Loire</option>
<option value="(38) Isère" region="Rhône-Alpes">(38) Isère</option>
<option value="(39) Jura" region="Franche-Comté">(39) Jura</option>
<option value="(40) Landes" region="Aquitaine">(40) Landes</option>
<option value="(41) Loir-et-Cher" region="Centre">(41) Loir-et-Cher</option>
<option value="(42) Loire" region="Rhône-Alpes">(42) Loire</option>
<option value="(43) Haute-Loire" region="Auvergne">(43) Haute-Loire</option>
<option value="(44) Loire-Atlantique" region="Pays de la Loire">(44) Loire-Atlantique</option>
<option value="(45) Loiret" region="Centre">(45) Loiret</option>
<option value="(46) Lot" region="Midi-Pyrénées">(46) Lot</option>
<option value="(47) Lot-et-Garonne" region="Aquitaine">(47) Lot-et-Garonne</option>
<option value="(48) Lozère" region="Languedoc-Roussillon">(48) Lozère</option>
<option value="(49) Maine-et-Loire" region="Pays de la Loire">(49) Maine-et-Loire</option>
<option value="(50) Manche" region="Basse-Normandie">(50) Manche</option>
<option value="(51) Marne" region="Champagne-Ardenne">(51) Marne</option>
<option value="(52) Haute-Marne" region="Champagne-Ardenne">(52) Haute-Marne</option>
<option value="(53) Mayenne" region="Pays de la Loire">(53) Mayenne</option>
<option value="(54) Meurthe-et-Moselle" region="Lorraine">(54) Meurthe-et-Moselle</option>
<option value="(55) Meuse" region="Lorraine">(55) Meuse</option>
<option value="(56) Morbihan" region="Bretagne">(56) Morbihan</option>
<option value="(57) Moselle" region="Lorraine">(57) Moselle</option>
<option value="(58) Nièvre" region="Bourgogne">(58) Nièvre</option>
<option value="(59) Nord" region="Nord-Pas-de-Calais">(59) Nord</option>
<option value="(60) Oise" region="Picardie">(60) Oise</option>
<option value="(61) Orne" region="Basse-Normandie">(61) Orne</option>
<option value="(62) Pas-de-Calais" region="Nord-Pas-de-Calais">(62) Pas-de-Calais</option>
<option value="(63) Puy-de-Dôme" region="Auvergne">(63) Puy-de-Dôme</option>
<option value="(64) Pyrénées-Atlantiques" region="Aquitaine">(64) Pyrénées-Atlantiques</option>
<option value="(65) Hautes-Pyrénées" region="Midi-Pyrénées">(65) Hautes-Pyrénées</option>
<option value="(66) Pyrénées-Orientales" region="Languedoc-Roussillon">(66) Pyrénées-Orientales</option>
<option value="(67) Bas-Rhin" region="Alsace">(67) Bas-Rhin</option>
<option value="(68) Haut-Rhin" region="Alsace">(68) Haut-Rhin</option>
<option value="(69) Rhône" region="Rhône-Alpes">(69) Rhône</option>
<option value="(70) Haute-Saône" region="Franche-Comté">(70) Haute-Saône</option>
<option value="(71) Saône-et-Loire" region="Bourgogne">(71) Saône-et-Loire</option>
<option value="(72) Sarthe" region="Pays de la Loire">(72) Sarthe</option>
<option value="(73) Savoie" region="Rhône-Alpes">(73) Savoie</option>
<option value="(74) Haute-Savoie" region="Rhône-Alpes">(74) Haute-Savoie</option>
<option value="(75) Paris" region="Ile-de-France">(75) Paris</option>
<option value="(76) Seine-Maritime" region="Haute-Normandie">(76) Seine-Maritime</option>
<option value="(77) Seine-et-Marne" region="Ile-de-France">(77) Seine-et-Marne</option>
<option value="(78) Yvelines" region="Ile-de-France">(78) Yvelines</option>
<option value="(79) Deux-Sèvres" region="Poitou-Charentes">(79) Deux-Sèvres</option>
<option value="(80) Somme" region="Picardie">(80) Somme</option>
<option value="(81) Tarn" region="Midi-Pyrénées">(81) Tarn</option>
<option value="(82) Tarn-et-Garonne" region="Midi-Pyrénées">(82) Tarn-et-Garonne</option>
<option value="(83) Var" region="Provence-Alpes-Côte-d'Azur">(83) Var</option>
<option value="(84) Vaucluse" region="Provence-Alpes-Côte-d'Azur">(84) Vaucluse</option>
<option value="(85) Vendée" region="Pays de la Loire">(85) Vendée</option>
<option value="(86) Vienne" region="Poitou-Charentes">(86) Vienne</option>
<option value="(87) Haute-Vienne" region="Limousin">(87) Haute-Vienne</option>
<option value="(88) Vosges" region="Lorraine">(88) Vosges</option>
<option value="(89) Yonne" region="Bourgogne">(89) Yonne</option>
<option value="(90) Territoire de Belfort" region="Franche-Comté">(90) Territoire de Belfort</option>
<option value="(91) Essonne" region="Ile-de-France">(91) Essonne</option>
<option value="(92) Hauts-de-Seine" region="Ile-de-France">(92) Hauts-de-Seine</option>
<option value="(93) Seine-Saint-Denis" region="Ile-de-France">(93) Seine-Saint-Denis</option>
<option value="(94) Val-de-Marne" region="Ile-de-France">(94) Val-de-Marne</option>
<option value="(95) Val-d'Oise" region="Ile-de-France">(95) Val-d'Oise</option>
<option value="(971) Guadeloupe" region="Départements d'Outre-Mer">(971) Guadeloupe</option>
<option value="(972) Martinique" region="Départements d'Outre-Mer">(972) Martinique</option>
<option value="(973) Guyane" region="Départements d'Outre-Mer">(973) Guyane</option>
<option value="(974) La Réunion" region="Départements d'Outre-Mer">(974) La Réunion</option>
<option value="(975) Saint-Pierre-et-Miquelon" region="Départements d'Outre-Mer">(975) Saint-Pierre-et-Miquelon</option>
<option value="(976) Mayotte" region="Départements d'Outre-Mer">(976) Mayotte</option>
<option value="(984) Terres Australes et Antarctiques" region="Territoires d'Outre-Mer">(984) Terres Australes et Antarctiques</option>
<option value="(986) Wallis et Futuna" region="Territoires d'Outre-Mer">(986) Wallis et Futuna</option>
<option value="(987) Polynésie Française" region="Territoires d'Outre-Mer">(987) Polynésie Française</option>
<option value="(988) Nouvelle-Calédonie" region="Territoires d'Outre-Mer">(988) Nouvelle-Calédonie</option>
</select>
<input type="submit" name="submit" value="Submit"/>
</fieldset>
</form>
</body>
</html> |
C'est à dire un formulaire complet et fonctionnel (on peut sélectionner un département et envoyer la donnée) (note: pour bien faire il faudrait mettre en place des labels).
Maintenant on peut se lancer dans le JS.
Créons un fichier .js (genre test.js) et lions le à notre HTML en plaçant dans le <head>, sous le <title></title>
<script type="text/javascript" src="test.js"></script> |
Ensuite dans ce fichier on colle addEvent() qui permet de gérer proprement les évènements
function addEvent(o, e, f){
if (o.addEventListener){ o.addEventListener(e, f, false);return true;} else if (o.attachEvent){ return o.attachEvent("on"+e, f);}
} |
La première chose à faire sera alors de binder les <selects>. Donc au chargement de la page il faudra binder les selects...
donc
addEvent(window, "load", bindSelects); |
Description:
on attribue la fonction "bindSelects" à l'évènement "load" de l'objet "window".
Donc dès que la page sera chargée ("load" correspond au gestionnaire d'évènements "onload" ) on va exécuter "bindSelects".
bindSelects va nous servir à associer une fonction au changement de valeur de notre sélecteur de régions
function bindSelects() {
addEvent(document.getElementById("regions" ), "change", regionChange);
} |
Ici, on associe la fonction "regionChange" à l'évènement "change" (gestionnaire "onchange" ) de l'objet "document.getElementById('regions' )", donc notre <select> d'id "regions".
Le but de regionsChange va être de filtrer les options du select#departements en fonction de la valeur de région.
Le plus simple, c'est de détruire toutes les options de select#departements qui ne correspondent pas à la région sélectionnée
function regionChange() {
// On récupère select#regions"
var regions = document.getElementById("regions" );
// On récupère la région sélectionnée
var region = regions.options[regions.selectedIndex].value;
// On accède à select#departements
var depts = document.getElementById("departements" );
// Puis pour chaque <option> on vérifie si l'attribut "region" a la bonne valeur
for(var i=depts.options.length-1; i>=0; --i)
if(depts.options[i].getAttribute("region" ) != region) {
// Si la région associée à l'option ne correspond pas à la région sélectionnée, on détruit l'option
depts.removeChild(depts.options[i]);
}
} |
*****
Petite parenthèse sur la construction "for(var i=depts.options.length-1; i>=0; --i)".
Pourquoi ne pas utiliser "for(var=0; i<depts.options.length; ++i)"?
Deux raisons:
- La boucle inversée est plus rapide que la boucle normale dans tous les navigateurs, la vitesse varie de 20% pour Internet Explorer ou Opera à 120% pour Firefox (voir Javascript Optimisation pour un test, vers le milieu de la page "Reverse Loop Counting" )
- Beaucoup plus important que l'optimisation (pas gigantesque dans notre cas), notre liste est mise à jour en direct, donc si on supprime un élément on va décaler tous ceux qui sont placés derrière... donc quand on va avancer on va en fait avancer de 2 unités et non une, puisque l'ancient prochain élément a pris la place de l'élément courant, qui vient d'être détruit.
Schéma de ce 2e point pour clarifier:
"<" représente la position actuelle du curseur, qui se déplace de haut en bas (avec la liste "accrochée" en haut)
Si on détruit l'élément courant, la liste devient
Puis on incrémente notre curseur...
et on a sauté l'élément "2" sans le traiter
Alors que dans l'autre sens
"<" est toujours notre curseur, mais qui se déplace de bas en haut cette fois (toujours avec la liste accrochée en haut)
On supprime l'élément courant
Puis on décrémente le curseur
et on est sûr de n'avoir sauté aucun élément
*****
Bien, testons donc maintenant notre page.
On ouvre le fichier HTML dans un navigateur, on sélectionne une région, la Bourgogne par exemple, et la liste des départements se réduit bien aux départements de Bourgogne.
Sélectionons maintenant une autre région, la Haute Normandie par exemple...
Et là c'est le drame, plus un seul département
Parce que le tri s'effectue sur notre liste de département déjà nettoyée, donc se réduit aux départements étant à la fois en bourgogne et en haute-normandie... autant dire peau de balle.
Il faut donc refaire nos fonctions de tri afin de pouvoir sauvegarder nos départements.
Commençons par créer une liste des départements, juste après avoir défini la fonction "addEvent" nous allons écrire
var deptsValues = new Array(); |
Nous créons un tableau vide qui contiendra les valeurs (noms) des départements.
Ensuite il nous faut sauvegarder ces valeurs. Le meilleur endroit pour le faire est très logiquement "bindSelects"
function bindSelects() {
// On récupère le select#departements
var depts = document.getElementById('departements');
// Et pour chaque département, on crée un tableau de deux cases contenant le nom du département et celui de la région associée, qu'on met dans le tableau de départements
for(var i=0; i<depts.options.length; ++i)
deptsValues.push(new Array(depts.options[i].value, depts.options[i].getAttribute("region" )));
addEvent(document.getElementById("regions" ), "change", regionChange);
} |
On voit que nous avons finement joué en prévoyant que, pour faire notre tri, nous aurons besoin du nom du département mais aussi du nom de sa région.
Attaquons nous maintenant à "regionChange". Commençons par enlever la boucle de tri, inutile
function regionChange() {
var regions = document.getElementById("regions" );
var region = regions.options[regions.selectedIndex].value;
var depts = document.getElementById("departements" );
} |
Première chose à faire: si il y a des <options> dans select#departements, il nous faut les enlever
function regionChange() {
var regions = document.getElementById("regions" );
var region = regions.options[regions.selectedIndex].value;
var depts = document.getElementById("departements" );
// Tant que select#departements contient des "option" (donc des enfants), on les dégage
while(depts.childNodes.length > 0)
depts.removeChild(depts.childNodes[0]);
} |
Maintenant il nous faut remettre en place les <option> correspondant à la région sélectionnée.
Pour ce, il nous suffit de boucler sur le tableau de départements, de tester si la 2e valeur de chaque couple correspond à la région sélectionnée et si oui de créer une <option> correspondant au département:
function regionChange(){
var regions = document.getElementById('regions');
var region = regions.options[regions.selectedIndex].value;
var depts = document.getElementById('departements');
while(depts.childNodes.length > 0)
depts.removeChild(depts.childNodes[0]);
// On boucle sur les valeurs de départements
for(var i=0; i<deptsValues.length; ++i)
if(deptsValues[i][1] == region) {
// Et si la région du département correspond à la région sélectionnée
// On crée une option
var opt = document.createElement('OPTION');
// À laquelle on donne la valeur et l'identifiant correspondant au département
opt.value = deptsValues[i][0];
opt.appendChild(document.createTextNode(deptsValues[i][0]));
// Et on ajoute cette option à select#departements
depts.appendChild(opt);
}
} |
Voilà, c'est terminé et ça fonctionne, nous avons créé un script non obtrusif: il améliore simplement le HTML sans le rendre inutilisable quand le JS n'est pas disponible, et nous l'avons fait en séparant totalement le code JS du code HTML.
Il nous reste une seule chose à faire: dans notre script nous avons utilisé les fonctions DOM "getElementById", "createElement" et "createTextNode".
Certains vieux navigateurs (netscape 4 et IE4) n'implémentent pas ces fonctions, nous allons donc leur dire de ne pas exécuter le script...
Non en fait, nous ne pouvons pas savoir quels navigateurs exactement n'implémentent pas les fonctionalités nécessaires, et si de nouveaux navigateurs apparaissent ils risquent de ne pas être gérés.
Nous allons donc détecter si les navigateurs disposent des fonctionalités dont on a besoin et ne les laisser exécuter le script que si oui.
Le Javascript nous offre pour celà un moyen très simple:
n'est vrai que si "objet" existe (et est "vrai" )
Donc
Ne va exécuter "code" que si "objet" n'existe pas
Nous allons donc ajouter ce petit test au début de bindSelects:
function bindSelects() {
if(!document.createElement || !document.createTextNode || !document.getElementById)
return;
var depts = document.getElementById('departements');
for(var i=0; i<depts.options.length; ++i)
deptsValues.push(new Array(depts.options[i].value, depts.options[i].getAttribute("region" )));
addEvent(document.getElementById("regions" ), "change", regionChange);
} |
Si "document.createElement" n'existe pas OU "document.createTextNode" n'existe pas OU document.getElementById n'existe pas,
On quitte immédiatement la fonction sans l'exécuter.
La fonction étant l'unique point d'entrée du script, c'est équivalent à ne pas exécuter le script.
Améliorations possibles:
Le script n'est pas parfait. Son plus gros problème, c'est sa redondance: dans l'absolu, le select#regions ne sert strictement à rien quand le javascript est désactivé (puisqu'il n'a aucun effet sur les départements et qu'un département contient ses informations de région).
Il faudrait donc créer ce <select> dynamiquement dans la fonction "bindSelects" au chargement de la page
En conclusion, la page finale a été testée et confirmée fonctionnelle sous Firefox 1.0.6, IE6SP1 et Opera 7.54 et 8.02.
Message édité par masklinn le 14-08-2005 à 18:40:13
---------------
Stick a parrot in a Call of Duty lobby, and you're gonna get a racist parrot. — Cody