This article introduces dojo.cache and presents a technique for externalizing your widget templates in swappable configuration files, where they can be referenced by a custom templateKey widget property.

Introducing dojo.cache

Dojo 1.4 adds a new core utility called dojo.cache. To appreciate it we first have to review how Dijit’s templatePath works. When you define a _Templated widget with a templatePath property, the content from that URL is fetched the first time you instantiate the widget, and made available as a string. All subsequent instances get the cached string. Furthermore, when you run a build, your templatePaths get replaced with templateStrings and their content is inlined into the output from the build. This improves performance considerably by removing those synchronous XHR requests, while remaining transparent to the developer.

dojo.cache generalizes this pattern, making the same functionality available from Dojo Core—synchronous, cached content retrieval that gets inlined during the build. dojo.cache usage is much like dojo.moduleUrl:

dojo.require("dojo.cache");
my.stringResource = dojo.cache("module.path", "relative.path.html" );

Runtime configuration

You’ll find many uses for dojo.cache. The ability to define and cache strings was immediately useful for me in the context of runtime configuration files. This is a fairly common technique in which you define and load modules ahead of most of the components to your app, to externalize constants and properties for the application that you don’t want to hard-code into each component. So, you might have something like this:

dojo.provide("myapp.defaultConfig");
// dojo.require("myapp.defaultConfig") populates myapp.config 
// with messages with a laid-back informal tone
myapp.config =  {
	loadingText: "We're loading that for you, won't be a sec.",
	loadErrorText: "Ugh, that didn't work out. Sorry."
};

…and a variation:

dojo.provide("myapp.formalConfig");
// dojo.require("myapp.formalConfig") populates myapp.config
// with messages with a more formal tone
myapp.config =  {
	loadingText: "Fetching requested resource",
	loadErrorText: "Error loading requested resource. Please accept our apologies."
};

In this example, switching the “tone” in your app is as simple as switching the one line that requires your config. Your code can use e.g. this.attr("content", myapp.config.loadingText) without having to know or care which variation it is supposed to be using.

Template configuration

Now with dojo.cache, we can manage templates in the same way, using object paths which can be looked up and requested at runtime during development, and inlined by the build for production:

myapp.config = { 
  templates: {
    someWidget: dojo.cache("myapp.resources", "someWidget.html"),
    otherWidget: dojo.cache("myapp.alternateResources", "otherWidget.html"),
    collapsiblePanel: dojo.cache("dijit.templates", "TitlePane.html"),
  }
}

The config lets us retrieve template strings by using a look-up like myapp.config.templates.someWidget. With Dojo, we can treat these types of object paths as strings and use dojo.getObject to receive the value they represent. In this sense the object path is like an address—it gives us the ability to reference a variable and potentially a long string like a template with a short, symbolic one. I use these configuration properties by referencing them in a (new and custom) templateKey widget property. The key provides a level of indirection that enables you to pull template management outside of your widget definition entirely. It looks like this:

<div dojoType="my.SomeWidget" templateKey="myapp.config.templates.someWidget"></div>

Template Reuse

When all that varies between a set of widget classes is the template markup, you can reuse one widget by simply passing the instance a new templateKey, for example:

<div dojoType="my.SomeWidget" templateKey="myapp.config.templates.someWidgetInversed"></div>

The Implementation

To make this happen we need to have all widgets use an extended dijit._Templated to put the templateKey property on the prototype, and handle it in the buildRendering step:

dojo.declare("myapp._Templated", dijit._Templated, {
  
  templateKey: "",

  buildRendering: function() {
    if(this.templateKey) {
      this.templateString = dojo.getObject(this.templateKey); 
    }
    this.inherited("buildRendering", arguments);
  }
});

.. which gets used by all our custom templated widgets, e.g.

dojo.declare("myapp.SomeWidget", [dijit._Widget, myapp._Templated], {
  templateKey: "myapp.config.templates.someWidget", // an optional, default value
});

Now, when we run our build, the config file you’ve specified gets the inlined templates, and the templateKey look-up works just as before. We could even monkey-patch this functionality into dijit._Templated and wring yet more goodness from the out-of-the-box Dijit & DojoX widgets.

Conclusion

Dijit’s architecture has always let you override templates, but in practice this has typically necessitated a subclass. The combination of runtime configuration and dojo.cache gives you new, powerful options that keep specifics out of reusable components, while maintaining resources in a manageable way. This is just one new technique that falls out of Dojo 1.4. Would you use it? What have been the highlights of 1.4 for you?