The Art of Loading Scripts

Filed in Best Practices , Client Side , General 4 comments

Nowadays our web applications are gaining an unimaginable complexity, and we started thinking about Front End Architects, Presentation Layer Developers, Usability Gurus, CSS/JS ninjas and of course jQuery evangelist…

One of the interesting topics I’ve found in my career as Web Developer is the way we are managing dependencies loading javascript on demand, in the smartest way. Recognize it, in this days we can’t just put all the javascript together in one file and minify it and may be obfuscate it, too. Actually our JS is object oriented, modular, full off elegant solutions to fulfill the needs of large scale applications with spectacular interactive GUI and impressive user controls.

Using

Although that’s why I want to share some solutions I’ve found over the web. The first approach to a solution I want to mention, is “Using” a simple library created by Eugenio Lattanzio (a good friend of mine who I admire a lot):

var using = (function(){
var loaded = {},
scriptUrl = $('script').attr('src').replace(/(\/|^)[\w\.\-]+$/, '$1');

return function(lib, callback, scope){
lib = $.isArray( lib ) ? lib: [ lib ];
var cb = function() {
lib.shift();
loaded[this.url] = true;
if( lib.length === 0) {
callback.call(scope);
}
};
$.each(lib, function(){
var u = scriptUrl + this.replace(/(\.js)?$/,'.js');
if( loaded[u] ) { cb(); }
else { $.getScript( u, cb); }
});
};
})();

Please take a look at his website:
http://ukesoft.com/blog/index.php/2010/08/25/cargando-javascript-bajo-demanda-con-jquery-simple/

And the usage could be something like this

using(['lib1.js', 'lib2.js'], function(){
alert('lib1.js and lib2.js are loaded');
});

As you can see it’s simple and smart, we gave it the libraries we want to load and then we can use them in the callback. The only disadvantage I’ve found is that uses $.getScript (believe me that things is evil dude) which I don’t trust and I’ll explain this later.

Modulous

Now, it’s the turn of my favorite, let me present Modulous (http://code.google.com/p/modulous/):

Modulous

We odd the inception of this library to Francis Albar, Brian Fletcher, Dennis Hall and Fred Welterlin. In 2010, I had the opportunity to work with this awesome guys and use their library in a particular project and see many improvements they’ve made. The library is now MIT Licensed, I’ve implemented it in many projects I’ve found in my daily work. Please take a look at the example:

http://www.welterlin.com/modulous/

One of the things I really appreciate from this library is that based in a dictionary of keywords, searchs these keywords in the entire DOM and if some of the reserved keywords is founded then proceed to load the javascript files that are being need. This was a revolutionary concept for me, as a Backend Developer I’ve had many scripts loaders in PHP, but taking this technique to the web give excellent results such as maximum performance, and reagroup responsibilities in a single small global object. That make me think in something very similar to a command-line pattern, where every module is like a command that are going to be called on demand. As you will see many of these libraries are jQuery based, and most of them separe functionality in modules. Now let’s see how modulous do the magic…

First of all, we’ve got the definition of the global object that will be responsible for calling the others scripts:

if (window.foo) _foo = foo;

// global FOO object : loads external JS scripts dynamically based on the existance of corresponding class name in the DOM
foo = {
….
};

// instantiate FOO object
foo.init();

Inside the object, we’ll find attributes to hold information such us info (where you should put the project name), lang, scriptsLoc (the path where you’ll place the javascript files wit the modules) :

foo = {
info : '[Client Name Here] Base JavaScript',
lang : 'en', // change this to an expression that gets the correct language value [en,es,vi,zh]
log : function() {
try{console.log.apply('',arguments);} catch(e) {}
},
scriptsLoc : location.pathname.split('/').slice(0,-1).join('/') + '/js/modules/',
mod : {
'accordion' : {},
'simplemodal' : {},
'carousel' : {},
'datepicker' : {},
'dismissable_alert' : {},
'validate' : {},
'slideshow' : {},
'scroll_table_bdy' : {},
'tabs' : {},
'tablefilter' : {},
'tablesorter' : {},
'tooltip' : {},
'validate' : {}
},
loaded : [],
….

Also we have got a useful function for showing logs on Firebug (i.e. foo.log(‘modulous rocks’) ), foo.mod is list of empty objects that will be our dictionary of keywords (module names), and we’ve got an array foo.loaded that will save the currently running modules.

Modulous: Init Function

Now let’s take an special look at the init function:

init : function() {

// remove non-js message from DOM
$('.enable-js').remove();

This is for removing a class that give us an advise that we are in a minimum user experience and that we should enable JS to obtain a full user experience.

// preload images so you don't get flickering
(function(){
var imagesToLoad = [
'css/images/bg-tooltip-left.png',
'css/images/bg-tooltip-right.png',
'css/images/bg-tooltip-image-left.png',
'css/images/bg-tooltip-image-right.png'
];
$('<div class="no-print" style="position:absolute;left:-9999px;top:-9999px;height:0;width:0;overflow:hidden;"/>').appendTo('body').html('<img src="'+imagesToLoad.join('"/><img src="')+'"/>');
})();

You know every time you have things like a tooltip, and the first time its appears the images can take a time to render… That’s why modulous can load this images previously and hide them at a very high position where nobody can see it.


// accessibility: [plugin] treat spacebar 'keypress' event the same as the mouse 'click' event
$.fn.klik = function(f){
return this.bind('click keypress', function(e) {
if (!e.keyCode || e.keyCode == 32){
return f.call(this, e);
}
});
};

This is another advantage for giving more accessibility to the site. And now we have arrived to where the action is, take a quick look first:

// setup and, if needed, load modules &amp;amp;amp;amp;amp;amp;amp;amp; plugins from other files
$.each(foo.mod, function(moduleName) {
$.extend(foo.mod[moduleName], {
className : '.mod-' + moduleName,
load : function(m) {
if($.inArray(moduleName, foo.loaded) < 0) {

var headElem = document.getElementsByTagName('head')[0];
var newScript = document.createElement('script');
newScript.type = 'text/javascript';
newScript.src = foo.scriptsLoc + 'foo.' + moduleName.replace(/_/g,'-') + '.js';
headElem.appendChild(newScript);

(function(){
if(!foo.mod[moduleName].init){
setTimeout(arguments.callee, 30);
return;
}
foo.mod[moduleName].init(m);
})();

foo.loaded.push(moduleName);
}
}
});

I’ll explain you in detail:

// setup and, if needed, load modules &amp;amp;amp;amp;amp;amp;amp;amp; plugins from other files
$.each(foo.mod, function(moduleName) {

This each will iterate trought our dictionary keywords.


$.extend(foo.mod[moduleName], {

This sentence will extend our empty object with a className attribute and a load function (later we will review how to construct a basic module).


className : '.mod-' + moduleName,
load : function(m) {
if($.inArray(moduleName, foo.loaded) < 0) {

We verify if the module hasn’t been loaded yet…


var headElem = document.getElementsByTagName('head')[0];
var newScript = document.createElement('script');
newScript.type = 'text/javascript';
newScript.src = foo.scriptsLoc + 'foo.' + moduleName.replace(/_/g,'-') + '.js';
headElem.appendChild(newScript);

… we should append the script. Plus this is the best way to do it I’ve learned that $.getScript is not an option for improve performance specially on IE, please take a look at this explanation:
http://stackoverflow.com/questions/1661224/stop-ie-from-loading-dynamically-included-script-twice
Otherwise IE will load the scripts twice, you can check it with fiddler.

(function(){
if(!foo.mod[moduleName].init){
setTimeout(arguments.callee, 30);
return;
}
foo.mod[moduleName].init(m);
})();

As you can see here, every module should have an init function, that will be called here to initiate the module.


foo.loaded.push(moduleName);

Finally, we save the running module in the foo.loaded array.

}
}
});

var module = $('.'+moduleName.replace(/_/g,'-'));
if (module.size()){
foo.mod[moduleName].load(module);
}
});

Modulous: Your First Module

Creating a hello world module should be easy if you follow this steps:

  1. Create a file called mod.helloworld.js and place it in the script folder that you set in foo.scriptsLoc, in my case ./js/modules/
  2. Put the name of the module in the dictionay inside our global object, in this case just go to foo.mod and add this line: ‘helloworld’ : {},
  3. Make sure you put this reserved name of the class somewhere in your HTML document, for instance <span class=”helloworld”>
  4. Add the following sample code to your mod.helloworld.js file
foo.mod.helloworld = {
init: function(){
foo.log('foo.helloworld.init');
alert('Hello World!! is running!!')

}
};

Easy as a pie!

Conclusion

As we can see we are improving every day the way we organize our jobs and the architecture of our web projects, I believe this is an excellent approach to obtain professional results. In my next article “The Art of Loading Scripts II” I’m going to share my vision about other libraries such as LABjs and RequireJS among others.

Related Links

1. Building Large-Scale jQuery Applications – http://addyosmani.com/blog/large-scale-jquery/
2. LABjs & RequireJS: Loading JavaScript Resources the Fun Way – http://msdn.microsoft.com/en-us/scriptjunkie/ff943568
3. LABjs: why not just concat? – http://blog.getify.com/2009/11/labjs-why-not-just-concat/

Posted by crisboot   @   7 March 2011 4 comments

Share This Post

RSS Digg Twitter StumbleUpon Delicious Technorati

4 Comments

Comments
Mar 8, 2011
7:49 pm
#1 Adriano :

Very nice!!!! I think i’ve read this before.. :)

Mar 8, 2011
7:52 pm
#2 Bogardus :

Muy bueno man!!… Gracias por compartir… ahora solo resta ponerlo en practica!

Mar 9, 2011
10:20 am
#3 Gabriel :

Copado Crisboot, voy a tener que aprender jQuery nomas!…

Mar 9, 2011
8:40 pm
#4 uke :

nice ;)

Leave a Comment

Previous Post
«
Next Post
»
PLD League