Awais Imran Awais Imran - 6 months ago 19
Javascript Question

jQuery doesn't work after content is loaded via AJAX

On this page I have a jQuery popup window and thumbnail resizable images. If I mouse over on the thumbnails, the images are resizing perfectly. Also, when I click on the big yellow TV button "QuickBook TV" in the footer, the popup appears perfectly as I want it to.

However, when I click on the "Next" or "Prev" buttons, AJAX is used to load the new content and my jQuery no longer functions for the popup or thumbnail images. I have searched a number of forums looking for information on this issue, but due to having limited knowledge of jQuery I've been unable to understand what I need to do.

Following is the popup jQuery

$(document).ready(function() {

$(".iframe").colorbox({ iframe: true, width: "1000px", height: "500px" });
$(".inline").colorbox({ inline: true, width: "50%" });
$(".callbacks").colorbox({
onOpen: function() { alert('onOpen: colorbox is about to open'); },
onLoad: function() { alert('onLoad: colorbox has started to load the targeted content'); },
onComplete: function() { alert('onComplete: colorbox has displayed the loaded content'); },
onCleanup: function() { alert('onCleanup: colorbox has begun the close process'); },
onClosed: function() { alert('onClosed: colorbox has completely closed'); }
});

//Example of preserving a JavaScript event for inline calls.
$("#click").click(function() {
$('#click').css({ "background-color": "#f00", "color": "#fff", "cursor": "inherit" }).text("Open this window again and this message will still be here.");
return false;
});
});


And this is the thumbnails jQuery

$(function() {

var xwidth = ($('.image-popout img').width())/1;
var xheight = ($('.image-popout img').height())/1;

$('.image-popout img').css(
{'width': xwidth, 'height': xheight}
); //By default set the width and height of the image.

$('.image-popout img').parent().css(
{'width': xwidth, 'height': xheight}
);

$('.image-popout img').hover(
function() {
$(this).stop().animate( {
width : xwidth * 3,
height : xheight * 3,
margin : -(xwidth/3)
}, 200
); //END FUNCTION

$(this).addClass('image-popout-shadow');

}, //END HOVER IN
function() {
$(this).stop().animate( {
width : xwidth,
height : xheight,
margin : 0
}, 200, function() {
$(this).removeClass('image-popout-shadow');
}); //END FUNCTION

}
);

});

Answer

jQuery selectors select matching elements that exist in the DOM when the code is executed, and don't dynamically update. When you call a function, such as .hover() to add event handler(s), it only adds them to those elements. When you do an AJAX call, and replace a section of your page, you're removing those elements with the event handlers bound to them and replacing them with new elements. Even if those elements would now match that selector they don't get the event handler bound because the code to do that has already executed.

Event handlers

Specifically for event handlers (i.e. .click()) you can use event delegation to get around this. The basic principle is that you bind an event handler to a static (exists when the page loads, doesn't ever get replaced) element which will contain all of your dynamic (AJAX loaded) content. You can read more about event delegation in the jQuery documentation.

For your click event handler, the updated code would look like this:

$(document).on('click', "#click", function () {
    $('#click').css({
        "background-color": "#f00",
        "color": "#fff",
        "cursor": "inherit"
    }).text("Open this window again and this message will still be here.");
    return false;
});

That would bind an event handler to the entire document (so will never get removed until the page unloads), which will react to click events on an element with the id property of click. Ideally you'd use something closer to your dynamic elements in the DOM (perhaps a <div> on your page that is always there and contains all of your page content), since that will improve the efficiency a bit.

The issue comes when you need to handle .hover(), though. There's no actual hover event in JavaScript, jQuery just provides that function as a convenient shorthand for binding event handlers to the mouseenter and mouseleave events. You can, however, use event delegation:

$(document).on({
    mouseenter: function () {
        $(this).stop().animate({
            width: xwidth * 3,
            height: xheight * 3,
            margin: -(xwidth / 3)
        }, 200); //END FUNCTION

        $(this).addClass('image-popout-shadow');
    },
    mouseleave: function () {
        $(this).stop().animate({
            width: xwidth,
            height: xheight,
            margin: 0
        }, 200, function () {
            $(this).removeClass('image-popout-shadow');
        }); //END FUNCTION

    }
}, '.image-popout img');

jQuery plugins

That covers the event handler bindings. However, that's not all you're doing. You also initialise a jQuery plugin (colorbox), and there's no way to delegate those to elements. You're going to have to simply call those lines again when you've loaded your AJAX content; the simplest way would be to move those into a separate named function that you can then call in both places (on page load and in your AJAX requests success callback):

function initialiseColorbox() {
    $(".iframe").colorbox({
        iframe: true,
        width: "1000px",
        height: "500px"
    });
    $(".inline").colorbox({
        inline: true,
        width: "50%"
    });
    $(".callbacks").colorbox({
        onOpen: function () {
            alert('onOpen: colorbox is about to open');
        },
        onLoad: function () {
            alert('onLoad: colorbox has started to load the targeted content');
        },
        onComplete: function () {
            alert('onComplete: colorbox has displayed the loaded content');
        },
        onCleanup: function () {
            alert('onCleanup: colorbox has begun the close process');
        },
        onClosed: function () {
            alert('onClosed: colorbox has completely closed');
        }
    });
}
Comments