While it is perfectly normal to have one or more JavaScript tags in your markup as prerequisites to your code, there may be situations in which you want to avoid this
When I build a piece of functionality that is contained in one JavaScript file, I try to avoid asking the back-end developer to include additional scripts in the markup. So, my philosophy is: my script provides a certain “chunk” of functionality. Whatever external resources my script depends on are my script’s problem and will be handled accordingly. As often as possible, I’d like the consumer to simply choose to use or not use my script. This decision should involve simply adding or removing my script from the markup. The remaining abstraction is my responsibility.
This may seem a bit altruistic, but so far I’ve never had to lower my standards on the issue. The key to keeping this philosophy is, of course, the ability to reliably load other scripts asynchronously. I say “reliably” because it’s not enough to simply inject the script into the head of the document. You need to know when that script has successfully loaded before you take further actions that depend on the script.
The good news is: modern browsers provide the “onload” event, allowing you to set up your handler without too much effort. But the bad new is: Microsoft’s Internet Explorer 8 and below do not implement that event. So, there is some work to do. It’s not too bad; it just means we need to fork our code a bit.
Oh, and by the way; you might be wondering why I didn’t simply use the jQuery.getScript() method. jQuery is awesome and we all love it more than coconut ice cream. But I strongly believe that it’s important to know how to do these things with native JavaScript. One day a client will tell you that for whatever reason, you can’t use jQuery. When that day comes, you’ll be ready.
So let’s have at it!
Example # 1
1 2 3 4 5 6 7 8 9 10 11 12 |
function loadScript(url,callback){ var script = document.createElement('script'); script.onload = function(){ //once the script is loaded, run the callback if (callback){callback()}; }; //create the script and add it to the DOM script.src = url; document.getElementsByTagName('head')[0].appendChild(script); }; |
Now here in Example # 1, we’ve created a function that takes a URL and a callback as arguments. The URL is required, the callback is optional. As you can see, this is pretty straightforward stuff:
- Create a script DOM element
- Assign an anonymous function to the “onload” event
- Set the script’s source
- Inject the script into the DOM
No worries.
Example # 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function loadScript(url,callback){ var script = document.createElement('script'); script.onreadystatechange = function(){ //once the script is loaded, run the callback if (script.readyState === 'loaded'){ if (callback){callback()}; }; }; //create the script and add it to the DOM script.src = url; document.getElementsByTagName('head')[0].appendChild(script); }; |
In Example # 2 we have rolled up our pant legs and stepped into the cold wet mud that is Internet Explorer 8 (and below). So here we will need to assign that same anonymous function to the script element’s “onreadystatechange” property. And this property will change as the “ready state” of the script element updates. When that ready state is “loaded”, then we can be confident that the external script has successfully loaded and executed. It’s a bit more work, but then again, Internet Explorer wouldn’t be such a charming little browser if it adhered to the same kind of common-sense standards as every other modern browser on the planet… but I digress.
Example # 3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Cross browser asyncronous JavaScript script loading | blog.kevinchisholm.cpom</title> </head> <body> <h1 id="container"> Please open your JavaScript console. </h1> <script> var scriptURL = 'http://examples.kevinchisholm.com/utils/script/script.php?sleep=2'; function loadScript(url,callback){ if(!url || !(typeof url === 'string')){return}; var script = document.createElement('script'); //if this is IE8 and below, handle onload differently if(typeof document.attachEvent === "object"){ script.onreadystatechange = function(){ //once the script is loaded, run the callback if (script.readyState === 'loaded'){ if (callback){callback()}; }; }; } else { //this is not IE8 and below, so we can actually use onload script.onload = function(){ //once the script is loaded, run the callback if (callback){callback()}; }; }; //create the script and add it to the DOM script.src = url; document.getElementsByTagName('head')[0].appendChild(script); }; loadScript(scriptURL,function(){ console.log('the remote script has finished loading'); }); </script> </body> </html> |
Well… Example # 3 certainly contains a bit more code, huh? In fairness, it’s heavily commented. But outside of that, what has happened is that as promised, we’ve forked the code so that we can support good ol’ IE, as well as all the other browsers that are made by sane people. I chose to check for the existence of document.attachEvent as a way of sniffing the browser. Some may disagree, but for me, it has always worked just fine.
If you follow the link to the full working example below, be sure to open your JavaScript console. When you do, you’ll see that the message from the actual external script always fires before the callback. Always. This is what we needed: the ability to load the script, and reliably know when it has loaded so that we can safely assume whatever resources it provides are available to us. So go ahead and try it in Internet Explorer 8 or Internet Explorer 7; it works just fine.
The full working example can be found here: http://examples.kevinchisholm.com/javascript/script-loading/
Summary
In this article, we learned how to implement a reliable solution for cross-browser asynchronous JavaScript loading. We discussed the need to fork our code in order to support Internet Explorer 8 (and below), and the “onreadystatechange” event that it implements.
Helpful Links for JavaScript loading
http://css-tricks.com/thinking-async/
http://friendlybit.com/js/lazy-loading-asyncronous-javascript/