@main[] ^header[JavaScript include] ^maindir[2]
^portrait[voloko]
Vladimir Kolesnikov ^email[voloko;]

JavaScript include19 March 2007


 
Task: Simplify the task of making large web projects.

Include 

Today it&6;s hard to imagine a website without JavaScript. And the closer the developer wants his project to be to the so-called web 2.0, the larger is javascript part of the code.

Numerous scripts are more difficult to structure and <head> elements in the beginning of each page start looking like this:

01 
02 
03 
04 
05 
06 
07 
08 
<script src="/js/als/widget/Box.js" type="text/javascript" encoding="UTF-8"></script>
<script src="/js/als/utils/Text.js" type="text/javascript" encoding="UTF-8"></script>
<script src="/js/domain/ClientsInfo/Widget/Properties.js" type="text/javascript" encoding="UTF-8"></script>
<script src="/js/domain/ClientsInfo/Widget/Address.js" type="text/javascript" encoding="UTF-8"></script>
<script src="/js/domain/ClientsInfo/Widget/Person.js" type="text/javascript" encoding="UTF-8"></script>
<script src="/js/domain/ClientsInfo/Widget/Company.js" type="text/javascript" encoding="UTF-8"></script>
<script src="/js/domain/ClientsInfo/Page/Index.js" type="text/javascript" encoding="UTF-8"></script>
... and 30 more similar lines ...
				

And there&6;s more to it. File Page/Index.js depends on Company.js, which also depends on Widget/Person.js and Widget/Address.js. And these are dependent on Widget/Date.js and Box.js and so on.

To make it worse, you have to follow the exact order. And what if there are many pages? And if you need to, say, break a widget into two files? Or add a couple of new classes? Or combine several scripts into a single file to speed up loading?

It would be rather convenient if all dependencies were included directly in js files.

Almost all mature languages have a special include function. JavaScript lacks it, but it can be simulated if necessary. Wouldn&6;t it be nice if you could write something like this in the beginning of Company.js:

01 
02 
03 
04 
05 
06 
07 
js.include('als.Template');
js.include('als.utils.Text');
js.include('als.widget.Box');
js.include('domain.ClientsInfo.Widget.Properties');
js.include('domain.ClientsInfo.widget.Address');
js.include('domain.ClientsInfo.widget.Person');
...
				

So, what stops you? Is web 2.0 only good for making color buttons?

Solution 

What are the underlying problems? Firstly, include function has to finish before code execution starts. Secondly, several files may depend on the same module and you wouldn&6;t want to run it twice. Thirdly, you need a way to pull files from server. All right, that&6;s enough for now.

It&6;s 2007 and most browsers support XHTTPRequest, so that&6;s what you should use. XHTTPRequest object works in two modes: asynchronous (with specified callback function) and synchronous (request is made upon running xtthp.open()). Use the latter&1;the code will be executed right in place of js.include. And then just call eval(xhttp.responseText):

01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
//js.js
js = {};
js.loadedModules = {};
…
js.include = function(path) {
	if(js.loadedModules[path]) return;

	var transport = js.getXHTTPTransport();
	transport.open('GET', js.rootUrl + path.replace(/\./g, '/') + '.js', false);
	transport.send(null);

	var code = transport.responseText;
	eval(code);
}
				

That&6;s it. Now, the include mechanism works if you just run js.js before code execution.

Modules 

If&6;s a good idea to have module declarations by themselves. For example, you can put js.module(&5;js.Event&6;) in the beginning of each file and create the following function:

01 
02 
03 
js.module = function(path) {
	js.loadedModules[path] = true;
}
				

This way the include knows that the script with such path has already been retrieved, even if < script> tag is used to refer to js files.

And besides, each script call is a server request. So, if you need to pull files as a constant set, it is best to combine them into one larger file. It would reduce the number of server operations and the browser-to-server traffic. Here is the code:

01 
02 
03 
04 
05 
06 
07 
08 
js.module("domain.Class1");
domain.Class1 = function() {…}

js.module("domain.Class2");
js.include("domain.Class1");

domain.Class2 = function() {…}
domain.Class2.prototype = new domain.Class1();
				

Comment

Declaring modules in the very beginning (before the include) enables to avoid infinite loops even in case of cyclic dependencies between files.


Namespaces 

Since files are arranged into folders like domain/ClientsInfo/Widget/Person.js, file Person.js should contain definition of class domain.ClientsInfo.Widget.Person. But if you try to create class domain.ClientsInfo.Widget.Person in the absence of domain.ClientsInfo.Widget, the interpreter will report error. However, it would be unreasonable to check in each file whether objects domain.ClientsInfo.Widget, domain.ClientsInfo and just domain exist.

Namespace declaration can be conveniently included in module declaration. For example:

01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
js.evalProperty = function(object, name, value) {
	if(object) {
		if(!object[name]) object[name] = value || true;
		return object[name];
	}
	return null;
}
js.evalPath = function(path, context, value) {
	context = context || window;
	var pos = path.indexOf('.');
	if(pos == -1) {
		return js.evalProperty(context, path, value);
	} else {
		var name = path.substring(0, pos);
		var path = path.substring(pos + 1);
		var obj = js.evalProperty(context, name, value);
		return js.evalPath(path, obj, value);
	}
}
js.module = function(path) {
	js.loadedModules[path] = true;
	js.evalPath(path);
}
				

Debugging

The called script is executed through eval(). Therefore, you won&6;t be able to fix it in Mozilla. Visual Studio allows a bit more, but doesn&6;t highlight syntax. You can manage by using fixable <script> tag.


Options 

Common (does not require additional libraries)   4,56 Kb,
Packed   2,72 Kb,
For prototype.js   3,44 Kb,
For prototype.js, packed   1,95 Kb.

^include[/bottomblok.html]