PDA

View Full Version : Lazy loading javascript framework



keypoint
21 Oct 2009, 3:12 PM
Hey,

Here is an implementation I am currently working on.
One of its goals is to allow the lazy loading of various js files.
As sometimes it is best to preload some files (maybe the most used modules), the included sample has a configuration file where you may specify such files to be loaded during the initialization phase.
I've seen many threads trying to acomplish the loading and then execute a callback; one after each load. That highly unnecessary - normally you just need to run something after a batch of files have been loaded. And you shouldn't need to specify inheritance rules either - the order in which files are loaded should be enough.
I think the included class accomplishes all these tasks. There is still enough room for extension (in the near future I will need to extend it to css too) but some feedback would be highly appreciated.
Tested under FF3.5, IE7/8 and Opera 10.

Usage notes: copy ext-base, ext-all and the resources to lib/vendor/extjs
Use the archive rather than the code below.

app.js


Ext.onReady(function() {new Ext.App();});

Ext.App = function(config) {
Ext.App.superclass.constructor.apply(this, arguments);

this.init();
}

Ext.extend(Ext.App, Ext.util.Observable, {
init: function() {
var fl = Keops.JsLoader;
fl.importFile('js/config.js', function() {return Ext.isDefined(Ext.App.Config);});
fl.executeWhenIdle(this.settleAutoload.createDelegate(this));
},

endInit: function(hasErrors) {
if (hasErrors) {
document.getElementById('loading-msg').innerHTML = '<span style="color: #c00;">Could not load required files!</span>';
return;
}

var hideMask = function() {
Ext.get('loading').remove();
Ext.fly('loading-mask').fadeOut({
remove : true
});
}

hideMask.defer(250);
},

settleAutoload: function() {
var fl = Keops.JsLoader;
Ext.each(Ext.App.Config.autoload, function(file) {
fl.importFile(file.filePath, file.verifyImport);
});
fl.executeWhenIdle(this.endInit.createDelegate(this));
}
});


config.js, with a commented out inexisting file (to check error handling)


Ext.ns('Ext.App');

Ext.App.Config = {
autoload: [
/*
{
filePath: 'js/kernel/Viewport-fake.js',
verifyImport: function() { return Ext.isDefined(Ext.App.ViewportFake); }
},
*/
{
filePath: 'js/kernel/Viewport.js',
verifyImport: function() { return Ext.isDefined(Ext.App.Viewport); }
}
]
};


JsLoader.js


Ext.ns('Keops');

Keops.JsLoader = Ext.extend(Ext.util.Observable, {

/**
* @private
*/
busyCount: 0,

/**
* @private
*/
failed: false,

/**
* @private
*/
timeoutTask: undefined,

constructor: function(config) {
this.addEvents(
'idle',
'importSuccess',
'importFailure'
);

// prepare to check that import was ok
this.timeoutTask = new Ext.util.DelayedTask(this.timeoutImport, this);

this.listeners = config ? config.listeners : {};
Keops.JsLoader.superclass.constructor.apply(arguments);
},

/**
* @param filePath string
* @param onComplete function
* @param timeout integer
*/
importFile: function(filePath, onComplete, timeout) {
//var loader = this;
++this.busyCount;

if (this.failed) {
this.importFileOver(filePath, onComplete);
} else {
var script = document.createElement('script');
script.src = filePath;
script.type = 'text/javascript';
Ext.fly(script).on('error', this.timeoutImport.createDelegate(this));
if (Ext.isIE) {
Ext.fly(script).on('readystatechange', function(e, scrpt) {
if (scrpt.readyState == 'loaded' || scrpt.readyState == 'complete') {
this.importFileOver(filePath, onComplete);
}
}, this);
} else {
Ext.fly(script).on('load', this.importFileOver.createDelegate(this, [filePath, onComplete]));
}

Ext.DomQuery.selectNode('#scripts').appendChild(script);
this.timeoutTask.delay(timeout || 5000);
}
},

/**
* @private
*/
timeoutImport: function() {
this.failed = true;
this.busyCount = 0;
this.fireEvent('importFailure', undefined);
this.fireEvent('idle');
},

/**
* @private
*/
importFileOver: function(filePath, onComplete) {
this.busyCount--;
if (!this.failed && onComplete()) {
this.fireEvent('importSuccess', filePath);
} else {
this.failed = true;
this.busyCount = 0;
this.fireEvent('importFailure', filePath);
}
if (this.busyCount <= 0) {
this.fireEvent('idle');
}
},

isImporting: function() {
return this.busyCount > 0;
},

hasFailed: function() {
return this.failed;
},

executeWhenIdle: function(callback) {
if (this.isImporting()) {
this.addListener('idle', this.executeWhenIdle.createDelegate(this, [callback]), this, {
single: true
});
} else {
this.timeoutTask.cancel();
callback(this.failed);
}
},

reset: function() {
this.timeoutTask.cancel();
this.removeAllListeners();
this.failed = false;
this.busyCount = 0;
}
});

Keops.JsLoader = new Keops.JsLoader();


Peace!