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?