How to use event delegation with jQuery

An important note before we begin: I do not use jQuery myself, because I very much believe in Vanilla JS and The Lean Web.

I wrote this article because jQuery is still widely used as of 2019, and that's not going to change overnight. I want to educate people so they can at least make their code a little bit more performant.

Let's begin!


In JavaScript, event delegation is a technique you can use when listening for events in the DOM. Instead of looping through each element and applying an event listener to each one, you listen for the event once on a common ancestor, and allow the event to "bubble" up through the DOM.

Let's look at an example. Imagine we have a table of best-selling music artists. When we click on a cell, we want its background colour to change to blue. Here's what our HTML might look like:

<table>
<thead>
<tr>
<th>Artist</th>
<th>Country/Market</th>
<th>Period active</th>
<th>Release year of first charted record</th>
<th>Genre(s)</th>
<th>Total certified units</th>
<th>Claimed sales</th>
</tr>
</thead>
<tbody>
<tr>
<td>The Beatles</td>
<td>United Kingdom</td>
<td>1960&ndash;1970</td>
<td>1962</td>
<td>Rock, Pop</td>
<td>278.4 million</td>
<td>600 million</td>
</tr>
<tr>
<td>Elvis Presley</td>
<td>United States</td>
<td>1954&ndash;1977</td>
<td>1954</td>
<td>Rock and roll, Pop, Country</td>
<td>225.2 million</td>
<td>600 million</td>
</tr>
<tr>
<td>Michael Jackson</td>
<td>United States</td>
<td>1964&ndash;2009</td>
<td>1971</td>
<td>Pop, Rock, Dance, Soul, R&B</td>
<td>231.8 million</td>
<td>350 million</td>
</tr>
<tr>
<td>Elton John</td>
<td>United Kingdom</td>
<td>1964&ndash;present</td>
<td>1969</td>
<td>Pop, Rock</td>
<td>186.4 million</td>
<td>300 million</td>
</tr>
<tr>
<td>Madonna</td>
<td>United States</td>
<td>1979&ndash;present</td>
<td>1982</td>
<td>Pop, Dance, Electronica</td>
<td>173.6 million</td>
<td>300 million</td>
</tr>
</tbody>
</table>

How NOT to do it #

Let's start with vanilla JS for a second. Here's one way you could tackle it, but it's not the best way:

// Get all table data cells
var cells = document.querySelectorAll("td");

// Loop through each one and add an event listener to it
for (var i = 0; i < cells.length; i++) {
cells[i].addEventListener("click", function() {
this.style.backgroundColor = "lightblue";
}, false);
}

This works, but the problem with this approach is that there are 35 td elements, which means the click handler is being added 35 times... That's really not ideal for performance. The anonymous function isn't great, either. It's not particularly flexible or readable.

Unfortunately, this is exactly what happens under the hood when most people write jQuery. I most often see people write event listeners like this when they jQuery:

$("td").on("click", function() {
$(this).css("backgroundColor", "lightblue");
});

This looks shorter, but it's still doing the same amout of work under the hood, adding the event listener to each td element individually... And the anonymous function still sucks. Yikes.

The RIGHT way #

Let's look at how we can refactor this using event delegation. It's not only more performant, but also much more readable. We'll start with vanilla JS again, then move onto jQuery.

// Get the closest common ancestor of the `td` elements
var tbody = document.querySelector("tbody");

// Change the background color of an element
function changeBackgroundColor(element, color) {
element.style.backgroundColor = color;
}

tbody.addEventListener("click", function(event) {

// Exit the function if the clicked element ISN'T a `td`
if (event.target.tagName !== "TD") return;

// Change the background color to light blue
changeBackgroundColor(event.target, "lightblue");

}, false);

Instead of getting all td elements, we're just getting the closest common ancestor: in this case, the tbody element.

I've created a function that we can use to change the background colour of the td elements. This is not only more readable, but also more flexible because we can pass in any element and any valid CSS color value.

In our click handler, we're still using an anonymous function, but we're passing in the event object. This allows us to check which element fired the event using the event object's target property. If it wasn't a td element, we're simply using a return statement to quit the function and do nothing else.

If it was a td element, though, the function keeps running. Now we can call our aptly named changeBackgroundColor function, passing in the element that was clicked (event.target) and the CSS color value we'd like to use (lightblue). This is much nicer because we're only adding the event listener once, and the code is more readable.

Here's how you'd do the same thing with jQuery:

// Get the closest common ancestor of the `td` elements
var $tbody = $("tbody");

// Change the background color of an element
function changeBackgroundColor(element, color) {
$(element).css("backgroundColor", color);
}

// When a `td` element is clicked,
// change its background color to light blue
$tbody.on("click", "td", function(event) {
changeBackgroundColor(event.target, "lightblue");
});

The main thing to note here is the second parameter that jQuery's .on() method takes; in this case, we've used the string "td" as our argument. As explained in the jQuery API Documentation, the parameter accepts:

A selector string to filter the descendants of the selected elements that trigger the event. If the selector is null or omitted, the event is always triggered when it reaches the selected element.

In other words, we're doing the same thing as we are in our vanilla JS example. We're attaching the event to the tbody element, and "filtering" it to only run when a td element is clicked.

The only difference is that because jQuery accepts this argument, we were able to remove this check from our callback function:

// Exit the function if the clicked element ISN'T a `td`
if (event.target.tagName !== "TD") return;

This is because jQuery handles that automatically when—and only when—you pass in this second argument.

Here's the jQuery version in action:

See the Pen Event delegation with jQuery by Kieran Barker (@kieranbarker) on CodePen.

If you have any questions about this article, please reach out on Twitter! I'm @barking_mad_dev.