Tip - Scripting Dynamically-Loaded Elements

Many times, once you've loaded content dynamically (for instance, with Ajax.Updater), you want to script elements in the new content — attach event handlers, that sort of thing.

Perhaps you have a central clickHandler function that should be called whenever anything with the class "clickable" is clicked. (This may not be the best example, as event delegation may well be a better answer in this particular case.) Let's assume you have a function for hooking those up that looks like this:

function hookClickables(parent)
{
    parent = $(parent);
    if (parent)
    {
        parent.select('.clickable').invoke('observe', 'click', clickHander);
    }
}

That looks for all "clickable" children of a given parent and hooks them up to the clickHandler function via Element.observe (indirectly, via Enumerable.invoke).

So your first take at dynamically loading content and hooking up the clickables might look like this:

// INCORRECT (in two ways)
function loadContent(target, url)
{
    new Ajax.Updater(target, url);
    hookClickables(target);
}

There are two problems with this, but let's start with the first: Ajax.Updater is (by default) asynchronous. That means that your hookClickables function gets called immediately after the update has been requested but almost certainly before it completes. So you won't hook up the new clickables, as they're not there yet.

This is easily fixed by using the onComplete callback described in the Ajax.Updater options:

// INCORRECT (one down, but one to go)
function loadContent(target, url)
{
    new Ajax.Updater(target, url, {
        onComplete: function()
        {
            hookClickables(target);
        }
    });
}

Now you won't try to hook up the clickables until after the content comes back from the server. (Note that loadContent doesn't wait around, it returns immediately after the request has been initiated, before the content arrives.)

That's the first step, but there's a second problem: Although by the time your onComplete handler is called the content has been set on the target element (via Element.update), the browser needs a moment to process it and update the DOM. (This is always the case with Element.update, the updated content is not immediately accessible.) With the code above, you haven't given it that moment.

This is also easily fixed! Just use Function.defer:

// CORRECT:
function loadContent(target, url)
{
    new Ajax.Updater(target, url, {
        onComplete: function()
        {
            hookClickables.defer(target);
        }
    });
}

Function.defer "…schedules the function to run as soon as the interpreter is idle" rather than running the function right away, which gives the browser the time it needs to update the DOM. Now your code will work reliably: If you load content via your loadContent function, once it arrives the "clickables" in it will be hooked up to the clickHandler.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License