Event Delegation with jQuery
Thursday, March 4, 2010
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
});
You can see this example code in action here.
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
});
You can see this example code in action here.
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');
});
You can see this example code in action here.
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?
Posted in jQuery with 11 comments
The main reason I prefer delegate() over live() (aside from that it’s more explicit about what’s happening) is that it doesn’t query as much - with live, you have to query all the elements for no good reason, just so that the right filter is set.
By Ben Hollis on Friday, March 5, 2010 at 01:41 AM
Great article again! (I get a little giddy whenever a new item appears in my feed reader ;)
Not that it matters too much, but I like the delegate style (when applicable) - I think it is nice and readable and shows exactly what you’re binding to. Though the proxy function with bind is very fun too!
(ps, the comment in the live example says… // this == li element instead of tr element… i think!)
By Mr Speaker on Friday, March 5, 2010 at 02:08 AM
@Mr Speaker, Thanks! Fixed the comment in the live example. :)
By Brandon Aaron on Friday, March 5, 2010 at 02:31 AM
@Ben Hollis
If you are using live then you don’t have to put your code in domready. Yes in that case it jQuery will still look for elements but at least it is not under dom ready
By Neeraj Singh on Friday, March 5, 2010 at 07:08 PM
Although delegate is an improvement it still is not a replacement for bind. Checkout this code.
http://gist.github.com/322813
By Neeraj Singh on Friday, March 5, 2010 at 07:09 PM
@Neeraj I forked the gist to include the correct alternative to the bind syntax you were trying to reproduce with delegate. For others I’m going to include the bind snippet you posted and the resulting delegate snippet in this comment.
Your bind method:
The equivalent delegate method:
You want to only bind the event to the parent and filter on the “a.top”. In your examples in the gist you were still binding the click event to each “a” tag itself instead of the parent… therefore losing the benefit of delegation.
By Brandon Aaron on Friday, March 5, 2010 at 07:34 PM
Ignoring chaining - with the option to add context to .live() in v1.4.2 its a close call for me vs .delegate()
The .delegate() syntax is maybe a bit clearer than .live() with context.
So to help me decide any pointers/insights into performance of .live() with context vs .delegate()?
By Johan on Saturday, March 6, 2010 at 02:36 AM
I prefer delegate.
I haven’t looked into this yet, but would delegate be the equivalent performance wise to .live with context element?
By Weixi Yen on Monday, May 24, 2010 at 09:06 PM
Finally event delegation in jQuery :)
By Mark Peterson on Sunday, August 8, 2010 at 08:13 PM
To my eye delegate’s syntax looks much better than live’s.
Also if you need early loading, why not do
$('document').delegate(...)Wouldn’t that give exactly the same result as using live, but without the overhead of looking for elements?By Sid Maskit on Monday, September 6, 2010 at 08:35 AM
I had some fun with this today. I’m using delegate on table rows, which each contain a mix of checkboxes, text boxes, and drop down lists. The challenge is delegating on the row, with multiple events (change click), but only wanting to respond to click events on the checkboxes, and only change events on text boxes and drop downs.
By Jmactacular on Thursday, September 9, 2010 at 12:26 AM