October 1st, 2007

Javascript on Demand: part two

Isn't it nice when a neat idea actually works? Javascript-on-demand (JOD), a technique that I toyed with a few months ago, is being put through trials on a real product. That the technique works is already proven, what's left is to determine whether the technique (using selectors to load javascript Assets conditionally after the DOM is loaded) offers better performance than the alternative (rendering the HTML with <script> tags already in the <head>). One problem I've been encountering lately in my work involves interactive page components that have javascript dependencies. When I drop a component onto a page, it often requires several integration points: 1) I put the HTML code into the <body>, 2) a script has to go in the <head>, 3) possibly a CSS file also goes in the head, 4) sometimes there's even some PHP that needs to get inserted elsewhere on the page. I could have built JOD from scratch, but since there is already an "Assets" class in Mootools, I'm using it (I'll expand on my opinion of Mootools in another post). The strategy leans significantly on Mootools, and its Assets object http://docs.mootools.net/Remote/Assets.js That Mootools has an Asset class is proof enough that this strategy is not a fanciful hack; it’s a well-established pattern. see: http://ajaxpatterns.org/On-Demand_Javascript see: http://www.ianring.com/blog/2006/11/javascript-on-demand/ This technique is also known as “Lazy Loading” . It has the following benefits:
  • Initial page load is faster
  • Overall bandwidth usage is less, since only the JS that is required is loaded
  • XSLT is easier to create (no more <script>s in <![CDATA[ ]]>)
  • markup becomes more purely semantic
  • New JS behaviours can be deployed separate from other deployments without incident
  • It bypasses the “same domain” policy - you can even host javascript snippets on a separate server or domain
  • It snuggles more intimately with CSS
Instead of rendering script elements in the markup, we use a “bootstrap” function to look through the DOM for elements that require JS assets, and load them if required. “Bootstrap” is where the word “boot” and “reboot” terms came from in computing, i.e. loading a simple process that in turn may start up a more complex process. In this case it’s an apt term for what we’re doing in JS. Bootstrap is just the little bit of extra leather on the top rear of a boot that you can use to pull it on. There is a legend about Baron Munchhausen, who was able to lift himself out of a swamp by pulling his own hair (in obvious violation of physics). In later versions he was pulling himself up by his bootstraps, which gave rise to the phrase “pull oneself up by one’s bootstraps”, which means “recover or succeed without external help”. Thus a “bootstrapper” is a “self-starter” – and the bootstrap is the little object you use to leverage up a larger effort, like lifting oneself out of a swamp. The <head> of a typical page will look like this: <head> <script src="js/mootools-release-1.11.js"></script> <script src="js/bootstrap.js"></script> </head> The Bootstrap script does a couple things:
  1. Uses the “domready” object to trigger itself. In addition to its obvious utility, it also ensures that Mootools is loaded.
  2. Applies a series of selectors to see if any special JS-dependent elements are present in the DOM
  3. Loads a JS asset for any that are found
The bootstrap.js file looks like this: window.addEvent('domready', function(){ if ($$('a.external')){ new Asset.javascript('js/external.js', {id: 'external'}); } if ($$('.editable')){ new Asset.javascript('js/editor.js', {id: 'editor'}); } }); Explanation: On line 2, we check if the current document contains an element matching “a.external”. If it does, then we load the JS asset controlling the behavior of external links (which is to open in a new window) The condition needn’t be just a $$ selector. You can include an asset depending on the time of day, the user’s browser version, the number of links on the page, authentication, or … anything. If the page has an external link on it, then we want to attach an onclick event to it. This is so much more elegant than peppering your HTML with "onclick" events in the markup. And if we ever want to change the behaviour of those links, we don't have thousands of <a> tags to find and edit. Here is the asset we're loading when "a.external" is found: external.js window.addEvent('domready', function(){ var xlinks = $$('a.js-external'); for (var i=0;i
  • Everything starts with the domready trigger. This ensures that Mootools is loaded before going on. Even though we checked for domready in the bootstrap, we need to do it again here, since the domready event will re-trigger new onload events when the event is added. The actual asynchronous event scheduling is a little tangled with all these onload events loading and triggering, but this works.
  • It returns false. Most JS behaviours should, so we can have one set of actions in the HTML for non-JS agents (form, action, href, postback, etc), and an enhanced set for JS-enabled agents which may replace, manipulate, or augment the DOM
  • It adds events to elements. Those events can be really simple or very complex, they may trigger the creation of other elements, behaviours could involve AJAX or JSON, etc. But it all starts with adding events to existing elements – these must be the same elements (using identical selectors) that were checked for existence in the bootstrap.
Using className to pass config variables to a script When an element’s behavior is simple, a simple class name is all it takes to identify it and load the required JS asset. However sometimes a behavior will require some config options or parameters. For instance, you might create a <textarea> which enforces a maximum length. But what is the maximum length for this field? You can pass additional modifiers via additional class names. One cool advantage of this technique is that the additional class name will modify the behavior of an element, but it can also modify the way it is styled via CSS. Thus an element that acts with behavior A can be styled with style A, while the same element with behavior B can be styled with style B. Events within the behavior that affect styling will add, modify, or remove classes as appropriate, offering a tighter weld between JS behaviours and CSS styling. class names are generally not used for default values for an element or script; they are used to pass in config values, like how many, what limits, which other elements are grouped or dependent, etc. Consider this hypothetical markup: <input type="checkbox" class="disabler disablergroup-d12" />Cheese <br/> <div id="d12"> Choose cheeses to include: <input type="checkbox"/>Mozzarella <input type="checkbox"/>Cheddar <input type="checkbox"/>Gouda </div> The markup for the checkbox indicates that it has a behavior of “disabler”. The disabler function looks at the className and finds a “disablergroup-” class, and knows that the script should toggle the “disabled” attribute of any <input> elements found within “#d12”. This technique seems elegant when there are only a few script parameters; with large numbers of script parameters it may become nasty, so one must consider each as they are built. As mentioned, I haven't yet determined if this technique offers improved performance, though it is clear that it enables cleaner code and more purely semantic markup. In a complex web application using a lot of AJAX, JOD would really come in handy - I'm just not sure whether it is sensible to use it on a (mostly) static web page. enjoy

Filed under Uncategorized

One Response to “Javascript on Demand: part two”

  1. ian Says:

    update:
    refactored the bootstrap function using a function; this version has some performance benefits, like using $E instead of $$, which spares the client from storing a large array of elements in memory.

    // bootstrap.js

    function addscript(selector,script,scriptid){
    if ($E(selector) && !$(scriptid)){
    new Asset.javascript(’/js/’+script, {id: scriptid});
    }
    }

    window.addEvent(’domready’, function(){
    addscript(’a.js-external’,'js-externallinks.js’,'js-externallinks’);
    addscript(’.js-editable’,'js-editinplace.js’,'js-editinplace’);
    });

Leave a Reply