jQuery Events Delegated Events


Example

Let's start with example. Here is a very simple example HTML.

Example HTML

<html>
    <head>
    </head>
    <body>
        <ul>
            <li>
                <a href="some_url/">Link 1</a>
            </li>
            <li>
                <a href="some_url/">Link 2</a>
            </li>
            <li>
                <a href="some_url/">Link 3</a>
            </li>
        </ul>
    </body>
</html>

The problem

Now in this example, we want to add an event listener to all <a> elements. The problem is that the list in this example is dynamic. <li> elements are added and removed as time passes by. However, the page does not refresh between changes, which would allow us to use simple click event listeners to the link objects (i.e. $('a').click()).

The problem we have is how to add events to the <a> elements that come and go.


Background information - Event propagation

Delegated events are only possible because of event propagation (often called event bubbling). Any time an event is fired, it will bubble all the way up (to the document root). They delegate the handling of an event to a non-changing ancestor element, hence the name "delegated" events.

So in example above, clicking <a> element link will trigger 'click' event in these elements in this order:

  • a
  • li
  • ul
  • body
  • html
  • document root

Solution

Knowing what event bubbling does, we can catch one of the wanted events which are propagating up through our HTML.

A good place for catching it in this example is the <ul> element, as that element does is not dynamic:

$('ul').on('click', 'a', function () {
  console.log(this.href); // jQuery binds the event function to the targeted DOM element
                          // this way `this` refers to the anchor and not to the list
  // Whatever you want to do when link is clicked
});

In above:

  • We have 'ul' which is the recipient of this event listener
  • The first parameter ('click') defines which events we are trying to detect.
  • The second parameter ('a') is used to declare where the event needs to originate from (of all child elements under this event listener's recipient, ul).
  • Lastly, the third parameter is the code that is run if first and second parameters' requirements are fulfilled.

In detail how solution works

  1. User clicks <a> element
  2. That triggers click event on <a> element.
  3. The event start bubbling up towards document root.
  4. The event bubbles first to the <li> element and then to the <ul> element.
  5. The event listener is run as the <ul> element has the event listener attached.
  6. The event listener first detects the triggering event. The bubbling event is 'click' and the listener has 'click', it is a pass.
  7. The listener checks tries to match the second parameter ('a') to each item in the bubble chain. As the last item in the chain is an 'a' this matches the filter and this is a pass too.
  8. The code in third parameter is run using the matched item as it's this. If the function does not include a call to stopPropagation(), the event will continue propagating upwards towards the root (document).

Note: If a suitable non-changing ancestor is not available/convenient, you should use document. As a habit do not use 'body' for the following reasons:

  • body has a bug, to do with styling, that can mean mouse events do not bubble to it. This is browser dependant and can happen when the calculated body height is 0 (e.g. when all child elements have absolute positions). Mouse events always bubble to document.
  • document always exists to your script, so you can attach delegated handlers to document outside of a DOM-ready handler and be certain they will still work.