AMD Module Patterns: Singleton

This is the first in a series of posts illustrating how common design patterns can be achieved using AMD-style JavaScript modules. We’ll also look at some new patterns that are somewhat unique to AMD, including one that should be called nothing less than the “Alley oop slam dunk”!

No, no, we’re not going to talk about the “Alley oop slam dunk” first! Let’s start at the basics with the simplest of all design patterns: the Singleton.

Singleton

The singleton pattern is a design pattern used to […] restrict the instantiation of a class to one object.

Wikipedia

Yah, well that’s the formal definition. In classical OO languages maybe you need a pattern for that. In javascript we have object literals. There’s no need to restrict anything. It’s as simple as this:

(If you’re new to AMD, stop here and review this intro now.)

// simple module with no dependencies and no supporting code
define({
    format: function (dicomDate) {
        return (dicomDate || '').replace(/(?:d{4})d{2}/, '$1/');
    };
});

This is just shorthand for the slightly more verbose version:

// simple module with no dependencies and no supporting code
define(function () {
    return {
        format: function (dicomDate) {
            return (dicomDate || '').replace(/(?:d{4})d{2}/, '$1/');
        }
    };
});

In fact, most AMD loaders probably insert a definition function automatically.

Since AMD only executes the definition function once, we’ve essentially created a singleton. In the classical sense, a singleton is a class with instance data and state, but we’re stretching the definition a bit. Why? Because this ain’t Java! 🙂

Here’s an example that’s more like the classical singleton pattern or all the Java wannabees:

define(function () {
    var instance; // look ma! no globals!
    function Singleton () {
        this.defaultDate = '';
    }
    Singleton.prototype = {
        format: function (dicomDate) {
            return (dicomDate || this.defaultDate).replace(/(?:d{4})d{2}/, '$1/');
        },
        setDefault: function (dateString) {
            this.defaultDate = dateString;
        }
    };
 
    return function getSingleton () {
        return (instance = (instance || new Singleton()));
    };
});

Return something!

The most important thing to remember when creating AMD modules is to RETURN SOMETHING! RETURN ANYTHING!

Whatever you return from the definition function is what other modules will receive when they request your module.

If you return an object, other modules will receive an object.

If you return a function, other modules will receive a function.

If you return a constructor, other modules will receive a constructor.

If you return a string… can you guess what’s gonna happen? Well, you probably guessed wrong. Unfortunately, some AMD loaders and compilers/optimizers don’t handle this case as you’d expect. They assume that a string in the first parameter position is a module id and will either return undefined or throw an exception.

// not all builders support a string singleton because
// a string looks like a module id.
define("WTF, AMD build tool. I am not a module id!");

This is ok, thought, right? Who is going to have a module that consists of a single string?

Hmmm…. what if your module is an HTML template? The bottom line is that you should be able to return anything. curl.js and cram don’t restrict the return type of the module since they inspect the parameters from back to front. The last parameter is always the module definition!

However, there’s another solution for loading large text strings: the “text!” plugin. You should consider using the text! plugin rather than wrapping the string in a define().

But guess what, the text! plugin uses XHR, and XHR doesn’t work cross-domain in legacy browsers. For cross-domain text resources, you can use JSONP instead. JSONP is pretty darn easy to implement using AMD:

// a module to convert some jsonp-based resources to AMD:
define(['require', 'mylib/Deferred'], function (req, Deferred) {
    var promise = new Deferred();
    req(['cdnlib/template?jsonp=define', 'cdnlib/data?jsonp=define'],
        function (template, data) {
            promise.resolve({ template: template, data: data });
        }
    );
    return promise; // could also use a callback function
});

By telling the JSONP service that our callback is named “define” (?jsonp=define), we can simply use the built-in AMD async require mechanism to fetch the resources from the cross-domain server. I used a promise in this example just because I like promises, but you could devise a different implementation using a callback function, of course.

Here’s what those resources would look like when they’re returned:

// cdnlib/template will return something like:
define('<div dojotype="dijit.form.Form">...</div>');
// cdnlib/data will return something like:
define({ test: 'bostonian', question: 'wicked?', answer: 'pissah!' });

Magic!

Next, we’ll look at the Decorator pattern.

Comments welcome!

— John

Be Sociable, Share!