
Lighten Your Load with jQuery live() And delegate()
Often times when you load content dynamically via Ajax into a page, you want the newly added elements to exhibit the same or similar behaviors and properties then those already on the page. Other times you have a large amount of DOM elements you need to add event listeners to. For the first scenario developers will often result to ‘re-instantiate’ the event binding in the callback function of the Ajax call and for the second scenario will loop through each element and attach an event listener.
If we take the case of registering mouseenter and mouseleave event listeners on, for example each column of a table, this can quickly get cumbersome, not to mention expensive in terms of memory. To iterate and attach those events to each of these elements is always going to be slow and you are potentially creating a ton of functions that is going to bring your application to it’s knees, especially in older browsers.
So how do we get around this? Event delegation is the answer to both the above scenarios and jQuery provides us with two options, live() and delegate(), to improve the performance of your application and make your code much more succinct and in so doing ‘lightening your load’. So, I am first going to look at live and delegate and will then go over why you might choose the one over the other.
jQuery live()
To keep this concise I will be using the same basic example for both live as well as delegate. The scenario I am using is, as I hinted to above, a scenario where we want to add a specific behavior to all cells in a table and when we load in new content, a new table, via Ajax, we want the new table cells to have the same behavior as the table that was loaded initially.
If you forget about event delegation for the moment, to achieve the first of the two above would require code such as the following:
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Live and Delegate</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
</head>
<body>
<table id="container"></table>
<div id="ajax_table_container"></div>
<form name="load_table" id="table_loader">
<input type="submit" value="Load Table" />
</form>
</body>
</html>
CSS
td {
padding:10px;
border:1px solid #333;
}
.hovering
{
background-color:#83D727;
padding:20px;
}
JavaScript
$().ready(function() {
var rows = 10;
var columns = 20;
for(i = 0; i < rows; i++) {
$("#container").append("<tr>")
}
$("tr").each(function() {
for(j = 0; j < columns; j++) {
$(this).append("<td>");
}
});
$("td").hover(function() {
$(this).toggleClass("hovering");
});
});
Ignore the ‘Load Table’ button for now and simply hover over the columns in the table. You see that on hover the column will increase in size and change to a green background color. Now add the following inside the $().ready function:
$("#table_loader").submit(function(event) {
event.preventDefault();
$("#ajax_table_container").load("table.html");
});
With this we are simply handling the submit event and loading in a new HTML snippet into the ajax_table_container div you defined earlier in your HTML. You will also need to create a file called table.html with the following contents:
<table id="ajax_container">
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<!-- Add around 10 rows -->
</tbody>
</table>
With the above demo go ahead and click the ‘Load Table’ button(if you are running this locally, you will need to run it via Apache or another HTTP server for the Ajax loading to work). What you will find now is that the initial table reacts perfectly to your event bindings but the table you loaded in dynamically does not. This is where live, and delegate, comes into play. First thing with event delegation is that you do not attach your event listeners to each and every DOM element that needs the behavior but, attach it once to a root object, in the case of live() this will be the document root.
Event Bubbling
What makes event delegation possible is the unique bubbling behavior of events. Let’s take a little detour and look at event bubbling. Whenever an event is fired, for example a click event, that event ‘bubbles’ up the DOM tree and will continue to do so until it encounters either an event listener, in this example a click event listener or, reached the top of the DOM tree in which case it will perform it’s default behaviour. This is demonstrated in the following graphic:

With an understanding of bubbling, let’s then look at how we can use live() to implement event delegation. Change your JavaScript to the code below:
$().ready(function() {
var rows = 10;
var columns = 20;
for(i = 0; i < rows; i++) {
$("#container").append("<tr>")
}
$("tr").each(function() {
for(j = 0; j < columns; j++) {
$(this).append("<td>");
}
});
$("#table_loader").submit(function(event) {
event.preventDefault();
$("#ajax_table_container").load("table.html");
});
$("td").live("hover", function(){
$(this).toggleClass("hovering");
});
});
Having a look at the live() portion of the code, we are attaching an event listener that will listen for hover events on table cells and once the event is triggered, it will either add or remove the specified CSS class. Basically, it does exactly the same as our code did previously with two big differences. The first being that it registers one event listener to the document root that will handle all events of type hover that bubbles up, as apposed to attaching listeners to each and every table cell in the document, secondly, this event listener will listen for this event for all element now and in the future.
To test this, open up the demo below, hover over the table cells and then load in the table via Ajax and hover over it’s cells. You will find that indeed even the table that was dynamically added to the page will now respond in the same way as the initial table for hover events.
jQuery Delegate()
Having seen live() in action let’s take a look at how we will achieve the same using the delegate function. Change your JavaScript to the following:
$().ready(function() {
var rows = 10;
var columns = 20;
for(i = 0; i < rows; i++) {
$("#container").append("<tr>")
}
$("tr").each(function() {
for(j = 0; j < columns; j++) {
$(this).append("<td>");
}
});
$("#table_loader").submit(function(event) {
event.preventDefault();
$("#ajax_table_container").load("table.html");
});
$("#container").delegate("td", "hover", function(){
$(this).toggleClass("hovering");
});
});
The end result of the above is similar to the live demo above but, the syntax is a little different so let’s look at this. Instead of passing in the event we want to listen for and a callback, with delegate we pass in a selector, then the event and then our callback function. The difference here is that delegate will only handle hover events on table cells, if those cells are inside a container with an id of ‘container’. So in our case, this relates to the first of our two tables. To have delegate handle events from any table cell on the page, change your delegate function to the following:
$("body").delegate("td", "hover", function(){
$(this).toggleClass("hovering");
});
The difference between live() and delegate()
So looking at the above you might ask your self, so why would I not just always use live() or delegate()? One argument I have heard is that if you need to avoid ambiguity and target a specific group of elements, for example limiting your event listener(s) to one single table on the page, then you would choose delegate over live. This might be a good reason although, I can be as unambiguous as I need to be just as easily with live as I can with delegate for example:
$("#container td").live("hover", function(){
$(this).toggleClass("hovering");
});
My feeling therefore is that the choice comes down to further performance improvements over and above the performance gains already achieved by using event delegation.
On a side note, I would really appreciate it if one of the core developers of jQuery can confirm this or, if I am wrong here, explain why they would choose the one over the other.
If you have a table that is located far down the DOM and wish to only target elements inside it, delegate should be your method of choice. If you want to handle events on a large set of elements across an entire document, let’s say anchor links, then live would be your choice. To look at all of this in more detail let’s fire-up Firebug.
Accessing Stored Data of a DOM element
I am not going to go into details regarding the jQuery Data API to much in this article, as I will do so in a follow on article but, what you need to know is the following. Internally jQuery uses a Data object into which it stores all types of information related to registered event listeners etc. of all the DOM elements you intend to interact with or have interacted with already, the great thing is, you have access to this information. We will exploit this fact to look at how the different ways to attached event listeners discussed here affects your page.
Assuming you have Firebug open, access the following page, this is the page that does not use event delegation. In Firebug, go to the console tab and enter the following code:
console.log($("td").length);
$("td").each(function() {
$(this).data("events");
});
Run the above code and you will see the following result:

From the above we can see that there exists 200 table cells in the document and each one has some data attached to them. To see the data, click on any of the td’s in the Firebug console and then click on the DOM tab. Below is the result:

For each of our table cells, we have attached four different event listeners. This means that there now exists 800 functions in the DOM, I am sure you can see how this can become a serious problem as the amount of cells grow. Next, let’s do the same exercise with live and delegate.
With live(), even though we specify a selector to which we want to bind our event listener, live() is actually attached to the document and not the DOM element specified in the selector. With that said, the event listener will only react on events that occur within the context specified by the selector. Open up the following page and execute the same code as you did above, this would be the result in Firebug:

We still see the list of all of the table cells as is to be expected but, what you will notice is none of them have the event listener attached. So where is our event listener then? As mentioned, it is attached to the document. Execute the following code in Firebug:
console.log($("td").length);
$(document).data("events");
You will now see the following result in Firebug:

The above clearly demonstrates that live has attached the event listeners to the document root and will then handle all triggered events as they bubble up and reach the document root. This does mean that if you were to limit the range of elements that you want to handle these events for to a single group that existed a long way down in the DOM, each event would have to bubble up all the way to the document body before it would be handled.
In contrast, let’s look at delegate(). Open the following page and execute the code below:
console.log($("td").length);
$(document).data("events");
The following will be the result in Firebug:

As stated, delegate does not attach to the document root but instead to the DOM element specified by the selector. Let’s run another code snippet to prove this. Run the following inside Firebug:
console.log($("td").length);
$("#container").data("events");
Your results will now look more like what we saw just a second ago with live():

A final note then about event delegation, the average amount of time it takes to register your event listener using either live() or delegate() is around 1ms, doing the same with our non delegation code takes around 27ms on a table with 600 table cells. As you can see from this, there is already a huge saving in terms of the time it takes to render your page. From the time that the events are registered, you need not be concerned with having to rebind events as new elements are loaded into the page. There will also never be more than one of each off the event handlers, which makes memory management and the total footprint of your application much smaller.
Before I end of this article we need to look at the other side of live() and delegate() and that is how to remove events that was bound. To accomplish this we have die() and undelegate().
jQuery die()
The die() method has two options for removing events registered with live(). The first of the two merely removes all event listeners that was added by live. Add the following to your HTML:
<form name="die" id="die">
<input type="submit" value="Remove All Events" />
</form>
And the following to your JavaScript:
$("#die").submit(function(event) {
event.preventDefault();
$("td").die();
});
Now open up the demo page. On initial load the cells in the table will perform as usual, clicking on ‘Load Table’ will load the new table and still, our event listener will handle the events as they happen. Now, click on the ‘Remove all Events’ button and hover over the table cells again, our event listeners are removed and hovering over the table cells has no affect. The second way to remove event listeners registered with live() is to use die() passing in the event you wish to remove as well as an optional callback handler that you no longer wish to have executed.
Now currently with live() we pass in the hover parameter as the event we want to listen for, we could have also passed in two different events as follows:
$("td").live("mouseenter mouseleave", function(){
$(this).toggleClass("hovering");
});
The above accomplishes the same end result but with hover, it is left to jQuery to register these two separate events as above. Now because of this, we can remove for example just the mouseleave event without removing mouseenter:
$("#die").submit(function(event) {
event.preventDefault();
$("td").die("mouseleave");
});
Open up the sample page and mouse around the table, after you click on the ‘Remove Mouse Leave’ button, hover over the cells again. You will see that now the mouseenter event is still handled but when you move out of the cell the mouseleave event is no longer handled and the individual table cells remains in the ‘active’ state.
jQuery undelegate()
The undelegate() function works much the same way with just a slightly different syntax. To remove all events that was registered with delegate use the following code:
$("body").undelegate();
Now, say for example you added an event listener to the entire body of the document but now, you only want to remove the listener from a specific portion of the body, this is possible with undelegate using the following code:
$("body").undelegate("#container", "mouseleave");
The last of the variants of undelegate is similar to the last example but with one additional parameter. The additional parameter that can be passed in is a callback function but, unlike die(), this is not a handler you wish to remove but, one you wish to have executed when the undelegate function is called.
And with that we have covered event delegation using jQuery and have added a very powerful technique to your scripting arsenal. I look forward to your comments and questions.
