Click here to Skip to main content
Click here to Skip to main content
Go to top

Challenge-Me: JavaScript Including Engine - Part II

, 10 Jul 2008
Rate this:
Please Sign up or sign in to vote.
How to include JavaScript files in other JavaScript files.

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 the Previous Part

The Challenge

To create an engine that will implement JavaScript’s missing functionality for including JavaScript files in other JavaScript 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 – we 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 a logical split, can’t there be a physical one? A header file (with a jsh extension) and a source file (the normal js file)? Something like in 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 a path relative to the IncludingEngine.js file and not to the current HTML document.

I did the implementation and another problem appeared: the JavaScript code embedded in the HTML document gets executed before finishing the execution of all external files added dynamically to the document’s head.

I did some research – reading and testing. While there are some people who argue that external JavaScript files are loaded in separate threads, and because of that you can never be sure of the 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 previously 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 a situation of not knowing the order of execution, but we can avoid this by loading a script only after the previous one was loaded and executed.

However, the embedded JavaScript code will be executed long before the execution of 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 intuitive 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 check 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 the "JavaScript Including Engine" article series: 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)

Share

About the Author

Alexandru Lungu
Architect Adrem Invest SRL
Romania Romania
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 challenge-me.ws
 

 

 
Don't forget to vote or share your comments.

Comments and Discussions

 
GeneralAwesome ideea! PinmemberJennifferKonstandt29-Jul-07 17:38 
GeneralNice PinmemberMircea Puiu23-Jan-07 4:38 
GeneralRe: Nice PinmemberAlexandru Lungu23-Jan-07 22:07 
GeneralRe: Nice PinmemberMircea Puiu23-Jan-07 23:03 
GeneralRe: Nice PinmemberAlexandru Lungu24-Jan-07 12:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 11 Jul 2008
Article Copyright 2007 by Alexandru Lungu
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid