A Slideshow Widget Example
Here's an example of the slide-show widget we're going to build in this article. To get a feel for how it's being used, do a view-source on this page.

If you like, you can stop here and go add a slideshow to your pages. But odds are you want to build a widget yourself. This article will show you how you can do that and use Dojo to go from idea to upgrade-able interface. Fast.
Intro
A key feature of the Dojo widget system is the ability to prototype, test, and tweak the component's UI and interactions. Dojo provides "templates" that you can use to rapidly prototype your UI in HTML and CSS and expose your widgets as markup on the pages you include them on. Dojo ensures that your work in prototyping isn't thrown away when it comes time for deployment. Incremental performance optimizations to be discussed in another article ensure that your widget is as fast as you need it to be.
Prototype
Templates in Dojo place HTML and CSS fragments into files which are consulted when a widget is constructed. The setup of event handlers and the creation of references to DOM nodes is handled through some extra attributes on your HTML markup.
In this article, we're going to show you how we built the slide-show widget you see above. There's not a ton of code to it, but we'll start with the smallest possible (loads but doesn't do anything) version of the widget. Here it is:
// tell the package system what classes get defined here
dojo.provide("dojo.widget.SlideShow");
// load dependencies
dojo.require("dojo.widget.*");
// define the widget class
dojo.widget.SlideShow = function(){
// inheritance
// see: http://www.cs.rit.edu/~atk/JavaScript/manuals/jsobj/
dojo.widget.HtmlWidget.call(this);
this.templatePath = dojo.uri.dojoUri("src/widget/templates/HtmlSlideShow.html");
this.templateCssPath = dojo.uri.dojoUri("src/widget/templates/HtmlSlideShow.css");
this.widgetType = "SlideShow";
}
// complete the inheritance process
dojo.inherits(dojo.widget.SlideShow, dojo.widget.HtmlWidget);
// make it a tag
dojo.widget.tags.addParseTreeHandler("dojo:slideshow");
Most things in this class are boilerplate, and only those properties that are necessary are given values. Every Dojo widget you'll create comes from a class constructor like this one.
The most interesting bits of this example are the template* properties. Template locations are specified relative to the root of the Dojo Subversion checkout, and these are made absolute (if necessary) when the widget is created. Meaning that you don't have to specify an un-portable absolute URL for your template files and you can move your widget file around without adverse consequences.
Dojo currently requires that templates define a single top-level DOM node which gets assigned to the domNode property of the widget. If multiple elements are defined in the top-level of a template file, only the first is used. The domNode property is defined in the DomWidget class, so every widget that inherits from it will have this property implicitly. A minimal template for our slide show might be:
HtmlSlideShow.html:
<div style="position: relative; width: 800px; height: 600px;"
dojoAttachPoint="imagesContainer">
<img dojoAttachPoint="img1" border="0" hspace="0" vspace="0"
class="slideShowImg" style="z-index: 1;" />
<img dojoAttachPoint="img2" border="0" hspace="0" vspace="0"
class="slideShowImg" style="z-index: 0;" />
</div>
HtmlSlideShow.css:
.slideShowImg {
position: absolute;
left: 0px;
top: 0px;
border: 2px solid #4d4d4d;
padding: 0px;
margin: 0px;
}
Like the script code above, these are just the minimum necessary to make things happen. We'll evolve our markup over the course of the article.
Connect
The example above isn't very functional. A couple of key components are missing:
- the widget doesn't respond to user interaction. You can't start or stop the slideshow.
- the slideshow doesn't actually show anything!
So assuming that we want the slideshow to pause or start when the user clicks, how would we get that done?
The traditional DOM way to do it might require code like this in our widget:
var _this = this; // closure magic
this.domNode.onclick = function(){
_this.togglePaused();
}
Now, this is fine if you don't mind clobbering other event handlers that might be set, but there are situations where this is a problem, so we'll spot our component author the super-spiffy Dojo event system, which might turn that code into:
dojo.event.connect(this.domNode, "onclick", this, "togglePaused");
Down to one line, not bad!
But what happens when you want that to also happen from a button that explicitly pauses or starts the slideshow too? Well, we'd have to get a reference to THAT node too and manually hook things up the same way. As you can see, we're explicitly tying the DOM structure to our code in a way that's going to make things more complicated to change later. This isn't so important when you're hacking up a one-off, but when you want other people to be able to easily customize your components, it becomes a problem.
This covers the DOM way, but what about the innerHTML way? If we were building the widget entirely using innerHTML (not uncommon), we might see something like:
someNode.innerHTML = "<div onclick='component.togglePaused();'>...</div>";
But what if we want more than one component on the page? We'd need some sort of ad-hoc naming or lookup scheme would need to be invented to make this code connect the togglePaused method to the right object. This might be the extended example:
var clickString = this.componentName+'.togglePaused();'; someNode.innerHTML = "<div onclick='"+clickString+"'>...</div>";
Pretty grotty. It's not hard to imagine the maintainability nightmares that result. You only need to pull down code from any of the myriad script example sites for proof.
So what we really need is something that gives us the componentization and non-destructiveness advantages of attaching event handlers the DOM way with the readability advantage of the innerHTML style. The Dojo template system to the rescue. In our template file, we'd attach our click handler to the node using the dojoAttachEvent property in the element we're attaching to directly. Here's the uber-simplified example:
<div dojoAttachEvent="onClick: togglePaused;">
<img class="slideShowImg" />
<img class="slideShowImg" />
</div>
The dojoAttachEvent property gives us a mapping between what the DOM Node's event name is (onClick) and what logical action to take as a result (togglePaused). And and we do it in the markup, where it belongs. Our widget now only has to worry about manipulating DOM nodes in response to actions, if at all. Note that we don't have to specify the this keyword or otherwise handle the scope of the node or method assignments. Since our template is for a particular widget, Dojo can safely connect each new widget's template nodes to its methods without having to ask us for assistance.
So events are pretty easy, what about handling the changing of the images to make the "slide show" thing happen? Good news again. Dojo allows us to easily connect the elements in our template file to properties in our widget class. In this case, a slide show will need to have 2 images; one for the current display and a second for the image being loaded which we will then transition into. Here's the new code we'd add to our widget class (other code removed for readability):
// ...
dojo.widget.SlideShow = function(){
// ...boilerplate and template* code here
// over-ride defaults
this.widgetType = "SlideShow";
// image nodes
this.img1 = null;
this.img2 = null;
}
// ...
And then in our abbreviated template, we might have:
<div dojoAttachEvent="onClick: togglePaused;">
<img class="slideShowImg"
dojoAttachPoint="img1" />
<img class="slideShowImg"
dojoAttachPoint="img2" />
</div>
The attribute dojoAttachPoint connects the elements to our widget properties when the template is created, and so code that runs later will be able to reference this.img1 to get a handle instead of doing something like document.getElementById(...) which would require knowing the unique ID of the element. Once again, Dojo templates make wiring together the parts of a widget faster, less brittle, and more maintainable than either the innerHTML or straight-DOM ways of doing things. This is rapid widget prototyping at its best.
So we've got event handlers and references to our DOM nodes. Instead of going through all of the features of the widget and walking you through all of the code now, we include it at the end of the article. Scroll to the end for all the details.
Making It A Tag
The last hurdle to making this widget something we'd consider working with is to make it easy to instantiate. Even in the first listing of SlideShow.js, we do that. Have a look at the last line:
dojo.widget.tags.addParseTreeHandler("dojo:slideshow");
We saw a brief example of using widgets earlier, but here's a refresher example of a Dojo tag being used through extra properties on an HTML element:
<img dojoType="SlideShow"
imgUrls="images/1.jpg;images/2.jpg;images/3.jpg;images/4.jpg"
transitionInterval="700"
imgWidth="800"
imgHeight="600"
delay="7000"
src="images/1.jpg" />
The parser that's built into Dojo knows what to do with that declaration, and normalizes it for us. In fact, we could feed the Dojo parser this XML fragment and get the exact same result:
<dojo:slideshow
imgUrls="images/1.jpg;images/2.jpg;images/3.jpg;images/4.jpg"
transitionInterval="700"
imgWidth="800"
imgHeight="600"
delay="7000" />
The dojoType attribute is a proxy for an XML tag name. Standards purists might argue that these extra attributes make the markup invalid, which is true. If you really really want full standards compliance anyway, just use the dojo: namespace for your widgets. However, XML in browsers is hit-or-miss and strict adherence to the letter of a spec and not its spirit is sometimes counter-productive. Since we are able to construct widgets that replace parts of a page arbitrarily, Dojo provides a baked-in an answer to concerns about how DHTML/Ajax works in browsers that don't support JavaScript or otherwise have strict accessibility requirements. Browsers that can handle it receive the upgraded UI while everyone else gets the "fallback", all without requiring developers to give up the ease of declaring their widgets where they want them in the page just extending markup. Toolkits that force developers to (manually) place widgets in the flow of the page in JavaScript are a pain to work with and have many of the same maintenance problems that DOM-based widget building approaches share. Markup purist or pragmatist, Dojo lets you have your cake and eat it too.
But back to our widget! To support some of the properties in this expanded usage listing, we would add corresponding placeholders in our class:
// ... this.imgUrls = []; // the images we'll go through this.delay = 4000; // give it 4 seconds this.transitionInterval = 2000; // 2 seconds this.imgWidth = 800; // img width this.imgHeight = 600; // img height // ...
For each of these properties, the values set on the inline constructor are cast to the same primitive type as the initial value. transitionInterval will be cast to an integer while imgUrls on the dojo:slideshow tag will be turned into an array that looks like:
["images/1.jpg", "images/2.jpg", "images/3.jpg", "images/4.jpg"]
If you want integers instead, you would have to convert them before using the values from the array, since it only splits the attribute string on the semicolon character.
Assigning to functions is subtly different, but no harder. Dojo includes an abstract event system that should be familiar to those who have used Aspect Oriented Programming (AOP). In a nutshell, this event system allows a developer to "register" some code to be called when another function is called without affecting the behavior of the initial function that is being used as the trigger. Assigning to a function property in an inline constructor allows us to likewise register a listener, but not to over-ride the default behavior of the widget method. We could alert some status information when the togglePaused method of our widget is fired by attaching to it like this:: this:
<img dojoType="SlideShow"
togglePaused="alert('showing image '+(this.imgIdx+1)+' of '+this.imgUrls.length);"
src="images/1.jpg" />
Here the this keyword refers to the widget object and not the DOM Node.
Further Reading
This article covers the basics of how Dojo widget construction, but not many of the configuration, optimization, and library facilities that Dojo provides for making GUI programming easier. While the documentation around most of these topics is currently lacking, you are encouraged to check out the Dojo wiki (http://dojo.jot.com) for more information on these topics.
Widget Code
There are some features that a slide-show should have that this article hasn't covered yet. We would want to cycling through images using a transition and make sure that doesn't happen until the background image is actually loaded, regardless of how long we specified the delay between images to be. Better to be late than broken. Don't worry if the event-driven code is a little bit difficult to follow. The entirety of the widget and an improved template follow. We omit the CSS file since it hasn't changed.
SlideShow.js:
dojo.provide("dojo.widget.SlideShow");
dojo.require("dojo.event.*");
dojo.require("dojo.xml.*");
dojo.require("dojo.widget.*");
dojo.require("dojo.graphics.htmlEffects");
dojo.widget.SlideShow = function(){
dojo.widget.HtmlWidget.call(this);
this.templatePath = dojo.uri.dojoUri("src/widget/templates/HtmlSlideShow.html");
this.templateCssPath = dojo.uri.dojoUri("src/widget/templates/HtmlSlideShow.css");
// over-ride defaults
this.widgetType = "SlideShow";
// useful properties
this.imgUrls = []; // the images we'll go through
this.urlsIdx = 0; // where in the images we are
this.delay = 4000; // give it 4 seconds
this.transitionInterval = 2000; // 2 seconds
this.imgWidth = 800; // img width
this.imgHeight = 600; // img height
this.background = "img2"; // what's in the bg
this.foreground = "img1"; // what's in the fg
this.stopped = false; // should I stay or should I go?
// our DOM nodes:
this.imagesContainer = null;
this.startStopButton = null;
this.controlsContainer = null;
this.img1 = null;
this.img2 = null;
this.loadNextImage = function(){
// load a new image in that container, and make sure it informs
// us when it finishes loading
dojo.event.kwConnect({
srcObj: this[this.background],
srcFunc: "onload",
adviceObj: this,
adviceFunc: "backgroundImageLoaded",
once: true, // make sure we only ever hear about it once
delay: this.delay
});
dojo.xml.htmlUtil.setOpacity(this[this.background], 1.0);
this[this.background].src = this.imgUrls[this.urlsIdx++];
if(this.urlsIdx>(this.imgUrls.length-1)){
this.urlsIdx = 0;
}
}
this.fillInTemplate = function(){
dojo.xml.htmlUtil.setOpacity(this.img1, 0.9999);
dojo.xml.htmlUtil.setOpacity(this.img2, 0.9999);
with(this.imagesContainer.style){
width = this.imgWidth+"px";
height = this.imgHeight+"px";
}
if(this.imgUrls.length>1){
this.img2.src = this.imgUrls[this.urlsIdx++];
this.endTransition();
}else{
this.img1.src = this.imgUrls[this.urlsIdx++];
}
}
this.togglePaused = function(){
if(this.stopped){
this.stopped = false;
this.endTransition();
this.startStopButton.value= "pause";
}else{
this.stopped = true;
this.startStopButton.value= "play";
}
}
this.backgroundImageLoaded = function(){
// start fading out the foreground image
if(this.stopped){ return; }
// closure magic for call-back
var _this = this;
var callback = function(){ _this.endTransition(); };
// actually start the fadeOut effect
// NOTE: if we wanted to use other transition types, we'd set them up
// here as well
dojo.graphics.htmlEffects.fadeOut(this[this.foreground],
this.transitionInterval, callback);
}
this.endTransition = function(){
// move the foreground image to the background
with(this[this.background].style){ zIndex = parseInt(zIndex)+1; }
with(this[this.foreground].style){ zIndex = parseInt(zIndex)-1; }
// fg/bg book-keeping
var tmp = this.foreground;
this.foreground = this.background;
this.background = tmp;
// keep on truckin
this.loadNextImage();
}
}
dj_inherits(dojo.widget.SlideShow, dojo.widget.HtmlWidget);
dojo.widget.tags.addParseTreeHandler("dojo:slideshow");
HtmlSlideShow.html:
<table cellpadding="0" cellspacing="0" border="0">
<tr>
<td dojoAttachPoint="controlsContainer">
<input type="button" value="pause"
dojoAttachPoint="startStopButton"
dojoAttachEvent="onClick: togglePaused;">
</td>
</tr>
<tr>
<td>
<div style="position: relative; width: 800px; height: 600px;"
dojoAttachPoint="imagesContainer"
dojoAttachEvent="onClick: togglePaused;">
<img dojoAttachPoint="img1" class="slideShowImg"
style="z-index: 1;" />
<img dojoAttachPoint="img2" class="slideShowImg"
style="z-index: 0;" />
</div>
</td>
</tr>
</table>
We put the images in a table so that controls can be added around it (in this case, just a pause/play button) and so that other elements can flow around the widget.
More Information
Dojo Status The Dojo Wiki Problems tackled by the Dojo widget system
About The Author
Alex Russell is the project lead for Dojo and can be reached at <alex@dojotoolkit.org>. His blog is at: http://alex.dojotoolkit.org