Dojo: Building Blocks of the Web October 17th, 2008 at 6:07 pm by Peter Higgins
Dojo is a very flexible toolkit. Most every aspect of its functionality is extensible by taking advantage of JavaScript’s dynamic nature. Today, I’m going to show you how you can write your own modular reusable code to create a generic component you can provide others (perhaps your development team, or designers?) to use in whichever way they desire … Just like the toolkit itself.
The problem we are about to solve is a common one: Ajax activity. When we submit a form, for instance, we’ll want a way to notify the user that background network activity is taking place, and to prevent the user from re-submitting by hastily clicking away.
Conceptually, this is a rather easy task. The long and short of it is: we’ll simply overlay the form with a semi-transparent div element, blocking the underlying content. To make it reusable and generic is a greater task, and the primary focus of this article.
To sneak a peak at what we’ll be working towards checkout the end result or a small cheeky sample before diving in.
Lets start by creating a class for a component to block the content. We’ll name it dojo._Blocker. The underscore preceding the class name, at least in Dojo, indicates a private class or method which users typically won’t need to instantiate on their own. We’ll address why we’re doing that in a minute, but for now let us focus on the actual blocking of a node:
dojo.declare("dojo._Blocker", null, { duration: 400, opacity: 0.6, backgroundColor: "#fff", zIndex: 999, constructor: function(node, args){ // mixin the passed properties into this instance dojo.mixin(this, args); this.node = d.byId(node); // create a node for our overlay. this.overlay = dojo.doc.createElement('div'); // do some chained magic nonsense dojo.query(this.overlay) .place(dojo.body(),"last") .addClass("dojoBlockOverlay") .style({ backgroundColor: this.backgroundColor, position: "absolute", zIndex: this.zIndex, display: "none", opacity: this.opacity }); }, show: function(){ // summary: Show this overlay }, hide: function(){ // summary: Hide this overlay } });
It might look like a lot, but it is really simple. dojo.declare declares a class. Here we’re defining a class called dojo._Blocker, mixing in the base class null, inheriting no other code. dojo.declare is part of Dojo Base, and provided by dojo.js. The third parameter is an object hash of properties and functions to mix in to our class, and consists of four configurable properties, and three functions. The most important function is named constructor, and is run when the class is instantiated using the JavaScript new function. The signature of the function accepts a node in the first position, and properties in the second position:
var node = dojo.byId("foobar"); var thinger = new dojo._Blocker(node, { /* properties */ });
After that, we can call thinger.show() or any of the other class methods when appropriate. If you look at the constructor function more closely, a couple of interesting things are taking place. The first thing happening, we’re mixing the args param into this, allowing the user to override the default opacity, backgroundColor, z-index and duration we’ve supplied as ’sane defaults’. Most of Dojo works this way, and is very easy to customize.
On the next line, we’re assigning the passed node parameter to this.node, to store the reference in our instance. We could accept a node: parameter in the args position, but the API I’ve setup here calls for a node to be passed separately, and matches the common Dojo API. This is important later, so just take my word for it for now.
Next, we create a <div> element, and store the reference as this.overlay. The overlay is the important node, and the one we’ll be placing somewhere to block the page. Using dojo.query, we chain a few commands to manipulate our newly created node. First placing the overlay as the last child of the <body> tag, adding a distinct class name (for custom styling), and applying some basic styles. Setting position:absolute on a child of body will allow us to position the node freely, and setting display:none prevents the overlay from being shown. We’re also mixing in our user-defined (or default) backgroundColor and zIndex, all in one fell swoop.
Now that our constructor is setup, and our blocker div is created and styled, we have to activate it somehow. We’ve added two methods, show and hide, to show and hide the overlay, respectively. Our show method grabs the position of the node we intend to overlay, and sets those coordinates on our overlay div via dojo.marginBox. The node is still invisible at this point, so we make another call to dojo.style to set the opacity to 0 (which is visible, just really hard to see) and display:"", undoing the none value we set previously. Immediately after that, we fade in the node, using the defined duration. The hide method counteracts those actions, effectively making a toggle.
The show function:
show: function(){ // summary: Show this overlay var pos = dojo.coords(this.node, true), ov = this.overlay; dojo.marginBox(ov, pos); dojo.style(ov, { opacity:0, display:"block" }); dojo._fade({ node:ov, end: this.opacity, duration: this.duration }).play(); }
You’ll notice I used a private function _fade. This was for my own convenience, but the results can be achieved a number of different ways. _fade is simply the shortest to type, and I’m fairly certain the API won’t be changing any time soon (though it could, which is what the underscore indicates).
// if _fade makes you uncomfortable, you can write it with public APIs: dojo.animateProperty({ node: ov, properties: { opacity: this.opacity }, duration:this.duration }).play(); // or with the (new in 1.1) dojo.anim: dojo.anim(ov, { opacity: this.opacity }, this.duration)
The code for the hide function simply fades out the node, and sets the CSS property display to none after completion:
hide: function(){ // summary: Hide this overlay dojo.fadeOut({ node: this.overlay, duration: this.duration, // when the fadeout is done, set the overlay to display:none onEnd: dojo.hitch(this, function(){ dojo.style(this.overlay, "display", "none"); }) }).play(); }
We can test this basic implementation by creating a simple test page. We have a simple styled div, with some basic content. Save the declaration code as Block.js and load it, and Dojo into a page. For now, we’ll use the Google CDN:
<html>
<head>
<title>basic dojo._Blocker</title>
<style type="text/css">
#blockme {
height:200px;
width:200px;
background:#ededed;
color:#000;
}
#blockme p {
padding:20px;
}
</style>
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/dojo/1.2.0/dojo/dojo.xd.js"
></script>
<script type="text/javascript" src="Block.js"></script>
<script type="text/javascript">
// just for this test:
dojo.addOnLoad(function(){
var blocker = new dojo._Blocker("blockme");
dojo.query("#login").connect("onsubmit", function(e){
e.preventDefault(); // stop 'real' submission
blocker.show();
// wait 2 seconds, and hide it
setTimeout(dojo.hitch(blocker,"hide"), 2000);
});
});
</script>
</head>
<body>
<h1>Block it Test:</h1>
<div id="blockme">
<form id="login">
<p>Name:
<input type="text" id="user" name="user"></p>
<p><button id="startBlock" type="submit">Login</button></p>
</form>
</div>
</body>
</html>So we know our single-use case works. We can create an instance of a _Blocker, and assign it to a node. We can do this over and over, as often as we like, but the process is a little cumbersome. With this structure in place, we can easily make an API to create and show the _Blocker, and be assured everything is “working”. We can wrap the _Blocker API with some simple functions: dojo.block() and dojo.unblock() …
Jumping right into optimization, and avoiding the possibility of future conflicts, we’ll wrap the code in a self-executing anonymous function, creating a closure:
(function(){ var d = dojo; d.declare("dojo._Blocker", null, { // all that code }); })();
We simply alias ‘d’ to the dojo namespace, and call d.declare. We can also assign any variables we want in this scope, and prevent them from polluting the global namespace. We are doing this for a reason, so let’s move on.
I don’t want to have to manually create a new dojo._Blocker every time I need to simply block a node after some action, so I am going to make a simple public API to do the magic for me. At first thought, you’d want to just wrap the existing code in a function, creating a new blocker every
time the function was called. Thinking forward, we want to create some kind of cache of blockers to
prevent duplicates from being created for a given node (in the case of repetitious failed logins, for instance). This is where the closure comes into play. First, we create a local object to store our blockers in:
(function(){ var d = dojo; d.declare("dojo._Blocker", null, { // all that code }); // cache of all blockers: var blockers = {}; })();
Then, define a dojo.block function, which accepts the same API as dojo._Blocker: node, args. This way, we’ll be able to call dojo.block("someNode");, and the rest of the work will be done for us:
d.mixin(d, { block: function(/* String|DomNode */node, /* dojo.block._blockArgs */args){ var n = d.byId(node); var id = d.attr(n, "id"); if(!id){ id = _uniqueId(); d.attr(n, "id", id); } if(!blockers[id]){ blockers[id] = new d._Blocker(n, args); } blockers[id].show(); return blockers[id]; // dojo._Blocker } });
We’re still within this scope, so ‘d’ is still dojo, and the blockers variable is available to us. We’re doing a little extra work above, using the id attribute of a node and creating a unique id if one does not exists. You see the first thing we do is pass the node attribute through dojo.byId, which returns a DomNode from a passed string or existing domNode, a convention used nearly everywhere in Dojo, and Dijit.
The _uniqueId function is quite simple:
// Generates a unique id for a node var id_count = 0; var _uniqueId = function(){ var id_base = "dojo_blocked", id; do{ id = id_base + "_" + (++id_count); }while(d.byId(id)); return id; }
Simply create a function within this scope, and a counter to guarantee uniqueness. After that, we create a blocker if one does not already exist, call the .show() method, and return the instance.
We can either call .hide() on the returned instance, or create a second public API: dojo.unblock, which will accept a node or string. Adding this to our dojo.mixin call provides just that function:
d.mixin(d,{ block: function(node, args){ /* same as before */ }, unblock: function(node, args){ // summary: Unblock the passed node var id = d.attr(node, "id"); if(id && blockers[id]){ blockers[id].hide(); } } });
Updating our basic HTML to use the new API is simple, and a lot shorter:
// just for this test: dojo.addOnLoad(function(){ dojo.query("#login").connect("onsubmit", function(e){ e.preventDefault(); // stop 'real' submission dojo.block("blockme"); // wait 2 seconds, and hide it setTimeout(dojo.hitch(dojo,"unblock","blockme"), 2000); }); });
Now we can block and unblock any node we choose, with a really simple byId API. For example, onclick a button, send off an Ajax request, unblock after completion, and inject the response data:
dojo.addOnLoad(function(){ dojo.query("#myButton").onclick(function(e){ dojo.block("someNode"); dojo.xhrPost({ url:"login.php", handle:function(data){ dojo.byId("someNode").innerHTML = data; dojo.unblock("someNode"); } }); }) });
The last step in all of this is to wrap it into dojo.query, allowing us to make calls like:
dojo.query(".severalNodes") .block() .forEach(function(n){ n.innerHTML = "changed"; }) .unblock() ;
Writing the dojo.query plugin is the easiest part yet:
d.extend(d.NodeList, { block: // d.NodeList._mapIn("block", true), // refs #7295 function(args){ return this.forEach(function(n){ d.block(n, args); }); }, unblock: // d.NodeList._mapIn("unblock", true) // refs #7295 function(args){ return this.forEach(function(n){ d.unblock(n, args); }); } });
In my code you see a “refs #7295″. This refers to Dojo tracker ticket #7295, which exposes a private function in the core NodeList script to more easily wrap the block and unblock functions. This works because we have made the API similar to what Dojo expects internally. Until #7295 is fixed, we have to simply iterate over the NodeList, calling the block function and passing the individual node:
// iterate and return the same NodeList to allow chaining return this.forEach(function(n){ d.block(n, args); })
Our final example will include a small piece of server-side logic, mostly to simulate network latency, but also utilizing our newly created chain-able API:
// just for this test: dojo.addOnLoad(function(){ var $ = dojo.query; $("#login").connect("onsubmit", function(e){ e.preventDefault(); // stop 'real' submission var ub = $("#blockme").block()[0]; dojo.xhrPost({ form:"login", handle:function(data){ ub.innerHTML = "<p>Server said:<br>" + data + "</p>"; $(ub).unblock(); } }); ; }); });
And update the form to be a ‘real’ form, with an action:
<div id="blockme"> <form id="login" action="hello.php" method="POST"> <p>Name: <input type="text" id="user" name="user"></p> <p><button id="startBlock" type="submit">Login</button> </p> </form> </div>
The PHP is very small:
<?php sleep(3); // fake delay for network echo "Hello, " . $_POST['user']; ?>
If you think the overlay is a little bland, the class we added initially allows you to style the overlay however you choose. Run over to AjaxLoad, and generate an icon. Then add a background property to nodes with the class dojoBlockOverlay
<style type="text/css"> .dojoBlockOverlay { background:#fff url(images/loading.gif) no-repeat center center; } </style>
As an added bonus, we can put this file somewhere in the Dojo source tree, and take advantage of the Dojo build and package system. Simply dojo.provide the component as the first line of code:
dojo.provide("dojo.Block"); (function(d){ /* all above code */ })(dojo);
And save the file as dojo/Block.js … Then, when you want to use the Blocker API, simply issue a dojo.require call:
dojo.require("dojo.Block");
In doing this, our module (with heavy comments and readable variable names) will be optimized automatically by the build system, resulting in a simple, semi-obfuscated file, with a resultant size of 1.3KB before gzip.
Dojo really can be whatever you like it to be, and can easily bend to your preferred coding style. It also provides you all the tools you need to package, optimize, and modularize these kinds of plugins, without worry of conflicts with Dojo, or any other library.



Posted October 18th, 2008 at 4:22 am
Very well done. You gave me a solution to a current problem and taught me some dojo and Javascript at the same time. Thanks.
Posted October 18th, 2008 at 12:10 pm
[…] 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 […]
Posted October 27th, 2008 at 3:41 pm
why not create a submit button function to disable it after it has submitted the form or during a post request?
Posted November 2nd, 2008 at 5:52 am
[…] 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 […]