Comprendre la délégation d'événement en JavaScript

Le , par Bovino, Rédacteur
La délégation d'événement, qu'est-ce que c'est ?

La délégation d'événement est une technique assez courante en JavaScript qui consiste à poser des écouteurs d'événement non pas sur l'élément HTML ciblé, mais sur l'un de ses ancêtres dans le DOM.

Comment ça marche ?

Le concept essentiel pour comprendre cette technique est la notion de bouillonnement d'un événement. À de rares exceptions prêt, quasiment tous les événement bouillonnent.
Si l'on se représente une page Web comme une feuille de papier sur laquelle on poserait d'autres feuilles de tailles différentes et placées à des endroits précis selon l'arborescence des balises et la mise en page CSS, on peut se représenter un événement (un clic sur une balise par exemple) comme étant intercepté par l'élément le plus au-dessus de l'empilement (et inversement, le plus profond dans l'arborescence du DOM). C'est donc cet élément qui va recevoir l'événement. Le bouillonnement est le mécanisme qui va faire « remonter » cet événement jusqu'au plus haut niveau de l'arborescence (la première feuille de papier) en passant par tous les éléments se trouvant à l'emplacement où l'événement a été déclenché, permettant ainsi à tous les gestionnaires d'événements associés de se déclencher.

En résumé, si l'on considère le code HTML suivant
Code html : Sélectionner tout
1
2
3
4
5
<body> 
    <div> 
        <span>Cliquer ici</span> 
    </div> 
</body>
et que vous placez un écouteur d'événement clic sur la div, cliquer sur le texte du span déclenchera l'événement de la div bien que ce span « cache » cette dernière. Ceci est possible grâce au bouillonnement.

Pour en revenir à notre délégation d'événement, nous pourrons savoir, avec l'objet Event associé à tout événement, quel élément HTML a réellement déclenché cet événement (Event.target) et si ce dernier est bien celui que nous souhaitons cibler.

À quoi cela peut-il servir ?

Il y a deux cas typiques d'utilisation de la délégation d'événement.

• Lorsque l'on souhaite associer des événements similaires à différents éléments.
Par exemple, imaginons que l'on souhaite afficher un texte initialement masqué en cliquant sur des items d'une liste ordonnée. Plutôt que de définir autant d'événement que d'éléments dans la liste, on pourra n'en utiliser qu'un seul placé sur la balise <ul>
Code html : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
<ul id="maListe"> 
    <li class="element" data-texte="foo">Item 1</li> 
    <li class="element" data-texte="bar">Item 2</li> 
    <li class="element" data-texte="baz">Item 3</li> 
    <li class="element" data-texte="toto">Item 4</li> 
    <li class="element" data-texte="titi">Item 5</li> 
    <li class="element" data-texte="tata">Item 6</li> 
    <li>Item 7</li> 
    <li>Item 8</li> 
    <li>Item 9</li> 
    <li class="element" data-texte="42">Item 10</li> 
</ul>
Code javascript : Sélectionner tout
1
2
3
4
5
6
7
document.getElementById('maListe').addEventListener('click', function(e){ 
    var initElem = e.target; 
    if(initElem.className != 'element'){ // Si l'élément n'est pas un de ceux à traiter 
        return; 
    } 
    alert(initElem.dataset.texte); 
});
Voir l'exemple sur JSFiddle.

Dans ce code, on cherche à afficher le contenu de l'attribut data-texte pour tous les élément ayant la classe element.
On commence d'abord par récupérer l'élément ayant reçu l'événement. On vérifie ensuite si cet élément est bien du type que l'on cible, si ce n'est pas le cas, on stoppe l'exécution de la fonction, sinon, on affiche le message.

• Lorsque l'on veut prévoir des gestionnaires d'événements sur des éléments n'étant pas encore présents dans la page.
Il est de plus en plus fréquent, avec les interfaces riches et AJAX, que le contenu d'une page évolue en fonction des actions de l'utilisateur.
On peut donc avoir à gérer des événements sur des éléments qui n'existent pas au chargement de la page mais qui sont susceptibles d'y apparaitre.
Seulement, lorsque l'on déclare un gestionnaire, il ne peut s'appliquer qu'à des éléments effectivement présents au moment de la déclaration de l'écouteur : JavaScript n'est pas devin ni prédictif.
Pour cela, on pourra facilement poser le gestionnaire sur un ancêtre connu (et existant) dans lequel seront insérés les futurs éléments.
Code html : Sélectionner tout
1
2
<div id="parent"></div> 
<button>Ajouter un élément</button>
Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
9
document.getElementById('ajout').onclick = function(){ 
    document.getElementById('parent').innerHTML = '<span id="enfant">Elément enfant ajouté dynamiquement</span>'; 
}; 
document.getElementById('parent').addEventListener('click', function(e){ 
    var initElem = e.target; 
    if(initElem.id == 'enfant'){ 
        alert('Vous avez cliqué !'); 
    } 
});
Voir l'exemple sur JSFiddle.

Au moment de déclarer l'événement, l'élément enfant n'existe pas, pourtant, lorsqu'on l'insère dans la page et que l'on clique dessus, le message s'affiche.

Aller plus loin
Il est important, quand on utilise la délégation d'événement, de faire attention que si l'élément ciblé possède des éléments enfants, alors Event.target peut ne pas correspondre à celui ciblé, il faudra dans ce cas remonter l'arborescence jusqu'à l'élément sur lequel le gestionnaire est posé en testant à chaque palier si l'élément en cours est celui recherché.
Code javascript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
var initElem = false, tmpElem = e.target; 
do{ 
    if(tmpElem.id == 'id_recherche'){ 
        initElem = tmpElem; 
    } 
} 
while(tmpElem = tmpElem.parentNode && tmpElem != this); 
if(initElem){ 
    // tmpElem ne vaut pas false si on entre dans cette condition 
    // donc l'élément recherché a été trouvé 
    // on peut donc faire les traitement voulus 
}

Il est aussi à noter que la plupart des bibliothèques JavaScript permettent d'utiliser la délégation d'événement.
Par exemple pour jQuery, la syntaxe
Code javascript : Sélectionner tout
$('#elem1').on('click', '.elems2', callback);
permet de déléguer la gestion des événements clic sur les éléments ayant la classe CSS elems2 sur l'élément dont l'identifiant est elem1.
N'hésitez pas à indiquer dans les commentaires les différentes syntaxes pour les autres frameworks.


Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :


 Poster un commentaire

Avatar de exe2bin exe2bin - Membre actif https://www.developpez.com
le 29/05/2015 à 14:28
Il est important, quand on utilise la délégation d'événement, de faire attention que si l'élément ciblé possède des éléments enfants, alors Event.target peut ne pas correspondre à celui ciblé, il faudra dans ce cas remonter l'arborescence jusqu'à l'élément sur lequel le gestionnaire est posé en testant à chaque palier si l'élément en cours est celui recherché.
Bel article mais je n'ai pas tout compris (voir ci-dessus);
pourrais tu reformulé
Avatar de Bovino Bovino - Rédacteur https://www.developpez.com
le 29/05/2015 à 14:35
Arf... désolé.

Avec un exemple, ce sera probablement plus compréhensible.

Imaginons le code HTML
Code html : Sélectionner tout
1
2
3
4
5
6
7
8
<div id="A"> 
    <div class="B"> 
        <span>Cliquer sur le premier B</span> 
    </div> 
    <div class="B"> 
        <span>Cliquer sur le second B</span> 
    </div> 
</div>
Imaginons que tu veuilles traiter les clics sur les éléments ayant la classe B, tu poses donc le gestionnaire sur la div A.
Sauf que lorsque tu cliques sur le texte, c'est la balise <span> qui va déclencher l'événement et ce sera donc elle qui sera référencée par event.target.
Donc si tu fais le test if(initElem.className != 'B'){ ... }, il sera vrai. Il est donc nécessaire de vérifier si l'un des ancêtres de event.target possède cette classe.
Avatar de SylvainPV SylvainPV - Rédacteur/Modérateur https://www.developpez.com
le 31/05/2015 à 11:24
Merci Bovino, c'est vrai que c'est un sujet courant et qu'on avait rien comme ressources dessus.

J'en profite pour te demander ton avis sur un micro-débat: penses-tu que cela ait un sens d'utiliser exclusivement de la délégation d'évènements sur l'élément de plus haut niveau (document.body) pour gérer tous les évènements d'une application ? Certains ont émis ce genre d'idées, notamment en parallèle avec l'utilisation de React et de l'architecture Flux, pour remplacer l'usage classique de la phase de propagation des évènements qu'ils trouvent confusante par leur propre système dispatcher (qui peut se résumer en gros à un pub/sub sur les composants React). Je suis pas un fada de React ni du fait de réinventer la roue, mais je dois avouer m'être demandé plusieurs s'il n'y avait pas une meilleure façon de faire que ces event.preventDefault() && event.stopImmediatePropagation() par ci par là.
Avatar de Bovino Bovino - Rédacteur https://www.developpez.com
le 02/06/2015 à 15:39
Salut Sylvain.

Je ne connais pas du tout React, mais je sais qu'affecter tous les événements sur le body a été la première approche utilisée par jQuery avec la méthode .live(). Ils ont changé d'optique en considérant que le fait de ne pas pouvoir stopper la propagation de l'événement pouvait être problématique. J'aurais tendance à me ranger à cet avis en considérant aussi que devoir gérer tous les événements d'un même type sur la page est souvent un peu surdimensionné comparé au besoin réel la plupart du temps.
Par exemple, si on doit gérer le clic sur un texte qui apparaitra au retour d'une requête AJAX ne nécessite pas pour autant que l'on gère tous les clics de toute la page constamment.
Donc à mon sens, l'idéal est de placer le gestionnaire sur le plus proche parent connu de l'élément visé.
Avatar de Paul_Le_Heros Paul_Le_Heros - Membre habitué https://www.developpez.com
le 05/07/2015 à 6:53
Bonjour,
Merci Bovino, pour avoir fait avancer le schmilblick...
Je me sens moins seul quand je lis "SylvainPV" :
une meilleure façon de faire que ces event.preventDefault() && event.stopImmediatePropagation() par ci par là
Il n'a pas l'air d'être satisfait de lui : « par ci par là »...
J'ai bien de la peine avec les événements et leurs handlers. J'ai beau (mal) chercher, je ne trouve rien sur le net pour clarifier définitivement cette situation. J'aime bien JQuery, alors si vous connaissez un tuto sur "le traitement des événements avec JQuery", dites moi ! Poyr vous donner une idée de l'aviron que j'ai pratiqué : il n'y a pas si longtemps que je m'usais à utiliser return !0; ou return !1; en sortie de handler pour signifier la fin ou non du traitement de l'événement, comme indiqué dans ma doc JS de Mathusalem (-: l'originale, peut-être !)
Bon courage à tous,
Paul
Responsable bénévole de la rubrique JavaScript : Xavier Lecomte -