PDA

View Full Version : Dynamic Script Load for ExtJS 2.0



dimitrij.zub
9 Aug 2008, 3:45 AM
Hi all.

I have developed this small piece of code to reduce the time my application needs to load at starttime and dynamically load modules if required.

So firt the code:



Ext.namespace('Ext.rt');
Ext.rt.LoadDialog = Ext.extend(Ext.Window, {

modal: true,
closable: false,
resizable: false,
draggable: false,
layout: 'fit',
hideMode: 'display',
progressBar: null,
steps: null,
status: null,
callback: null,
component: null,
dependency: null,
width: 300,
height: 35,
dependencies: null,

initComponent: function() {

this.dependencies = new Array();

this.progressBar = new Ext.ProgressBar({
id: Math.random()
});
Ext.apply(this, {
items : [this.progressBar]
});
this.on({
beforeshow : function(comp) {
comp.center();
}
});
Ext.rt.LoadDialog.superclass.initComponent.apply(this, arguments);
},

load: function(component, url, callback, steps) {

if (component)
this.dependencies.push([component, url]);

if (steps) {
this.steps = steps;
this.status = 0;
}
if (callback)
this.callback = callback;

var scripts = document.getElementsByTagName("script");
for (i=0;i<scripts.length;i++)
if (scripts[i].getAttribute('src')== url) {
if (component!=null)
this.onLoadComplete(component);
return;
}

this.show();
this.progressBar.updateProgress(this.status/this.steps, '#{global.loading_component}');

var e = document.createElement("script");
e.src = url;
e.type="text/javascript";
document.getElementsByTagName("head")[0].appendChild(e);
},

depends: function(component, url) {
var scripts = document.getElementsByTagName("script");
for (i=0;i<scripts.length;i++)
if (scripts[i].getAttribute('src')== url)
return;
this.dependencies.push([component, url]);
},

next: function() {
this.status++;
this.progressBar.updateProgress(this.status/this.steps);
},

done: function() {
this.progressBar.updateProgress(1.0);
this.hide();
},

onLoadComplete: function(component) {
this.next();

if (component) {
var slice = this.getIndex(component);
this.dependencies.splice(slice, 1);
}

if (this.dependencies.length == 0)
eval(this.callback+"()");
else {
this.load(null,this.dependencies[0][1]);
}
},

getIndex: function(component) {
for (var index = 0; index < this.dependencies.length; index++) {
if (this.dependencies[index][0] == component)
return index;
}
},

onFormActionStart: function(action) {
Ext.rt.Application.Loader.show();
Ext.rt.Application.Loader.status = Ext.rt.Application.Loader.steps-1;
if (action && action.options)
Ext.rt.Application.Loader.progressBar.updateProgress(Ext.rt.Application.Loader.status/Ext.rt.Application.Loader.steps, action.options.waitMsg);
else
Ext.rt.Application.Loader.progressBar.updateProgress(Ext.rt.Application.Loader.status/Ext.rt.Application.Loader.steps, '#{global.status_loading_data}');
},

onFormActionEnd: function(action, success) {
Ext.rt.Application.Loader.progressBar.updateProgress(1.0);
Ext.rt.Application.Loader.hide();
var options = null;
if (action)
options = action.options;
if(success){
if (options) {
if(options.reset)
this.reset();
Ext.callback(options.success, options.scope, [this, action]);
}
this.fireEvent('actioncomplete', this, action);
} else {
if (options)
Ext.callback(options.failure, options.scope, [this, action]);
this.fireEvent('actionfailed', this, action);
}
}
});
now how to use it:

First create a global loader in your applications onReady function:



Ext.rt.Application.Loader = new Ext.rt.LoadDialog();
Lets say you want to load the a module and once its loaded you want to run an action.




Ext.rt.Application.Loader.load(
"Ext.rt.Module", //the name of the module
"/Ext.rt.Module.js", //the location of the file
"Ext.rt.Module.onPlug", //the function to call once the module is completely loaded
5); //estimated steps of loading

will load the module. Now the module might have dependencies and how to know if the module is loaded at all and it should be working in all browsers!

you will need to extend your modules by those lines:



Ext.rt.Application.Loader.depends(
'Ext.rt.AnotherModule', //the module we depend on
"/Ext.rt.AnotherModule.js"); //the location of the dependency

Ext.rt.Application.Loader.onLoadComplete('Ext.rt.Module'); //inform the loader that this module is loaded.
You can go ahead and add more dependencies in any script you are depending on. Just trigger the dependencies before you inform about a sucessfull load of the module.

The loader will add those to a list of dependencies and add the script tags to your dom. Each time a module informs about its load to be complete, the dependency will be removed. If no more dependencies are to be loaded the callback will be run.

You are welcome to modify the code and post improvements.

Regards :)

caerolus
7 Sep 2008, 8:44 AM
If I am correct, the dependencies are loaded after the current script is processed, right?
In such a case, if my script foo.js has a variable called F, and my script foo2.js uses that F variable, foo2 would tell the loader that it depends on foo:

Ext.rt.Application.Loader.depends('foo','foo.js');But, after that, foo2.js is processed, and gives an error because F does not exist yet.
Any way of loading the depended scripts before to make this work?=D>

dimitrij.zub
7 Sep 2008, 10:43 AM
I dont know of any browser independend and solid solution to check if the script is completely loaded :( I checked on the inet a lot of google results to figure that out. Maybe i can have a deeper look into the dojo loader. But anyway. This problem should occur if you are using js files not as libs/modules but as splitted linear script (which will make them very unmanagable if you get more and more of those and you lose compeltely their reusability). To help you out... :

I guess what you have is something like:

foo.js:


var F = 1;
Ext.rt.Application.OnLoadComplete("foo");


foo2.js:


F++;

Ext.rt.Application.depends("foo", "foo.js");
Ext.rt.Application.OnLoadComplete("foo2");


In this case it will not work. Because in order to be sure, that the script is loaded. The following happens:

1. You trigger loading of foo2.js.
2. Script is loaded from the server
3. Script foot2.js is processed line by line which means:
4. you try to do F++;
4. you add the dependency of foo
5. you tell that foo2.js is loaded.
6. Loader checks if any dependency is still to be loaded
7. it loades foo.js
8. foo.js creates F
9. foo.js informs of it being loaded
10. loader checks for dependencies -> None.
11. loader triggers the callback.

So. i.o. to make it work you should rewrite your scripts to:

foo.js -> Leave it as it is

foo2.js:


function doStuffWithF() {
F++;
}
Ext.rt.Application.depends("foo", "foo.js");
Ext.rt.Application.OnLoadComplete("foo2");


The loader which triggers the dynamic loading:


Application.rt.Loader.load("foo2", "foo2.js", "doStuffWithF", 2);


what will happen:

1. You trigger loading of foo2.js.
2. Script is loaded from the server
3. Script foot2.js is processed line by line which means:
4. you create function doStuffWithF. F++ is not checked nor performed.
4. you add the dependency of foo
5. you tell that foo2.js is loaded.
6. Loader checks if any dependency is still to be loaded
7. it loades foo.js
8. foo.js creates F
9. foo.js informs of it being loaded
10. loader checks for dependencies -> None.
11. loader triggers the callback.
12. doStuffWithF() is executed
13. F++ is being performed.

Some comment: :)

You should avoid using global vars and use instead an application class to hold them and their state as well as use it for the initialization of your application. Check out those to better undertand what this script was intended to manage:

1. http://extjs.com/learn/Manual:Basic_Application_Design
2. http://extjs.com/learn/Tutorial:Writing_a_Big_Application_in_Ext

Normaly you would include the loader into the mentioned application.js

Have fun and thanks for using :)

harley.333
7 Sep 2008, 12:44 PM
Personally, I've created a modular architecture where scripts are not loaded until necessary. To solve the "X is undefined" error, I use the following type of script:


<script type="text/javascript" src="X.js"></script>
<script type="text/javascript">
var f = function() {
if (typeof X == "undefined") {
f.defer(1);
} else {
new X("<% =Request.Form["cmpId"] %>").init();
}
};
</script>

The "X is undefined" error can also be read as "X is not defined yet." So, I use the "defer" method to wait until the object is defined.

As you can see, each module has a constructor and an "init" method. I only do this for more flexibility. The "cmpId" is the Component ID where I want the object to render whatever it needs to render. "cmpId" is passed to the server from the Ext application (usually, from a navigation tree), and then passed back down to the browser as a parameter to the object's constructor. Obviously, other parameters can be passed as the developer sees fit.