When You Should Use jQuery’s Live and Delegate Methods

Recently, I was working on a project at Hashrocket that involved many different types of elements being introduced into the DOM after it was loaded, and most of those elements needed Javascript events to be attached to them when they were written. Since the release of jQuery version 1.3, everyone has heard of and used the .live() method for attaching these elements whenever possible. Live works great if you’re attaching behavior to an element that may show up pretty much anywhere in the DOM tree. However, most times the events being attached are for an element that only appears within a certain context.

"Well, hey, since these list items are only going to need this click event attached if they show up in my #xmas_list ul, why not reduce the scope by using a selector like $(‘#xmas_list li’)? That will speed things up, right?"

Well, that’s better. But reading through the API docs on .live(), we see this:

The handler passed to .live() is never bound to an element; instead, .live() binds a special handler to the root of the DOM tree.

Oh my. So when you write a live event binding like $(“a[rel=external]”).live(newWindow), you’re really attaching an event to the document and triggering this event when the element’s event “bubbles up” the DOM tree to the document. So here’s the basics of what transpires when this link is clicked:

  1. User clicks the link
  2. The click event fires on any parent elements and parents of those elements until we finally arrive at…
  3. the document. An event attached to the document that fires whenever a click event occurs (even on elements that do not fit the live binding selector criteria), a function is called that tests the event’s current target to see if it matches a selector that jQuery is currently keeping track of with a live event binding.
  4. If a match is found, the corresponding event is called with that element as its context.

Wow. Every click event that bubbles up to the document gets checked? That seems a bit inefficient. Even with the id prepended to the selector chain, this process occurs. How do we get the intended behavior of only listening up to a specified parent node instead? Depending on the version of jQuery you’re using, there’s one or two ways.

The first, which works in version 1.3 and later, is the .delegate() method. This allows you to start at a certain context and live bind events to any newly introduced descendants to that context. The syntax is very similar, with only a minor change to where the target element is specified. Following our Xmas list example, here’s how we’d bind a click event on any list items within it:

$(“ul#xmas_list”).delegate(“li”, “click”, function() { /* do stuff */ });

This ensures that the listener created to check whether or not a list item within the Xmas list was clicked bubbles up only to the Xmas list ul, and not all the way out to the document. A far more efficient way to “live bind” events.

The other way, which I think may be counter to the way many are used to reading jQuery selectors, is calling .live() with the specified context. According to the documentation, this method of passing in a context works with version 1.4 and later. Here’s what the same event binding method would look like using .live() instead:

$(“li”, $(“ul#xmas_list”)[0]).live(“click”, function() { /* do stuff */ });

For those who are wondering what the [0] is for after the Xmas list selector, this is referencing an individual DOM element rather than a jQuery object (or collection of objects). This means that if you had more than one Xmas list on a page (using a class selector in that instance, of course), you would have to switch back to the delegate syntax of live binding anyways. Personally, I think the selector reads wrong, since jQuery selectors are supposed to mimic the way CSS selector chains work. In CSS, you wouldn’t write “li, ul#xmas_list” to style any lis in that particular ul. You’d go from the outside in. Delegate has the same feel.

Now if anyone’s really interested in how .delegate() works under the hood, a great resource to check out is jQuery Deconstructed. Any time you want to see the underlying Javascript behind the methods and properties in jQuery, check out this resource. The specific area we’re going to look at is the delegate function. Expand that accordion and you’ll see this:

delegate: function( selector, types, data, fn ) {
    return this.live( types, data, fn, selector );
},

What this shows us is that the .live() method is doing the work all along! In fact, if you check out other jQuery methods like .first() and .size(), you’ll see that many methods are either simple aliases or alternate method calls with additional parameters sent in (see: .detach()). What they are there for is to improve readability. If someone new to jQuery saw .first() being called on a set of elements, it would be pretty obvious what the return would be. However, seeing .eq(0) might not be as apparent.

So when you’re writing your live events using jQuery, always think about whether you should specify a context or not. In theory, your page interactions will be a little bit snappier, especially when you have a large amount of event-driven code.

Notes

  1. shaneriley posted this