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); }