Working with Events, Part 3: More Event Delegation with jQuery
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:
[html]- The first item.
- The second item.
- The third item.
- The fourth item.
Class of the last clicked item:
[/html]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:
[js] $("li").click( function( event ) { $("#display").text(event.target.className); }); [/js]
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:
[js] $("ul").click( function( event ) { $("#display").text(event.target.className); }); [/js]- 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:
[html]- The first item.
- The second item.
- The third item.
- The fourth item.
Class of the last clicked item:
[/html]
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>
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:
- 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.