Sometimes it makes sense to dynamically load JavaScript library code only at the point at which it's required. If functionality which uses that library is used rarely then it makes little sense incur the overhead of an additional HTTP request or data transfer on page load.
We can dynamically load any JavaScript and add it to the document with the following function.
Making a dynamic request
function loadScript(sScriptSrc) {var oHead = document.getElementById('head')[0];var oScript = document.createElement('script');oScript.type = 'text/javascript';oScript.src = sScriptSrc;oHead.appendChild(oScript);}
This approach isn't without problems though. The request will load asynchronously which means that any JavaScript which depends on code returned by the request may continue to run before the request completes resulting in errors.
Callback event handlers
As described in far greater detail in JavaScript Madness: Dynamic Script Loading browsers provide the ability to specify a callback function to run once the script load completes. In most it's as simple as assigning a callback function to the script.onload event handler.
oScript.onload = callback;
Internet Explorer, as in so many cases, does things differently. It supports a onreadystatechange event handler to which we can assign a callback function. It's slightly more complicated than that used by other browsers as one has to check a return status before running the callback.
oScript.onreadystatechange = function() {if (this.readyState == 'complete') {callback();}}
Combining the parts gives us:
function loadScript(sScriptSrc, oCallback) {var oHead = document.getElementById('head')[0];var oScript = document.createElement('script');oScript.type = 'text/javascript';oScript.src = sScriptSrc;// most browsersoScript.onload = oCallback;// IE 6 & 7oScript.onreadystatechange = function() {if (this.readyState == 'complete') {oCallback();}}oHead.appendChild(oScript);}
Detecting script load with timeouts
Reading a number of sources however it seems that there is a lack of trust in the cross browser robustness of these event handlers. One could instead use timeouts to poll for the existence of required functions returned by the script request and run our callback once detected.
I wrote the following function to achieve just this recently. The function takes two main parameters, a string representing the function to poll for and a reference to the callback function to run once it's found.
function onFunctionAvailable(sMethod, oCallback, oObject, bScope) {if (typeof(eval(sMethod)) === 'function') {bScope ? oCallback.call(oObject) : oCallback(oObject);} else {setTimeout(function () {onFunctionAvailable(sMethod, oCallback, oObject, bScope);}), 50}}
Two additional parameters allow one to define the scope of "this" within the callback function.
The hardest part of this function was working out how to detect the existence of a function from a string. Fortunately there's no shortage of people I can turn to with JavaScript skills better than my own. Thanks to Christian, Stuart and Andrew for their input.
Hey Ed - I was trying to implement a version of onFunctionAvailable but having some trouble getting it to work in Safari. Perhaps my edits are causing the problem. I was a little confused on what the parameter oObject and bScope represent and removed them. Any chance you'd give an example of how to call your function? Thanks in advance, I found your blog helpful. btw - here is my cutdown version: --- function onFunctionAvailable(sMethod) { var a = sMethod.split('('); if( a.length < 1 ) { return; } var funcName = a[0];
if( typeof(eval(funcName)) === 'function' ) { eval(sMethod); } else { setTimeout( function () { onFunctionAvailable(sMethod ); } ), 1000 }
}
ejeliot,
you saved me lots of heartache. much appreciated.
An example will be appeciated too :)
Nice! I wasn't aware of the onload/readystatechange properties of the script object. BTW from what I've read eval is considered a security risk and you are going ot want to use square bracket syntaxt to detect that function - if( typeof( window[funcName] )
Hi, have you tried to detect if an error ocurred while loading the script?
I wrote the following script, works in Firefox but not in IE:
var resourceElement = document.createElement('script'); [... etc etc ...]
resourceElement.onerror = function(message) { alert(message); };
For IE, the only thing I tried is using window.onerror , but then I can't identify which script failed. Anyway it works if you just want to know if *any* of the required scripts failed, but with a really poor level of accuracy.
I'd appreciate any help or thoughts you may share. Better if you write an article about handling dynamic JS loading errors. :)
Thanks,
LK
Thanks for the sum up. Very good information in here.
One question though: Wouldn't it be sufficient if you simply call the callback function from your (dynamically loaded) JavaScript file? This should work at least as long as you have control over that file.
it worked very well I think this: var oHead = document.getElementById('head')[0];
to be replaced as var oHead = document.getElementsByTagName('head')[0];
anyway fine.
Web Tips in Javascript
In IE 7:
this.readyState
was 'loading' subsequently 'loaded'.
I changed:
if (this.readyState == 'complete')
to:
if (this.readyState == 'loaded' || this.readyState == 'complete')
Maarten Maarten
Sounds like the first option you mentioned if definitely out, and the timeout with (typeof(abc) == 'function') or (typeof(abc) == 'object') is the only way to go. But, what if the function or object is only partially loaded at this point? Or do browsers load the entire script file before executing? Seems like there is still no final answer on how to do this. Thanks.
If you want to test whether one particular function ("Foo()") has been loaded, use this:
function RunFooWhenReady(param) { if(window.Foo==undefined) { setTimeout(function() RunFooWhenReady(param);},50); } else { Foo(param); } }
I suspect the call to 'oCallBack' inside the closure may lead to issues in case of successive calls to the import function:
oScript.onreadystatechange = function() { if (this.readyState == 'complete') oCallback(); //
Hi Ed,
wondering if:
will raise an exception when sMethod is not defined.
Thanks a Lot.... from last 4 days i am trying to do something...not able to do... but with the help of this code, finally..finally :) .... Thanks a lot... i save the code for my future reference....
Thanks for this. I'm sort of learning by trial and error. I looked around for dynamically loading javascript and everything I found was either too complicated for me or it wouldn't work the way I wanted it to.
I had this up and running in 15 minutes. I changed it around a bit so that it gets the name of the javascript file based on the name of the html file in my right iframe and then sends the javascript to the left iframe when you click on the image map in the right iframe.
Thanks
The example was useful - except that it doesn't work: would you mind replacing getElementById with getElementsByTagName in your code example, as Till suggested above?
Thanks! *t
Wow thank you, works like a charm :) I used this method to load the +1 button asynchronously, I needed to know when the Google JS was loaded. You solved my problem!