Delegation in jQuery, or Why Isn’t My New HTML Responding to Click Events?

I was recently working on a project that lets a user add items to a list. The HTML for each new item appears on the page via jQuery. I ran into a problem, though. I had created several jQuery methods allowing the user to edit, delete, and do other things to the list items by clicking on buttons associated with each item. But newly-added items were not responding to click events. They would start responding to clicks only after I reloaded the page. Everything in my code seemed to be working properly; the newly-added HTML was in the right place; why weren’t the clicks working?

Then I remembered event delegation.

Here’s an example of how event delegation works. A user of my project can change the name of a task by clicking on the name, typing the new name, and clicking away from the name. My original code for changing the name looked like this:

$(".edit_task").on("ajax:success", function(event, data, status, xhr){
    newName = $(data.template).find(".task_name").html();
    $(this).parents(".task_parent").find(".task_name").html(newName);
}

Each task is inside a class called edit_task. When the name is clicked, the new name is saved to the database and then an Ajax request is made. If it’s successful, the edit_task class is targeted, and the new task name is displayed inside that class on the screen.

That works fine for tasks that already exist when the page first loads. That’s because the “ajax:success” event listener gets bound to elements when the page first loads, including all existing instances of the edit_task class. If a new task is added after the page has loaded, the event listener is not listening to the newly-added instance of edit_task.

How to solve this problem? You can just delegate the event listener to an element further up the hierarchy that already exists.

In my project, a div with a container class wraps around the entire task list. This class exists even if there are no tasks yet; it will always be there. Therefore, I changed my code to use the container class as the event listener. I just changed the first line of the above code to the following:

$(".container").on("ajax:success", ".edit_task", function(event, data, status, xhr){

There are just two changes here. The DOM element at the beginning gets changed to “.container”, and then, inside the parentheses, after “ajax:success” (the event we’re listening for, which might also be something like “click” or “select” or “blur”), we add the actual element that’s being targeted, the one that originally appeared at the beginning – in this case, edit_task.

Simply put, just replace:

target.on(event, function)

with:

higher-element.on(event, target, function)

It’s that simple. You’ve now future-proofed your jQuery. Thanks, event delegation!