ensure - Ensure JavaScripts/HTML/CSS are loaded on-demand when needed

A tiny JavaScript library that provides a handy function "ensure" which allows you to load JavaScript, HTML, CSS on-demand and then execute your code. ensure ensures that relevant JavaScript and HTML snippets are already in the browser DOM before executing your code that uses them.

Introduction

ensure is a tiny JavaScript library that provides a handy function ensure which allows you to load JavaScript, HTML, CSS on-demand, and then execute your code. ensure ensures that the relevant JavaScript and HTML snippets are already in the browser DOM before executing your code that uses them.

Background

Websites with rich client side effects (animations, validations, menus, pop-ups) and AJAX websites require large amounts of JavaScript, HTML, and CSS to be delivered to the browser on the same web page. Thus, the initial loading time of a rich web page increases significantly as it takes quite some time to download the necessary components. Moreover, delivering all possible components upfront makes the page heavy, and the browser gets sluggish when responding to actions. You sometimes see pull-down menus getting stuck, popups appearing slowly, window scroll being sluggish, and so on.

The solution is not to deliver all possible HTML, JavaScript, and CSS on initial load, instead to deliver them when needed. For example, when the user hovers the mouse on the menu bar, download the necessary JavaScript and CSS for the pull-down menu effect as well as the menu HTML that appears inside the pull-down. Similarly, if you have client side validations, deliver the client side validation library, the relevant warning HTML snippets, and the CSS when the user clicks the 'Submit' button. If you have an AJAX site which shows pages on demand, you can load the AJAX library itself only when the user does the action that results in an AJAX call. Thus, by breaking a complex page full of HTML, CSS, and JavaScript into smaller parts, you can significantly lower down the size of the initial delivery and thus load the initial page really fast and give the user a fast and smooth browsing experience.

Benefits of ensure

ensure saves you from delivering unnecessary JavaScript, HTML, and CSS upfront, and instead loads them whenever needed, on-demand. JavaScript, HTML and CSS loaded by ensure remain in the browser, and the next time ensure is called with the same JavaScript, CSS, or HTML, it does not reload them and thus saves from repeated downloads.

The above code ensures that Some.js is available before executing the code. If SomeJS.js has already been loaded, it executes the function right away. Otherwise, it downloads Some.js, waits until it is properly loaded, and only then it executes the function. Thus, it saves you from delivering Some.js upfront when you only need it upon some user action.

Similarly, you can wait for some HTML fragment to be available, say a popup dialog box. There's no need for you to deliver HTML for all possible popup boxes that you will ever show to users on your default web page. You can fetch the HTML whenever you need it.

You might think you are going to end up writing a lot of ensure code all over your JavaScript code that will result in a larger JavaScript file than before. In order to save your JavaScript size, you can define short-hands for commonly used files:

While loading the HTML, you can specify a container element where ensure can inject the loaded HTML. For example, you can say load HtmlSnippet.html and then inject the content inside a DIV named "exampleDiv".

You can also specify JavaScript and CSS that will be loaded along with the HTML.

ensure has a test feature where you can check if a particular JavaScript class or some UI element is already available. If it is available, it does not download the specified components, and executes your code immediately. If not, it downloads them and then executes your code. This is handy when you are trying to use some utility function or some UI element and you want to ensure it is already there.

The above example checks if Microsoft AJAX library's Sys class is already there. It will be there if Microsoft AJAX library was already loaded. If it's not there, it loads the library and then calls the code.

The real work is, however, done in ensureExecutor. Basically, ensure creates an instance of ensureExecute and passes the relevant data, callback, and scope to it. The real work for loading the stuff and calling back the callback is done within ensureExecutor.

First, ensureExecutor does some preparation on the parameters and ensures the valid parameters are there. Then, it fires the init function to initialize the currently available Framework (jQuery/Microsoft AJAX/Prototype) for some common AJAX operation. Then, it fires the load function to load the necessary components and fire the callback.

The load function calls loadJavascripts, loadCSS, and loadHtml sequentially. This ensures the HTML is loaded only after the JavaScript has been successfully loaded and CSS loading is either done or already started.

loadJavascripts is the tricky function. It loads an external script by either creating a <script> tag or using XMLHTTP to download an external script. Safari requires XMLHTTP because there's no way to know when a <script> tag has successfully downloaded.

The idea is to issue script download and wait until all scripts are downloaded. When done, it fires the complete callback, and then the loadCSS or loadHTML function gets fired.

The loadCSS function is rather painless. The only gotcha is, in Internet Explorer 6, you have to add a <link> tag only when the code is executing in the window object's context. My other article at CodeProject about UFrame explains this problem in detail.

Looks simple, but much sweat and blood has gone into this to make it work perfectly in all popular browsers. But still, Safari 2 does not support the onload or onreadystatechange events. So, for Safari, the trick is to make the XMLHTTP call to download the script and then execute it. However, this means you cannot ensure a script from an external domain on Safari as XMLHTTP calls work only with the current domain.

One cool trick is to execute the downloaded script at a global context. You might think it's easy to do using the eval function. But it does not work. eval executes the call only within the current scope. So, you might think, you can call eval on a window object's scope. Nope, does not work. The only fast, cross browser solution is this approach:

It creates a <script> tag inside the <head> node and then passes the script text to it.

Real Life Examples

The test application shows you some common uses of ensure. For example, when a button is clicked, you need to call some JavaScript function. That JavaScript function is (or can easily be) in an external JavaScript file. Here's how you do it:

When the button is clicked, HtmlSnippet.html and HtmlSnippet.css get loaded. The content in HtmlSnippet.html is injected inside a DIV named resultDIV. When the content is available and successfully injected, the callback function is fired where the code tries to hook on a button that has come from HtmlSnippet.html.

You may have skipped another cool aspect, the whole callback function is fired on the context of the <input> button. The third parameter to ensure ensures this. You see, I have passed this as the scope, which is the button itself. Thus, when the callback fires, you can still use this to access the button.

The third example in the test application shows how several HTML, JavaScript, and CSS are loaded to provide two UI effects - a background fade-in and a popup dialog box.

Download Code

Conclusion

Now you can ensure that the necessary JavaScript, HTML, CSS are available before using them. Ensure you use ensure throughout your web application to ensure fast download time, and yet ensure UI features are not compromised, and thus ensure richer user experience, ensuring fast page loading.

While in Firefox and Chrome every callback will fire, in Opera and IE only the first one will. The funny thing is, that every JS-file (1 to 3) is loaded and injected. So it's not some buggy callback function that breaks js execution from my pov.

Please help me solve this problem - i think it is related to the 'delegate' function but till now i have no clue how and why.

I'm testing ensure on one of my sites. Either I'm missing something or there's what seems to be a flaw in the logic whereby if it fails to load a CSS file it will not only carry on trying to load the remainder of the files but it will also invoke the callback function. There isn't currently support for an error callback to be invoked instead of the success callback if a file fails to load, as far as I can tell.

Is that correct? I can add these features myself I think if they are not implemented.

How-ever, if i have a variable where i stored the file name and i use that instead of a constant string as shown above, then ensure is not working and it is not ensuring the javascript file loaded before it does the callback. Please see below code which does not do the work for me:

var myJSFileName = "locale/mylangfile1.js";

ensure({js: myJSFileName }, function() { loadUIJavaScriptFiles(); });

What is the reason for this? This looks like a sort of limitation in the design of ensure?

That solution will not work - well sort of depending on how implemented - let me illustrate ( hope you all can follow hard to explain )

It will work if you are are sending only one ensure call over a small period. However, if you are doing multiple ensure calls over a brief period it will not work...

A little background on my situation first to explain why I am doing multiple calls in a short period. In my framework I am building I want to supply a list of scripts to load and then register each one that loaded amoung other things. Now on to the example of where it will not work.

On page load I call this function ( with your solution ) and my function simplified for display purposes:

when you run this script the alert for all three will be "modal" not the wanted "dDumper","blockUI","modal". This is because the callbackArguments is updated three times before the actual callbacks are processed ( kinda like a race condition ). Don't ask me to explain the scoping problem because I can't quite wrap my head around it yet but suffice to say it is a problem.

Here is my solution that I tested and it works ( even made sure it was endeed made at the ensure script callback point not process right away as a (function() { ... })(x) closure would ).

I see in the globalEval code that you put all the data downloaded, in the html content of the page.

If I have a big JavaScript file (with 500 lines) my browser memory will be increasing with this amount of data or this will be saved in the local internet temporary files of the browser?

I'm using Ext and I had developing some custom controls and my JavaScript files have an average of 500 lines of code. In a single page I can use up to 20 controls. What are the browser memory cost for this?

I like the whole concept of loading the javascript dynamically only when you need to.

My question is once the javascript has been downloaded the first time technically it should be in the users browser cache. So if you browse away from the page and then come back to it, would it be possible to load the javascript from the browser cache instead of making another call to the external javascript file?

I just checked your site again and I think it seems to actually load the javascript from browser cache, although the 3rd example on your page seems to take 1 - 2 seconds the very first time but then it's ok not sure if that's by design?

Thanks for responding back, I've been testing it using the demo page ()[^]

I find that the first 2 examples do as you said load quickly, but with the last example I see it pause while it's loading something then it pops up the window. What I'm not sure is if it's trying to load the content remotely hence the delay or whether it's just slow