Tips for Building a JavaScript Library


ou're an experienced developer. You learned a long time ago how to make your code generic and factor it for optimum reusability, neatly organizing it into tight, independent packages. Need a multithreaded web scrapper? You have a C# assembly that does that, thank you. Logging to the event viewer from T-SQL? No problem, there's that script you developed a few years ago. Want transformed text embedded in a graphic? Got it covered. The toolkit you've built over the years equips you to easily handle almost any task.

Almost.

Well… There is that bit of untidiness with your JavaScript. But everyone has that, right? I mean, with the hundreds of snippets smattered across the internet how could anyone keep those straight? Sure, you've made the obligatory attempt. You put the most often used routines into a single common include file. But the file grows with almost every new project, and each project leveraging this file uses only a fraction of its contents. The practice of monolithic includes is… unsatisfying.

Fear not. There is a better way.

About this Article

This article describes some of the impediments that make fashioning and maintaining a library in JavaScript troublesome and discusses some techniques for surmounting these obstacles. These techniques have been adopted by CodingMonk and are manifest as the JACL (JavaScript Application Code Library) framework. As such, most of the examples are given in that context, yet the techniques described here lend themselves equally well to a personal implementation.

The code depicted here has been tested under both the Firefox and Internet Explorer browsers, including Microsoft's implementation of HTAs (HTML applications). I suspect that these techniques could also be ported to additional browsers (e.g. Opera, Safari) with minimal effort.

Before going further, I'd like to make it clear that my way is not the only way. There are a number of gifted JavaScript developers around the world and I expect there exist several additional techniques not covered here. Please keep in mind that what works well for me may not fit well in the preferences of another developer.

What Makes a Code Library?

A fundamental preconception that people have when they hear the word "library" is that it is a collection of smaller parts. Although I can lump all of my scripts into a single reusable file and call it "Jim's library", that doesn't really hold true to the spirit of the word. Likewise, just as we wouldn't checkout every book from the shelves of a real library when researching English history, we shouldn't have to download the complete code when we're only using one or two of its features.

So, our library should be organized into functional modules from which we can pick and choose what we need.

This deceptively simple diktat is absent from the majority of JavaScript "libraries" available on the internet. Why? Because modules inject unexpected complexity into a library, especially highly functional ones leveraging other modules as dependencies. We'll identify some for these complexities and address them now. When we're finished, we'll have a basis upon which to build a working, expandable framework for our JavaScript library.

Modules Including Modules

As a code library matures, it's almost a given that one module will eventually need to include another. In JACL, for example, the Log module is automatically included by the Core module, so logging functionality is always available. Another example: each of JACL's self-updating controls leverages an AJAX implementation housed in the ServerRequest module.

So how do we include a JavaScript file from within another JavaScript file? Obviously the most common way to include JavaScript from HTML is with the script tag:

     

<script src="http://www.codingmonk.com/jacl/jacl.js"></script>

     

So from within a JavaScript file, one possibility is to leverage document.write() to inject customized script tags whenever a library module needs to meet a dependency. A la:

     

document.write( "<script src='http://www.codingmonk.com/jacl/jacl.js'> </script>");    

     

One complication associated with dynamic inclusion of JavaScript: it is not immediately obvious when the included file has successfully loaded. Despite their similarities, it's easy to forget that the above two lines are not exactly equivalent. To be precise:

     

  • The first, declared within the HTML document, includes the JavaScript file while the page is first loading, but before scripts are run.
  • The second, processed after the page has finished loading and declared scripts are running, identifies subsequent scripts that the browser should load when it gets the opportunity.

     

So as rule: JavaScript included from JavaScript is not immediately available, but JavaScript included from a tag in the unmodified HTML document is.

To demonstrate, we can do this:

     

<html>

<script src="http://www.codingmonk.com/jacl/jacl.js"></script>

<script language = "JavaScript">

jacl.appMain = function(){alert("Something wicked this way comes...");};

</script>

</html>

     

But we can't we do this:

     

<html>

<script language="JavaScript">

document.write( "<script src='http://www.codingmonk.com/jacl/jacl.js'></script>");

jacl.appMain = function(){alert("Something wicked generates an error...");};

</script>

</html>

     

Because the second example produces an error message in the spirit of "jacl is undefined".

A more sophisticated alternative is to leverage the browser DOM, like so:

     

<html>

<script language="JavaScript">

var o=document.createElement("script");

    o.src="http://www.codingmonk.com/jacl/jacl.js";

    o.onreadystatechange = function(){

if(this.readyState == "loaded"){

jacl.appMain = function(){alert("Something wicked this way comes...");};

}

};

document.body.appendChild(o);    

</script>

</html>

     

Use of the DOM to dynamically load JavaScript provides a solution to our problem in the form of the onreadystatechange event. This event fires only when the script has loaded completely, at which point we can reference functions and objects provided by the included file with impunity.

Initial Module Inclusion

Despite the DOM example in the last section, the practice of including JavaScript from JavaScript gains us nothing when used where traditional HTML script tags are possible. If used appropriately, it enables us to manage our library entries and their dependencies intelligently, but it is still necessary to include the first module by hand.

This brings us full-circle to the first code snippet listed in this article:

     

<script src="http://www.codingmonk.com/jacl/jacl.js"></script>

     

As it turns out, there is considerable usefulness in including an initial module as we would include a traditional script file. It provides for us select core routines without having to wait for a load event, including implementation of principals discussed above which load modules programmatically. Additionally, it gives us a place to perform library initialization. This can include, among other things, an application-defined entry point where we can be certain all library modules are loaded and ready.

To demonstrate, below is a working example of an HTML page leveraging JACL's Plot module to draw a sine wave. For those that are curious, it can cut-and-pasted into an html file and viewed it in any browser.

     

<html>

<body>

<script src="http://www.codingmonk.com/jacl/jacl.js"></script>

<script language="JavaScript">

    jacl.use("jaclPlot");

    jacl.appMain=function(){

        jacl.plot.setOrigin(50,100);

        jacl.log.info('Graphing sine wave...');

        for(var t=0;t< 300;t++)

            jacl.plot.point(t,Math.sin(t/30)*30);

        };

</script>

</body>

</html>

     

Notice that our initial inclusion of the now familiar JACL Core module is done explicitly as an HTML tag. This ensures that the library's objects (in this case the jacl object) are present, as are library defined code. In the JACL framework, this includes the use method, which is simply a refined implementation of the DOM principals discussed in the previous section, and the appMain method which is similar in principal to the onreadystatechange event but fires only once when all modules have finally been loaded.

Avoiding Multiple Inclusions

Another complexity inherent in the practice of modules-including-modules occurs when two modules share the same dependency. If not handled, the same module can be included twice. The worst case scenario results in self-referencing recursion and a page that never finishes loading.

The simplest solution is to set a flag when a module is included for the first time, doing nothing on subsequent inclusions. By leveraging our object hierarchy, this is almost laughably easy as the individual objects created by a module act as flags simply by virtue of their existence:

     

if(jacl.plot==null) { //already created?

    jacl.plot=new jacl_plot(); //no? then let's create an instance

    // implementation goes here

}

     

The only drawback to this is that, while easy, the approach must be enforced across all library modules. A better alternative, one that can be implemented in the library object itself is to track which modules have been already been requested and don't add them via the DOM more than once.

Establishing a Library's Location

Astute observers will notice one important nuance in the sine wave example:

     

jacl.use("jaclPlot");

     

The use method does not require a fully qualified file path. At first glance this seems a trivial feature. After all, if we need to specify the URL when we include one module, it is only a small inconvenience to provide the URL with subsequent inclusions. The real problem arises, again, when modules include other modules. In the example where we included modules with the DOM, the path was hard-coded, as in:

     

o.src="http://www.codingmonk.com/jacl/jaclPlot.js";

     

This isn't going to do us any good if we try to access our library locally, or if we move it to a different domain. What we need is for the module to determine the library's location relative to the client. As it turns out, this isn't too difficult. Since we're including an initial core module with a fully qualified filename all we have to do search the document's script objects and parse the path from the correct one:

     

var path="";

for(t=0;t<document.scripts.length;t++){

        var val=document.scripts[t].outerHTML.toLowerCase();

        if(val.indexOf("jacl.js")==-1) continue;

        var s=val.indexOf("src=\"");

        if(s!=-1){

            s+=5;

            path=val.substring(s,val.indexOf("jacl",s));

            break;

        }

    }

     

So the use method knows to expand:

jaclplot

into

http://www.codingmonk.com/jacl/jaclplot.js

by using the resulting path variable. Of course, storing the path as a global variable clutters the namespace. This is just one of several advantages of leveraging an object hierarchy for our JavaScript library…

Organizing a Hierarchy

The best, most useful of libraries can suffer and die because it is not kept organized. As libraries grow, they become more difficult to keep arranged. No matter how many modules your library is broken down into, if the net result is just a chaotic bundle of routines, then the library hasn't helped as much as it could.

Namespace hierarchies help us to stay organized. More than just factoring code into modules, this technique structures our code in precise and predictable ways. Notice from our examples how every use of the JACL library stems either directly or indirectly from the global jacl object. JACL's implementation of the previous path-finding example populates a member variable rather than a global one:

    jacl.path

By doing this, we need not worry ourselves with the possibility of naming collisions, where another module or otherwise appropriated script defines a variable named "path" too.

Continuing the review of the fully working sine-wave example given previously, notice that JACL's logging module adds an object which encapsulates various pieces of logging functionality:

     

jacl.log.trivial("Here is a message");

     

Other JACL modules go to greater lengths to categorize their code:

     

jacl.input.textbox.applyEditMask(tbInput, "###-##-####");

     

How diverse you choose to make your hierarchy is, of course, a personal preference.

Review a real implementation

As was pointed out numerous times, the techniques proposed in this article are more than theoretical. For a complete working example, one moderately more fleshed out, I highly recommend taking a look at the most recent implementation of Coding Monk's JACL framework:

     

www.codingmonk.com/downloads/jacl.zip

     

Happy coding!

   

Print | posted @ 8/17/2008 10:19:40 PM

Comments have been closed on this topic.