Event Delegation with jQuery
jQuery makes implementing event delegation quick and easy! In version 1.4.2 there are now three ways to utilize event delegation: .live()
, .delegate()
, and do-it-yourself. Each technique progressively gives you a little more control and flexibility. Lets take a look at these three techniques with a quick 101 crash course on event delegation first.
Event Delegation 101
At its core event delegation is all about letting events bubble up the DOM tree to a parent element. This provides several advantages such as only binding one event handler instead of potentially 100s and it works with elements currently in the DOM at runtime and those which are injected after runtime.
Imagine you have a large table of data and you want to do something when the user clicks on a row. You might first start out by binding a click event to each <tr>
which would look like this.
$('tr').bind('click', function(event) {
// this == tr element
});
If you have lots and lots of table rows it could take a while to bind all those events. Not to mention the browser now needs to keep track of all those event handlers. Instead, we can use event delegation by binding the click event to the table (or any parent element, maybe even the body) and letting the event bubble up to the table. Then we can inspect the event to see which element was actually clicked on. jQuery either does this for you or makes this part easy and we’ll look at how to do this in just a moment.
The second reason you might want to use event delegation is for automatically handling dynamic data. Lets say you needed to dynamically add rows to your table. Well, then you’d have to also bind the click event to those new rows. With event delegation the event is actually bound to the table element and any new rows do not need a new event bound. Awesome!
.live()
The .live()
method, added in jQuery 1.3, provides the most simple way to implement event delegation and is suitable for simple scenarios. Here is some example code illustrating a click event being captured for all <tr>
elements (old or newly created) on a page.
$('tr').live('click', function(event) {
// this == tr element
});
Notice, I said “page”. The .live()
method binds events to the document by default. You can actually change this, in jQuery 1.4, by passing in a new context to jQuery. If you are unfamiliar with the context in jQuery, I recommend reading this blog post: Understanding the Context in jQuery.
One of the gotchas of this method is that it isn’t exactly chainable like other jQuery methods. For example if you use a jQuery method that changes the selection of elements, such as .children()
or .parent()
, before calling .live()
then it will not work. In other words, it works best when only used with the given selector. The following code example doesn’t work.
$('#myContainer').children('table').find('tr').live('click', function(event) {
/*** does not work! ***/
});
You might expect that the previous example would still handle click events for <tr>
elements. However, it isn’t going to work at all because of the .children()
method. So, .live()
is a simple method for simple scenarios.
To stop .live()
events, you’d use the .die()
method.
.delegate()
The .delegate()
method was introduced in jQuery 1.4.2 and provides a more focused way of doing your event delegation. Keeping with the table example lets look how we can do it with the .delegate()
method.
$('table').delegate('tr', 'click', function(event) {
// this == tr element
});
First off, unlike .live()
events the event handler is actually bound to the selected element (“table” in this case). Then the click event is filtered to only fire when the <tr>
was clicked on. This is a great method as it makes it easy to specify (and understand) which element you want to delegate from and which elements to filter the event on.
This method also clears up the confusion with regards to the chainability that the .live()
method introduced. Here is the example code that didn’t work with .live()
but does work with delegate.
$(#myContainer').children('table').delegate('tr', 'click', function(event) {
// this == tr element
});
Again, the reason this works and .live()
doesn’t is because .delegate()
is actually binding the event to the selected parent and then filtering based on the selector passed to the .delegate()
method.
To stop delegated events, you’d use the .undelegate()
method.
Do-It-Yourself
The last technique is the ability to just do-it-yourself. This is for advanced use-cases where you want to have more flexibility than .delegate()
or .live()
. Using a method called .closest()
, which was introduced in jQuery 1.3, we are going to look at the event.target
(which element the event happened on) and see if it or any of its parents are the element we want to filter the event on. Keeping with the table example our code looks like this.
$('table').bind('click', function(event) {
// this == table element
var $tr = $(event.target).closest('tr');
});
As you can see we are just using the good’ol .bind()
method to bind our click event to the table element directly. Then when the table is clicked we check the event.target
to see if it is the <tr>
or if one of its parents is the <tr>
. If neither of these cases are true then $tr
is an empty jQuery collection.
Summary
Use .live()
if you just need to add some quick event delegation or handle events on elements that don’t yet exist in the DOM. Beware though that you shouldn’t chain .live()
and by default it binds event handlers to the document element.
Use .delegate()
when you want a little more control and want to chain method calls.
Use the do-it-yourself technique when you want ultimate control and flexibility with your event delegation.
Which do you prefer and why?