Managing Source Code with Modules

If you've programmed for any amount of time, you've been exposed to the concept of grouping related pieces of code into related blocks, whether they be called libraries, packages, or modules, and pulling in these resources when you need them via a mechanism like an import statement or a #include preprocessor directive. Dojo's official means of accomplishing the same kind of concept is via dojo.provide and dojo.require, respectively.

In Dojo parlance, reusable chunks of code are called resources and collections of related resources are grouped into what are known as modules. Base provides two incredibly simple constructs for importing modules and resources: dojo.require and dojo.provide. In short, you include a dojo.provide statement as the first line of a file that you want to make available for a dojo.require statement to pull into a page. As it turns out, dojo.require is a lot more than just a placeholder like a SCRIPT tag; it takes care of mapping a module to a particular location on disk, fetching the code, and caching modules and resources that have previously been dojo.require d. Given that each dojo.require statement incurs at least one round trip call to the server if the resource is not already loaded, the caching can turn out to be a tremendous optimization; even the caching that you gain from requiring a resource one time and ensuring it is available locally from that point forward is a great optimization.

Motivation for Managing the Mayhem

For anything but the smallest of projects, the benefits of using this approach are irrefutable. The ease of maintenance and simplicity gained in extending or embedding code in multiple places is a key enabler to getting work done quickly and effectively. As obvious as it may sound, importing code in the manner just described hasn't always been obvious to web developers, and many web projects have turned into difficult-to-maintain monstrosities because of improper source code management in the implementation. For example, a typical workflow has been to take a JavaScript file that's in a static directory of a web server and to insert it into the page using a SCRIPT tag like so:

<script src="/static/someScript.js" type="text/javascript"></script>

OK, there's probably nothing wrong with that for one or two script tags—but what about when you have multiple pages that need the same tools provided by the scripts? Well, then you might need to include those SCRIPT tags in multiple pages; later on down the road you might end up with a lot of loose scripts, and when you have to start manually keeping track of all of them, the situation can get a little bit unwieldy. Sure, back in the day when a few hundred lines of JavaScript might have been all that was in a page, you wouldn't have needed a more robust mechanism for managing resources, but modern applications might include tens of thousands of lines of JavaScript. How can you possibly manage it all without a good tool for fetching on demand and lazy loading?

In addition to mitigating the configuration management nightmare that might otherwise await you, the dojo.provide and dojo.require abstraction also allows the build tools that are provided in Util to do pretty amazing things like condense multiple files (each requiring a synchronous request) into a single file that can be requested and endure much less latency. Without the right abstractions that explicitly define dependences, build tool features that could be freebies suddenly become impossibilities.

A final benefit of a well-defined system like dojo.provide and dojo.require is the ability to manage related resources by clustering them into namespaces so that overall naming collisions are minimized and code is more easily organized and maintained. Even though dojo namespaces are really just hierarchies of nested objects simplified with dot notation, they are nonetheless quite effective for organizing namespaces and accomplish the very same purpose.

In fact, organizing resources by namespace is so common that Dojo provides a Base function called dojo.setObject. This function works by accepting two arguments. The first argument is an object hierarchy that will be automatically created, and the second value is what will be mapped to the hierarchy:

dojo.setObject(/* String */ object, /* Any */ value, /* Object */ context)
//returns Any

Example 2-2 illustrates.

Example 2-2. Namespace organization with dojo.setObject

var foo  = {bar : {baz : {qux : 1}}}; //nest some objects the 'long' way
console.log(foo.bar.baz.qux); //displays 1

//Or you could opt to do it in one crisp statement without matching all of
the braces...
dojo.setObject("foo.bar.baz.qux", 1); //crisper syntax
console.log(foo.bar.baz.qux); //displays 1

//If you supply an optional context, the Object is set relative to the
context instead of
//the global context, dojo.global
var someContext = {};
dojo.setObject("foo.bar.baz.qux", 23, someContext);
console.log(someContext.foo.bar.baz.qux); //displays 23

The use of dojo.setObject is nothing more than syntactic sugar, but it can significantly declutter code and the tediousness of matching braces, etc., whenever you do need it.

Tip

The OpenAjax Alliance (http://www.openajax.org ), a consortium of vendors banding together to promote openness and standards amongst advanced web technologies, strongly encourages the practice of using dotted object notation to organize namespaces.

Custom Module Example Over XDomain

A short concrete example is in order to put dojo.require and dojo.provide into perspective. First, consider a simple module that provides a trivial function, such as Fibonacci. In Example 2-3, the resource is also associated with a module. Although grouping resources into modules is not strictly necessary, it is almost always good practice. Throughout this book, you'll commonly see dtdg (for Dojo: The Definitive Guide ) used to denote a generic namespace for modules.

Example 2-3. Defining a simple simple module (dtdg.foo)

/*
   The dojo.provide statement specifies that this .js source file provides a
   dtdg.foo module. Semantically, the dtdg.foo module also provides a namespace for
   functions that are included in the module On disk, this file
   would be named foo.js and be placed inside of a dtdg directory.
*/
dojo.provide("dtdg.foo");

//Note that the function is relative to the module's namespace
dtdg.foo.fibonacci = function(x) {
  if (x < 0)
    throw Exception("Illegal argument");

  if (x <= 1)
    return x;

  return dtdg.foo.fibonacci(x-1) + dtdg.foo.fibonacci(x-2);
}

Tip

You will almost always want to group your resources into logical modules and associate them with a namespace. In addition to being a good implementation practice, it also prevents you from inadvertently clobbering symbols in the global namespace as well as preventing anyone else from doing the same to you. After all, that's one of the motivators for using dojo.provide and dojo.require in the first place!

In another page somewhere, you determine that you want to use your dtdg.foo module to amaze the elementary school math junkies. Instead of rewriting your well-tested function from scratch and potentially making a mistake that could lead to embarrassment, you instead decide to reuse it via dojo.require. Example 2-4 shows how you would use a local module in conjunction with the rest of the toolkit being loaded over the CDN. This example assumes that the following HTML file is saved alongside a directory called dtdg that contains the module from Example 2-3.

Example 2-4. Using a local module with XDomain bootstrappping

<html>
    <head>
      <title>Fun With Fibonacci!</title>

      <script
        type="text/javascript"
        src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js"
        djConfig="baseUrl:'./'">
      </script>

      <script type="text/javascript">
        dojo.registerModulePath("dtdg", "./dtdg");
        dojo.require("dtdg.foo");
        /* at this point, the dojo.require is being satisfied asynchronously
        because we're using an Xdomain build of Dojo. Better wrap any references
        to dtdg.foo in an addOnLoad block */

        dojo.addOnLoad(function(  ) {
            dojo.body(  ).innerHTML = "guess what? fibonacci(5) = " +
dtdg.foo.fibonacci(5);
        });
      </script>

    </head>
    <body>
    </body>
</html>

The key concept to take away from the previous listing is that it's fairly straightforward to dojo.require a resource into the page and then use it. However, there are a few finer points worth highlighting.

For local installations, Dojo looks in the root level directory of the toolkit for modules—but when you're performing XDomain loading, the "real" root directory of the toolkit would be somewhere off on AOL's server. Thus, a special configuration switch, baseUrl is passed into djConfig in order to designate the starting point for looking up local modules—dtdg.foo in this case.

Tip

djConfig is simply a means of providing specific configuration parameters to the toolkit. It is covered in the next section, but for now, just roll with it.

The dojo.registerModulePath function simply associates a top-level namespace, its first parameter, to a physical directory that is relative to baseUrl, its second parameter.

Warning

Don't forget to take extra care when configuring module paths with dojo.registerModulePath. It is not uncommon to be off by one level in the directory structure if you forget that the relative directory is specified to the dojo.js file—not the root level of the toolkit. Additionally, ending a module path with a trailing forward slash has been known to intermittently cause problems, so you should take care to avoid that practice as well.

Everything that is defined in the custom module is made available via the call to dojo.require. For example, if the dtdg.foo module had contained additional functions or symbols, they would be available after the dojo.require("dtdg.foo") statement. As usual, we didn't reference anything provided by dtdg.foo outside of the addOnLoad block.

Tip

There is not necessarily a one-to-one mapping between .js source files and the functions that are dojo.provide d in them, but it is generally enforced as a matter of style. Exceptions include cases where some functions might not be exposed because they are considered private as far as an API is concerned.

You may have also noticed a call to dojo.body( ) in the previous code listing. Essentially, this call is just a shortcut for returning the body of the current document—as opposed to document.body, which is considerably less convenient.

Fibonacci Example with Local Toolkit Installation

For comparison purposes, Example 2-5 shows the very same example, but this time it uses a local installation of Dojo with the dtdg module located at the root level of the toolkit alongside the dojo directory that contains Core so that no reference to baseUrl or a call to registerModulePath is necessary. This convenience is available because Dojo automatically searches for modules in the directory alongside Core, which is a logical and convenient location to maintain them.

Example 2-5. Using dtdg.foo with a local toolkit installation

<html>
    <head>
      <title>Fun With Fibonacci!</title>

      <script
        type="text/javascript"
        src="your/relative/path/from/this/page/to/dojo/dojo.js" >
      </script>

      <script type="text/javascript">
        dojo.require("dtdg.foo");
        /* we use an addOnLoad block even though it's all local as a matter of habit*/
        dojo.addOnLoad(function(  ) {
            dojo.body(  ).innerHTML = "guess what? fibonacci(5) = " +
dtdg.foo.fibonacci(5);
        });
      </script>

    </head>
    <body>
    </body>
</html>

Building a Magic Genie Example Module

As an example of some of the concepts from this chapter, let's build a module. Because life can be so hard at times, a magic genie that can give us answers whenever we need it would be a godsend. (Dojo might simply be pure automation that seems like magic, but genies are real magic.)

To get started building a module, recall that it's a good idea to namespace it. Example 2-6 sticks with the dtdg namespace, which we've been using so far in this book, and associates a Genie resource with it. If you don't already have a local directory called dtdg, go ahead and create one now. Inside of it, open up a new file called Genie.js, where we'll include the magic shown in Example 2-6.

Example 2-6. The implementation for a magic genie module

//always include the dojo.provide statement first thing
dojo.provide("dtdg.Genie");

//set up a namespace for the genie
dtdg.Genie = function(  ) {}

//wire in some predictions, reminiscent of a magic 8 ball
dtdg.Genie.prototype._predictions = [
        "As I see it, yes",
        "Ask again later",
        "Better not tell you now",
        "Cannot predict now",
        "Concentrate and ask again",
        "Don't count on it",
        "It is certain",
        "It is decidedly so",
        "Most likely",
        "My reply is no",
        "My sources say no",
        "Outlook good",
        "Outlook not so good",
        "Reply hazy, try again",
        "Signs point to yes",
        "Very doubtful",
        "Without a doubt",
        "Yes",
        "Yes - definitely",
        "You may rely on it"
    ];

//wire in an initialization function that constructs the interface
dtdg.Genie.prototype.initialize = function(  ) {

    var label = document.createElement("p");
    label.innerHTML = "Ask a question. The genie knows the answer...";

    var question = document.createElement("input");
    question.size = 50;

    var button = document.createElement("button");
    button.innerHTML = "Ask!";
    button.onclick = function(  ) {
        alert(dtdg.Genie.prototype._getPrediction(  ));
        question.value = "";
    }

    var container = document.createElement("div");
    container.appendChild(label);
    container.appendChild(question);
    container.appendChild(button);

    dojo.body(  ).appendChild(container);
}

//wire in the primary function for interaction
dtdg.Genie.prototype._getPrediction = function(  ) {
    //get a number betweeen 0 and 19 and index into predictions
    var idx = Math.round(Math.random(  )*19)
    return this._predictions[idx];
}

Essentially, the listing does nothing more than provide a Function object called dtdg.Genie that exposes one "public" function, initialize.

Tip

In Dojo, the convention of prefixing internal members that should be treated as private with a leading underscore is common and will be used throughout this book. It's important to really respect such conventions because "private" members may be quite volatile.

The listing is laden with comments, and from a web development standpoint, the logic should hopefully be easy enough to follow. (If it's not, this would be a great time to review some HTML and JavaScript fundamentals elsewhere before reading on.)

To actually put the magic genie to use, we'll need to modify the basic template, as shown in Example 2-7.

Example 2-7. A web page utilizing the magic genie

<html>
    <head>
        <title>Fun With the Genie!</title>

        <script
          type="text/javascript"
          src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js">
          djConfig="modulePaths:{dtdg:'./dtdg'},baseUrl:'./'">
        </script>

        <script type="text/javascript">
            // require in the module
            dojo.require("dtdg.Genie");

            // safely reference dtdg.Genie inside of addOnLoad
            dojo.addOnLoad(function(  ) {

                //create an instance
                var g = new dtdg.Genie;

                //fire it up, which takes care of the rest
                g.initialize(  );
            });
        </script>
    </head>
    <body>
    </body>
</html>

This example illustrates the reusability and portability of the dtdg.Genie module. You simply require it into the page and, once it's initialized, it "just works." (And so long as the user doesn't read the source code, it truly remains magical.) A finer point worth clarifying is the use of djConfig to configure Dojo before bootstrapping: the modulePaths is inlined to qualify the location of the module relative to baseUrl, which is defined as the current working directory. Thus, from a physical standpoint, the file structure might look like Figure 2-1.

The way that your module should be laid out on disk

Figure 2-1. The way that your module should be laid out on disk

Get Dojo: The Definitive Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.