More on Dojo “plugins”

Dojo doesn't call any of the hundreds of optional/add-on modules available on either CDN a "plugin", though they all ultimately behave as such. Dojo has had a custom package system for several years now: a way to modularize script files and create dependencies between them, loading them as you see fit and optionally compressing or concatenating them as part of the automated build system.

So for the sake of argument, we'll call the various Dojo modules available "plugins". On top of Base Dojo (dojo.js), there are a number of great plugins available "out of the box". This is how you load them:

 
// cookie setter-getter plugin
dojo.require("dojo.cookie");
 
// advanced string substitution stuff
dojo.require("dojo.string");
 
// the back-button management plugin
dojo.require("dojo.back");
 
// advanced Animation support. chain/combine, some prefab fx
dojo.require("dojo.fx");
 
// advanced number formatting functions
dojo.require("dojo.number");
 
// currency conversion, formatting, etc
dojo.require("dojo.currency");
 
// date formatting and utility functions
dojo.require("dojo.date");
 
// fast selector-based live api plugin
dojo.require("dojo.behavior");
 

Most are simply optional utility functions to help with the tasks you will face as a developer (especially if you are considering internationalization and accessibility issues). Load as needed, omit as desired. (There are a series of UI controls in Dijit, and hundreds more plugins in DojoX, again, all available on the CDNs, all just a require() away)

You could of course point <script> tags at each of the files, but should you change the location of anything you'll need to adjust each path to accomodate. By using the Dojo package system we're able to create a list of requirements to load, and only need to ensure the relative location to dojo.js is correct. dojo.js typically lives in a folder named 'dojo'. All dojo.* modules are floating around there as well. eg:

src/
	dojo/
		- dojo.js
		- behavior.js
		- number.js

As a design decision, Dojo uses clear namespaces to compartmentalize all plugins. This prevents any possible overlap and destruction of items fighting for the same name. If you've ever tried to include both Prototype and jQuery in a page you know what I'm talking about, as both "fight" for the right to use the global "$" function (jQuery for it's entire API and Prototype as a byId selector). jQuery bows out of that contest by providing a 'noConflict' function, allowing Prototype to keep $, and tells users to do something similar to:

 
(function($){
	// I can use $ again!
})(jQuery);
 

to regain control of the $ in a localized scope. This is similar to something I do in plugd, and other places throughout the Dojo plugin landscape (though I do it because typing 'd' is three characters less than typing 'dojo', not because of any interoperability issues):

 
(function(d, $){
	// I can use $, too!
})(dojo, dojo.query);
 

So the issue isn't that you can't "plugin" to Dojo, it is simply that as a best practice Dojo suggests you don't ... Doing so will likely cause headaches in the future. Though the API's in Base Dojo (dojo.js) are always backward compatible between releases, the addition of new APIs may conflict with whatever name you've chosen. I'm actually experiencing this myself a bit in plugd, where I chose to place all the plugins into the dojo namespace directly. For example, plugd's `create` function came first, and creates DOM markup from either a single element or complex markup. Dojo's `create` (as of 1.3) does not do this, `dojo.place` does.

In hindsight, I should have copied the dojo namespace into plugd and made my extensions all namespaced plugd. That way, using it in the dojo namespace would simply be:

 
(function(dojo){
	// that's an interesting circle, though JavaScript likely allows it.
	// not tested this approach any, so don't quote me.
})(plugd);
 

But the idea of stubbing functions onto dojo.query for the chaining and whatnot is entirely appealing, and entirely valid. dojo.query returns an instance of a dojo.NodeList, leaving the work of querying to a single module focused exclusively on querying, and treating the extensions externally. The typical way would be:

 
dojo.extend(dojo.NodeList, {
	setColor: function(color){
		// summary: set the color to "color", and return this NodeList
		return this.style("color", color);
	}
});
 

Though, as per my dojo.Patterns talk and brief blog on the topic, I would suggest making a single function to act upon a node (byId or node reference), and make that act upon the nodes in the list. This, simply put, is faster. There is no overhead of querying the DOM, instantiating a nodelist and so on, unless you know you need to. This pattern looks just like:

 
(function(d){
 
	d.setColor = function(n, color){
		// summary: A function to set a color for some node
		d.style(n, "color", color);
	}
 
	// make this function an iterable when used with dojo.query
	d.NodeList.prototype.setColor = d.NodeList._adaptAsForEach(d.setColor);
 
})(dojo);
 

Now we can use the setColor method either way: fast and on a single node, or marginally slower and across all nodes. Having the choice is nice:

 
	// way one: force a byId lookup from a string:
	dojo.setColor("someId", "blue");
 
	// way two: from a saved node reference eg:
	var node = dojo.byId("someNode").parentNode;
	dojo.setColor(node, "green");
 
	// way three: in bulk, allowing for further chaining:
	dojo.query(".someNodes").setColor("red");
 

The most 'complicated' bit about this pattern is having to set a function directly on NodeList.prototype ... To shelter you from dealing with prototype's directly, I've devised a fun plugd plugin, aptly named "dojo.plugin". It does two things:

  1. Creates dojo.fn as an alias to dojo.NodeList.prototype, so you can simply write a plugin like: dojo.fn.myPlugin = function(props){ ... }
  2. Defines a dojo.plugin function to create both a single function and NodeList variant with a single syntax

To use, simply require in the module (assuming you have plugd checked out. Alternately you can simply download the single file and use a <script> tag and place it anywhere):

 
// load the resource/plugin
dojo.require("plugd.plugin");
// define dojo.setColor and dojo.fn.setColor:
dojo.plugin("setColor", function(n, color){
	dojo.style(n, "color", color);
});
 

With those few lines, again, all the available patterns are made available to us. There are a whole collection of "dojo plugins" in the plugd project, all of which follow this "stub into dojo namespace willy nilly" pattern in the hopes some will be upgraded to Dojo Core or Base.

The source for the "plugd.plugin" module is simple. Sans comments, it weighs about 8 lines:

 
dojo.provide("plugd.plugin");
(function(d){
 
	d.fn = d.NodeList.prototype;
	d.plugin = function(pluginNamespace, fn, way){
 
		//>>excludeStart("safetyFirst", kwArgs.noWarnings == "on");
		// leave safety in optionally, build with noWarnings=on to disable check
		if(d[pluginNamespace]){
			console.warn("cowardly won't clobber '" + pluginNamespace + "' method");
			return fn; // Function
		}
		//>>excludeEnd("safetyFirst");
 
		var f = d[pluginNamespace] = fn;
		d.fn[pluginNamespace] = d.NodeList[way || "_adaptAsForEach"](f);
 
		return f; // Function
	}
 
})(dojo);
 

I added a "safetyFirst" #ifdef-like block for the build system. This way, I can disable the checking to see if my plugin will conflict with an existing function as a build option, further reducing the code. There is very little smoke and mirrors in Dojo - teaching fundamental JavaScript principals and suggesting some working practices along the way. Checkout some of the sample plugins in plugd's trunk, or check it out directly into a plugd/ folder next to your dojo/ folder and dojo.require some stuff. Most of the code is commented to the hilt, and few (if any) of the functions exceed 8 lines of code. Excellent way to view and understand some fairly cool JavaScript magic.

2 Comments

  1. codylindley:

    Slick! I really enjoy how this example can take a persons understanding of jQuery and extend it into the world of Dojo.

  2. Learning Dojo | SitePen Blog:

    [...] (article) More on Dojo “Plugins” [...]