Comment remplacer les gestionnaires d’événements présents dans une page web en redéfinissant le comportement onclick, onmouseover… des éléments XHTML ? Tout simplement en utilisant la magie du DOM et de Javascript. Généralement, lorsqu’on veut appliquer une fonction sur un élément XHTML (ouvrir un popup lorsqu’on clique sur une image, par exemple), on utilise une fonction popup() déclenchée par un événement onclick placé dans la balise comme ici : <img src= »/img1.gif » onclick= »popup() » />. C’est parait simple, efficace et rapide… Or, ça peut vite devenir compliqué, inefficace et lent…
Comment ça, il est pas frais mon gestionnaire ?
En effet, il faut ajouter systématiquement le gestionnaire d’événement pour chaque élément concerné. Ce qui rend le code moins lisible. Ce qui augmente aussi les risques d’erreurs de saisie ou de copié-collé qui prennent toujours plus de temps qu’on l’imagine à résoudre.
Mais surtout, cette façon de procéder n’entre pas dans la logique d’une saine séparation entre le contenu (texte, image, multimédia), encadré rigoureusement par nos balises XHTML, gardiennes du temple sémantique ! Balises, que l’on prendra soin de styler avec CSS, de préférence dans un fichier séparé.
Alors, si vous pensiez mettre des événements onclick, onmouseover ou onchange en veux-tu en voilà, vous avez tout faux… En ce qui me concerne, c’est surtout depuis le jour où Thanh m’a regardé d’un drôle d’air quand je lui ai montré mon… gestionnaire onclick, que je me suis remis en question 😉
Redéfinissez-moi tout ça
A la place de ce gestionnaire d’événement chronophage et pas très Web 2.0, je vous propose un exemple qui nous permettra de redéfinir le comportement des balises img se trouvant à l’intérieur d’une div images :
function defineEventInContainer(container,element) { var strContainer = document.getElementById(container); var arrElements = strContainer.getElementsByTagName(element); var intElements = arrElements.length; for (var i=0; i<intElements; i++) { arrElements[i].onclick = toto; } } function toto() { alert("Fonction déclenchée sur les balises img contenues dans le paragraphe images"); }
<body onload="defineEventInContainer('images','img');">
Cette fonction attend deux arguments : le premier contient un id qui servira à délimiter la portée de notre fonction (le container ou bloc parent), et le deuxième contient la balise dont on veut modifier le comportement.
Ensuite, on affecte le premier argument à la variable strContainer. La ligne suivante parcours la portion du document à la recherche des éléments spécifiés, et empile le résultat dans un tableau que l’on affecte à la variable arrElements. A ce stade, nous avons un array comportant tous les éléments img contenus dans la div images.
Nous comptons ensuite le nombre d’items contenus dans arrElements, ce qui nous permet de faire une boucle pour parcourir les images une à une tout en leur affectant la fonction toto().
N’oublions pas que cette fonction doit être présente dès le chargement de la page : un coup de onload dans le body avec les arguments qui vont bien, and the cat’s in the bag!
Je suis d’accord avec vous, c’était bien la peine de mettre autant de neurones sur l’affaire pour finir avec un pauvre alert(). Mais je suis sûr que vous trouverez mieux à mettre sous la dent de notre bon toto().
Appliquer une fonction selon la classe CSS
C’est génial !
me suis-je écrié lorsque tout fut mis en place. Cette fonction serait, à n’en pas douter, d’une redoutable efficacité pour appliquer n’importe quelle fonction Javascript à n’importe quel élément.
J’allais me mettre au clavier pour vous annoncer la bonne nouvelle, quand je me suis dit : C’est bien beau tout ça, mais ne serait-ce pas mieux de pouvoir appliquer une fonction selon la classe, de manière à rendre cette fonction encore plus sympathique ?
C’est ainsi que bien plus tard, je me suis retrouvé sur le site de Robert Nyman en train de décortiquer la fonction getElementsByClassName() pour l’intégrer à ma fonction defineEventInContainer() ! Ce qui donne, si nous voulons récupérer tous les liens avec la classe info-liens contenus dans le document :
function defineEventByClass() { var arrReturnElements = getElementsByClassName(document, "a", "info-liens"); var intReturnElements = arrReturnElements.length; for (var i=0; i<intReturnElements; i++) { arrReturnElements[i].onclick = toto1; } }
Le seul changement apparent est le remplacement de document.getElementById(…) par getElementsByClassName(…). J’ai écrit en apparence, car ce getElementsByClassName ne fait pas partie du DOM. C’est là tout le génie de Robert Nyman et Jonathan Snook d’avoir écrit une fonction que l’on peut utiliser de façon transparente. Sous le capot on peut lire :
function getElementsByClassName(oElm, strTagName, oClassNames) {
var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
var arrReturnElements = new Array();
var arrRegExpClassNames = new Array();
if(typeof oClassNames == "object") {
for(var i=0; i<oClassNames.length; i++) {
arrRegExpClassNames.push(new RegExp("(^|\s)" + oClassNames[i].replace(/-/g, "\-") + "(\s|$)"));
}
}
else {
arrRegExpClassNames.push(new RegExp("(^|\s)" + oClassNames.replace(/-/g, "\-") + "(\s|$)"));
}
var oElement;
var bMatchesAll;
for(var j=0; j<arrElements.length; j++) {
oElement = arrElements[j];
bMatchesAll = true;
for(var k=0; k<arrRegExpClassNames.length; k++) {
if(!arrRegExpClassNames[k].test(oElement.className)) {
bMatchesAll = false;
break;
}
}
if(bMatchesAll) {
arrReturnElements.push(oElement);
}
}
return (arrReturnElements)
}
// --- // Array support for the push method in IE 5
if(typeof Array.prototype.push != "function") {
Array.prototype.push = ArrayPush;
function ArrayPush(value) {
this[this.length] = value;
}
}
Les deux cerises sur le gâteau sont la possibilité d’utiliser des noms de classe avec un tiret entre deux mots, ainsi que le support des classes multiples appliquées à un élément. Ainsi, pour récupérer tous les éléments possédant les classes chaussettes, roses, et help au sein de la balise dont l’id est intro, on utilisera la fonction defineEventByIdAndClasses() :
function defineEventByIdAndClasses() { var arrReturnElements = getElementsByClassName(document.getElementById("intro"), "*", ["chaussettes", "roses", "help"]); var intReturnElements = arrReturnElements.length; for (var i=0; i<intReturnElements; i++) { arrReturnElements[i].onclick = toto3; } }
Le changement affecte la même ligne : nous plaçons dans la variable arrReturnElements, le résultat de la fonction getElementsByClassName(), avec les arguments adéquats. Notez l’utilisation du sélecteur universel *, le joker pour spécifier n’importe quel élément XHTML.
Pour cibler les élément abbr avec la classe info, le résultat de la fonction getElementsByClassName(document.getElementById(« intro »), « abbr », « info ») sera affecté à la variable arrReturnElements.
A vous de jouer maintenant
Voilà, c’est presque terminé. Il ne manque qu’un petit exemple fonctionnel histoire de constater que tout marche comme prévu et de faire un peu de pub à ma page loupanthère 😉
Linkographie
- The Ultimate getElementsByClassName
- http://www.robertnyman.com/…
- http://www.snook.ca/jonathan
- La fonction getElementsByClassName ultime…
- A propos du DOM
- http://openweb.eu.org/dom/
- Un bon point de départ pour commencer 😉
- http://fr.wikipedia.org/wiki/Document_Object_Model
- wikipédia, toujours de bon conseil
- http://xmlfr.org/w3c/TR/REC-DOM-Level-1/
- La bible du DOM traduite en français
- A propos de Javascript
- http://www.w3schools.com/js/
- JavaScript is easy to learn! You will enjoy it!
- http://www.aidejavascript.com/
- Une bonne référence en français pour découvrir Javascript
- http://www.devguru.com/technologies/javascript/home.asp
- Site en anglais clair et structuré pour continuer l’apprentissage de Javascript
Effectivement très utile !
Et maintenant, pour éviter le <body onload…> tu as aussi
try{window.onload=function(){loadDivs();}}
catch(e){window.attachEvent("onload", loadDivs);}
On peut aussi imaginer passer le nom de la function à exécuter en paramètre de defineEventByClass…
Rhaaa la la c’est bien inspirant tout ça !
J’oubliais… "loadDivs" étant une function dans laquelle on regroupe tout ce qui se lance au chargement de la page, c’est un exemple, pas quelque chose qui existe, hein /o
Tout ça pour Toto … Quel veinard ^^
@Talou -> Merci pour le try… catch. Dans loadDivs() je mets quoi exactement ? Des fois, faut m’expliquer comme si j’avais 5 ans…
@xuxu -> Toto insistait, alors… Au fait, sympa les bannières Copaing.net ^^ Ca me donne une idée 😉
ça parle de quoi ici?
@thanh -> Si aussi tu codais un peu plus, au lieu de serrer des mains et de distribuer des cartes… :p
Ben justement j’avais vu de la lumière j’ai cru qu’on servait à boire!
Vivement le vin, donc 😉
Encore plus fort ! Si tu veux supprimer le javascript de ton doc html, tu peux carrément utiliser la fonction addevent de Scott Andrew (http://www.scottandrew.com/weblo...
function addEvent(obj, evType, fn){
if (obj.addEventListener) {
obj.addEventListener(evType, fn, true);
return true;
} else if (obj.attachEvent) {
var r = obj.attachEvent("on"+evType, fn);
return r;
} else {
return false;
}
}
où obj est ton element html,
evType, ton type d’évènement (click, over, out…)
et fn, la fonction que tu appelles.
Par exemple pour initier un script dans un doc html, tu peux faire
addEvent(window, ‘load’, maFonction);
et là, c’est vraiment Magic !
@Gregoire -> Je connaissais pas du tout cette méthode addEventListener qui a l’air aussi puissante que subtile. Je teste dès que possible !
P.S. Le web c’est génial : c’est quand tu crois avoir compris un truc, que tu découvres des choses vraiment intéressantes 😉
Et si vraiment t’as pas compris, reprend un verre!
lol, pour ça, je crois que je ferais mieux d’attendre l’apéro-blog du 20 🙂
br1o : j’ai mis une function qui s’appelle loadDivs, ok, mais elle peut s’appeler autrement, et dedans tu mets toutes les fonctions que tu veux charger au démarrage… Admettons que tu aies besoin d’attacher une action à tous les éléments de class XX et et une autre action à tous les éléments de class YY, eh bien tu as une fonction
function start()
{
defineEventByClass("img_foo", "f_foo");
defineEventByClass("div_bar", "f_bar");
}
en admettant que la fonction defineEventByClass permette de définir en paramètres le nom de class et le nom de la fonction à appeler dans ce cas…
Ca marche aussi avec l’astuce encore plus élégante de Grégoire.
Merci pour ces informations, l’astuce de Gregoire étant trés "élégante".
Par contre, je me heurte à un problème… le passage de paramètres.
En gros, j’ai un système de navigation par boutons images.
Chaque bouton doit valider le formulaire de la page actuelle avant d’être redirigé.
Je voudrais que ma fonction "toto" récupère des informations fixées pour chaque lien (appel de la nouvelle page) et permette la validation du formulaire.
J’ai eu l’idée de coller ces paramètres (du style "11" pour la page 1-1 ou "12" pour la page 1-2…) dans le paramètre ID de chaque lien.
Mais impossible de récupérer la valeur de l’id du lien cliqué.
ex :
<div id="images">
<a id="11"…><img…></a>
<a id="12"…><img…></a>
…
</div>
et l’appel de la fonction : onload="addEvent(images, ‘click’, toto, this.id);"
Mais ça ne marche pas, this.id ne semble pas être possible d’obtenir comme ça.
Merci pour vos commentaires.
Bon, comme je dois me débrouiller seul…
Voilà ce que j’ai fait :
Dans chaque formulaire (dans chaque page donc) je créé deux champs cachés (page et etape).
Sur mes liens, je créé un évenement onMouseOver qui renseigne ces valeurs à chaque passage de la souris.
J’ai repris la première fonction de br10, toto renseigne la valeur document.forms[‘form1’].action avec l’état actuel de chaque champ caché puis poste le formulaire.
Voilà, ce n’est peut être pas trés élégant mais ça marche.
Bonjour Bewonder,
Quand je n’ai pas la réponse à une question, je laisse passer un peu de temps au cas où quelqu’un d’autre aurait une solution. Concernant la façon dont tu as résolu ton problème, je n’ai pas mieux à proposer 😉 Well done !
Bonjour à tous,
C’est fort sympathique de pouvoir redefinir les evenement mais je suis confronté au meme probleme que bewonder, c’est a dire que si je reprend l’exemple ici, il faudrait que ma methode toto puisse avoir acces au moins à l’instance de l’objet qui la declenche … y a t’il un moyen de pouvoir faire ca ?
ou peut t’on tout simplement envoyer des parametres dans la methode toto ?
merci.
J’ai trouvé la solution !!
exemple :
document.getElementById(‘machin’).onclick = function() { toto(argument1, argument2, etc…); };
Ca peut être utile pour ceux qui devellope à la façon AJAX sans framework ! On peut élaborer de véritables applications en couplant ça avec du php.
Tchao
Bravo et merci de la part de tout ceux qui bloquaient dessus 😉
bonjour, es que vous pouvez m’indiquer des docs important concernant le dom..
merci
Bonjour yafa, concernant le dom, voici quelques liens supplémentaires :
slayeroffice.com/articles…
nyams.planbweb.com/dom.ht…
gilles.chagnon.free.fr/co…
Salut Bruno! Décidément j’arrive toujours a me retrouver chez toi 😀 juste pour te signaler que les sources JS ne passent pas (comme tu es en pleine refonte de design je prefere te l’dire ;-))
A bientôt!
Antoine
@guiralantoine > yep, je corrige ça peu à peu quand je tombe sur les billets qui comportent des exemples de code. Avec plus de 230 billets en tout, ça prend du temps… Désolé 😉
J’avoue que pour ma part, je préfère de loin faire confiance à un framework éprouvé comme jQuery qui permet en une ligne d’associer des fonctions d’événement à tous les éléments DOM que l’on souhaite. Bref, c’est un gain de temps des plus intéressants, quand on ne souhaite pas s’investir dans une R&D importante et que l’on est intéressé plus par le résultat que la manière.
D’autres préféreront Prototype, voire des choses plus complètes comme Scriptaculous et autres MooTools.
@Martin: A l’époque je ne connaissais pas jQuery et je ne sais même pas si ça existait 😉 Depuis que j’ai découvert ce framework jQuery, je ne peux plus m’en passer.
N’empêche que quand on n’est pas développeur, c’est quand même intéressant de connaitre les différentes strates qui nous ont amenés des événement onclick dans la balise aux comportement non obstrusifs…
Je m’étais bien grillé quelques neurones pour comprendre et mettre en place la solution présentée dans ce billet ^^
PS pour tous : comme j’ai rajouté Markdown Extra, j’ai plein de pétouilles à régler dans l’affichage des billets. J’y travaille 😉
Génial votre discutions ; ou comment effacer tout les événements de mes pages !
Bonjour,
le lien cité en fin d’article « AideJavascript.com » est devenu un site parking.
Cdt,