Dojo: Progress!

NOTE: Please see Bob Leurck's example for some updates on this code.

The last time I posted about dojo we looked at creating a simple dojo widget. The final aim was to produce a web-oriented progress bar.

Well, now it's finished. Head over to the demo page for XML Manager (my latest product) to try it out. It's a web progress bar because it adapts to the amount of time a server request is expected to take. Each little box takes a little bit longer than the last one, so that it can cover short wait periods just as well as long wait periods.

This post will take a look at the JavaScript, HTML and CSS code to implement this progress bar as a dojo widget. In the last post the code I gave was actually for dojo 0.1. Since dojo 0.2 is the latest version, I've moved to that for the final implementation. There aren't really any big changes.

On thing I did learn about dojo &mdash don't use your own onload method. This really screws up dojo and prevents it from running properly. Instead you need to go native:

dojo.event.connect(window, "onload", yourFunction);

This actually makes your HTML cleaner and you get the benefits of dojo's memory-leak-avoiding event-system (that would be one of the big reasons for using dojo in the first place).

OK, let's look at some JavaScript first. Here's the JavaScript you use on the page where you want a widget to appear. This is just normal JavaScript code appearing in the script element or in an external JavaScript file referenced using the src attribute of the script element. Personally I prefer using exernal JavaScript files as it keeps everything nicely separated. So, here are the relevant bits from the demo.js external JavaScript file, as used by the XML Manager demo mentioned above.

demo.js

dojo.require("dojo.widget.*");
dojo.hostenv.setModulePrefix('ricebridge', 'ricebridge');
dojo.widget.manager.registerWidgetPackage('ricebridge.widget');
dojo.require("ricebridge.widget.RicebridgeProgress");


function startProgress() {
  var progress = dojo.widget.manager.getWidgetById("progress");
  if( progress ) {
    progress.startProgress();
  }
}

function stopProgress() {
  var progress = dojo.widget.manager.getWidgetById("progress");
  if( progress ) {
    progress.stopProgress();
  }
}

Argh! What's all that dojo gunk at the top? Well the dojo documentation about creating your own widgets only shows how you to create them in the dojo.widget package. For production use, you'll really want to put them in your own package. In this case, I've chosen the package ricebridge.widget. The first line at the top is a standard dojo incantation to include the dojo.widget package. The next two lines set up the ricebridge.widget package, so that dojo will look for it in a ricebridge folder in the same folder as the dojo.js file. For the moment, I'm just accepting that this little invocation works and getting on with it. The last line just tells dojo to include our new package. It's the same idea as the first line.

The two functions startProgress and stopProgress are the main control points for the widget. They use the dojo widget manager to find the progress bar wiget object, and start and stop it, by calling methods that we will define in the progress widget itself. In the XML Manager demo, these methods are called when a server request is sent, and then when it returns, to start and stop the progress bar display.

Here's how you use the widget in your HTML code:

demo.htm

<div dojoType="RicebridgeProgress" widgetId="progress"
  numboxes="30" width="300" height="20" multiplier="10"
  basecolor="#ccc" oldcolor="#666" hicolor="#00f">
</div>

That's nice and easy, isn't it? The dojoType attribute places this div under the control of the dojo widget system, which will turn it into our progress bar using an HTML template (coming up!). We've defined the name RicebridgeProgress to point to our progress bar widget. We also use the dojo-specific widgetId attribute to identify the widget object. This widget identifier is used by the getWidgetById method of the widget manager to find the widget. We used this method in the demo.js file above. You'll notice that there are a lot of custom attributes on this div as well. This is how dojo enables a widget to have a custom set of parameters. For our web progress bar, we use the following attributes to set these parameters:

Attribute What it does
numboxes Number of indicator boxes in the progress bar.
width width of the bar in pixels
height height of the bar in pixels
multiplier time in millis to increase the wait period of the next box by
basecolor color of inactive box
oldcolor color of box reached in previous operation
hicolor color of box reached in current operation

So how does dojo know what to show for a widget? Where is the widget HTML defined? dojo uses an HTML and CSS template system for this purpose. Let's look at those files now. In the case of the progress bar they're pretty simple as we build part of the widget HTML dynamically use the JavaScript DOM API.

The other question is where to put these files. They go in the ricebridge/widget folder, corresponding to the ricebridge.widget dojo package. This folder structure is placed in the same root folder as the dojo.js file as per the dojo package discovery convention.

Here's the widget HTML:

progress.htm

<div dojoAttachPoint="main" class="rbprogress">
<table border="0"><tr dojoAttachPoint="bar"></tr></table>
</div>

We're going to build the boxes using html td elements. I'm using a table because it makes it easy to get the boxes all the same size. You could use different elements, such as an HTML list. The td elements will be created dynamically, based on the numboxes parameter.

The other thing to notice are the dojoAttachPoint attributes. These will be used by the widget JavaScript object to access parts of the widget HTML. We'll see this in action in a minute.

Each widget can also have some CSS associated with it. Here's the CSS for the progress bar:

progress.css

div.rbprogress {
  border:  1px solid #666;
  margin:  0px;
  padding: 0px;
}
div.rbprogress table {
  width:    100%;
  height:   100%;
  border:   0px;
  margin:   0px;
  padding:  0px;
}
div.rbprogress td {
  border: 1px solid #666;
  margin: 2px;
}

This is fairly standard stuff. At the moment I am not sure how to customise this for each widget instance on an HTML page. It seems one would have to set the required styles dynamically using JavaScript. It is something I will be looking at in the future.

OK. That's all that out of the way. Let's get to the meat. Here is the JavaScript code that defines the widget itself. There's a good bit here, so we'll go through it one step at a time. This JavaScript file is placed in the same folder as the progress.htm and progress.css files.

At the top of the file, we inform the dojo package system of the objects we are “providing”. Notice that we provide two objects, HtmlRicebridgeProgress and RicebridgeProgress. Based on the dojo widget example this is just the way that you do it, so cut, paste and don't ask too many questions! After the “provides”, we “require” any additional dojo stuff that we need to implement the widget. Obviously we'll need the dojo.widget package to get at the dojo widget support (our widget inherits from the generic dojo widget object), and we need dojo.lang for the dojo.inherits method, used at the end of the file.

RicebridgeProgress.js

dojo.provide("ricebridge.widget.HtmlRicebridgeProgress");
dojo.provide("ricebridge.widget.RicebridgeProgress");

dojo.require("dojo.widget.*");
dojo.require("dojo.lang.*");

ricebridge.widget.HtmlRicebridgeProgress = function() {
  dojo.widget.HtmlWidget.call(this);

  this.templatePath    = dojo.uri.dojoUri("ricebridge/widget/progress.htm");
  this.templateCssPath = dojo.uri.dojoUri("ricebridge/widget/progress.css");
  this.widgetType      = "RicebridgeProgress";

  this.numboxes        = 10;
  this.multiplier      = 100;
  this.width           = 500;
  this.height          = 50;
  this.basecolor       = '#ccc';
  this.hicolor         = '#00f';
  this.oldcolor        = '#666';

  var running = false;
  var box     = 0;

  this.fillInTemplate = function() {
    this.main.style.width  = this.width+'px';
    this.main.style.height = this.height+'px';
    for( var bI = 0; bI < this.numboxes; bI++ ) {
      var td = document.createElement('td');
      td.style.backgroundColor = this.basecolor;
      this.bar.appendChild(td);
    }
  }

  this.startProgress = function() {
    running = true;
    this.markNext();
  }

  this.markNext = function() {
    if( running && box < this.numboxes ) {
      this.bar.childNodes[box].style.backgroundColor = this.hicolor;
      var _this = this;
      dojo.lang.setTimeout(function(){_this.markNext()},this.multiplier*(1+box));
      box++;
    }
  }

  this.stopProgress = function() {
    running = false;
    for( var bI = 0; bI < box; bI++ ) {
      this.bar.childNodes[bI].style.backgroundColor = this.oldcolor;
    }
    for( var bI = box; bI < this.numboxes; bI++ ) {
      this.bar.childNodes[bI].style.backgroundColor = this.basecolor;
    }
    box = 0;
  }
}
dojo.inherits(ricebridge.widget.HtmlRicebridgeProgress, dojo.widget.HtmlWidget);
dojo.widget.tags.addParseTreeHandler("dojo:RicebridgeProgress");

So let's start by defining an object for our widget. This object will be called ricebridge.widget.HtmlRicebridgeProgress. The Html prefix seems to be required by dojo - I have not tried to do this without it. The Ricebridge prefix is there because we need a way to avoid conflicts with the names of existing widgets when we use the dojoType attribute. Um, isn't that what the ricebridge.widget package is for? Yeah, I thought so too, but apparently not. Again, this seems the safest bet at the moment until I find a better way.

The first few lines of the widget definition are taken from the dojo widget article. We call the dojo HtmlWidget constructor, as the first step in inheriting from it (the second step occurs at the end of the file, using the dojo.inherits method). The next three lines set up some standard dojo widget properties, indicating the location of the HTML and CSS template files, and the widget type name, as used by the dojoType attribute.

These lines are pretty much boilerplate. Next we have the object-specific properties. Dojo checks these names against the attributes of the widget div in your HTML page and sets any that match. This is how the parameterisation discussed above works. It's a nice bit of dojo magic.

After these public fields, we define two private fields, running and box, to hold the current progress bar state. I'm using Douglas Crockford's suggestions for JavaScript field encapsulation.

Next comes the fillInTemplate method. This is a dojo interface method that should be in every widget object. It does the final work of building the widget DOM. In our case we create the correct number of td elements, corresponding to the number of boxes requested in the widget div numboxes parameter. Notice that dojo has converted the dojoAttachPoint references into widget object properties of the same name. Thus, this.main and this.bar refer to the correct elements in the HTML template. Another handy dojoism.

The startProgress and stopProgress methods do exactly what they say. These are the methods called by our main JavaScript file, as mentioned above. All they do is update the color of each table cell as time passes. We use a utility method, markNext, to handle the setTimeout callback. Notice that this uses the dojo.lang.setTimeout method, and not the normal JavaScript setTimeout method (doing things the "dojo way" keeps you out of trouble). Also, we use the _this variable to maintain a reference to the widget object in the anonymous function passed as an argument to the setTimeout method.

And that's pretty much it. You can check out the web progress bar in action over on the XML Manager demo page.

So should you use dojo? Technically it must be one of the most advanced JavaScript frameworks out there. It's a bit weak on the documentation side, but you can always read the source, which is nice and neat and pretty easy to follow. After this little evaluation exercise, I'm going to go with it. I have an opportunity at the moment to put dojo through it's paces in a client project and I'm going to work it hard. I'll let you guys know how it goes.

One more thing, Alex Russell, the dojo maintainer, was nice enough to comment on my previous dojo post. That indicates that the project leaders are pretty committed and that the dojo developers actually care about their users. Not a bad sign at all!




This entry was posted in Web. Bookmark the permalink.

Leave a Reply

Your email address will not be published.