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:
- Creates
dojo.fnas an alias todojo.NodeList.prototype, so you can simply write a plugin like:dojo.fn.myPlugin = function(props){ ... } - Defines a
dojo.pluginfunction 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.

codylindley:
Slick! I really enjoy how this example can take a persons understanding of jQuery and extend it into the world of Dojo.
29 January 2010, 10:26 amLearning Dojo | SitePen Blog:
[...] (article) More on Dojo “Plugins” [...]
5 March 2010, 8:42 am