Working with Events, Part 3: More Event Delegation with jQuery
read 13 commentsEvent 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:
- <ul class="myList">
- <li class="red">The first item.</li>
- <li class="green">The second item.</li>
- <li class="yellow">The third item.</li>
- <li class="blue">The fourth item.</li>
- </ul>
- <p>Class of the last clicked item: <span id="display"> </span>
- </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:
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:
- The first item.
- The second item.
- The third item.
- The fourth item.
Class of the last clicked item:
Event delegation has two main advantages:
- setting a single event listener instead of multiple ones is obviously faster;
- 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>.
- }
- });
- 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:
- <ul class="myList">
- <li class="red"><b>The <i>first <u>item</u></i></b>.</li>
- <li class="green"><b>The <i>second <u>item</u></i></b>.</li>
- <li class="yellow"><b>The <i>third <u>item</u></i></b>.</li>
- <li class="blue"><b>The <i>fourth <u>item</u></i></b>.</li>
- </ul>
- <p>Class of the last clicked item: <span id="display"> </span>
- </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>
- while( elem.nodeName != "LI" && elem.parentNode) {
- elem = elem.parentNode;
- }
- if(elem.nodeName == "LI") {
- }
- });
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:
- if($elem.length) {
- }
- });
- 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:
- });
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.
- );
- });
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.
- );
- });
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:
- );
- });
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.
















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!
Excellent work Louis! Congratulations on your first article.
You rule!!! Thank you very much!
I have your first edition book and what is funny is that I read your blog more often than the book itself.
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!
what a nice article!Thanks for your sharing!
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.
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
"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 ?
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
thiskeyword.Thanks for the additional info.
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.
Thank you. Nothing that I can think of works across browsers, unfortunately.
Kickass!! for a beginner like me :-)