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.

dojo.show, .hide, .toggle, and more …

This is obsolete. All code maintained in: plugd

I've been ill this week (with the changing weather and whatnot) and found myself in a near inebriate state from a hefty dose of NyQuil one evening, unable to fall asleep. So, mind wandering, I started contemplating why Dojo doesn't have a .show() or .hide() method, and a .toggle() to switch them. What started as a simple experiment turned into a rather fun exercise -- exploring several aspects of the Dojo Toolkit I've not really tinkered with before, and ending up as some weird pseudo-api-compatibility shim.

(You can peek at the full code now, or download the minified version if you are impatient.)

Dojo doesn't assume things about what you want to happen. That's the reason there is no simple show()/hide() pair stubbed off the dojo namespace. It is too easy to do however you intended it to be:

 
d.query(".nodes").style({ display:"none" }); // hide
d.query(".other > .nodes").style({ display:"" }); // show
 

But we all know display:none behaves differently than visibility:hidden in CSS, and sometimes we want to use that property to hide or show our elements:

 
d.query(".nodes").style({ visibility:"hidden" }); // hide
d.query(".other > .nodes").style({ visibility:"visible" }); // show
 

Being clever (really, it was the NyQuil), I wrote a show and hide method that allowed you to specify at runtime which behavior you prefered: toggle with the visibility property, or the display property. Going a step further, I added an option to specify if you preferred display:block to be used instead of simply removing the display property. To force the functions to always use the visibility property:

 
<script>var djConfig = { keepLayout:true }</script>
<!-- script>
var djConfig = { useBlock:true }</script-->
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.2.0/dojo/dojo.xd.js"></script>
<script src="http://higginsforpresident.net/js/plugins/base.js"></script>
 

This allowed you to write code like:

 
d.query("#foo > li").hide().onclick(function(e){ e.preventDefault(); }).show();
 

Which is a horrible example, but we've only just implemented this function. Following the same pattern outlined in another similar article, we created dojo.show and dojo.hide functions, and wrapped them into into dojo.NodeList -- anything you can do to a single DOM Node you 'ought to be able to do to a list of them with the same API (excluding the node). dojo.query just being the most popular method of obtaining a list of nodes, and a single-element NodeList not being something Dojo teaches fundamentally, though is entirely useful.

Once I had show and hide implemented - it all went downhill. I added speed selectors named "fast", "slow", and "mild" to the show and hide methods, which now fade in/out still returning the NodeList for more chaining. I added a wrap() method which creates an element and places it in the DOM around some other element (and mapped it in to NodeList). Then I added an .append() method to only NodeList, to append some node to the current selected node and an appendTo() which adds each of the list of nodes to some node matched by a passed CSS selector. By reusing Base Dojo APIs, I was able to do all of this with a minimal ammount of code.

For instance, a .hover() method to register events for the normalized onmouseenter/onmouseleave (one of those rare things IE did right) consists of:

 
dojo.extend(dojo.NodeList, {
    hover: function(over, out){
         return this.onmouseenter(over).onmouseleave(out ? out : over);
    }
});
 

This way we can register a function for each of the events, or only pass one function and delegate between the events. Pretty nifty. Registering one or two functions to a node is something I do quite often. I realized I'd implemented jQuery's hover method (though with an optional second parameter - allowing the first function to delegate the event) and got to thinking about what other API's could be mapped in this way.

I started goofing around and mapped in aliases from css() to style(), each() to forEach(), bind() to connect(), and making tiny little stub functions like val():

 
dojo.extend(dojo.NodeList, {
    val: function(set /* optional */){
        return this.attr("value", set);
    },
    css: dojo.NodeList.prototype.style
});
 

I wrapped those in a buildExclude tag called "compat" - but in hindsight didn't feel they were worthwhile to keep, but fun none the less.

Being silly (remember the NyQuil ...) I also added speeds for the show and hide methods labeled "granny", "racecar", "snail", "rocket", and "peller" with values from 100 to 7600, and decided while they were fun to have they were not very practical in any real application. Rather than just remove the options, I decided to find a way to simply remove them in production.

The Build exclude stuff is simple. Consider the code:

 
var speedMap = {
	"slow"    : 1800,
	"fast"    : 420,
	//>>excludeStart("sillyness", kwArgs.silly == "off");
 
	// these are just to be silly:
	"granny"  : 7600,
	"racecar" : 200,
	"snail"   : 1200,
	"rocket"  : 100,
	"peller"  : 3500,
 
	// this is "public API" bewlow, down here so we can build-exclude
	// the above silly-ness and not worry about the comma breaking
	// after build.
	//>>excludeEnd("sillyness");
 
	"mild"    : 900
};
 

If we run a build with silly:"off" in the profile, the exclude pair named "sillyness" will be remove from the shrunken/minified output. Setting compat:"off" will disable the aliases, and setting redundant:"off" will disable the .get() function and a .destroy() method, both with little value. We all know how painful a stray comma can be, so take special note to what comes before and after your exclusion blocks. My technique is illustrated above.

Another interesting function I added was .animate() to NodeList. Dojo Base has a powerful function called .animateProperty() and a slightly less configurable wrapper dojo.anim() -- neither of which are included as functions on NodeList out of the box. You must dojo.require("dojo.NodeList-fx") to have the various animation functions mapped in. The reason being: NodeList-fx requires the advanced animation chaining and combining code in fx.js, and not everyone wants or needs all those extended functions. To keep the Base size down, Core provides the additional code. By using chain and combine, we're able to make truly parallel or sequential animations without too much worry about performance issues, but I wanted an animation function I could use with dojo.query without the extra code -- my NodeLists aren't usually hundreds of elements, and that much animation makes me dizzy. Also, the NodeList-fx API's return the _Animation instance created, allowing better control over the progress and state, and I just want the NodeList back so I can keep chainin' ...

I chose .animate() because .anim() and .animateProperty() are provided by dojo.NodeList-fx, and didn't want to conflict:

 
dojo.extend(dojo.NodeList, {
    animate: function(props, duration, easing, onEnd){
	return this.forEach(function(n, i, a){
		var anim = d.anim(n, props, duration, easing);
		if(onEnd && i == a.length - 1){
			d.connect(anim, "onEnd", this, onEnd);
		}
	}, this); // dojo.NodeList
    }
});
 

This creates and plays the animations instantly rather than considering the performance implications, so I wouldn't suggest doing this in large pages, but in the simple cases -- it's perfect. With just a few lines of code, we've provided a quick and easy way to animate some css properties without the extra fx and NodeList-fx modules:

 
dojo.query("li").show().animate({ height:40 }).onclick(function(e){ /* code */ });
 

And the last item of the evening was the silliest of all: implement dojo.conflict() based on Neil's awesome 'bling' article:

 
d.conflict = function(){
	// summary: Create our $
	$ = d.mixin(function(){ return d.mixin(d.query.apply(this, arguments), $.fn); }, { fn: {} });
}
if(d.config.bling){ d.conflict(); }
 

Dojo doesn't (rather, tries very hard not to) conflict with the other Libraries, and doesn't use any global variables other than the namespace(s). The $ function is quite handy to type: Prototype uses it for byId, jQuery for the entire API, Moo, etc, etc. As a Dojo user, you too can participate in the the quest for the holy bling, potentially causing conflicts along the way. This too can be disabled (excluded) by setting conflict:"off" in the build profile.

By calling dojo.conflict() (or setting djConfig = { bling:true }), you are able to take control of the global $ variable and map it to dojo.query() so you can write Dojo code that looks like this:

 
$("#someId > span")
    .hover(function(e){
         $(e.target)[(e.type == "mouseover" ? "addClass" : "removeClass")]("hovered");
    })
    .style({ color:"red" })
    .appendTo("#otherId div")
    .wrap("a").attr("href","http://dojotoolkit.org")
    .onclick(function(e){
         if(!confirm("Really leave?")){
             e.preventDefault();
         }
    });
 

I've put the full code as well as a minified version you can drop into any page that has Dojo on it. Feel free to use it freely, the license is identical to Dojo's (AFL/newBSD) and is available to them under the terms of my CLA so they are welcome to use it, too.

I've also put up the sample pluginProfile.js code, should you be interested in building this too, into your dojo.js directly, or experimenting with the buildExclude syntax. The entire API adds 958 bytes of code (after gzip. With comments it is roughly 13k), which is slightly more than I had hoped for (before I got carried away with new functions it was a staggering 581 bytes). With it you are provided the some handy APIs:

  • dojo.show(node, speed)
  • dojo.hide(node, speed)
  • dojo.wrap(node, nodeType)
  • dojo.query(..).show(speed).hide(speed).wrap(nodeType)
  • dojo.conflict(); $(...).show(speed)
  • dojo.query(..).append(selector)
  • dojo.query(..).appendTo(selector)
  • dojo.query(..).animate({ /* props */ })
  • dojo.query(..).hover(function(e){}, function(e){})

The whole package can also be downloaded as a tarball, including a simple test. This is the "initial version", 0.1: http://higginsforpresident.net/js/plugins-0.1.tar.gz

Writing Dojo "plugins" is fun. Any other simple functions you can think of? What convenient stuff would you like to see stubbed off of dojo.query/dojo?

A change of face

Last Wednesday at our weekly team meeting, Alex Russell announced his resignation as long-time Project Lead for the Dojo Toolkit, the open-source project I've been contributing to for the past two years. I still may be in partial shock (and partial euphoria) about the resignation, but not near much as finding out the nominee: me!

Today, the voting was closed, and I have officailly been named the new [and improved?] Dojo Project Lead by a unanimous 27-to-zero vote of my peers, the Dojo core developers. Contained within those ranks of committers are some of the most talented, friendly, and innovative developers I've ever had the pleasure of working with, and am humbled by the outcome. I'd like to thank everyone involved: Your faith in my abilities, and our common vision and dedication are going to ensure the Dojo Toolkit will remain a major presence within the Open Source, OpenWeb, and Ajax communities for a very long time. Our collective commitment to excellence and never-ending desire to push the limits of browser capabilities will continue to thrive, even as the landscape changes beneath us.

As much as I love talking about myself, I'm going to keep this brief. Alex addressed some highlighted questions in his initial announcement, and I'd like to follow up with a small [read: single item] Q&A of my own:

Q: What does this mean?
Not too terribly much. Dojo is a very mature project: The product of countless man hours, real-world testing, use, mistakes, changes, and growth. We've established who we are, and what we intend to do, and my goal is the keep us all on that same track -- working together to create and maintain the best open source set of tools for web developers. The Toolkit is, in truth, run by smaller groups of committers and contributors -- each applying their own expertise to the components they own or otherwise maintain, myself included.

It doesn't, however, mean there won't be change! We change every day as it is: seldom does a day pass that a bug isn't fixed, filed, or commented upon, or a new function or component becomes a reality. It is too early for me to have formulated any serious plans or opinions on the future of Dojo, but It will likely mimic that which I am already a strong proponent of: ongoing open innovation, providing great tools, enabling the community, embracing the community support and contributions, all while never wavering from the overall goal: dojo.greatness();

I'm looking forward to this experience, and to working with all of the exceptional talent the Ajax community has to offer. I'm usually available as phiggins on irc.freenode.net (#dojo, among others), and you may already know me as dante in the Dojo Forums and Trac, so drop by -- I'm happy to answer any questions or concerns.
I'll have several more announcements and whatnot coming as time progresses, so be on the look ...

Thanks again to everyone for their support. I am confident the power and direction of Dojo will only continue to improve, and it would never have gotten this far without each and every one of you.

Dojo Degradability

As a quick example of unobtrusive and degradable Dojo, I'm going to add couple of lines of JavaScript to this blog post, and convert the existing unordered list labeled "Blog Roll" into a new widget I created, which will be released with Dojo 1.2. Here goes:

 
dojo.addOnLoad(function(){
  dojo.require("dojox.widget.Roller");
  dojo.addOnLoad(function(){
    dojo.query("#sidebar #linkcat-2 > ul").instantiate(dojox.widget.Roller, { delay:3200 });
  });
});
 

This "embedded addOnLoad" trick is really fun. After the initial/body onLoad, I'm dynamically pulling in the Roller code, and all of its dependencies. This could be avoided if I had included the Roller class in a custom build, but I'm more concerned with the content rendering initially than I am with limiting the number of XHR requests (we're doing it after onLoad, so no blocking is taking place).

Without JavaScript enabled, it should appear as a simple unordered-list. At the time of this writing, there are only three links in my blog roll (I have friends, I promise), but this ensures the list will only ever take up a single line of screen real estate, regardless of the list length.

The code is degradable, reusable, and completely valid XHTML. What more could you ask for? If validation doesn't concern you, you can simply add a dojoType="dojox.widget.Roller" to an existing unordered list, and let the automatic parsing take over for you, creating even less work. There is a solution in Dojo for everyone's needs.

The test file for the Roller, and RollerSlide can be viewed in the nightly builds.

Updates from Portland

So it's a little late to be sending out updates of my stay in Portland, OR -- this is more or a 'reflections' of Portland blog writing -- but all valid none the less. It has been an exceptional adventure.

I landed in Portland mid-afternoon last Sunday to a beautiful sunny day, typical of a Portland summer. The weather stayed consistently pleasant for the duration, which is not atypical, but entirely unexpected (and welcome). I love PDX in the summer. I must remind myself what the winters are like, lest I whimsically decide to rent an apartment and move back ...

I attended OSCON last week. I didn't spend much time in actual talks, but had a chance to sit and chat with a number of people I both had and hadn't met prior. Working for a virtual company, it is easy to detach yourself from face to face communications. I can go whole days without actually having to say a spoken word, though I've spent the entirety of it communicating with people from all regions of the world. I got a chance to hang out with some semi-familiar-faces: Dylan Schiemann (my boss, and CEO SitePen, Inc) ended up on the west side, Alex Russell of Dojo fame, whose company is always welcome (the man has some weird special insight into them interwebs, and it is certainly entertaining to hear him rant), Matthew Russel, author of a relatively new [awesome] Dojo book: "The Difinitive Guide", and several other Dojo-hackers and/or SitePen folk: Gavin Dougtie (Google), Jason Cline (SitePen), Chris Barber (CB1, Inc), John Locke (Freelock) ...

... And more importantly, the meeting of great new folks: I had the pleasure of briefly meeting an energetic and charming Leslie Hawthorn of Google Summer-of-Code fame (I'm mentoring this year), a vocal and hilarious Zend Framework user Jason E, Simon Willison, a seemingly all-around great guy, the guys behind Orbited: Michael Carter, Jacob Rus, and Adrian Weisberg (who got an ad-hoc/realtime port of some trivial jQuery code into some Dojo Base functions, which I pulled off even after a long evening of dojo.beers and dinners ... which reminds me: I'd like to finish my port and replace the rest of the code if you'd like), as well as a number of random Dojo-enthusiasts from [unnamed] places I was surprised/unaware was using Dojo in the first place. The DDD:4.5-slash-dojo.dinner() was a great success: Random seemingly star-trek themed bar on East Burnisde provided a wonderful atmosphere to let loose and talk shop (among other things) with a bunch of faces old and new (over buffalo burgers no less). I've missed the beer in Portland.

It really gives you a jolt of pride when someone notices you wearing a Dojo t-shirt and takes the time to come over to talk about it, though it depresses me to see some of the really great applications written using Dojo, only to find out they are stashed away behind corporate firewalls, probably never to be seen or even mentioned publicly. It makes marketing more challenging, but at least I know, and am proud to continue on contributing to this phenomenal truly Open Source project.

The most interesting talk I attended was Steve Souders' talk about optimizing pages ... it was fun to see at least half of the techniques suggested and investigated are already available in Dojo, and the rest seemed like they could be automated with our build system in one way or another. It seems like a lot of people know nothing about Dojo's tried-and-true package system and the transparency it provides between keeping code modular and optimization, development versus deployment, obfuscation versus human-readble code. All of the things non-Dojo-users do to speed their pages up are included out-of-the-box with Dojo: ShrinkSafe to minimize bytes on the wire by shrinking (safely) variables, removing comments and excessive whitespace, automatic concatenation of "layers" of modules in a very clean and safe manner ... We can even concat "modular CSS files", stripping comments, inline-ing @import calls and so on, anything to limit the overhead of initial page loading. I'm secretly amazed. It gets even better in 1.2, coming in just a bit: stubs loading (another of Steve's tips), post-onLoad safe module loading might just be my new favorite thing since sliced bread.

I attended a django/python meetup, spending a good portion of the evening describing existing Dojo functionality, mostly when people made comments like "wouldn't it be cool is [my framework] did this?" -- "Oh, right Dojo has done that for a while now" ... I didn't hear about dojango until the next day, which is unfortunate, it would have been a perfect topic of conversation. I met John Resig briefly, though didn't talk as much shop as I'd have liked ...

For those of you wondering: No, I never found a shoe store that sells Birkenstocks, though officially retired the old pair after a seven-hour sea-kayak adventure. Don't kayak barefoot, at least the 17-foot sea-kayak variety. Lesson learned. I nearly destroyed my brother-in-law's brand new boat on it's maiden voyage on a river never intended to be run in a seventeen foot fiberglass tube. There was a reason the Mulhallah river wasn't in the "Flatwater guide of Oregon": The water isn't flat. Sea kayaks simply don't have the mobility a "normal" kayak has, and I'll be damned if I didn't run through one of the rapids only to get sucked into an eddy and bunched up against a sunken tree. I was fine for the most part, simply unable to fight the force of the rapids slowly rolling me (boat included) on end, and eventually over. I almost freed myself without damage or incident, but gave up when I heard the distinct noise of cracking fiberglass. Remember, the boat is seventeen feet long, and at the time of me removing the skirt, probably three-quarters submersed in the running water wedged against a tree. I was able to climb out (all the while holding on to said tree with one hand), roll the boat around and swim it downstream, handoff to my [extremely shaken] sister, swim further downstream and collect my own paddle and Nalgene bottle. Not only did I not lose any gear, I was still wearing my hat. Our peaceful, "zen-like" kayak adventure had instantly become exciting, despite the exceptionally painful bruises on both shins, and that ominous feeling of of sinking boat, which actually turned out to be true: I was taking on water until we beached for lunch and surveyed the damage: a small crack, large enough to seep water, and a six-inch scrape through the fiberglass on the underside. Bet you are glad you got that kevlar upgrade, huh, Bill? (Sorry about the boat. At least it's broken in now!). Tenacious tape is some amazing shit.

For the record, sea kayaking isn't my thing. The rapids part was fun, but atypical for this sport. The part the was supposed to be enjoyable was drowned by passing motorboats on the Willamette and an exceptionally hot sun. I, for the first time ever, have a very bad sunburn in the shape of a very nice Type III PFD. I'd rather do whitewater and/or "regular" kayaking. It didn't take long for me to answer my sister when she suggested we do this in Alaska ... Cold and Wet are my two least favorite sensations, especially when combined.

It was great to see all the old QuiVaH folks again (who are seemingly no longer doing shows together). I think every last one of them is married now, most to people I knew, some to people I didn't, and Tae is still out being the talent-asian. RJ is still RJ, Q is still Q, just as fun and funny but with a better job, Haven is doing well for himself, and anxious to jump into some Dojo, which is great news in itself, Tae and Ran are living together near Goose Hollow (I actually ran into Ran [no pun intended] at the Django meet-up: he was dealing poker downstairs the whole evening), and they all really made my trip to PDX "complete" ...

Now I get all day tomorrow to decide if I want to miss my Tuesday-morning-flight and go apartment hunting ... decisions, decisions. Anyone up for dinner? Last chance to eat in this treasure-trove of excellent food offerings ...