10
X

Working with Events, Part 3: More Event Delegation with jQuery

read 13 comments

Event delegation, as described in the first article of this series, is a way to take advantage of event bubbling to avoid binding an event listener more than once. jQuery 1.3 and the upcoming jQuery 1.4 have many features that make using event delegation in your web pages easier. The aim of this tutorial is to help you understand how these new features work.

From traditional event listening to event delegation

Since an event occurring on an element is propagated to all of its ancestors, an event listener can be bound to a single ancestor of numerous elements instead of being bound to all the elements individually.

Consider the following list items:

Continue Reading Below
HTML:
  1. <ul class="myList">
  2.   <li class="red">The first item.</li>
  3.   <li class="green">The second item.</li>
  4.   <li class="yellow">The third item.</li>
  5.   <li class="blue">The fourth item.</li>
  6. </ul>
  7. <p>Class of the last clicked item: <span id="display"> </span>
  8. </p>

If we want to display the class of an item when it is clicked, a traditional event listener in jQuery would be written like this:

JavaScript:
  1. $("li").click( function( event ) {
  2.   $("#display").text(event.target.className);
  3. });

The event object passed to the handler has a target property which corresponds to the element that has been clicked.

The equivalent using event delegation would look like this:

JavaScript:
  1. $("ul").click( function( event ) {
  2.   $("#display").text(event.target.className);
  3. });

  • The first item.
  • The second item.
  • The third item.
  • The fourth item.

Class of the last clicked item:

Event delegation has two main advantages:

  1. setting a single event listener instead of multiple ones is obviously faster;
  2. any new element later added to the list will have the same behavior (as demonstrated in Working with Events, Part 1).

This translation to event delegation that we've just made is, however, slightly too simple, since our original aim was to display only the class of the list items. Using the previous snippet, the class of the unordered list itself can be displayed (by clicking to the left of a bullet-point). We thus have to make sure that the target of the click is a <li>.

JavaScript:
  1. $("ul").click( function( event ) {
  2.   if(event.target.nodeName == "LI") {
  3.     $("#display").text(event.target.className);
  4.   }
  5. });

  • The first item.
  • The second item.
  • The third item.
  • The fourth item.

Class of the last clicked item:

Scanning the ancestors of the event.target: the .closest() method

With such a simple document, event delegation is really that easy to achieve. But things can get more complex if the items have children elements:

HTML:
  1. <ul class="myList">
  2.   <li class="red"><b>The <i>first <u>item</u></i></b>.</li>
  3.  
  4.   <li class="green"><b>The <i>second <u>item</u></i></b>.</li>
  5.   <li class="yellow"><b>The <i>third <u>item</u></i></b>.</li>
  6.  
  7.   <li class="blue"><b>The <i>fourth <u>item</u></i></b>.</li>
  8. </ul>
  9.  
  10. <p>Class of the last clicked item: <span id="display"> </span>
  11. </p>

In this case, if the user clicks on one of the words item, the event.target will be a <u>. It is therefore necessary to loop through all of the ancestors of this original target to find the element that is "interesting" for us: the <li>

JavaScript:
  1. $("ul").click( function( event ) {
  2.   var elem = event.target;
  3.   while( elem.nodeName != "LI" && elem.parentNode) {
  4.     elem = elem.parentNode;
  5.   }
  6.   if(elem.nodeName == "LI") {
  7.     $("#display").text(event.target.className);
  8.   }
  9. });

Notice that if the user clicks outside of an <li>, we have to stop looping through the ancestors at some point; in this case when we find the root of the document (which has no parentNode).

jQuery 1.3 introduced the .closest() method, which replaces this loop by a single line of code:

JavaScript:
  1. $("ul").click( function( event ) {
  2.   var $elem = $(event.target).closest("li");
  3.   if($elem.length) {
  4.     $("#display").text($elem.attr("class"));
  5.   }
  6. });

  • The first item.
  • The second item.
  • The third item.
  • The fourth item.

Class of the last clicked item:

The parameter passed to the .closest() method is a CSS selector that will match the first interesting ancestor.

The context parameter

It can be noted that, when the click occurs outside of an <li>, we could stop looping through the ancestors before hitting the document root, but instead as soon as we hit the <ul>. jQuery 1.4 will introduce an optional context parameter to .closest() for this purpose:

JavaScript:
  1. $("ul").click( function( event ) {
  2.   $("#display").text($(event.target).closest("li", this).attr("class"));
  3. });

Here this is the <ul> element to which the event listener has been bound. This optional parameter improves the performance of event delegation as it prevents the wasted resources of searching for an ancestor which cannot exist.

Event delegation in a single line: the .live() method

jQuery 1.3 also introduces .live(), a method which binds the event listener and implicitly calls the .closest() method to determine whether your event handler should be executed or not.

JavaScript:
  1. $("li").live("click", function( event ) {
  2.   $("#display").text(
  3.     $(event.currentTarget).attr("class")
  4.   );
  5. });

Note how different the syntax using the .live() method is. It seems that we have switched back to a traditional event binding. However, a big difference is that the event listener will work for any existing and future element matching our original selector. There is, of course, nothing magic: behind the scenes .live() simply binds the event listener to the document root and filters any event.target using the .closest() method.

A notable difference with the traditional event binding syntax is that we are not using the target property of the event object inside our event handler, but rather the currentTarget. Indeed, the target of the event is possibly a child of one <li>, whereas the currentTarget property refers to the element that has been found by the implicit .closest().

As explained on the Event Object documentation, the currentTarget property is supposed to be the current DOM element within the event bubbling phase, i.e. the element on which the event has been detected by the event listener, which will always be the element on which the event listener was bound. When using .live(), the currentTarget would therefore always be the document root and you would need to use .closest() once again on the original target to find an interesting ancestor.

JavaScript:
  1. $("li").live("click", function( event ) {
  2.   $("#display").text(
  3.     $(event.target).closest("li").attr("class")
  4.   );
  5. });

This kind of code is required with jQuery 1.3, but as of jQuery 1.4, the currentTarget is modified internally and can thus be used to avoid the extra .closest() inside the event handler.

The context parameter

In jQuery 1.3 all live event listeners were effectively bound to the document root. This can hurt the performance of a web page since all events detected by an event listener will trigger the execution of an implicit .closest(), even though the event target might be totally out of interest.

In jQuery 1.4, .live() should be able to bind the event listener to a specific and more focused element of the document by taking advantage of the context of your jQuery object:

JavaScript:
  1. $("li", $("ul")[0]).live("click", function( event ) {
  2.   $("#display").text(
  3.     $(event.currentTarget).attr("class")
  4.   );
  5. });

The context is the second parameter used to build our jQuery object. To be useful for .live() it has to be set as a pure DOM element, hence the [0] after $("ul"). Brandon Aaron has a useful article explaining the context parameter in detail.

Unbind for .live(): the .die() method

Just like .bind() has its .unbind() counterpart that allows for event listeners to be unbound, .live() has its .die() counterpart.

Unlike .bind(), .live() does not allow for namespaced events to be used.

Dealing with events that don't bubble

There are some events for which event delegation is traditionally not possible, simply because they do not bubble. The promise of jQuery 1.4 is nevertheless to make those events compatible with .live(). How this is achieved will be covered in the upcoming fourth part of this tutorial.


comment feed

13 comments

  1. Rubens Mariuzzo

    Wow! What a great article! I am really impressed of how this article explain from basic event delegation with jQuery with the latest upcoming changes presented in the jQuery Conference 2009. Thanks Louis-Rémi!

  2. Excellent work Louis! Congratulations on your first article.

  3. Mario

    You rule!!! Thank you very much!

  4. Mario

    I have your first edition book and what is funny is that I read your blog more often than the book itself.

  5. Nate Eagle

    I really appreciate this article: I could deal with about 1/3 on initial read -- now I have a speedier way to add click events! -- but will have to let some of the rest simmer and return to this article in a day or two. This is a great example, though, of an accessible but substantive lesson. Thanks for your craft and care!

  6. what a nice article!Thanks for your sharing!

  7. Sasha Sklar

    Thanks for the great article. I'm increasingly becoming a fan of event delegation, but there are some basic patterns that have to be re-thought in context. Suppose you're building a basic image gallery. You have some links that correspond to images, clicking on a link opens the corresponding image. Using traditional event assignment, you loop through those links and bind in the link's position in the collection: clicking the first link shows the first image.

    In event delegation, you don't have that position, so you're logic becomes a little more complicated or you have to explicitly associate a link to the corresponding image. I've being doing this with hash parameters, which has the additional benefit of working well with SWFAddress.

    • Louis-Rémi Babé

      Hello Sasha,

      For that purpose you can use the .index() method of jQuery. I recommend caching the jQuery object on which index is used, as long as no new elements are introduced and their order don't change, for performance.

      Thank you all for the feedback,

      Louis-Rémi

  8. moshi

    "When using .live(), the currentTarget would therefore always be the document root and you would need to use .closest() once again on the original target to find an interesting ancestor."

    Why don't you use the this keyword ?

    $("li").live("click", function( event ) {
      $("#display").text(
        $(this).attr("class")
      );
    });
    
    • Louis-Rémi Babé

      Oh you're right indeed.

      I actually started to use .live() with jQuery 1.4pre and was able to use event.currentTarget immediately. I only found out recently that currentTarget behaved differently in jQuery 1.3 and didn't thought about using the this keyword.

      Thanks for the additional info.

  9. goodwill

    Very cool explanation- just wondering if there is any simple way to mimic the livequery functionality on item update? I mean, for example, to trigger an ajaxForm binding when the innerHtml is updated.

  10. Kickass!! for a beginner like me :-)

10 Pings

  1. [...] jQuery déclenche des événements au niveau de l’élément sur lequel on clique (en général, un lien). Ces événements sont propagés à travers les ancètres de l’élément dans l’arbre DOM. Et ceci jusqu’à ce qu’ils soient trappés ou arrivent à la racine (ce qui est nommé event delegation). Ce fonctionnement permet par exemple de faire des événéments live qui fonctionnent correctement même lorsque des éléments sont ajoutés au DOM de façon dynamique (voir, par exemple, ce blog) [...]

  2. [...] on utilise le mécanisme d’event delegation (voir cet article ou celui ci) afin de répercuter ce comportement sur l’ensemble des éléments existants ou à venir, [...]

  3. [...] Working with Events, Part 3: More Event Delegation with jQuery Dec 01 2009 [...]

  4. [...] even thousands, of elements on a page is to use event delegation. As Louis-Rémi Babé described in Working with Events, Part 3: More Event Delegation with jQuery, jQuery’s .live() method makes event delegation dead easy. A simple tooltip script using [...]

  5. [...] even thousands, of elements on a page is to use event delegation. As Louis-Rémi Babé described in Working with Events, Part 3: More Event Delegation with jQuery, jQuery’s .live() method makes event delegation dead easy. A simple tooltip script using [...]

  6. [...] the coming jQuery 1.4 release is supposed to support it. More details about this can be found at learningjquery.com. I was curious to se how this context was being used and if it would allow me to limit the impact [...]

  7. [...] 原文地址:http://www.learningjquery.com/2009/09/working-with-events-part-3-more-event-delegation-with-jquery 原文作者:  Louis-Rémi Babé [...]

  8. jQuery 1.4正式发布:一起来看有哪些新变化(下篇) - CSS9.NET

    [...] jQuery中的事件委托(英文) [...]

  9. [...] can now read my first post, More Event Delegation with jQuery, on this official jQuery blog. I actually decided to split the original content. There will be a [...]

  10. [...] For more information: Louis-Rémi Babé does a wonderful job of explaining the transition from traditional event binding into event delegation in his article - Working with Events, Part 3: More Event Delegation with jQuery [...]

Sorry, but comments for this entry are now closed.