Understanding event delegation

6th March 2012

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.

<table>
    <tr></tr>
    <tr></tr>
    <tr></tr>
    <tr></tr>
    ... many more rows
</table>

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!

  • http://codebymonkey.com Paul Adam Davis

    Well I just learnt something new!
    Thanks for this, Ben. :)

  • http://www.jackfranklin.co.uk Jack Franklin

    Worth pointing out explicitly that every method jQuery had prior to 1.7 of binding events, such as click, hover, live, bind, delegate and so on are now all effectively deprecated, if you take a look at the source for .click in 1.7: http://james.padolsey.com/jquery/#v=git&fn=jQuery.fn.click you can see it just calls .on.

  • http://twitter.com/letsallplaygolf Jay Vincent

    Thank you for finally explaining this in a way that makes sense to me! I’ve never fully understood this, and I’ve been using .on() in exactly the same way I used .live() and it just wasn’t working on dynamically inserted content – but now I can see why.

  • Marius Adrian

    Great article! Thank you. But… it was a pain to read your post because the background color is killing my eyes. Please change it

    • http://twitter.com/ilmv Ben Everard

      Hi Marius, I agree, the orange/yellow was a bit to steep… I’ve changed it to red now, a big improvement I think.

  • Shahab

    a question would be, how can we specify which TR has been clicked ? and what if we have many div blocks like bellow generated by php all come with the same ID and the same name and class?


    a number


    Add

    how do you alert the number on click on Add?

    • beneverard

      How can you ‘determine’ what table row the click has come from? You could use the .index() method to find that elements index, you could put add a class to the table row and check it on the click event, you could do the same with data-* attributes and the .data() method.

      I don’t understand the second part of your question, could you clarify please.

      • Shahab

        how can you add a class to the table and call the specific table with the class name? if the table will be generated dynamically ? all the class names for all the tables will be the same unless you change the class names dynamically too. this is my thought.

        as for the 2nd part: imagine that a container of 1 item in a shopping cart, when you have price, picture, add to cart btn, the price + picture + …. should be added to cart by clicking on add to cart, correct?

        ok, here we go: the container has been generated dynamically with a class name like class=”item1″ and so all the containers in that page will have item1 as the class name, you will have access to the first div with class = “item1″ ONLY. so the question is how may I find the 5th container’s price if the user clicked on add to cart in the 5th