Understanding event delegation

3 min read javascript

For the past few years I’ve been rocking jQuery… well, doing okay at it. Ever since jQuery 1.7 came out we’ve been able to play with the new .on() method, however it was only recently when I realised why I should be using .on() instead of .live(), and indeed any other event methods.

Event delegation is the topic of today’s post as it was the primary reason for me not understanding .on(), and I’m writing about it because it took someone to tell me about it for it to sink in… so keep reading and I may be able to do the same for you.

Our example code

So for the purpose of this post, let’s assume we have a table, and in that table we have 500 rows, we’ll only show a few but imagine there are many more there.

... many more rows

For our example event we’ll fire an alert box when a row is clicked, nice and simple.

Event bubbling

One thing you need to understand about events in jQuery is event bubbling, it’s quite simple really, when an event fires on an element, it also fires on each of its parents up the DOM to the document*. This means when we click on one of our table rows the table also receives a click event, and so does each parent until we hit the document level.

  • Some events don’t do this, such as submit

Non-delegation event binding

So if you’re like me and haven’t used event delegation yet you might use a very simple selector to base your event on, we want to target the click event on all rows right? Easy!

$('table tr').click(function() {
alert("A normal click event");

So that was pretty easy, and it does exactly what we want it to do, but what are we actually telling jQuery to do? We’re asking it to bind an event to every matching element based on our selector, this event is being bound to 500 individual elements!

It goes without saying that this is an inefficient method of binding an event, but how can we improve it?

Event delegation

Right, so how can event delegation improve our standard selector? Remember what we discussed previously about events bubbling up the DOM, every table row click also fires a table click? Well we can take advantage of event bubbling by applying our click event to the parent table, and check if the clicked item was the table row. In other words we’ll be delegating the click event to the table.

We’re doing a bit more work in our event, but we now only have one bound event. Actually, as developers we’re not doing any extra work… jQuery gives us the tools to delegate easily, take a look at this:

$('table').on('click', 'tr', function() {
alert("We're delegating baby");

So lets explain exactly what we’ve done, our selector targets the table and the first parameter of the on method is the event type, click in this instance. The second parameter is where our delegation is set, we pass through the context of tr, this tells jQuery that we’re delegating the click event from the table row to the parent table.

This method improves performance as the number of events bound is significantly reduced (from 500 to one), but we also take advantage of another side effect, being able to fire events against dynamically inserted content. So if you dynamically add another 500 rows to the table our last event will still fire on all of them, with no extra work. Whereas the .click() event would not, as the new rows would not have an event bound directly to them.

Alternative to .live()

At uzERP I migrated our JavaScript framework from Prototype to jQuery about two years ago, and ever since we’ve been using the .live() event method for practically everything, as lots of our content is dynamically inserted from AJAX requests.

The .live() method employs event delegation to allow you to bind events to an element, even if it doesn’t exist at runtime or is replaced thereafter. The issue is .live() bubbles the event right up to the document level, and then checks where it came from to determine which event(s) to fire, that’s a lot of bubbling.

The .live() method was deprecated in jQuery 1.7, and only remains for legacy reasons. Thus take a moment to improve your code and go and remove .live() for .on(), I know I will be.

Happy delegating!