Dojo: The Missing APIs
In my last article I created a set of small, useful utility functions on top of the Base Dojo APIs for personal consumption. Patterns I'd been following and using in Dojo for some time that Dojo didn't have "official" functions for. Well, I've taken it "a little too far", and created a (if I do say so myself) rather cool mini-open-source-project called plugd.
I've put up a minified version of the most recent "stable" version, and submitted all the code to subversion. Before gzip, the plugin is 2.5k (1.1k after), so I've broken my 1k threshold -- but I think the extra 100 bytes are worth it. So what's new? Where did the bytes comes from!?
Michael Carter of Orbited fame stumbled into #dojo recently asking if Dojo had a simple "create some Dom from text" function like jQuery:
$(" <div class='bar'></div> ").appendTo("#baz");
(note: wordpress doesn't like div tags in code blocks? what's that about?)
And unfortunately, we don't. Dojo's functional approach to things would say: "But dojo.query is a dom selection function, not a dom creation function", which is entirely reasonable. In order to accomplish the above, you'd have to inject magic [bloat?] into dojo.query functionality to determine if the selector was in fact some DOM, and branch internally to return a NodeList from the created single element. This doesn't seem ideal: it changes the meaning of dojo.query. You can already use query this way by passing a node to dojo.query (and dojo.create from the plugd plugin):
var n = dojo.create('div'); dojo.query(n).appendTo("#foo"); // or, because people seem to like single lines: dojo.query(dojo.create('div')).appendTo("#foo")
Which is, to me, acceptable in most cases. It does not, however, solve the "complex markup" use case described initially. It would be really convenient if I were able to make calls like:
var n = dojo.create(" <div class='bar'> <p class='baz'>Hi, <span>Dojo</span> </div> "); dojo.place(n, dojo.body(), "first");
So that's where to 100 bytes came from. I added some simple detection to the dojo.create function to either create "just a node from a tag", or a full structure of DOM nodes (with the caveat that it is only allowed a single top-level dom node, such as the div in the example). It could likely be more robust, and work around some cross-browser compatibility issues but I'm aiming for size here and can generally remember to not create invalid markup this way. (Note, there is already a much more robust Dom setter utility in the dojo.html plugin already in the Dojo Toolkit, this syntax is just an extremely lightweight version of that)
Truthfully though, that should not have taken 100 bytes -- there's more. I added other stuff, and rearranged some others:
- deprecated { bling:true }. bling is a silly word. the djConfig parameter is now simply conflict. eg: djConfig = { conflict:true } will create $, and $().ready() API's, mapping $ to dojo.query and providing the .fn stub for your own plugins.
- added dojo.toggle(node), matching the API for the $().toggle() method already in place
- exposed the .val() function in NodeList - it seemed useful
- added a private _stash method developers can use for advanced chaining, and a public .end() function to get out of it.
NodeList._stash and .end are nice little utilities, too: Sometimes after making a selection of domNodes you want to return a subset of those nodes, or children relative to those nodes, etc, and returning a new NodeList from the last call makes the most sense (so you can continue chaining, except different nodes this time). Even more often, you would like to break out of that list, and back into the original list.
For example, the .create() method returns a "stashed NodeList" because it makes the most sense to return the newly created node(s). wrap() uses the same logic, though provides an optional 'out' by not returning a stashed list by default. By using _stash in the plugin, and utilizing end() we can write Dojo code that looks like:
$(document).ready(function(){ $("a[href^=http://]") .addClass("external") .wrap("div", true) // now we're "in" the new list .addClass("aroundExternalLink") .end() // we're back to the <a>'s, but they have a <div> around them now .onclick(function(e){ ... }) });
I've also added DOH unit tests for all base.min.js functions, and added some other experimental plugins like a very simple block overlay based on an earlier article, and a cross-browser position:fixed implementation that is incomplete, but has potential. None of the experimental code has very good in-line documentation though, so use with caution (if at all). base.js is mostly documentation, if you feel like reading. If you are interested in contributing to this mini-project, let me know ... Ohloh is complaining it only has one developer, and I'd love to see a whole collection of these "unofficial plugins" out in the wild.
maddesa:
I like this. THere is one other missing API I get asked about a lot from jQuery – parents(). Is there a way I do not know about to search for a node, once found look for an arbitrary ancestor? For instance find some tag in a table and then easily get a handle to it’s first parent?
3 November 2008, 10:14 amdante hicks:
in the plugd source I implemented a simple .parent() function (in trans.js iirc), but am unfamiliar with the jQuery idea of what parent() and parents() are, so haven’t gone any further than just returning a list of parentNodes for a passed list. I can see value in traversing the DOM upwards though — I’ll think of am implementation. There was a Dojo bug report about making query work backwards, perhaps something in there would be useful … definitely something to think about.
3 November 2008, 1:25 pmdante:
update: dojo.query(”<p>foo</p>”).addClass(”woot”).appendTo(”#bar”) works now in plugd trunk.
11 November 2008, 6:19 pm