Author Archive

140 Characters of awesome

A few days ago I tweeted a line of code and decided it was wonderful enough to warrant further explanation. While the code itself may have fit into 140 characters, the examples and use cases go on and on. Because of a few architectural decisions made by Dojo long ago, these 140 characters are powerful and flexible as well as mindlessly simple to use.

One thing a dojo.NodeList does not have by default in Base (dojo.js) is a way to fetch and inject content into a node (or nodes). This isn't necessarily an oversight, there are several ways this can be accomplished. In fact it is so simple an operation with so many possible edge cases it may even not be worth implementing. Here I present to you my own take on this super simple concept, built by using standard Base (dojo.js) functionality.

The function will be called grab(). It will grab a url, and inject it into some nodes. Here is the first iteration of this idea:

 
dojo.extend(dojo.NodeList, {
	grab: function(url){
		dojo.xhr("GET", { url:url })
			.addCallback(this, function(response){
				this.addContent(response, "only");
			});
		return this;
	}
});
 

We're simply mixing a new function in dojo.NodeList.prototype via dojo.extend, giving all instances of a NodeList this method. The plugin calls "return this;" to allow further chaining. Ajax operations by default are asynchronous

We work with NodeLists via dojo.query, so to use this code now we simply query the dom and load some content via Ajax:

 
dojo.addOnLoad(function(){
	dojo.query("#header").grab("header.html");
});
 

That is all well and good and works brilliantly until you need to a) load non-html content b) issue an xhrPost instead of get or c) configure the xhr call in any way. No worries, with just a few more bytes we can add all of that functionality piggybacking on the behavior of dojo.Deferred and objects.

First, we'll add support for mixing in extra parameters into the object passed to the XHR call. Currently, we're only passing { url: url }, giving the Ajax call an endpoint to fetch. dojo.xhr accepts a lot of options in this magic object, so allowing a developer/user to mix in their own custom information here is as simple as calling dojo.mixin with an optional parameter. mixin() is safe like this, in that if extraArgs is undefined or null nothing happens. The url:url aspect is still retained, and any args passed as extraArgs are overwritten into the call.

 
dojo.extend(dojo.NodeList, {
	grab: function(url, extraArgs){
		dojo.xhr("GET", dojo.mixin({ url:url }, extraArgs))
			.addCallback(this, function(response){
				this.addContent(response, "only");
			});
		return this;
	}
});
 

This now allows us to mix extra items into the Ajax call like timeout, sync, error handlers and so on. Back to the nature of Ajax calls being asynchronous: if we issue a grab() call on some node, the next items in the chain will not apply to the new content until an undetermined point in the future. We can overcome this limitation by passing { sync:true } to the Ajax call, making the operation synchronous and thus making the following chains not execute until after the transfer is complete.

 
dojo.addOnLoad(function(){
	dojo.query("#content").grab("/article/12345", { sync: true })
		.find("p.title").onclick(function(e){ alert(this.innerHTML); });
});
 

If we had omitted the { sync:true } here, the find() call would be querying the dom of the node with id="content", though the content of /article/12345 url would not yet be in the container. We don't have to use synchronous Ajax here, it is just a possibility. For instance, we could have connected the click handler to the #content NodeList and delegated from the bubble, or used plugd's connectLive plugin to simple connect to "p.title" and delegated that way.

We can use the extraArgs in so many ways it's ridiculous. What if you wanted to know if the loading error'd out for some reason:

 
dojo.query("#content").grab("/foo.html", {
	error: function(e){
		console.warn("error fetching foo.html");
	}
});
 

Or, more importantly, what if you wanted to load content other than plain HTML. Perhaps you have a JSON object to load in. This is entirely possible because of the way dojo.Deferred works. When we call addCallback() on the Deferred chain in the plugin we're really registering the last chain. Anything we mix in to the Ajax call directly is executed before the final callback which calls this.addContent(). The secret is: the return value from a previous callback is passed to the next callback in the chain. So if we register a load: callback in the extraArgs we are permitted a pre-processing step for whatever content comes in. We simply need to supply an alternate handleAs to tell Ajax to process as JSON, and the load callback to process the JSON and return HTML so addContent works.

 
dojo.query("ul > li").grab("person.json", {
	handleAs:"json",
	load: function(data){
		// again, broken string for a broken wordpress highlight.
		return dojo.replace("<" + "p>{username} - {age} / {sex}</" + "p>", data);
	}
});
 

The [new in Dojo 1.4] function dojo.replace does simple template substitution. In the above example, data.username is substituted into {username}, and the full HTML is returned to the second callback for this Ajax call, then injected into the appropriate node(s). In version prior to Dojo 1.4 you can use dojo.string.replace to accomplish mostly the same goals (though the templating is done differently, ${username} would need to be substituted). Above will inject a paragraph into each of the matched list items with some data. If our data came back as an array, we could simply make up the list items as well.

 
dojo.query("ul#mainlist").grab("tweets.json", {
	handleAs:"json",
	load:function(data){
		var response = [];
		dojo.forEach(data, function(item){
			// string intentionally broken up because wordpress highlight breaks it.
			response.push(dojo.replace("<l" + "i><" + "p>{username} - {age}</" + "p></l" + "i>", data));
		});
		return response.join("");
	}
});
 

Here we're building up a string of list-items to be injected into the unordered list with id="mainlist". When the full response is joined and returned, that value is processed by addContent and injected appropriately.

The last little piece of the puzzle is to assume you don't want to issue and xhrGet call. There are several HTTP verbs to use, each with their own purpose: PUT, DELETE, or POST come to mind. With a simple adjustment to the grab function we can allow the developer to optionally overwrite this as well:

 
dojo.extend(dojo.NodeList, {
	grab: function(url, extraArgs, method){
		dojo.xhr(method || "GET", dojo.mixin({ url:url }, extraArgs))
			.addCallback(this, function(response){
				this.addContent(response, "only");
			});
		return this;
	}
});
 

Now, if we pass a method parameter the default value of "GET" is ignored. Otherwise the defaults take over. Again utilizing the extraArgs "mixin", we can make an Ajax POST request and send along custom data:

 
dojo.query("#login").grab("/user/login", {
	content:{
		name: dojo.byId("username").value,
		pass: dojo.byId("password").value
	}
}, "POST");
 

One implementation detail I don't like about grab is the forced emptying of the target node. In the addCallback function where we call addContent with a parameter of "only" we are forcefully emptying out the nodes before injecting the new content. Unfortunately, this is the only behavior we've permitted here. It would be trivial to omit to "only" (and less bytes, too) and require the developer to call empty() manually before injecting new content if that behavior was desired. By default then we'd be adding the content "last", which is the default addContent position. Let's rework the grab function to handle this:

 
dojo.extend(dojo.NodeList, {
	grab: function(url, extraArgs, method){
		dojo.xhr(method || "GET", dojo.mixin({ url:url }, extraArgs)).addCallback(this, "addContent");
		return this;
	}
});
 

It got even smaller! the "(scope, method)" pattern used in addCallback above is found throughout Dojo, and is super handy. That function "pair" (this.addContent) will be passed whatever value is returned from the Ajax call still, in the first position. The difference is we are not permitted to pass a position (without lamba's in JavaScript). Now, to inject some content into a node and empty it we must do that manually:

 
dojo.query("#foo").empty().grab("bar.html");
 

Without the empty(), "bar.html" content would be appended to the node rather than replace the content. I have yet to decide which behavior I like better. Opinions? I'll need to decide that before adding this functionality to Base Dojo proper. It currently lives in plugd, along with a slew of other new functionality for Dojo 1.4.

One last thing to do to grab function: there is no point in sending off the request if no nodes were found in the query. A simple check on the .length of the NodeList will prevent any unnecessary XHR calls from taking place:

 
dojo.extend(dojo.NodeList, {
	grab: function(url, extraArgs, method){
		this.length && dojo.xhr(method || "GET", dojo.mixin({ url:url }, extraArgs)).addCallback(this, "addContent");
		return this;
	}
});
 

And there you have it, 140 characters of awesome. (it may be a bit more with the longhand variables, pre-shrinkage. Also the inclusion of the extend call and so on add a few bytes, but in plugd's base.js these were already there for the other plugins provided, so is moot)

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 ...