Introducing The Dojo Toolkit
Introducing Dojo page 1 : Introducing Dojo page 2 : Introduction to JavaScript toolkits
Ajax: transporting data
dojo.xhr provides a simple, powerful API for using Ajax. In newer versions of Dojo, the simple dojo.xhr() call wraps the existing methods from previous versions: dojo.xhrGet, dojo.xhrPost, dojo.xhrPut and dojo.xhrDelete.
dojo.xhrGet( {
url:"/path/to/remote.html",
load:function (data) {
dojo.byId("updateArea").innerHTML = data;
}
});
Many things are simple to achieve with such calls, like retrieving the contents of a remote file by issuing a GET on the server, and injecting the result into a node (as seen above) or progressively trapping a native Form and using Ajax to POST the results to the server, as seen below:
<!-- a simple form: -->
<form id="sampleForm" action="submit.php" method="POST">
<input type="text" name="username" />
<button type="submit">login</button>
</form>
<script type="text/javascript">
dojo.addOnLoad(function() {
var form = dojo.byId("sampleForm"); // save ref to form
dojo.connect(form, "onsubmit", function(e) {
// connect to, and disable the submit of this form
e.preventDefault();
// post the form with all it's defaults
dojo.xhrPost( {
form: form,
load: function(data) {
// set the form's HTML to be the response
form.innerHTML = data;
}
});
});
});
</script>
A simple PHP script to process the POST and send back some text would look like so:
<?php
print "You Sent:";
print_r($_POST);
?>
All the dojo.xhr* methods use a single object hash, or property bag, as the only parameter, so there's no need to remember the order of parameters - just the common names and what they do:
url: The endpoint to target.handleAs: defaults totext, but allows you to modify the way the callback receives the data. Valid built-in options are:text,javascript,json, orxmltimeout: time (in milliseconds) to wait before throwing a failure, should the data not come back.load: a function to call when the data arrives. The data is passed to the function as the first parameter.error: an error handler function to define.sync: A Boolean to toggle whether or not thisXMLHttpRequestis blocking or runs in the background. This defaults tofalse, which indicates asynchronous operation.handle: A function that is fired in the result of error or load success. The data object passed is either the data, ortypeof"Error". It is provided as a convenience.form: AdomNode(or string ID of a node) to use as the content when submitting. As seen in the example above, theurl:parameter is retrieved from the form'sactionattribute. You may specify an alternate url by passing bothform:andurl:parameters.content: A JSON object of data to send to the url.
Later in this article, we'll show the "magic" behind dojo.hitch and dojo.partial, which give you more flexibility over the scope in which your load:, error: and handle: callbacks are called.
FX: A powerful, flexible animation API
Base dojo.js includes simple fade methods, and a powerful animateProperty method, which animates any CSS property. All animation methods return an instance of dojo._Animation, the core object providing control over the sequence. To create and run a fade animation, we'd do the following:
dojo.addOnLoad(function() {
dojo.fadeOut( {
node:"someNode", // a node ref, byId
}).play();
});
dojo._Animation instances have play(), stop(), status(), gotoPercent(), and pause() methods for control. With the exception of dojo.anim, all use a single object hash for defining the options. Some of the more useful options include:
var anim = dojo.fadeOut( {
node: "someNode", // node to manipulate
duration: 3000, // time in ms to run the animation
easing: function(n) {
// a linear easing function, alter the progression of the curve used
return n;
},
delay: 400, // time in ms to delay the animation when calling .play()
rate: 10 // a framerate like modifier.
});
anim.play();
There are 30+ available easing functions in the optional dojo.fx.easing component. Simply dojo.require() it to use them:
dojo.require("dojo.fx.easing");
dojo.addOnLoad(function() {
dojo.fadeOut( {
node:"someNode",
easing: dojo.fx.easing.bounceOut // bounce towards the end of the animation
}).play();
});
They also fire synthetic events at various stages of the cycle. What follows is an involved example illustrating all of them:
dojo.addOnLoad(function() {
dojo.fadeOut( {
node:"someNode",
beforeBeing: function() {
console.log("the animation will start after I've executed");
},
onBegin: function() {
console.log('the animation just started');
},
onEnd: function() {
console.log('the animation is done now');
},
onPlay: function() {
console.log('the animation was started by calling play()');
},
onStop: function() {
console.log('the animation was stopped');
}
onAnimate: function(val) {
// fired at every step of the animation
console.log('current value: ', val);
}
})
});
The most commonly used event is onEnd. For instance, supposed you want to fade out some content, replace it via Ajax, and fade it back in:
var n = dojo.byId("someNode");
dojo.fadeOut( {
node: n,
onEnd: function() {
dojo.xhrGet( {
url: "newContent.html",
load: function(data) {
n.innerHTML = data;
dojo.fadeIn({ node: n }).play();
}
})
}
}).play();
The node: parameter can either be a DOM Node reference, or a string to be passed through dojo.byId. In this example, we stored the reference as "n", and reuse it in our callback.
You can also use dojo.connect for advanced usage with animations, the _Animation
// create a simple loop
var fadein = dojo.fadeIn({ node: "someNode" });
var fadeOut = dojo.fadeOut({ node: "someNode" });
// call fadeout.play() anytime fadein's onEnd is fired:
// and re-play fadein when fadeout's onEnd is fired:
dojo.connect(fadein, "onEnd", fadeout, "play");
dojo.connect(fadeout, "onEnd", fadein, "play");
// start the loop
fadeout.play();
Fading is great, and useful, but is only provided as a convenience wrapper around dojo.animateProperty. The property undergoing the animation is opacity:
// simulate fadeIn
dojo.animateProperty( {
node:"someNode",
properties: {
opacity: 1
}
}).play();
// as opposed to:
// dojo.fadeIn({ node: "someNode" }).play();
But animateProperty is entirely more robust and flexible. With it, you can animate any number of properties across a single node:
dojo.animateProperty( {
node:"someNode",
properties: {
// end is assumed:
opacity:0,
// define a start AND end value:
marginLeft: {
start:5, end:250
}
// start is calculated, use unit "em"
padding: {
end:5, unit:"em"
}
}
}).play();
Of course all the same dojo._Animation events and configuration options still apply. The properties hash accepts several formats. When passed a start: value and and end: value, the node is forced to those properties. When only passing an end: value, the start: value is calculated based on the node's current state. When only passed an integer, it is used as an end: value. The unit: parameter is assumed to be "px" unless otherwise specified, though use it with caution as some browsers do not convert "em" and "pt" to pixel values very well, or not at all.
Notice marginLeft in the example above. In CSS the value would be margin-left:, though the hyphen is illegal in JavaScript. A camelCase version is used instead per standard CSS to JavaScript property name translations, eg marginLeft.
You'll probably notice the animateProperty syntax is relatively verbose, as well as most every example so far has immediately called .play() on the returned _Animation. A shorthand function in Base Dojo exists to wrap common conventions (though it breaks the convenience of the "object hash" paradigm):
dojo.anim("someNode", {
opacity:0,
marginLeft: { start:5, end:250 },
padding: 50
});
This example produces the same results as the much longer example above. You sacrifice flexibility for convenience with dojo.anim, as the parameters are ordered, and the animation is automatically play()ed. This really only scratches the surface of the Dojo Animation API.
Advanced JavaScript utilities
Including object-oriented helpers like dojo.declare and dojo.mixin, as well as native prototypical inheritance helpers like dojo.extend, and dojo.delegate is extremely helpful. There are also useful scope-manipulation functions - like the often-used dojo.hitch and the elegant dojo.partial - included for your convenience.
By far the most magical of functions is dojo.hitch, which creates a function that will only ever execute in a give scope.
var foo = function() {
var bar = function(arg) {
console.log("was passed: ", arg);
}
dojo.fadeOut( {
node: "nodeById",
onEnd: dojo.hitch(this,"bar")
}).play();
}
The important thing to note here is the function created by hitch isn't executed immediately. We retain the scope of this in the example, calling a local function. There are a few great dojo.hitch articles available on DojoCampus, further exploring the potential of Javascript scope-manipulation.
dojo.partial behaves similarly to dojo.hitch, though it assumes a global scope. Using hitch or partial, you can 'curry' in JavaScript.
var style = dojo.partial(dojo.style,"someNodeId");
// anytime we execute this function, we'll style a node with id="someNodeId"
style("opacity",0.5);
style("backgorundColor","#fff");
// it also acts as a getter:
var val = style("width");
console.log(val);
dojo.mixin simply mixes objects together from right to left:
var obj = { a: "foo", b: "bar" };
dojo.mixin(obj, { b: "baz", c: "bam" });
console.log(obj);
// Object a=foo b=baz c=bam
We lose the initial value of b:, having mixed in a new value.
To create a new object, and simply add properties to it, we can use dojo.clone (which also works on DOM Nodes):
var obj = { a: "foo", b: "bar" };
var newObj = dojo.clone(obj);
dojo.mixin(newObj, { b: "baz", c: "bam" });
console.log(obj, newObj);
// Object a=foo b=bar Object a=foo b=baz c=bam
declare is Dojo's Class creator. Without delving too deeply into its powerful API, we will say that it allows you to create reusable objects in an object-oriented manner, wrapped around JavaScript's prototypical nature. In the below example we create a Person class, and then create an instance of that Person:.
dojo.declare("Person", null, {
constructor: function(nick, name, age) {
this.nick = nick;
this.name = name;
this.age = age;
this.location = null;
},
setLocation:function(loc) {
this.location = loc;
},
getLocation:function() {
return this.location;
}
});
var dante = new Person("dante","Peter Higgins", 28);
dante.setLocation("Tennessee");
console.log(dante.getLocation());
We can use mixins within declare to create new classes that inherit from other classes. Below we make an Employee class that inherits from Person. Employees will receive additional relevant fields:
dojo.declare("Person", null, {
constructor: function(nick, name, age) {
this.nick = nick;
this.name = name;
this.age = age;
this.location = null;
},
setLocation:function(loc) {
this.location = loc;
},
getLocation:function() {
return this.location;
}
});
dojo.declare("Employee", Person, {
employeeId: 0,
setId: function(id) {
this.employeeId = id;
}
})
// I am employed:
var dante = new Employee("dante","Peter Higgins", 28);
dante.setLocation("Tennessee");
dante.setId(42);
console.log(dante.employeeId);
This way, we can create People and Employees, and differentiate them by their properties and/or methods.
Using dojo.mixin, we can add custom properties to instances of a class, as the instances are just decorated objects:
dojo.declare("Person", null, {
constructor: function(nick, name, age) {
this.nick = nick;
this.name = name;
this.age = age;
this.location = null;
}
});
var dante = new Person("dante","Peter Higgins", 28);
dojo.mixin(dante, {
employeeId: 42
});
console.log(dante.employeeId); // 42
Using dojo.extend, we can modify the class directly. The extended properties will be available in all instances defined after the extend() occurred:
dojo.declare("Person", null, {
constructor: function(nick, name, age) {
this.nick = nick;
this.name = name;
this.age = age;
this.location = null;
}
});
// add Eye-color functions to the Person Class
dojo.extend(Person, {
eyeColor:"adefault",
setEyeColor: function(color) {
this.eyeColor = color;
}
});
var dante = new Person("dante","Peter Higgins", 28);
console.log(dante.eyeColor); // default
dante.setEyeColor("brown");
console.log(dante.eyeColor); // brown
The flexibility provided by dojo.declare, dojo.mixin and dojo.extend is visible throughout the entire Dojo Toolkit. Every aspect of Dojo, Dijit, or DojoX can be extended, modified, reused or otherwise hacked up as you see fit. For instance, all Dijits inherit from a base class named dijit._Widget, which is subject to all the rules of standard dojo.declare extension points mentioned above.
Built-in namespacing support
Never worry about the location of your code again! The namespaces dojo, dijit, and dojox are all assumed to be sibling folders of each another, and are located ../namespace relative to dojo/. You can create a custom namespace to encapsulate your own code simply by adding a sibling folder:
+ dojo-1.2/
+ dojo/
+ dojo.js
+ dijit/
+ dojox/
+ custom/
+ kernel.js
All that is required to notify Dojo about your page is dojo.provide(). In dojo-1.2/custom/kernel.js:
dojo.provide("custom.kernel");
dojo.require("dojo.io.iframe");
custom.init = function() {
// comments get removed as part of the Build process
console.log("I am custom code!");
}
dojo.addOnLoad(custom.init);
In your pages, you simply dojo.require your package:
dojo.require("custom.kernel");
If you'd like your code tree to live outside the dojo-1.2 folder, simply register the path relative to dojo.js:
+ dojo-1.2/
+ dojo/
+ dojo.js
+ dijit/
+ dojox/
+ custom/
+ templates/
+ Template.html
+ kernel.js
Then in your HTML, register the module path. In this case, custom is two-folders below dojo.js:
dojo.registerModulePath("custom", "../../custom");
dojo.require("custom.kernel");
The most important item is locating dojo.js on your web server. Everything "just works" after that. Once the module path has been registered, you can access any file within that path with ease:
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.declare("custom.Widget", [dijit._Widget, dijit._Templated], {
templatePath: dojo.moduleUrl("custom", "templates/Template.html");
});
Summary
This tutorial barely scratches the surface of the tools Dojo provides for building your web application or enhancing your web site. To get more help, visit the Dojo web site. Not only is Dojo backed by a thriving community providing support (on the Dojo Forums, #dojo on irc.freenode.net, and DojoCampus), it also has commercial support options from available SitePen for guaranteed results. Dojo also has the support and backing of a number of prominent companies within the web industry. We also encourage you to get involved to help us continue to make Dojo even better.