JavaScript Includes Revisited

I’ve given in in the JavaScript and Modules matter. I’m doing now what all the cool kids are doing: Using XMLHttpRequest synchronously to load external JavaScript files. This has the advantage that it works synchronously, a very important thing for includes. Therefore I don’t need callback and moduleLoadedhackery anymore. Also I have sensible error checking, i.e. I can notice when loading a JavaScript module failed. This enables me to have multiple search paths, for example, and sensible error handling.

But it comes at a cost, too. XMLHttpRequest is only supported by “modern browsers”. But since moderns browsers means any browser of the last five years I don’t care too much. When XMLHttpRequest is not supported, I suppose I can’t rely on other useful stuff neither. The synchronous requests also mean that there might be a slight performance hit. But the advantages of synchronous includes outweigh this by far. Finally, XMLHttpRequest doesn’t provide the means to recognize when a synchronous request is stalled. This is not of too much concern, since a stalled request usually means that an include files can’t be downloaded and so the JavaScript application is not usable anyways. It might be a concern for sensibly degrading, though.

Update: Here is the code to the updated module. Using it is now as simple as this:

<script src="include-http.js"></script>
<script>
    include.include("my.module");
    myfunc(...);
</script>

Porting JavaScript to Internet Explorer

While porting a small AJAX application I wrote to Internet Explorer, I encountered a few problems:

  1. IE doesn’t like <script> tags without a matching closing tag. This is especially a problem if you use the src attribute and try to use an XHTML-like closing tag like this:
    <script src="..."/>
    

    In this case IE doesn’t draw anything at all, since it keeps looking for the end of the script and doesn’t find it. This is especially a problem with templating toolskits like Kid. If Kid encounters a construct like <foo></foo> it will correctly shrink this to <foo/> in the XML output. The solution is rather easy: Insert a newline character between openening and closing tag in the template. Since Kid has now way to determine whether this whitespace is significant it will keep it in the output.

  2. It seems that IE doesn’t like trailing commas in JavaScript object literals:
    var o = {
        x: 'Foo',
        y: 1,
    };
    

    I like to insert trailing commas in cases like this. It’s easy to forget to add one if extending the property list. Firefox parses the trailing comma, while IE doesn’t. (IE follows the ECMAScript standard here, which doesn’t allow the trailing comma. Indeed in array literals a trailing comma has a special meaning, so it’s probably a good idea to disallow the trailing comma in object literals.)

  3. My AJAX application queries a web service for a list of items and displays that list in a table. The table is generated in HTML code like this:
    <table id="mytable">
        <tr>...</tr>
    </table>
    

    I then used JavaScript code like the following to populate that table:

    var table = document.getElementById("mytable");
    var trTag = createRow();
    table.appendChild(trTag);
    

    This worked fine in Firefox, but no matter what I did, IE refused to render the table rows. After much hair tearing I remembered that Firefox’s DOM Inspector showed a TBODY element just beneath the TABLE element and TR elements are attached to the former. The DOM Inspector basically showed the following structure:

    • TABLE id=mytable
      • TBODY
        • TR (my header row from the HTML)
      • TR (a dynamically generated row)

    So I changed my code to append the newly generated rows to the TBODY instead:

    var table = document.getElementById("mytable").
        getElementsByTagName("tbody")[0];
    var trTag = createRow();
    table.appendChild(trTag);
    

    This works in Firefox and IE.

Loading Modules in JavaScript

One of the weaknesses of the JavaScript language is that it does not have the concept of “importing” or “including” other source files or modules. In client-side JavaScript as used in web browsers you have to include all JavaScript files you intend to use in the main HTML file like this:

<html>
    <head>
        <title>...</title>
        <script src="A.js"></script>
        <script src="B.js"></script>
        <script src="S.js"></script>
    </head>
    ...
</body>

In this case S.js is a page-specific JavaScript script that uses features from A.js and B.js.

The standard way to load other JavaScript files dynamically is to add a script element programmatically to the head section of the document, using DOM:

function include(filename) {
    var scriptTag = document.createElement("script");
    scriptTag.setAttribute("lang"), "javascript"));
    scriptTag.setAttribute("type"), "application/javascript"));
    scriptTag.setAttribute("src", filename);
    document.getElementsByTagName("head")[0].appendChild(scriptTag);
}

Unfortunately this approach has some problems as well: The include file is loaded “lazily”, i.e. the next time the JavaScript interpreter hands back control to the browser. (At least it is this way in Firefox.) If, for example, a file A.js defines a function func_a, the following will not work:

include("A.js");
func_a();

My first solution was to use a callback, invoked when the module has been loaded:

function include(filename, callback) {
    var scriptTag = createScriptTag(filename); // as above
    scriptTag.onreadystatechange = function() {
        if (scriptTag.readyState == "complete") {
            callback();
        }
    }
    scriptTag.onload = function() {
        callback(file);
    }
}

This solution uses the non-standard onload (Firefox) or onreadstatechange (IE) events. It is invoked like this:

include("A.js", function() {
    a_func();
});

This solution also has several problems: It is non-standard, it is ugly (though this can’t be helped, due to the asynchronous script loading), and including a module from a module still doesn’t work. To explain the last problem, I will use a script S.js which depends on the function a_func defined in A.js, which in turn depends on b_func defined in B.js. Now the code should look like this:

// S.js
include("A.js", function() {
    a_func();
});

// A.js
include("B.js", function() {
    a_func = function() {
    }
    a_func.prototype = new b_func();
});

// B.js
// ...

The problem is that the callback in S.js is not necessarily processed after the callback in A.js, leading to a_func being undefined.

The solution I have come up with is not particularily nice, but this can’t really be helped. Basically each module has to call a function when it has fully loaded, like this:

// A.js

include.module("B.js", function() {
    // ...

    include.moduleLoaded("A");
});

The main HTML file must load the include.js before loading any other module and modules should only be loaded via include.module. Also my implementation uses “real” module names, e.g. a.b.c instead of a/b/c.js. This allows me to expand the mechanism easily, for example to add a module search path.

Sample implementation