Skip to main content

Using Dojo and JSON to Build Ajax Applications

April 27, 2006

{cs.r.title}







"Ajax" is a buzzword that is gaining momentum and popularity. Applications such as Gmail and Google Suggest serve as massive advertisements for asynchronous JavaScript. As users gain familiarity and their expectations increase, the demand for such interfaces is rising.

In this article, I will show how to build Ajax-enabled applications using Dojo and JSON--two very different but complementary technologies that can significantly enhance the interface and usability of web applications. With the help of some simple examples, you will learn how to use Dojo and JSON to build Ajax applications. This article will also demonstrate how to create custom Dojo widgets by implementing the popular "autocomplete" feature for HTML text boxes.

Overview

Ajax in the simplest sense is taken to mean the process of exchanging data between a web server and a web browser using JavaScript on a loaded web page. In the practical sense, it means avoiding browser requests in favor of asynchronous JavaScript processing, thus making the request/response process transparent to the user. To accomplish this, we can either write our own verbose JavaScript code or use already tested and functional libraries such as Dojo.

What Is Dojo?

Dojo is a set of powerful JavaScript libraries that provide a simple API to a plethora of features. One of these features is the ability to make HTTP requests and receive their responses. This is the main functionality of Dojo that we will utilize. Aside from providing Ajax functionality, Dojo also provides packages for string manipulation, DOM manipulation, drag-and-drop support, and data structures such as lists, queues, and stacks.

What Is JSON?

JSON is a Java library that helps convert Java objects into a string representation. This string, when eval()ed in JavaScript, produces an array that contains all of the information that the Java object contained. JSON's object notation grammar is suitable for encoding many nested object structures. Since this grammar is much smaller than its XML counterpart, and given the convenience of the eval() function, it is an ideal choice for fast and efficient data transport between browser and server.

JSON is designed to be used in conjunction with JavaScript code making HTTP requests. Since server-side code can be written in a variety of languages, JSON is available in many different languages such as C#, Python, PHP, and of course, Java!

Getting Started

Download the sample application and do an ant war to generate dojo_json.war. You will need a servlet container like Apache Tomcat. Copy dojo_json.war into the webapps directory and, assuming you're running Tomcat on port 8080 on localhost, browse to the following URL:

http://localhost:8080/dojo_json/index.jsp

If you see a page with a welcome message, all is well.

A Simple HTTP Request

In the first example, we will use Dojo to provide a welcome message when the user clicks on a button. This will illustrate the steps involved with making an Ajax call using Dojo. Browse to http://localhost:8080/dojo_json/example1.jsp and after entering your name, click the Submit button. The JavaScript alert box that pops up contains a welcome message that was generated on the server. Let's look at the source for example1.jsp.

<html>
<body onLoad="onLoad();">
<head>
<title>Example 1</title>
<script language="javascript" src="js/dojo.js"></script>
<script language="javascript">
  dojo.require("dojo.io.*");
  dojo.require("dojo.event.*");

  function onLoad() {
   var buttonObj = document.getElementById("myButton");
   dojo.event.connect(buttonObj, "onclick",
          this, "onclick_myButton");

  }

  function onclick_myButton() {
   var bindArgs = {
    url: "welcome.jsp",
    error: function(type, data, evt){
     alert("An error occurred.");
    },
    load: function(type, data, evt){
     alert(data);
    },
    mimetype: "text/plain",
    formNode: document.getElementById("myForm")
   };
   dojo.io.bind(bindArgs);
  }
</script>
</head>
<body>
<form id="myForm">
<input type="text" name="name"/>
<input type="button" id="myButton" value="Submit"/>
</form>
</body>
</html>

The first order of business is to import the Dojo libraries, namely dojo.js. This contains all of the Dojo classes needed to use most of its features. Dojo libraries are organized in packages just like Java code. For this example, we will need to import two packages. The dojo.io package contains classes that allow us to make HTTP requests using protocols such as XMLHTTPTransport. The dojo.event package is designed to provide a unified event system for DOM and programmatic events.

Next, we assign an onclick event handler to our button. Traditionally, we might do something like:

<input type="submit" onclick="someFunc();"/>

which serves its purpose but has the nasty side effect of associating only one event handler per event. The dojo.event.connect() method allows us to associate functions in different classes to HTML elements. Here, we assign onclick_myButton as the handler for the onclick event for myButton.

Once onclick_myButton executes, we will make the call to welcome.jsp and receive the response. The magical dojo.io.bind() function is where the power lies. It takes as argument bindArgs, an array of name/value pairs. In this example, we specify five pairs:

  1. url: The URL to make the request to.
  2. mimetype: The response type expected.
  3. load: Code to execute upon success.
  4. error: Code to execute upon error.
  5. formNode: The ID of the form whose fields to submit as parameters to the URL.

A complete list of possible arguments can be found here.

Once the call to dojo.io.bind(bindArgs) is made, the wheels are set in motion and, depending on whether the request encountered any errors, either the load or error code is executed. Both load and error take three arguments:

  1. type: The type of function; it will always be set to load for load() and error for error().
  2. data: The response received. If mimetype is specified as text/plain, data contains the raw response. If, however, text/json is used, data contains the value of eval('(' + responseReceived + ')'), where responseReceived is what the call returned.
  3. evt: The event object.

Alternately, we could have also specified event handlers using dojo.event.connect() by creating a handleResponse(type,data,evt) function and associating it with the request:

var req = dojo.io.bind(bindArgs);
dojo.event.connect(req, "load", this, "handleResponse");

In this case, we wouldn't need to specify the load attribute in bindArgs.

Note: For security reasons, Firefox does not allow the value for url in bindArgs to reference a URL on another domain. Internet Explorer is more lenient and allows cross-domain requests.

Using JSON to Transport Java Objects

As alluded to earlier, JSON helps us transform Java objects into eval()able JavaScript strings. In this example, the user is viewing a list of books. When the user's mouse hovers over a book title, we also retrieve and display all other relevant information about the book using Ajax. Example 2, found at http://localhost:8080/dojo_json/example2.jsp, illustrates this behavior.

Let us take a closer look at the steps involved in achieving this effect.

Creating and Instrumenting the Java Objects

The Book class is a POJO with four fields. The BookManager has a method that returns a Book object based on a bookId and another that returns List of all books. In this simple example, I've hardcoded five books into the BookManager.

The Book.toJSONString() method is responsible for converting a Book object into a JavaScript eval()able String using the JSON API.

public String toJSONString() throws JSONException {
JSONObject jsonObj = new JSONObject();
jsonObj.put("bookId", new Integer(this.bookId));
jsonObj.put("title", this.title);
jsonObj.put("isbn", this.isbn);
jsonObj.put("author", this.author);
return jsonObj.toString();
}

The JSONObject class acts as a converter for the Book object into a string wrapped in curly braces, with colons between the names and values and commas between the values and names--in other words, an eval()able JavaScript string. In a similar manner as shown above, the JSONArray class can convert collections of Java objects into JavaScript arrays.

Transferring and Handling JSON-Encoded Data

Based on a bookId, the book.jsp page returns a Book object in the form of a JSON string. The JSP does a lookup for the bookId using the BookManager.getBook(bookId) method and prints out the results of the Book.toJSONString() method. So accessing http://localhost:8080/dojo_json/book.jsp?bookId=1 will print out the JSON string for the Book with the bookId value of 1.

Back on the client side, book information is displayed in a list. Each element in the list has a mouseover (trMouseOver) and mouseout (trMouseOut) event handler attached to its <tr>. On each mouseover, we call book.jsp and pass in the bookId as a parameter. The resulting data will be the JSON String for that Book. We have specified the mime-type as text/json so that the returned value is automatically eval()ed. Let's look at the JavaScript code responsible for sending the request and processing the response:

function trMouseOver(bookId) {
getBookInfo(bookId);
}

function trMouseOut(evt) {
var bookDiv = document.getElementById("bookInfo");
bookDiv.style.display = "none";
}

function getBookInfo(bookId) {
var params = new Array();
params['bookId'] = bookId;
var bindArgs = {
  url: "book.jsp",
  error: function(type, data, evt){
          alert("error");
         },
  mimetype: "text/json",
  content: params
};
var req = dojo.io.bind(bindArgs);
dojo.event.connect(req, "load", this, "populateDiv");
}

function populateDiv(type, data, evt) {
var bookDiv = document.getElementById("bookInfo");
if (!data) {
  bookDiv.style.display = "none";
} else {
  bookDiv.innerHTML = "ISBN: " + data.isbn +
                "<br/>Author: " + data.author;
  bookDiv.style.display = "";
}
}

Another subtle difference in this example is the use of the content property of bindArgs instead of formNode. The content attribute is a key/value mapping of properties to be constructed into parameters passed with the data request.

Once data is transformed into a JavaScript object, we can easily access properties of a Book and display it to the user in a <div> element, as shown above.

Advanced Dojo: Creating an Autocompletion Widget

The two examples seen above cover the core of most Ajax applications: exchanging data with a web server using JavaScript. Once the basics are understood, it is up to the developer to deliver powerful and useful UIs using Ajax tools such as Dojo. Dojo's extensible framework for widget and module development can greatly ease this process. In this section, we will create a custom widget that will allow us to offer autocompletion for our form fields.

What Is a Dojo Widget?

In the general sense, widget is a name given to a UI element such as a button, text box, scroll bar, etc. When creating a Dojo widget, we have the entire HTML language at our disposal and can create widgets that have multiple input elements, custom styles, and more. You may define event handlers for your widgets, customize their behavior, and even reuse them when creating other widgets. In this example, we will create a widget that will provide an autocompletion feature for any text box in an HTML form.

Components of a Widget

When developing widgets, the developer must decide which UI elements the widget has and how they will work. When using Dojo, this means deciding on what HTML elements to use and writing the JavaScript code to make these elements behave as needed. The autocomplete example has four files associated with it:

  • dojo.js: All required Dojo libraries.

  • js/dojo/utils/templates/AutoComplete.html: Contains UI elements, including HTML tags such as <div> and <input>. The template file can contain any valid HTML and is bound by the single requirement that it contain one root element. If more than one element is found at the top level, the first one is considered the root.

  • js/dojo/utils/AutoComplete.js: JavaScript code for the widget. Usually a class extending one of the Dojo Widget classes.

  • example3.jsp: The file where the widget is used. Usually contains a tag exported by the widget.

Learning and understanding how UI elements can be accessed and manipulated using JavaScript is the key to creating custom widgets. Once Ajax functionality is baked into the JavaScript, creating rich UIs becomes a task of creativity rather than difficulty.

Accessing UI Elements

As mentioned above, specifying a root element of the UI is mandatory. In the autocomplete example, the root element is a <div>. This element can be accessed in the AutoComplete class using this.domNode. The variable this.domNode stores a reference to the HTML object, so code like the following is possible:

this.domNode.style.display = "none";

To access any other element of the UI from the JavaScript, you may traverse this.domNode until you find what you're looking for, or instead use the dojoAttachPoint property.

<input type="text" name="someTextField" dojoAttachPoint="myTextField"/>

If the above has been defined in the HTML file, it can be accessed using this.myTextField in the JavaScript. Like this.domNode, this.myTextField is also a reference to the HTML object.

Dojo makes it easier to attach multiple event handlers to UI elements, both using JavaScript and element attributes. As illustrated earlier, to programatically attach an event handler to an element, we can use dojo.event.connect():

dojo.event.connect(this.domNode, "onclick", this, "myFunction")

The above will attach an onclick event handler myFunction() from the current class to the root element of the UI. Alternately, we may also specify this in the HTML code for the <div>:

<div dojoAttachEvent="onclick: myFunction">

<div dojoAttachEvent="onclick, onmouseover: myFunction">
Programming the Widget

Now that there is a way to access and manipulate the UI and detect all types of user actions, all that's left is writing the JavaScript code to make the widget behave as desired. Each widget is backed by a corresponding class that has access to its UI elements and is responsible for responding to user actions. The following snippet of the JavaScript code from the AutoComplete class is enough to illustrate the skeleton of the widget.

dojo.provide("utils.AutoComplete");

dojo.require("dojo.dom");
...

dojo.widget.tags.addParseTreeHandler("dojo:AutoComplete");

utils.AutoComplete = function() {

// call super constructor
dojo.widget.HtmlWidget.call(this);

// load template
this.templatePath =
    dojo.uri.dojoUri("utils/templates/AutoComplete.html");

this.widgetType = "AutoComplete";

// Instance variables
this.action = "";
this.formId = "";
this.form = {};
...

this.postCreate = function() {
  this.form = document.getElementById(this.formId);
  this.textbox = document.getElementById(this.textboxId);
  dojo.event.connect(this.textbox, "onkeyup",
                    this, "textboxOnKeyUp");
  ...
}

this.textboxOnKeyUp = function(evt) {
  if (this.isKey(evt.keyCode, 38, 40)) {
   this.checkUpDownArrows(evt.keyCode);
  } else {
   bindArgs = {
    url:  this.action,
    mimetype:   "text/javascript",
    formNode:   this.form
   };
   var req = dojo.io.bind(bindArgs);
   dojo.event.connect(req, "load", this, "populateChoices");
  }
}

// Handler for "load" action of dojo.io.bind() call.
this.populateChoices = function(type, data, evt) {
  ...
}
}

// define inheritance
dj.inherits(utils.AutoComplete, dojo.widget.HtmlWidget);

We have defined a JavaScript class, AutoComplete, that is the "code behind" the widget. When defining a custom widget, Dojo requires the developer to explicitly specify the class being provided using the dojo.provide() function. The inheritance is indicated using the dj.inherits() function, and is realized via the call to dojo.widget.HtmlWidget.call(this). The template file for the widget is loaded by assigning a value to the this.templatePath variable of the parent class.

Dojo widgets are used on HTML pages using custom tags. Tags can be defined when writing the associated widget class. In the above example, the exported tag is dojo:AutoComplete, which is used in the HTML file in the following manner:

<dojo:AutoComplete
action="getBooks.jsp"
textboxId="bookName"
formId="bookForm"/>

The above will output the contents of the AutoComplete.html template to the browser. Any attributes specified in the tag can be accessed using the this.<attribute> syntax in the JavaScript class.

The nature of the autocomplete is to detect user input and provide a list of possible values that the user might be trying to enter. When a user enters any input, the textboxOnKeyUp is executed, and a call to the web server is made with the user input passed through. The web server returns a list of values to be displayed to the user as choices. We display these choices to the user in a <div> tag.

Any code omitted from the listing is just JavaScript that populates the <div> tag, choices. It also detects user key strokes, such as Tab to move to the next form field and other such details.

It's important to understand the role each of these four files plays in defining and using a widget, so take a look at them and see how they fit into the big picture. The creators of Dojo have made an honest effort to separate UI, programming, and usage into distinct files and, best of all, this methodology is easily extensible when developing powerful UIs.

The autocomplete example can be seen at http://localhost:8080/dojo_json/example3.jsp.

Conclusion

Dojo is a fine choice for a toolkit when developing Ajax and other JavaScript-intensive applications. However, there are other products, such as Scriptaculous and AjaxAnywhere, that offer similar functionality and need to be considered when writing small- to mid-size applications. Dojo's usefulness lies in a core set of libraries that features not just Ajax functionality but an entire API geared towards full-scale application development.

Resources

width="1" height="1" border="0" alt=" " />
Zarar Siddiqi is an analyst for the University of Toronto.
Related Topics >> Programming   |   Web Development Tools   |