Click doesn’t work after AJAX load – jQuery

If you use AJAX on your website, beware that events like click, submit, hover might not work if you don’t attach them properly. I will be using click event to describe the problem, but all of this applies to most events in jQuery.

Problem

Let say I have an item with a title and hidden description. The description is shown on click with this code:

$('.toggle-item').click(function(e){
    e.preventDefault();
    $(this).parent().next('.category-item-content').toggle();
});

I load new items without reloading my page with AJAX with this code:

$('#category-wrapper').load(link.attr('href'));

When I load a website everything works fine until I load new items or replace old once. Ajax still works fine but toggle for item description doesn’t work anymore. WAT? Check this jsfiddle below for working example of that problem.
Note, you can change tabs and view JS and HTML that was used for this example.

Solution 1

Click event is attachted to existing elements on page load. The key word here is “existing”. When we load something with AJAX we manipulate DOM. We are placing totally new element that didn’t exist when page loaded. So what we need to do is to attach that event after placing new content. If you use .load() function (like in my example) you just need to repeat $('.toggle-item').click() in the callback function:

$('#category-wrapper').load(link.attr('href'),function(responseText, textStatus, XMLHttpRequest){
    $('.toggle-item').click(function(e){
        e.preventDefault();
        $(this).parent().next('.category-item-content').toggle();
    });
});

that way, we are also attaching click event to newly added elements.

We could of course move $('.toggle-item').click() outside callback and make it as a function to not repeat ourselves (for better maintenance and all, you know, right? DRY?), but I want you to understand the why and how.

If you use .ajax() function instead of .load() just attach event in .done() part (success hook) of AJAX call. Mu updated jsfiddle with solution numero uno below:

Solution 2

What if we don’t want to attach event every time AJAX is called? Is there any other, more “global” solution? Of course there is. We can use .on() function. Yay!

But wait!

Isn’t .click() same as .on('click')? Yes it is. But here’s a fun part – we can delegate event.

Delegated events have the advantage that they can process events from descendant elements that are added to the document at a later time

So instead of attaching click event on completed ajax we just attaching it more errm “globally”:

/* Toggle category item */
$('#category-wrapper').on('click','.toggle-item',function(e){
	e.preventDefault();
	$(this).parent().next('.category-item-content').toggle();
});

We could attach it to $('body').on('click','.toggle-item',function(){}); but:

For best performance, attach delegated events at a document location as close as possible to the target elements. Avoid excessive use of document or document.body for delegated events on large documents.

Remember – delegated event must be attached to the element that exist on the page at the time your code makes the call and doesn’t change during ajax calls – some kind of wrapper. This is the reason why people are attaching it to body, but if it’s possible select the element that is closer to targeted element.

On a side note in the earlier versions of jQuery instead of .on() we used .live() or .delegate() functions.