Creating a custom dojo package with a custom dojo widget
We have been using the Dojo toolkit right from the beginning, but back then we only used the Editor widget, and only on one page. From there, we gradually added Dojo functionality, with Dialogs on a few more pages, and just recently, Buttons (custom ones at that) on every page! The toolkit has become an integral part of our application, and we realized the hard way that the number of objects downloaded gets really big really quickly if you don’t have a good profile.
There was documentation on profiling Dojo, and documentation on custom widgets, but numerous sources of information and too much code reading was necessary to figure out how to profile Dojo with a custom widget! Hopefully, writing it all down here will benefit those who come after us…
Profiling
Dojo is a very big toolkit. Chances are, your app won’t use half the features, so the toolkit has a small core, dojo.js, that knows how to pull in components as necessary. This makes the initial install trivial. The problem is, as you use more of the toolkit, you are making the user pull down many small files, and this greatly increases latency, not to mention browser caching is defeated in many cases. Dojo’s solution is a concept of profiling, where you define the objects that you need, and bake them into a custom compressed dojo.js file. This way the browser gets all/most of the code in a single, compressed, and cacheable download.
Sources
The first step to creating our own Dojo package, is to download the source:
svn co http://svn.dojotoolkit.org/dojo/tags/release-0.4.1
Conventions
Next, we will write our very own Coupa Button. Dojo gives you some flexibility in defining custom widgets, but we’re Rails developers, so convention triumphs over configuration, and the dojo conventions for custom widgets are:
- the widget should be called
<you>.widget.<Component>, so in our case,coupa.widget.Button - the code should be placed in
release-0.4.1/../<you>/widget/<Component>.js - the HTML/CSS associated with the widget should go in
release-0.4.1/../<you>/widget/templates - the images associated with the widget should go in
release-0.4.1/../<you>/widget/templates/images
We end up with with this directory structure:
Code
The conventions continue into the code for the custom widget. We only wish to change the CSS and the images used by Dojo’s Button class, and by following conventions, we minimize the code we have to write in our Button.js:
dojo.provide("coupa.widget.Button");
dojo.require("dojo.widget.Button");
dojo.widget.defineWidget(
"coupa.widget.Button",
dojo.widget.Button,
{
templateCssPath: dojo.uri.moduleUri("coupa", "widget/templates/ButtonTemplate.css"),
inactiveImg: "../coupa/widget/templates/images/coupaButton-"
}
);
It almost reads like english: we are providing a coupa.widget.Button, and to do that, we need dojo.widget.Button. We define the former as a subclass of the latter, overriding two of the attributes. Short and sweet. Also note the use of the dojo.uri.moduleUri helper made possible by the conventional directory structure.
Profile
We then create a profile release-0.4.1/buildscripts/profiles/coupa.profile.js:
var dependencies = [
"dojo.widget.Dialog",
"dojo.widget.DropdownDatePicker",
"dojo.widget.Button",
"coupa.widget.Button"
];
dependencies.prefixes = [
["coupa", "../coupa"]
];
load("getDependencyList.js");
Again, the minimum information needed to do the job: we want to include these objects, and find them in this path prefix in addition to Dojo’s.
Now, you might have noticed a file called manifest.js in our coupa directory in the screenshot above. This file tells Dojo’s dynamic class loader what components live under a directory, so it knows to look here when it needs these components. We don’t need this file now that our button is baked into dojo.js, so we won’t elaborate on it in this article, but it is good for testing our widgets, in which case we use the minimal dojo.js.
Finally we are ready to generate our very own dojo.js:
cd release-0.4.1/buildscripts
ant -Dversion=0.4.1 -Dprofile=coupa -Ddocless=true release intern-strings
This will read the object list defined in profiles/coupa.profile.js and prepare a release without documentation, including associated HTML/CSS objects.
Deploy
Now we can deploy our new Dojo package:
mkdir $RAILS_ROOT/public/javascripts/dojo
cd ../release/dojo
cp -r dojo.js iframe_history.html src nls $RAILS_ROOT/public/javascripts/dojo
and let’s not forget our custom widget:
cd ../../..
cp -r coupa $RAILS_ROOT/public/javascripts
Why deploy the huge Dojo source tree and our full code structure when all the interesting stuff is already in dojo.js, you ask? By convention, Dojo is deployed with its full source tree, and so should custom extensions. Our images need to be deployed in that location, for one, and our code needs to be in source control somewhere anyway. Deploying them in the standard locations also allows us to debug them by simply substituting in the minimal dojo.js. Lastly, we might want to use a widget, like Editor2, in only a few places, and don’t wish to enlarge our dojo.js with it, in which case we can just leave it out of the profile, and it will be loaded from the source tree.
Instantiate
We’ve come to the very last step, which is, of course, to use our new button:
<div id="do_stuff_button" dojo:type="coupa:Button" onclick="doStuff()">Hit me, Baby!</div>
and for buttons that are inserted into a page through AJAX we need to manually instantiate them:
dojo.widget.createWidget('do_stuff_button')
Conclusion
That’s all for today, folks!
Dojo is not for everyone. In fact, there’s a different library out there for each developer’s needs, it seems. But those whose needs are met by the Dojo toolkit, will no doubt appreciate the efforts of Alex Russell and his team in creating an astoundingly comprehensive set of tools and widgets with incredible consistency and quality, which is all the more amazing given how much time they spend babysitting n00bs like myself on #dojo!





