![]() |
Web Development »
Client side scripting »
General
Advanced
License: The Code Project Open License (CPOL)
Challenge-Me: Javascript Including Engine - Part IIBy Alexandru LunguHow to include js files in other js files. |
Javascript, Windows, WebForms, Dev, QA
|
||||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
Download
source files and examples - 7.67 Kb
Foreword
This is the second part of a three part article and for better understanding I recommend reading the first part before reading this part.
Part II
From Previous Part
The challenge - To create an engine that will implement the JavaScript’s missing functionality of including in js files other js files.
The solution - In the first part I presented a solution that can be easily deduced and implemented by everyone who has some knowledge about dynamic html and the way an external JavaScript file is loaded and executed. But it has a serious drawback: all files should contain only function declarations and object declarations (but only if they're not related to objects from other files).
Therefore in this part we’ll see a totally different approach.
Solution 2
The Logic
Let’s rethink the solution.
Ideally, only the part with the $include function of the file should be executed – and will name this part header, and then, when the time is right, the rest – body, but as I stated above, it cannot be done. So if we talk about header and body, then we have a logical split of the file, and where there is logical split, can’t there be a physical one? A header file (with the jsh extension) and a source file (the normal js file)? Something like C?
Let’s examine the implications of this approach.
We first load all the headers, and from them we can determine the order in
which the source files must be loaded. Perfect. Just, what we need.
From the
previous implementation we’ll keep the code that lets us specify path relative
to the IncludingEngine.js file and not to the current html document.
I’ve done the implementation and another problem appeared: the JavaScript code embedded into the html document gets executed before finishing the execution of all external files added dynamically to the document’s head.
I’ve done some research – reading and testing. While there are some people arguing that external js files are loaded in separate threads, and because of that you can never be sure of the finish loading order and the execution order, I cannot agree with the last part.
From my tests, it seems indeed that the browsers (especially Firefox, not 100% sure about IE) loads the scripts in different threads; BUT if a script ends loading before a previous defined script, its execution is delayed until the previous script ends its loading and execution, and after that is executed; this way all scripts are executed in the right order.
Unfortunately, this order of execution is messed up if you dynamically insert
or remove script objects.
Now we’re in the situation of not knowing the order
of execution, but we can avoid this by loading a script only after the previous
was loaded and executed.
However the embedded JavaScript code will be executed long before the execution of the external scripts (most of the time), but the other way around is a possibility too.
So our code from the html document can use the code from the external script only after these are all executed.
So we must have an event like window.onload to tell us when the external
files have finished their execution. Since there is the possibility for external
scripts to be executed first, an event to handle both situations would be even
better.
And more intuitively would be a function that will be executed when
“everything is ready”. And, for obvious reasons we will call this function
The Implementation
var IncludingEngine = {};
IncludingEngine.FindPath = function()
{
var scripts = document.getElementsByTagName("script");
var foundNo = 0;
for (var i=0; i<scripts.length; i++)
{
if (scripts[i].src.match(this.FileName))
{
this.Path = scripts[i].src.replace(this.FileName, "");
foundNo ++;
}
}
if (foundNo == 0)
throw new Error("The name of this file isn't " +
this.FileName + "!\r\nPlease change it back!");
if (foundNo > 1)
throw new Error("There are " + foundNo + " files with the name " +
this.FileName + "!\r\nThere can be only one!");
}
IncludingEngine.Init = function ()
{
this.FileName = "IncludingEngine.js";
this.Path = "";//the root path of the engine;
//all files included by this engine will be relative to this path
this.FilesLoaded = new Array(); //will store the files already loaded
//in order not to load (execute) a file many times
//status files
//0 - just identified
//1 - loading
//2 - loaded
this.HeaderFilesStatus = new Array();//hashtable with the status of the header files
this.SourceFileStatus = new Array();//hashtable with the status of the source files
this.SourceFiles = new Array();//array with the source files
//(used to create the head tag with them in the reverse order)
this.FindPath();
}
//Loads the header files (.jsh)
IncludingEngine.HeaderScriptLoad = function()
{
if (this.readyState)
if (this.readyState != "complete")
return;
IncludingEngine.HeaderFilesStatus[this.src] = 2;
var done = true;
for(var el in IncludingEngine.HeaderFilesStatus)
if (IncludingEngine.HeaderFilesStatus[el] != 2)
done = false;
if (done)
{
var head = document.getElementsByTagName("head")[0];
for (var k = IncludingEngine.SourceFiles.length - 1; k >= 0; k--)
{
if (IncludingEngine.SourceFileStatus[IncludingEngine.SourceFiles[k]] == 0)
{
var script = document.createElement("script");
script.src = IncludingEngine.SourceFiles[k];
script.type = "text/javascript";
script.onload = script.onreadystatechange = IncludingEngine.SourceScriptLoad;
IncludingEngine.SourceFileStatus[IncludingEngine.SourceFiles[k]] = 1
head.appendChild(script);
}
}
}
var head = document.getElementsByTagName("head")[0];
head.removeChild(this);
}
//Loads the source files (.js)
IncludingEngine.SourceScriptLoad = function()
{
if (this.readyState)
if (this.readyState != "complete")
return;
IncludingEngine.SourceFileStatus[this.src] = 2;
var done = true;
for(var el in IncludingEngine.SourceFileStatus)
if (IncludingEngine.SourceFileStatus[el] != 2)
done = false;
if (done)
{
if (Main)
{
if (typeof Main == "function")
Main();
}
}
}
function $include()
{
var head = document.getElementsByTagName('head')[0];
for (var i = 0; i < arguments.length; i++)
{
if (IncludingEngine.HeaderFilesStatus[arguments[i] + ".jsh"] == null)
{
var script = document.createElement("script");
script.src = IncludingEngine.Path + arguments[i] + ".jsh";
script.type = 'text/javascript';
script.onload = script.onreadystatechange = IncludingEngine.HeaderScriptLoad;
IncludingEngine.SourceFiles.push(IncludingEngine.Path + arguments[i] + ".js");
IncludingEngine.SourceFileStatus[IncludingEngine.Path + arguments[i] + ".js"] = 0;
IncludingEngine.HeaderFilesStatus[script.src] = 1;
head.appendChild(script);
}
}
}
IncludingEngine.Init();
I think that the implementation is pretty much self-explanatory. Init is mostly like in the first solution and FindPath is identical. HeaderScriptLoad is for loading the headers and generating the proper order to load the source scripts and SourceScriptLoad is for loading the source files in the order generated earlier.
As you can observe, I didn't checked for circular dependencies; this
is implemented in the next part.
Notes:
-
I’ll edit this post to add the link to the last part as soon as I post
it.
- The latest version of this implementation can be found at IncludingEngine.jsFramework.com
| You must Sign In to use this message board. | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 10 Jul 2008 Editor: |
Copyright 2007 by Alexandru Lungu Everything else Copyright © CodeProject, 1999-2009 Web18 | Advertise on the Code Project |