Articles index









Create a Custom Javascript/AJAX Widget with Dojo

Last updated: 2007-09-18

AJAX has been a buzzword for at least a couple of years now. And as expected, there have been numerous JavaScript toolkits developed to aid in the development of AJAX-y websites. Awhile ago, I set out to choose my own toolkit of choice. I soon found myself choosing between Prototype/Scriptaculous, and Dojo. As a Java Swing developer, one thing I love to do is to create my own UI widgets, and mix them in with standard Swing widgets. Dojo, I noticed, provides hooks to perform similar feats. So I settled on the Dojo toolkit.

After familiarizing myself with Dojo for awhile, I finally decided to create my own custom widget. A common complaint about Dojo currently is its lack of documentation. The subject of creating a custom Dojo widget provides no exception. So I thought I would track the steps I took in creating my own widget in this article.


The strength indicator, rendered with the password field.

How strong is your password?

The widget I'll develop in this article is a password strength indicator. This widget will be designed to work in conjunction with a password field; specifically, a password field in which a user is creating a new password for his or her online account. When the user enters a new password into the password field, the strength indicator widget will provide feedback to the user on how secure the password is. The widget will consist of a label and a graphical status bar. The label will display a textual description to the user (e.g. "Weak"), while the status bar will provide visual feedback (e.g. an insecure password would be represented by a short, red status bar, while a secure one might be represented by a long, green bar). The widget will also contain a text element designed to present advice to the user to increase their password's strength (e.g. "The password needs to be longer").

Furthermore, since we're talking about AJAX here, the password-strength logic will reside on the server; as the user enters a new password, that password will be submitted to the server, which will in turn evaluate the strength of the password and pass data back to the client, for rendering by our strength indicator.

In my implementation, I included a few other widgets as well. First, as with most account-creation forms, I included both a create-a-password field and a confirm-the-password field to ensure that users don't mistype their created password (a mistake that, owing to the password field's obfuscated characters, the user would likely not detect). I also included a checkbox with which the user could toggle the functionality provided by the strength indicator. There are a few other things to note within this article. First, I assume some familiarity with using Dojo, and JavaScript in general. Second, although this article describes the creation of a password strength indicator, I won't spend much time on the actual algorithm used by the server to determine password strength, nor do I assert that the algorithm I use is by any means the best possible implementation.


The filesystem structure of the StrengthIndicator widget, as viewed within Eclipse.

The basic parts

Any dojo widget consists of three basic parts: a Javascript component, an HTML component, and a CSS component. This separation of concerns will seem somewhat familiar to developers accustomed to Model-View-Controller patterns. The dojo pattern might be described as Structure-Logic-Style. In addition to providing any necessary logic, the JavaScript component is where the widget as a whole is configured. I named my widget StrengthIndicator. So, in following Dojo's naming conventions, my widget's three components were named, respectively, StrengthIndicator.js, StrengthIndicator-template.html, and StrengthIndicator-style.css.

So where should you place these components? It's easiest to create a directory, named after yourself, your organization, etc, at the same level as your Dojo root directory. Then, within that directory, create a subdirectory called widget. In my case, the site I was working on was for SharedPlan. All of my JavaScript code was located in jsp/js, my dojo root directory being jsp/js/dojo. Therefore, I created jsp/js/sharedplan/widget. Inside that widget directory, I placed the StrengthIndicator.js file. Generally, the HTML and CSS files should be placed into a sub-sub-directory called template. Therefore, I created jsp/js/sharedplan/widget/template.and in there placed StrengthIndicator-template.html and StrengthIndicator-style.css.

Finally, you'll need one more file called __package__.js within your jsp/js/sharedplan/widget/template directory. This file will make all of your custom widgets available for use within your Dojo-based application.

StrengthIndicator-template.html

Logically (to me, anyway) it makes sense to start with the HTML component. So let's look at that code first:

<div class="strength-outer" dojoAttachPoint="mainDiv">
	<b>Strength</b>: <span id="description" dojoAttachPoint="description">${this.description}</span>
	<div class="colorbar-outer"><div id="colorBar" dojoAttachPoint="colorBar"></div></div>
	<div dojoAttachPoint="advice"></div>
</div>
Contents of StrengthIndicator-template.html

This code should, for the most part, look fairly straightforward. The entire widget is surrounded by a div whose ID is strength-outer. A span by the ID of description exists to display a message to the user. The aforementioned "progress bar" is formed by a div belonging to the class colorbar-outer (in order to provide a border around the status bar). Just inside that div is another div, this one with the ID of colorBar. This is the actual div that will vary in length and color in order to provide visual feedback to the user regarding their password's strength. Finally, at the bottom of the main div is another div; this one will display advice to the user when his or her password is judged to be weak.

While most of the HTML should look familiar to you, there are a few dojo-specific pieces. For example, many tags contains an attribute called dojoAttachPoint. The value of these attributes will come into play later as we look at the JavaScript file. In general terms, they provide a mechanism by which to refer to the specific HTML component from within the JavaScript code. You'll also notice the ${...} variable-replacement notation. This essentially works the opposite as the dojoAttachPoint attribute, in that it allows the HTML components to refer to variables defined within the JavaScript file. Again, we'll explore this more as we look at the JavaScript file.

StrengthIndicator-style.css

Next, let's look at the CSS file:

.strength-outer {
	background-color: #FFFFFF;
	border: 1px #333333 solid;
	padding: 5px;
}
.colorbar-outer {
	border: 1px #000000 solid;
	margin: 5px;
	width:200px;
	height:15px;
}
#colorBar {
	width:0%;
	height:100%;
	background-color: #999999;
}
Contents of StrengthIndicator-style.css

Again, there should be no surprises here for anyone accustomed to Cascading Style Sheets. The borders and paddings are defined for the divs whose classes are strength-outer and colorbar-outer. The latter's margin is also set; its height is as well, since it contains no content and would otherwise default to a height of zero. Finally, div representing the color bar (with the ID of colorBar) is given an initial width of 0%, a height of 100% (to ensure that it fills up the entire 15px height of its parent's container, the div of class colorbar-outer) and a neutral starting color of mid-grey. Note that the width and background color will change as visual indicators.

StrengthIndicator.js

Now, let's look at the JavaScript file:

dojo.provide("sharedplan.widget.StrengthIndicator");

dojo.require("dojo.widget.Parse");
dojo.require("dojo.widget.HtmlWidget");

dojo.widget.defineWidget(
    // widget name and class
    "sharedplan.widget.StrengthIndicator",
    // superclass
    dojo.widget.HtmlWidget,
    function() { },
    {
        percent: 0,
        description: "0%",
        color: "#000000",
        templatePath: dojo.uri.dojoUri("../sharedplan/widget/template/StrengthIndicator-template.html"),
        templateCssPath: dojo.uri.dojoUri("../sharedplan/widget/template/StrengthIndicator-style.css"),
        isContainer: false,
        snarfChildDomOutput: false,
		setPercent: function(pct) {
			// summary: sets the "strength" percentage, and updates the display acordingly
			this.percent = pct;
			this.colorBar.style.width = pct + "%";
			if (pct < 40) {
				this.colorBar.style.backgroundColor = "red";
				this.description.innerHTML = "very weak";
			} else if (pct < 55) {
				this.colorBar.style.backgroundColor = "orange";
				this.description.innerHTML = "weak";
			} else if (pct < 70) {
				this.colorBar.style.backgroundColor = "yellow";
				this.description.innerHTML = "questionable";
			} else if (pct <= 90) {
				this.colorBar.style.backgroundColor = "green";
				this.description.innerHTML = "acceptable";
			} else {
				this.colorBar.style.backgroundColor = "blue";
				this.description.innerHTML = "strong";
			}
		},
		setAdvice: function(advice) {
			this.advice.innerHTML = advice;
		},
		setVisibility: function(b) {
			this.mainDiv.style.display = (b) ? "block" : "none";
		}
    }
);
Contents of StrengthIndicator.js

The first lines, dojo.provide("sharedplan.widget.StrengthIndicator");, ensures that the totality of our HTML, CSS and JavaScript code will be available as a widget called StrengthIndicator. dojo.require(...) calls include the Dojo code that our widget requires. dojo.widget.HtmlWidget is required, for example, because the StrengthIndicator widget subclasses it (meaning that it inherits all of the functionality of HtmlWidget, and then adds its own functionality on top.) Finally, we call the dojo.widget.defineWidget method. Although the listing looks fairly daunting, we in fact are merely passing four items to dojo.widget.defineWidget:

  1. The fully-qualified name of the widget. Fully-qualified in this case means that in addition to the name of the widget, we should also include, using dot-notation, the path to our JavaScript file starting from the directory that contains the Dojo root directory.
  2. The superclass of the widget
  3. An initializer function, that will run whenever an instance of the widget is created. In the case of StrengthIndicator, we don't need any initializer to run, so we simply pass an empty function.
  4. An array of properties. These properties—which can be of any type, including functions—are attached to the widget.

The properties mentioned above can be referenced using ${...} notation from within the HTML file. For instance, StrengthIndicator-template.html contained this bit: <span id="description" dojoAttachPoint="description">${this.description}</span>. That will cause the value of description, which is initially set to "0%", to appear within the span of id description. Note that the description property is referred to as this.description, and not merely description. The properties also tend to be a mix of properties that Dojo expects, and those that are specific to your widget. In the case of StrengthIndicator.js, the following properties have meaning to Dojo:

  • templatePath: This is the relative path to the HTML file
  • templateCssPath: This is the relative path to the CSS file
  • isContainer: This tells Dojo whether the widget itself is designed to contain other widgets (e.g. in the case of a panel or popup dialog)
  • snarfChildDomOutput: This is relevant only with isContainer is true; it helps prevent issues when children nodes are not logical DOM nodes within the DOM node represented by the custom widget

StrengthIndicator also contains its own basic properties:

  • percent: A numeric representation of the strength of the current password, where 0 is weakest and 100 is strongest.
  • description: A textual representation of the strength of the current password, e.g. "Weak".
  • color: The visual, color-based representation of the strength of the current password, where the "warmer" the color (e.g. red), the weaker the password.

In addition, three functions are attached to the widget:

  • setPercent(percent): This method expects a number between 0 and 100 to be passed to it; sets the strength percent field accordingly, and adjusts the widget's UI to represent the new value.
  • setAdvice(advice): This method expects a string to be passed to it; sets the advice text to the given value.
  • setVisibility(b): This method expects a boolean to be passed to it; sets the widget to be visible or invisible depending on whether true or false is passed.
It is within those functions that the dojoAttachPoint attribute, seen in the HTML file within some tags, comes into play. Any element in the HTML page containing a dojoAttachPoint attribute can be referenced from within the JavaScript file, and hence, manipulated with functions. For example, StrengthIndicator-template.html defined this element: <div dojoAttachPoint="advice"></div> It can subsequently be referenced within the JavaScript file as this.advice. The setAdvice() function does this via: this.advice.innerHTML = advice;

__package__.js

Finally, we need to configure __package__.js:

dojo.kwCompoundRequire({
    common: [
        "sharedplan.widget.StrengthIndicator"
    ],
    browser: [    ]
});

dojo.provide("sharedplan.widget.*");
Contents of __package__.js

kwCompoundRequire() defines the modules; in this case, the StrengthIndicator is assigned to the common host environment. The dojo.provide() call then makes the module available for use.

Using the StrengthIndicator

That's really all there is to creating a custom Dojo widget. So, how do we use the widget in an HTML page? The same way we would any other Dojo widget; first, we include the widget code at the top of the page:

<script language="javascript">
// ...
dojo.require("sharedplan.widget.StrengthIndicator");
// ...
</script>

Then, we simply create a div and add a dojoType attribute:
<div dojoType="sharedplan:StrengthIndicator" id="strind"></div>

Now, the StrengthIndicator is ready for use. In my implementation, I hooked up the password field to a method called testPwdStrength() using dojo.event.connect:

	var pwdFormObj = document.forms["createform"];
	var pwdFieldObj = pwdFormObj.elements['password'];
	dojo.event.connect("around", pwdFieldObj, "onblur", "testPwdStrength");

Thus, whenever the user exits (blurs the focus of) the password field, the testPwdStrength() function will be invoked. The testPwdStrength() function, in turn, looks like this:
function testPwdStrength() {
	var formObj = document.forms["createform"];
	var cb = document.getElementById('doPwdStr').checked;
	if (cb == false) { return; }
	var pwd = formObj.password.value;
	if (pwd == "") { return; }
	var u = "Dispatcher?action=testPassword&password="+escape(pwd);
	var uname = formObj.username.value;
	var fname = formObj.firstname.value;
	var lname = formObj.lastname.value;
	if (uname && uname != "") { u += "&username=" + uname; }
	if (fname && fname != "") { u += "&firstname=" + fname; }
	if (lname && lname != "") { u += "&lastname=" + lname; }
	var bindArgs = {
		url: u,
		error: function(type, data, xhro) {
			if (data.message) { data = data.message; }
			alert("An error occurred. " + data + "..." + xhro);
		},
		load: function(type, data, xhro) {
			var xmldoc = xhro.responseXML;
			var scoreNode = xmldoc.getElementsByTagName('score');
			var score;
			var adviceNode = xmldoc.getElementsByTagName('advice');
			var advice;
			if (scoreNode.length > 0 && scoreNode[0].childNodes.length > 0) {
				score = parseFloat(scoreNode[0].childNodes[0].nodeValue);
			}
			if (adviceNode.length > 0 && adviceNode[0].childNodes.length > 0) {
				advice = adviceNode[0].childNodes[0].nodeValue;
			}

			var strind = dojo.widget.byId('strind');
			if (strind) {
				var percent = parseInt(score * 100);
				strind.setPercent(percent);
				if (advice) { strind.setAdvice(advice); }
			}


		},
		mimetype: "text/xml"
	};
	dojo.io.bind(bindArgs);
	return false;
}

The first portion of the function gathers up values from the form. It checks whether the the user has toggled the widget off, or the password field is empty; if either is true, the function simply returns. Otherwise, it begins constructing the URL to which to send the AJAX request. It gathers up values from the username, firstname, and lastname form fields (which the serverside algorithm will use). Then, it creates an array of arguments to pass to the dojo.io.bind() method: the url argument is the newly-contructed AJAX URL; the error argument is a function to be called if an error occurs during the AJAX call; the load argument is the callback function for successful AJAX calls; the mimetype argument is set to "text/xml" to tell the server that we want to receive our requested data in XML format. Finally, we invoke dojo.io.bind() and pass the newly-created argument array, in order to invoke the AJAX call.

The interesting part is the content of the load callback function. This function expects the AJAX call to return XML in a format like the following:

<?xml version="1.0" encoding="UTF-8"?>
<result>
	<score>0.25714287</score>
	<advice>Don't use a dictionary word<br/>
	The password needs to be longer<br/>
	Include more uppercase letters<br/>
	Include more numbers or special characters<br/>
	</advice>
</result>
The score tag contains a floating-point number, between 0 and 1, which will be converted to the strength percentage and description. The advice tag contains any advice to the user on making a weak password more secure. The load function parses those values out of the XML DOM, converting the score value to an actual floating point number. It then locates the StrengthIndicator widget by its ID and, if it's found, sets the widget's percent and advice values. The widget will then automatically update.

Conclusion

And that's all there is to creating a custom Dojo widget. Of course, the nitty-gritty complexity of creating a Dojo widget may vary, depending on the task at hand. But overall, any widget will simply require these steps:

  • Create these three files: MyWidget.js, MyWidget-template.html, and MyWidget-style.css.
  • Place the .js file in path_to_root_js_directory/myname/widget. Place the HTML and CSS files in path_to_root_js_directory/myname/widget/template
  • Create one __package__.js file for all of the widgets you might be creating, and place it in path_to_root_js_directory/myname/widget.
  • Within the HTML page in which you'd like to use your widget, import your code via: dojo.require("myname.widget.MyWidget");
  • Then, place a suitable element within the page, and give it a dojoType that references your new widget: <div dojoType="myname:MyWidget" id="mywidget1"></div>





Privacy Policy: Any information collected, whether from online help, registration, or any other means, will be used only for support purposes. Such uses are: to send customers registration information, to verify that a user is registered in order to provide support, and to notify the user if an important or useful bug fix or upgrade has been made. Under no circumstances will customer information be sold or otherwise given to any other party, person, or organization. We're in the business to develop software, not sell people's information.