5,693,062 members and growing! (17,472 online)
Email Password   helpLost your password?
Web Development » Client side scripting » General     Advanced License: The Code Project Open License (CPOL)

Challenge-Me: Javascript Including Engine - Part II

By Alexandru Lungu

How to include js files in other js files.
Javascript, Windows, WebForms, Dev, QA

Posted: 22 Jan 2007
Updated: 10 Jul 2008
Views: 18,390
Bookmarked: 9 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
21 votes for this Article.
Popularity: 5.20 Rating: 3.93 out of 5
2 votes, 9.5%
1
0 votes, 0.0%
2
0 votes, 0.0%
3
0 votes, 0.0%
4
19 votes, 90.5%
5
Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article

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 Main.



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

Here are all the parts of "Javascript Including Engine" article: Part I, Part II, Part III.
Also you may want to visit the IncludingEngine website for more info.






 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Alexandru Lungu


Alexandru Lungu graduated in 2004, first in his class, Cybernetics, Statistics and Computer Science faculty from A.S.E. Bucharest, and two years later finished his master in project management.

His motto: “Challenge is Life!”

Now, he works as project manager, developer, consultant depending on the “Challenge”.

You can reach him at allex4project@yahoo.com.

Occupation: Web Developer
Location: Romania Romania

Other popular Client side scripting articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 5 of 5 (Total in Forum: 5) (Refresh)FirstPrevNext
GeneralAwesome ideea!memberJennifferKonstandt18:38 29 Jul '07  
GeneralNicememberMircea Puiu5:38 23 Jan '07  
GeneralRe: NicememberAlexandru Lungu23:07 23 Jan '07  
GeneralRe: NicememberMircea Puiu0:03 24 Jan '07  
GeneralRe: NicememberAlexandru Lungu13:09 24 Jan '07  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 10 Jul 2008
Editor:
Copyright 2007 by Alexandru Lungu
Everything else Copyright © CodeProject, 1999-2008
Web19 | Advertise on the Code Project