Archive for the ‘dojo’ Category.

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 …

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.

Google Analytics after onLoad

update: This was written during Dojo 1.1, and is outdated. The current documentation can be found at http://docs.dojocampus.org/dojox/analytics/Urchin, as some things have changed regarding parameters and setup since this blog post.

You've all seen it, especially in the Ajax world. The page is stalled, waiting to render, the status bar woefully proclaims "waiting for google-analytics.com ... " The culprit being the ever popular urchin tracker, and the [synchronous] script tags placed
at the bottom of your webpage:

 
<script type="text/javascript">
    var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
    document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
    var pageTracker = _gat._getTracker("UA-123456-7");
    pageTracker._initData();
    pageTracker._trackPageview();
</script>
 

This is usually before the body tag is closed, which causes [at least] Dojo's addOnLoad() function to wait until after ga.js has been loaded, and executed. I notice it most on the SitePen Dojo QuickStart guide. The whole guide is sent as valid HTML and CSS, then enhanced to break major sections into a custom tabbed view, with simple navigation. Unfortunately, all the code to do that is run from within an addOnLoad function, and my poor pitiful ethernets here in Tennessee make ga.js take upwards of five seconds to load. Long story short, sometimes I'm presented with a nasty jolt of rendering as the navigation is added, and the unselected chapters go hidden.

I decided it was a safe bet I could just append a script tag to my head element, and run that onLoad, allowing the rest of the code to execute as soon as the Dom was ready, regardless of Google being ready. This should work with any page that uses Dojo >= 0.9 ... I'm going to test it on this site for a while before considering making it another utility of the Toolkit. It's working so far, let's see if it picks up my traffic (the Analytics dashboard said it was getting data, but I just started it today, so I won't know until tomorrow).

This is just plain JavaScript, with some fancy scope magic for convenience. It uses Dojo because I do.

 
dojo.provide("dojox.analytics.ga");
dojo.mixin(dojox.analytics.ga, {
	// _acct: String
	//		your GA urchin tracker account numbers.
	_acct: dojo.config.urchin || "",
 
	// _loadInterval: Integer
	// 		time in ms to wait between checking again
	_loadInterval: 420,
 
	_loadGA: function(){
		// summary: load the ga.js file and begin initialization process
		var gaHost = ("https:" == document.location.protocol) ? "https://ssl." : "http://www.";
		var s = dojo.doc.createElement('script');
		s.src = gaHost + "google-analytics.com/ga.js";
		dojo.doc.getElementsByTagName("head")[0].appendChild(s);
		setTimeout(dojo.hitch(this, "_checkGA"), this._loadInterval);
	},
 
	_checkGA: function(){
		// summary: sniff the global _gat variable Google defines.
		// 		if it exists, run _gotGA, otherwise, do another interval
		setTimeout(dojo.hitch(this, window['_gat'] ? "_gotGA" : "_checkGA"), this._loadInterval);
	},
 
	_gotGA: function(){
		// summary: initialize the tracker, we've got ga.js loaded
		var ga = this._tracker = dojo.hitch(_gat, "_getTracker", this._acct)();
		ga._initData();
		ga._trackPageview();
		this.GAonLoad.apply(this, arguments);
	},
 
	GAonLoad: function(){
		// stub function to fire when urchin is complete
		// you also have access in this function to this._tracker, which is the
		// root tracker instance you called _initData() on
	}
 
}); 
 
// start it all up after body is ready:
dojo.addOnLoad(dojox.analytics.ga,"_loadGA");
 

I've hard-coded my UA-# into the code, but you would be able to define it as part of your djConfig variable, either before dojo.js is loaded:

 
<script type="text/javascript">
    var djConfig = { parseOnLoad:true, urchin:"UA-123456-7" };
</script>
 

or directly on the script tag:

 
<script src="dojo/dojo.js" djConfig="urchin: UA-123456-7, parseOnLoad:true"></script>
 

you can see the file is namespaced dojox.analytics.ga in the example. If the file were actually located there, you would be able to simply dojo.require("dojox.analytics.ga"); to include analytics tracking on any Dojo-enabled page.

update: It works! (or at least seems to.) All of yesterday's traffic apparently was tracked, and showed up in my analytics dashboard this morning.

update 2: This is now in trunk, as of revision [14006], and is known to work with 1.1 without issue. 1.0 is untested. It will be released with 1.2.

Sortable object

To follow up of Wolfram's blog about sorting an object hash by values, I thought i'd make a simple function to work on most single key-pair objects, the JavaScript version of an associative array:

 
var obj = {
	car: 300, bike:60, motorbike:200, airplane:1000,
	helicopter: 500, rocket: 8*60*60
};
 
function sortObj(theObj, idx){
	// summary: sort an object hash by a numerical value
	var sortable = [];
	for(var i in theObj){
		sortable.push([i, theObj[i]]);
	}
 
	sortable.sort(function(a,b){
		return a[idx] - b[idx];
	});
 
	var newObj = {};
	dojo.forEach(dojo.map(sortable,function(elm){
		return elm[0];
	}), function(elm){
		newObj[elm] = theObj[elm];
	});
	return newObj;
}
 
console.log(sortObj(obj,1));
 

Branching for IE

I'm working on a really cool demo, and it really pushes the limits of what our current offering of web browsers are capable of doing via JavaScript animations (yes, it's one of those flash-like UI's I recreated in DHTML with surprisingly good results) ... Anyway, it uses a lot of images, and they ALL have png alpha channels, making IE6 display this horrible pale green/blue outlay border we all know and love.

So I'm faced with an interesting twist on a common problem, and I think I found a solution I am happy with: Branch for IE6. This is no ordinary branch, mind you, this is the magical forced upgrade branch. Technically one line of javascript, a function, fired onLoad based on browser sniffing:

 
var newp = function(){
 // IE6 branch of this demo
 window.location.href = "http://" +
  (confirm("Is it 2008?") ?
   "webkit.org" : "mozilla.org") +
  "/";
}
 
// setup our branch launch:
dojo.addOnLoad((dojo.isIE < 7 ? newp : init));
 

I found humour in it ... It performs a whole lot better than 188 filter: directives, and really, I have no desire to put that burden on anyone's PC. It's 2008 folks, don't test out hot demos using Internet Explorer 6, you will usually be disappointed (when compared to the likes of Safari 3 and even IE7)

Please, Microsoft, for the love of jebus --- end of life IE6 so we, as innovators of the web, can push forward and stop wasting time supporting a near deceased browser anyway, which still has the highest percentage of market share due to your unwillingness to force people up to IE7 ... What version of IE will be out when you finally DO end-of-life it? 9? 10?

I have actually kind of grown to love ie7 -- minus some quirks (solved by dojo sniffing and css sniffing) and poor png support, it actually performs very well, and better than FF2 in some cases. (Granted ff3 picks up any slack ff2 left off, but that's the whole point of these "browser wars" anyway. keep pushing forward ... ) To that end, I reiterate: Microsoft, for the love of jebus ...

update: I've put the demo online.