How To - Load Scripts Dynamically

Sometimes you find that you want to load a script file dynamically without refreshing the entire page. For example, perhaps your site offers an optional "live chat" interface and to keep initial page load fast you only want to load the script for it if the user opens it. With modern browsers, this is quite easy.

Loading the script

Actually loading the script is trivial: Add a new script element to the head of the page:

var head;
var script;
 
head = $$('head')[0];
if (head)
{
    script = new Element('script', { type: 'text/javascript', src: 'dynamic.js' });
    head.appendChild(script);
}

The browser will load the script asynchronously (the code above completes before the script is loaded).

Knowing when the script loads

There are a couple of ways to know when the script has finished loading.

Using a callback

If you're in control of the script you're loading, the easiest way is to have it call a callback in the script that loaded it. Here's an example page that dynamically loads a script when you click a button, where the script "calls back" when it's loaded:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>QD Dynamic Load Example</title>
<style type="text/css">
</style>
<script type="text/javascript" src='libs/prototype.js'></script>
<script type="text/javascript">
function init()
{
    $('btnGo').observe('click', go);
}
function go()
{
    var head;
    var script;
 
    head = $$('head')[0];
    if (head)
    {
        script = new Element('script', { type: 'text/javascript', src: 'dynamic.js' });
        head.appendChild(script);
    }
}
function scriptLoaded(scriptName)
{
    alert('Script "' + scriptName + '" loaded');
}
document.observe('dom:loaded', init);
</script>
</head>
<body>
<input id='btnGo' type='button' value='Go'/>
</body>
</html>

Here's the dynamic.js file:

function coolFeature()
{
    alert('Coolness!');
}
scriptLoaded('dynamic');

When you click the "Go" button, the dynamic.js file is loaded and calls scriptLoaded. Note how the dynamically-loaded script has access to the other scripts on the page. Those other scripts, of course, also now have access to the new script, so we could modify our scriptLoaded function to call the cool new feature:

function scriptLoaded(scriptName)
{
    if (scriptName == "dynamic")
    {
        coolFeature();
    }
}

Using a timer

If you're not in control of the script you're loading, you can still check whether it's loaded by watching for a symbol it defines to become defined. In the case of our dynamic.js file above, we can watch for the 'coolFeature' function to appear:

var head;
var script;
 
head = $$('head')[0];
if (head)
{
    script = new Element('script', { type: 'text/javascript', src: 'dynamic.js' });
    head.appendChild(script);
    new PeriodicalExecuter(function(pe) {
        if (typeof window['coolFeature'] != "undefined")
        {
            alert("Script loaded!");
            pe.stop();
        }
    }, 0.25);
}

That uses a PeriodicalExecuter to check for the symbol every quarter second, triggering some code (in our case an alert) when it does and stopping watching. A more robust solution might use a general-purpose function, a timeout, and success/fail callbacks:

var head;
var script;
 
head = $$('head')[0];
if (head)
{
    script = new Element('script', { type: 'text/javascript', src: 'dynamic.js' });
    head.appendChild(script);
    watchForSymbol({
        symbol:     'coolFeature',
        timeout:    3,
        onSuccess:  function(symbol) {
            alert(symbol + " loaded!");
        },
        onTimeout:  function(symbol) {
            alert(symbol + " didn't load before timeout!");
        }
    });
}
function watchForSymbol(options)
{
    var stopAt;
 
    if (!options || !options.symbol || !Object.isFunction(options.onSuccess))
    {
        throw "Missing required options";
    }
    options.onTimeout = options.onTimeout || Prototype.K;
    options.timeout = options.timeout || 10;
    stopAt = (new Date()).getTime() + (options.timeout * 1000);
    new PeriodicalExecuter(function(pe) {
        if (typeof window[options.symbol] != "undefined")
        {
            options.onSuccess(options.symbol);
            pe.stop();
        }
        else if ((new Date()).getTime() > stopAt)
        {
            options.onTimeout(options.symbol);
            pe.stop();
        }
    }, 0.25);
}
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License