Menu

Prototype: Easing AJAX's Pain

April 5, 2006

Bruce Perry

This article describes Prototype, an open source JavaScript library to create an object for an AJAX application. I explain how to use Prototype by describing an environmentally oriented web application that displays an annual atmospheric carbon dioxide (CO2) level. First, I will discuss Prototype's benefits and describe how to set up Prototype in your application. Second, I will delve into the nitty-gritty of how this application puts the library to good practical use.

Why Prototype?

Why didn't I just create a plain old JavaScript object (POJO) for my application, instead of introducing an open source library? For one, Prototype includes a nifty collection of JavaScript shortcuts that reduce typing and help avoid the reinvention of the wheel. The commonly touted shortcut is $("mydiv"), which is a Prototype function that returns a Document Object Model (DOM) Element associated with the HTML tag with id "mydiv". That sort of concision alone is probably worth the cost of setting up Prototype. It's the equivalent of:

document.getElementById("mydiv");

Another useful Prototype shortcut is $F("mySelect"), for returning the value of an HTML form element on a web page, such as a selection list. Once you get used to Prototype's austere, Perlish syntax, you will use these shortcuts all the time. Prototype also contains numerous custom objects, methods, and extensions to built-in JavaScript objects, such as the Enumeration and Hash objects (which I discuss below).

Finally, Prototype also wraps the functionality of XMLHttpRequest with its own Ajax.Request and related objects, so that you don't have to bother with writing code for instantiating this object for various browsers.

Setting Up

So how do you set up Prototype? First, download it from prototype.conio.net. Prototype is open source and available under an MIT-style license. The download includes a file named prototype.js. This is a JavaScript file that defines the various functions and objects your application will use. Add the prototype.js file to your application by using a script tag in the HTML code.

<head>...

<script src="js/prototype.js" type="text/javascript">

</script>

<script src="js/co2.js" type="text/javascript">

</script>

<script src="js/eevpapp.js" type="text/javascript">

</script>...

</head>

The same directory that holds this application's HTML file contains a directory named js. This js directory contains prototype.js, an object definition in the file co2.js, which this article describes, as well as the rest of the application's client-side code in another file: eevapp.js.

Since the application imports the Prototype file, the JavaScript code in the other imported .js files can use Prototype's objects and extensions as if they were declared and defined locally. JavaScript does not require statements such as import or require to use objects from other JavaScript files that the browser commonly imports.

Climate Change

How is Prototype going to help with our application's requirements? What are the requirements?

The User Interface is a web browser, such as Safari 1.3, Firefox 1.5, Opera 8.5, or Internet Explorer 6. The application is designed to display a small data set of annual atmospheric carbon dioxide (CO2) levels. This is a number, like 377, that represents the parts-per-million CO2 level in the air for a specific year. I decided to store these years and numbers in a JavaScript object that the browser downloads with the application. It is not necessary to use a database to store this data, which is small in size and needs neither security nor authentication. The data is designed for consumption by the interested public. The application does, however, give this JavaScript object the ability to add new data on the fly when necessary.

CO2 Applet

Figure 1-1 shows the browser screen for the application. In the upper left corner is an applet that displays the CO2 level in the atmosphere when the user chooses a year.

Figure 1-1
Figure 1-1. The select tag shows CO2 level--click for full-size image.

This data is derived from the Mauna Loa Observatory, run by the U.S. government in Hawaii. The measurements are taken at high altitude in pristine conditions. Scientists throughout the world use the data in their climate-change research.

There are only 46 different annual levels, associated with the years 1959-2004 (the 2005 level is not available at this time). Therefore, it makes sense to store this information in a JavaScript object. Even if we choose to display the monthly levels associated with those years with this tool (over 500 separate numbers), it still might make sense to store the data on the client. No database or extra server hits are required.

Where Prototype Comes In

The eevapp.js file contains the code that executes when the user selects a year in the selection list widget.

//instantiate an object 

//defined in the co2.js file 

var co2lev = new CO2Levels();

//the onload event handler executes

//when the browser is finished loading the page.

window.onload=function(){

 

$("co2_select").onchange=function(){

    $("co2ppm").innerHTML= 

    co2lev.getYear($F("co2_select")); 

    }



};

The value "co2_select" is the id value of the selection list that the user clicks to choose a year.

<select id="co2_select" .../>

The code:

 $("co2_select")

returns a DOM Element reference, the equivalent of document.getElementById("co2_select");.

The code sets the onchange event-handler attribute of the selection list element to a JavaScript function, as in: $("co2_select").onchange=function(){...}.

In simpler terms, when the user chooses a year in the select tag, the browser executes the defined function. What does this function do? The function gets a reference to an HTML div element with the id "co2ppm", once again, using Prototype's shortcut. The innerHTML property of this div element, representing what the user sees in the browser, is set to the return value of a method call:

co2lev.getYear($F("co2_select"));

The object named co2lev is instantiated at the top level of the eevapp.js file.

var co2lev = new CO2Levels();

This object has a method called getYear(). This method takes a string representing a year as a parameter, such as "1999". The method returns the CO2 level associated with that year.

Pithy Syntax

That dollar sign pops up in the syntax again: $F("co2_select"). The $F() Prototype function returns the value of an HTML form element, in this case the selection list, when I pass its id into the method as a parameter. The application uses this function to change the displayed CO2 level each time a user chooses a different year.

One tip to remember with $F(): it won't return a value from a selection list unless the list's child option elements have value attributes, as in: <option value="1959">1959</option>. In other words, at least the Prototype version I was using (1.4.0) would not return a value with $F() if I left out the value="..." part.

Objective View

The CO2Levels object definition appears in a different JavaScript file, aptly named co2.js. Figure 1-2 shows a UML class diagram describing the object.

UML class diagram
Figure 1-2. UML class diagram

Here is the entire code for the CO2Levels object. The first line instantiates a new object, using syntax derived from Prototype. The levels local variable is an object, like an associative array, that links years with their CO2 levels. I have omitted most of the years for the sake of readability.

var CO2Levels=Class.create();

CO2Levels.prototype = {

/*

Source: http://cdiac.esd.ornl.gov/ftp/trends/co2/maunaloa.co2

Mauna Loa Observatory, Hawaii

*/

initialize: function(){

    this.levels={ "1959":315.98,"1960":316.91,

    "1961":317.65,"1962":318.45,

    "2003":375.64,"2004":377.38};

    this.levelsHash=$H(this.levels);

},

    

getYear: function(year){

    if (! isNaN(year)) {

        return this.levelsHash[year];

    }   else {

        return 377;

    }

},

    

keys: function(){

    return  this.levelsHash.keys();

},

    

values: function(){

    return  this.levelsHash.values();

},



inspect: function(){

    alert( this.levelsHash.inspect());

},



add: function(year,level){

    var tmp = new Object();

    tmp[year] = level;

    this.levelsHash=this.levelsHash.merge(tmp);

}



}

Creating Prototype Objects

Using the Class.create() method in Prototype returns a JavaScript object that automatically provides new instances of this object with an initialize() method. This is similar to a constructor method, such as in Java. The initialize() method will be called each time the code creates a new CO2Levels object. The rest of the code defines the prototype, or blueprint, for this CO2Levels object, including the behavior for its initialize() method. What does initialize() do? It creates a local variable called levels, which refers to an object that holds all of the data: the CO2 levels associated with their years. The code then converts this object to a Prototype Hash object, to provide more functionality for the object (such as the ability to view the object contents and dynamically add new data).

this.levelsHash=$H(this.levels);

Prototype Hash Object

There's that syntax again: $H(). This function takes a JavaScript object as its parameter and returns Prototype's Hash object. Similar to hash table structures in other languages, the Hash has an associative array structure, along with with several methods that are designed to manipulate its data, as well as add new data to the Hash.

For example, the Hash.keys() method returns an array of all of the Hash's keys (such as all of the years in our data). The values() method returns an array of values (the CO2 levels). The merge() method adds new keys and values to the Hash.

Delegator

Our own CO2Levels object uses the concept of delegation, wherein calls to its own keys(), values(), and add() methods delegate the real work of these operations to the internal Hash object. This object is stored as a local variable: levelsHash.

Let's look at the getYear() method.

getYear: function(year){

    if (! isNaN(year)) {

        return this.levelsHash[year];

    }   else {

        return 377;

    }

}

The built-in JavaScript method isNaN() returns false if its parameter can be evaluated as a number (such as "2000"), and true otherwise (as in isNaN("hello")). If the getYear() parameter passes this test, then the code uses a common JavaScript expression to return the value of a key or property: this.levelsHash[year] (for example, this.levelsHash["2004"] evaluates to 377.38). The browser then displays this numerical value inside the HTML div element.

Add Stuff to an Existing Hash

The CO2Levels object has an add() method, which can add new keys and values (additional years and CO2 levels) to the existing data.

add: function(year,level){

    var tmp = new Object();

    tmp[year] = level;

    this.levelsHash=this.levelsHash.merge(tmp);

}

This method creates a new Object from its two parameters (representing the year and CO2 level), which might look like: {"2005":381}

The code then passes this object to the Prototype Hash object's merge() method. This method combines the new object with the Hash's existing data, essentially merging them into one group of data or associative array.

The merge() method returns the existing data with the new property/value pair(s) appended to the end.

With some refactoring, the code could use XMLHttpRequest to fetch any new levels from the Mauna Loa Observatory, then add them to our existing client-side data.

Inspect

Finally, Prototype's Hash object also has an inspect() method. This method creates a readable display of the Hash's contents, as in Figure 1-3.

alert window shwing hash contents
Figure 1-3. Looking inside a Hash

The CO2Levels object delegates the task of its own inspect() method to its internal Prototype Hash object.

inspect: function(){

    alert( this.levelsHash.inspect());

}

This is a useful debugging tool for viewing the current contents of a Hash object.

An upcoming article discusses an AJAX caching strategy used with the same application. It introduces Prototype's Ajax.Request object, which reduces the amount of code that has to be devoted to using the XMLHttpRequest object. This is the important top-level JavaScript object used in AJAX applications for making HTTP connections with a server behind the scenes.