Utilisez le DOM et Javascript comme un chef

dom-javascript 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 😉

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