PDA

View Full Version : ext.ux.JSLoader - load JavaScript on demand



mindplay
10 Oct 2008, 10:22 AM
Due to several problems with the various ways I've seen others trying to dynamically load additional classes and run external JavaScript on the fly, I decided to write my own.



Ext.ux.JSLoader = function(options) {

Ext.ux.JSLoader.scripts[++Ext.ux.JSLoader.index] = {
url: options.url,
success: true,
options: options,
onLoad: options.onLoad || Ext.emptyFn,
onError: options.onError || Ext.ux.JSLoader.stdError
};

Ext.Ajax.request({
url: options.url,
scriptIndex: Ext.ux.JSLoader.index,
success: function(response, options) {
var script = 'Ext.ux.JSLoader.scripts[' + options.scriptIndex + ']';
window.setTimeout('try { ' + response.responseText + ' } catch(e) { '+script+'.success = false; '+script+'.onError('+script+'.options, e); }; if ('+script+'.success) '+script+'.onLoad('+script+'.options);', 0);
},
failure: function(response, options) {
var script = Ext.ux.JSLoader.scripts[options.scriptIndex];
script.success = false;
script.onError(script.options, response.status);
}
});

}

Ext.ux.JSLoader.index = 0;
Ext.ux.JSLoader.scripts = [];

Ext.ux.JSLoader.stdError = function(options, e) {
window.alert('Error loading script:\n\n' + options.url + '\n\n(status: ' + e + ')');
}

The following simple example demonstrates how to use this, and shows the prototype for the onLoad and onError callbacks:


Ext.onReady(function(){
new Ext.ux.JSLoader({
url: '/scripts/my_script.js',
onLoad: function(options) { alert('Script Loaded'); },
onError: function(options, e) { alert('Error loading script'); }
});
});

The events are both optional.

You may prefer not to use the onLoad callbacks, and instead just place the code to execute in the loaded script - if the loaded script contains classes, that may not be the most elegant way to go about it, hence this event is available.

If no onError event is provided, a default error handler will display an alert with the URL and a description of the error.

The error handler will catch exceptions, so if your loaded script contains syntax errors, you will get an onError event. You will also get an onError event (with an error code, e.g. 404) if the HTTP request failed.

Your loaded script executes in the global scope, which means you can use it to load extensions or other classes on the fly.

dddaaa123
11 Oct 2008, 7:44 AM
Good job!

jay@moduscreate.com
11 Oct 2008, 3:52 PM
This isgreat. I love that you have it event based.

woomboom
12 Nov 2008, 7:45 PM
Thanks Mindplay!

I modified it some so that it can be used to load ext components (dynamically generated if you like) on the fly.

JSLoader.js

Ext.ux.JSLoader = function(options) {

Ext.ux.JSLoader.scripts[++Ext.ux.JSLoader.index] = {
url: options.url,
success: true,
jsLoadObj: null,
options: options,
onLoad: options.onLoad || Ext.emptyFn,
onError: options.onError || Ext.ux.JSLoader.stdError
};

Ext.Ajax.request({
url: options.url,
params: options.params,
scriptIndex: Ext.ux.JSLoader.index,
success: function(response, options) {
var script = Ext.ux.JSLoader.scripts[options.scriptIndex];
try {
script.jsLoadObj = Ext.decode(response.responseText);
Ext.applyIf(script.jsLoadObj,{jsLoad: function(){return Ext.ComponentMgr.create(script.jsLoadObj);}});
var comp = script.jsLoadObj.jsLoad();
if (comp.remoteInit){
comp.remoteInit();
}
} catch(e) {
script.success = false;
script.onError(script.options, e);
}
if (script.success) script.onLoad(comp,script.options);
},
failure: function(response, options) {
var script = Ext.ux.JSLoader.scripts[options.scriptIndex];
script.success = false;
script.onError(script.options, response.status);
}
});
}

Ext.ux.JSLoader.index = 0;
Ext.ux.JSLoader.scripts = [];

Ext.ux.JSLoader.stdError = function(options, e) {
// throw(e);
window.alert('Error loading script:\n\n' + options.url + '\n\nstatus: ' + e);
}


example usage


new Ext.ux.JSLoader({
url: 'dynamic_comp.cfm', //You can generate components dynamically, I use ColdFusion
params: {foo: 'bar'},
//put anything else you will want to pass to the onLoad function here like
closable: 1,
onLoad:function(comp, options){
//your component will be delivered here
//do what you want with it like at it as a tab to a tabpanel
getMainPanel().add({
title: 'New Tab'
closable: options.closable, //the options contain everything that was defined in your JSLoader config
border: false,
layout: 'fit',
items: [comp]
}).show();
getMainPanel().doLayout();
}
});


There are two was to return the components. One using the lasy render with xtype or the other is generating the components by using new and returning it. I will show both ways

First Way

dynamic_comp.cfm xtype Way


{
xtype: 'panel',
layout: 'column',
title:'Dashboard',
layoutConfig: {
// The total column count must be specified here
columns: 1
},
//This is not required but if it is defined, it will be called by the JSLoader in the scope of this panel
remoteInit: function(){
this.title = 'Im Alive';
}
}


This is easy enough.

Second Way

Return an object with the defined function jsLoad() and return the component.
JSLoader will call the jsLoad function if it exists and pass the component you return to the onLoad method you defined in the JSLoader config

dynamic_comp.cfm Function Way


{
jsLoad: function(){

var subPanel = new Ext.Panel({
title: 'Sub Panel'
})

var thePanel = new Ext.Panel({
title: 'Panel 1',
items: [subPanel]
})

return thePanel;
}
}


Hope this helps someone.

Ronaldo
14 Nov 2008, 4:01 AM
Hi!

Wauw, I haven't tried it but I surely will!
Please consider adding this to the ux extension repository, so we all won't need to look search the forum for the latest code if you haven't done this already.

Ronaldo

mindplay
14 Nov 2008, 5:09 AM
The whole thing went to through major rewrites on my end since that post.

The first rewrite used Events, not just callbacks, and supported dynamically loading components, respected the order in which you loaded things (e.g. guaranteed that you could load a components and a subclass in the correct order), and supported JS and CSS. It was very fast, because it fired up as many simultaneous requests as the browser would allow, and although these might finish out of order, it would inject the scripts in the order you requested them.

In my second rewrite, I'm back to using callbacks. It turns out that using events actually does not make much sense, since the load() event could happen at any point after object creation - you might not have time to attach another listener and actually be guaranteed that the load() event hasn't already passed you by.

I also went from injecting <script>...</script> tags, to the more traditional <script src="..."></script> method instead. I chose this approach because injecting the code makes it impossible to debug your scripts - you can no longer see which file the code came from, which in my case was pretty much disasterous; we're loading some 30-odd different scripts on-demand, and will be loading somewhere around 50-100 different scripts when this project is done, so tracing an error was a real pain...

Checking if the <script> and <link> tags are fully loaded turned out to be quite an interesting cross-browser challenge, but was worked out fully for FF2-3, IE6+, Opera, Chrome (tested on Safari and FF3 on Mac as well).

Unfortunately, the onError event no longer exists in the last implementation, as there is no cross-browser way (only IE can do it) to see if a <script> or <link> tag failed to load.

But it has all of the other features mentioned for the second implementation, plus an extra method a'la PHP's require_once() that loads a script only once, but still respects the order in which you requested the resources.

Another small drawback is the somewhat lower speed, compared to the second rewrite. There is not a lot I can do about that.

I'm negociating with my boss, that we publish the final script as open source. It will most likely be available, with an article, on our blog at some point.

I will post here when that happens...

nouveauc
5 Dec 2008, 1:36 AM
great jobs. I think a system of desktop based on qwikioffice destop but with dynamic js,css or component loaders;fonction include(async) or require(sync):-?.I want to know your opinions;)

mjlecomte
5 Dec 2008, 3:11 AM
As far as how you treat the returned resource, whether you eval it or include it to the head etc., I just made a config option so you tell the class how to handle the response. So if you're debugging, you just switch your toggle to include the file in the head, if you want to use eval you toggle for that. I also was implementing such a toggle in case someone thought they were over exposing themselves to whatever cross site scripting attack.

moegal
11 Jan 2009, 8:33 AM
mindplay,

do you have the latest version of this code? Are you able to release it and how. I could really use this in my current project.

Thanks, Marty

weckmann
22 Oct 2009, 6:50 AM
hmm... i tried using this to load 3 scripts in a row:

1. TinyMCE
2. Ext.ux.ManagedIFrame
3. Ext.ux.TinyMCE

and right after those 3 calls of the JSLoader, i wanted to use the Ext.ux.TinyMCE...

But unfortunately already the loading of the Ext.ux.TinyMCE script failed because he did not know anything about the core TinyMCE objects...

After a while I found out that this maybe is because the TinyMCE script is so large, and this is somehow too much for being loaded on the fly... because when i fired this function a few times in a row, it worked after the third time ;-) So, this is not really made for such large scripts i guess...

Does anyone know how to load such large scripts on-the-fly for sure?
I'm not so keen on forcing everybody to load the whole TinyMCE script right from the beginning, even if he is never using an editor...

F.o.b.
26 Oct 2009, 1:53 AM
Where to download the latest version ??

I'm having troubles with IE8

Tanks

dtex-lab
9 Jan 2010, 12:27 AM
HI
There is a way also to unload a script (a script loaded on demand with this extension)?

Thanks

p.s: the latest version of this script will be very appreciate...

kresnandya
24 Mar 2011, 10:08 PM
is this extension have the latest version?
because it doesn't work for ie8