Le Post Infeeny

Les articles des consultants et experts Infeeny

La délégation d’évènement avec jQuery

Une fonctionnalité méconnue et pourtant très utile de jQuery est la délégation d’évènement. Elle permet de brancher un gestionnaire d’évènement sur un élément parent de l’élément que l’on veut réellement écouter.

Pas assez clair? Pour mieux comprendre faisons d’abord un petit rappel.

La notion de « bulle »

Lorsque l’on clique sur un élément du DOM, l’évènement « click » est déclenché sur le nœud en question, puis est propagé sur tous ses nœuds parents jusqu’à remonter sur l’élément racine du document, c’est-à-dire « body ».

Dans l’exemple suivant, si vous cliquez sur le bouton, l’évènement « click » sera d’abord déclenché sur le <button>, puis sur la <div>, puis <article>, <section> et enfin <body>.

<body>
  <section>
    <article>
      <div>
        <button></button>
      </div>
    </article>
  </section>
</body>

Cette remontée du DOM aura lieu que vous traitiez ou non l’évènement à quelque niveau que ce soit – bien qu’il soit possible de bloquer la bulle, mais c’est un autre sujet. Il est donc possible d’intercepter l’évènement à plusieurs reprises en branchant des gestionnaires d’évènement à différents niveaux.

$(document).ready(function () {
  $("article").on("click", function (e) {
	// Gestionnaire d'évènement sur <article>
  });
  
  $("button").on("click", function (e) {
	// Gestionnaire d'évènement sur <button>
  });
});

Dans ce dernier exemple, on branche deux gestionnaires d’évènement, un sur <button> et un sur <article>. Si l’on clique sur le bouton, le gestionnaire d’évènement branché sur <button> sera déclenché en premier, puis suite à la remontée de la bulle le long du DOM, ce sera le tour de celui qui est branché sur <article>. On voit que l’ordre dans lequel ils ont été abonnés importe peu.

Comment mettre en place la délégation

La méthode on() de jQuery permet d’indiquer un paramètre supplémentaire : un sélecteur désignant les nœuds enfants que l’on souhaite écouter. Dans ce cas, le gestionnaire branché sera déclenché lorsque l’évènement se sera propagé jusqu’à l’élément, mais uniquement si la cible initiale – par exemple l’élément sur lequel on a vraiment cliqué – respecte le sélecteur indiqué en paramètre.

$(document).ready(function () {
  $("article").on("click", "button" /* Sélecteur supplémentaire */, function (e) {
	// Gestionnaire d'évènement sur <article>
	// mais qui ne se déclenchera que si on a cliqué sur <button>
  });
});

C’est donc la présence de ce paramètre supplémentaire qui induit la délégation d’évènement dans jQuery.

Vous avez compris le principe. Voyons maintenant trois cas d’utilisation concrets, issus de mon expérience, qui vous démontreront tout l’intérêt que peut revêtir cette fonctionnalité.

Beaucoup d’éléments à écouter

Imaginez que vous affichiez un tableau HTML sur votre page web, que celui-ci contienne un million de cellules, et que vous souhaitiez brancher un gestionnaire d’évènement pour écouter les clicks sur chacune d’entre elles.

<table id="big-table">
  <tr>
    <!-- Ici plein de cellules -->
    <td></td>
    <td></td>
  </tr>
</table>

Si vous le faites en vous branchant individuellement sur chacune des cellules, vous allez enregistrer un million de gestionnaires d’évènement en mémoire. Inutile de vous dire que ce n’est pas une très bonne idée.

En utilisant la délégation vous pouvez brancher un seul gestionnaire d’évènement au niveau du tableau lui-même. Ce gestionnaire sera alors en mesure de traiter les clicks sur chacune des cellules.

$(document).ready(function () {
  $("#big-table").on("click", "td", function (e) {
	// Gestionnaire d'évènement unique pour l'ensemble des <td>
  });
});

A ce stade, vous devriez commencer à vous poser la question suivante : comment savoir quelle cellule a été clickée?

Pour ce cas de figure, jQuery a tout prévu, et dans l’argument passé en paramètre au gestionnaire d’évènement – appelé « e » dans notre exemple – vous trouverez trois propriétés qui vous permettront d’obtenir des informations.

  • e.target désignera l’élément qui a déclenché initialement l’évènement, c’est-à-dire la cellule.
  • e.currentTarget désignera l’élément sur lequel l’évènement est en train de buller, c’est-à-dire le tableau.
  • e.delegateTarget désignera l’élément sur lequel est branchée la délégation, c’est-à-dire ici encore le tableau.

Notez aussi que le mot-clef this comme e.currentTarget désignera toujours l’élément sur lequel l’évènement est en train de buller, c’est-à-dire celui correspodant au sélecteur que l’on a indiqué en paramètre de la fonction on().

Les fragments de code HTML

Imaginez que vous affichez une liste d’items.

<ul id="list">
  <li class="list-item"><button type="button" class="delete">Supprimer</button></li>
  <li class="list-item"><button type="button" class="delete">Supprimer</button></li>
  <li class="list-item"><button type="button" class="delete">Supprimer</button></li>
  <li class="list-item"><button type="button" class="delete">Supprimer</button></li>
</ul>

Chacun de ces items peut être supprimé ou ré-ordonné dans la liste par glissé/déposé. Pour ce faire, on branche sur chacun des items des gestionnaires d’évènement chargés de traiter ces opérations.

$(document).ready(function () {
  $("#list .list-item").on("dragstart", function (e) {
	// Gestionnaire d'évènement de glissé/déposé
  });
  
  $("#list .list-item .delete").on("click", function (e) {
	// Gestionnaire d'évènement de suppression des items
  });
});

Une première remarque – inspirée de l’exemple précédent – est qu’il est possible de brancher les gestionnaires d’évènement par délégation sur la liste elle-même. Seulement voilà : cette liste est susceptible à tout moment d’être mise à jour par écrasement, suite par exemple à l’appel d’une requête Ajax.

function refreshList() {
  $.ajax("/UNE-URL-QUELCONQUE").done(function (html) {
  	$("#list").replaceWith(html);
	// Il faut rebrancher tous les gestionnaires d'évènements 😦
  });
}

Le problème, c’est qu’une fois cette opération d’écrasement effectuée, il faut rebrancher tous nos gestionnaires d’évènements car les éléments du DOM auxquels ils étaient rattachés ont été supprimés du document. Notez au passage qu’il est inutile de s’inquiéter du désabonnement de ces gestionnaires d’évènements, jQuery les nettoyant automatiquement lors de l’appel à la méthode replaceWith().

Pour pallier à l’inconvénient d’avoir à tout rebrancher à chaque rafraichissement de la liste, nous allons tout d’abord encadrer celle-ci avec un nouveau parent.

<div id="list-wrapper">
  <ul id="list">
    <!-- Ici les items -->
  </ul>
</div>

Nous allons ensuite brancher nos gestionnaires d’évènements par délégation sur ce nouvel élément.

$(document).ready(function () {
  $("#list-wrapper").on("dragstart", ".list-item", function (e) {
	// Gestionnaire d'évènement de glissé/déposé
  });
  
  $("#list-wrapper").on("click", ".list-item .delete", function (e) {
	// Gestionnaire d'évènement de suppression des items
  });
});

Le résultat de ces changements est que lorsque la liste sera écrasée, l’élément qui l’encadre lui ne le sera pas, et les gestionnaires d’évènements qui sont branchés seront par conséquent bien conservés. Par ailleurs, bien que le contenu de la liste soit mis à jour, ses items respectent toujours le sélecteur indiqué en paramètre de la délégation d’évènement. Comme ce sélecteur n’est appliqué que lorsqu’un évènement est capturé au niveau du délégué, les gestionnaires seront bien déclenchés.

Il est très courant de nos jours de mettre à jour des pans entiers d’une page web avec des appels Ajax, et ce procédé vous permettra de vous simplifier grandement la vie quant à la gestion des évènements sur ces fragments.

Gestion de la propagation d’évènements

Imaginez maintenant que lorsque l’utilisateur clique n’importe où sur la page, il faille escamoter certains morceaux de celle-ci, comme typiquement une popup.

<body>
  <div class="popup">
    <button type="button">Cliquez-moi</button>
  </div>
</body>

Pour cela le plus simple est de brancher un gestionnaire d’évènement au niveau le plus haut de la page, c’est à dire sur <body>.

$(document).ready(function () {
  $("body").on("click", function (e) {
	$(".popup").hide();
  });
});

Cette mécanique fonctionnera parfaitement, excepté qu’elle dissimulera également la popup lorsque l’on clique dans le contenu de celle-ci, par exemple sur le bouton qu’elle contient. Or ce n’est pas ce que nous souhaitons.

Encore une fois nous allons pouvoir utiliser la délégation pour corriger ce problème. Pour cela, nous allons enregistrer un nouveau gestionnaire d’évènement click sur le <body> de la page. Mais attention, nous allons l’enregistrer avant celui que nous avons déjà indiqué. Ce gestionnaire d’évènement agira par délégation sur notre popup.

$(document).ready(function () {
  $("body").on("click", ".popup", function (e) {
    e.stopImmediatePropagation();
  });

  $("body").on("click", function (e) {
	$(".popup").hide();
  });
});

Puisque nous avons branché deux gestionnaires d’évènements sur <body>, lors d’un click sur la page jQuery les traitera tous les deux dans l’ordre où nous les avons indiqué. Mais une différence importante est que le premier ne sera déclenché que lorsque le click aura été propagé depuis la popup ou son contenu. Comme on l’a déjà vu le second gestionnaire d’évènement sera lui déclenché quelque soit l’endroit que l’on cliquera sur la page.

Or dans le premier cas, on empêche la bulle de se propager aux autres gestionnaires d’évènement en utilisant la méthode stopImmediatePropagation(). Ainsi, on inhibe l’exécution de tous les autres gestionnaires d’évènements qui pourraient avoir été branchés sur le même élément – ici <body>.

Le résultat est que si l’on clique sur le bouton, le premier gestionnaire d’évènement sera en mesure de capturer ce click au moment où celui-ci atteindra la popup, et l’empêchera d’être capturé par l’autre gestionnaire d’évènement.

Au passage, si l’on place un point d’arrêt dans le premier gestionnaire d’évènement et que l’on clique sur le bouton, on constate que l’argument qui est passé en paramètre contient les informations suivantes :

  • e.target désigne le <button>.
  • e.currentTarget et this désignent <div class= »popup »>.
  • e.delegateTarget désigne <body>.

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :