PlanetDomain News Centre - http://www.planetdomainnews.com/news
Build Your Own AJAX Web Applications Part 3/3
http://www.planetdomainnews.com/news/articles/162/1/Build-Your-Own-AJAX-Web-Applications-Part-33/Page1.html
By SitePoint Books
Published on 12/7/2006
 
This three part series will guide web developers through the exigent AJAX language. The term AJAX refers to a loose grouping of technologies that are used to create dynamic, interactive web content. At the conclusion of this series, you will be able to build your first working AJAX application. This tutorial is an excerpt from Matthew Eernisse’s new book, Build Your Own AJAX Web Applications, supplied by SitePoint.

Page 1

Build Your Own AJAX Web Applications - Part 3

Matthew EernisseBy Matthew Eernisse, SitePoint.com

Chapter 3. The "A" in AJAX
It's flying over our heads in a million pieces.

-- Willy Wonka, Willy Wonka & the Chocolate Factory

The "A" in AJAX stands for "asynchronous," and while it's not nearly as cool as the letter "X," that "A" is what makes AJAX development so powerful. As we discussed in Chapter 1, AJAX: the Overview, AJAX's ability to update sections of an interface asynchronously has given developers a much greater level of control over the interactivity of the apps we build, and a degree of power that's driving web apps into what was previously the domain of desktop applications alone.

Back in the early days of web applications, users interacted with data by filling out forms and submitting them. Then they'd wait a bit, watching their browser's "page loading" animation until a whole new page came back from the server. Each data transaction between the browser and server was large and obvious, which made it easy for users to figure out what was going on, and what state their data was in.

As AJAX-style development becomes more popular, users can expect more interactive, "snappy" user interfaces. This is a good thing for users, but presents new challenges for the developers working to deliver this increased functionality. In an AJAX application, users alter data in an ad hoc fashion, so it's easy for both the user and the application to become confused about the state of that data.

The solution to both these issues is to display the application's status, which keeps users informed about what the application is doing. This makes the application seem very responsive, and gives users important guidance about what's happening to their data. This critical part of AJAX web application development is what separates the good AJAX apps from the bad.

Planned Application Enhancements

To create a snappy user interface that keeps users well-informed of the application's status, we'll take the monitoring script we developed in the previous chapter, and add some important functionality to it. Here's what we're going to add:

  • a way for the system administrator to configure the interval between polls and the timeout threshold
  • an easy way to start and stop the monitoring process
  • a bar graph of response times for previous requests; the number of entries in the history list will be user-configurable
  • user notification when the application is in the process of making a request
  • graceful handling of request timeouts

Figure 3.1 shows what the running application will look like once we're done with all the enhancements.

The code for this application is broken up into three files: the markup in appmonitor2.html, the JavaScript code in appmonitor2.js, and the styles in appmonitor2.css. To start with, we'll link all the required files in to appmonitor2.html:

Example 3.1. appmonitor2.html (excerpt)

1542_fig3.1
Figure 3.1. The running application


Page 2
Organizing the Code

All this new functionality will add a lot more complexity to our app, so this is a good time to establish some kind of organization within our code (a much better option than leaving everything in the global scope). After all, we're building a fully functional AJAX application, so we'll want to have it organized properly.

We'll use object-oriented design principles to organize our app. And we'll start, of course, with the creation of a base class for our application -- the Monitor class.

Typically, we'd create a class in JavaScript like this:

function Monitor() {
this.firstProperty = 'foo';
this.secondProperty = true;
this.firstMethod = function() {
// Do some stuff here
};
}

This is a nice, normal constructor function, and we could easily use it to create a Monitor class (or a bunch of them if we wanted to).

Loss of Scope with setTimeout

Unfortunately, things will not be quite so easy in the case of our application. We're going to use a lot of calls to setTimeout (as well as setInterval) in our app, so the normal method of creating JavaScript classes may prove troublesome for our Monitor class.

The setTimeout function is really handy for delaying the execution of a piece of code, but it has a serious drawback: it runs that code in an execution context that's different from that of the object. (We talked a little bit about this problem, called loss of scope, in the last chapter.)

This is a problem because the object keyword this has a new meaning in the new execution context. So, when you use it within your class, it suffers from a sudden bout of amnesia -- it has no idea what it is!

This may be a bit difficult to understand; let's walk through a quick demonstration so you can actually see this annoyance in action. You might remember the ScopeTest class we looked at in the last chapter. To start with, it was a simple class with one property and one method:

function ScopeTest() {
this.message = "Greetings from ScopeTest!";
this.doTest = function() {
alert(this.message);
};
}
var test = new ScopeTest();
test.doTest();

The result of this code is the predictable JavaScript alert box with the text "Greetings from ScopeTest!"

Let's change the doTest method so that it uses setTimeout to display the message in one second's time.

function ScopeTest() {
this.message = "Greetings from ScopeTest!";
this.doTest = function() {
var onTimeout = function() {
alert(this.message);
};
setTimeout(onTimeout, 1000);
};
}
var test = new ScopeTest();
test.doTest();


Page 3

Instead of our greeting message, the alert box that results from this version of the code will read "undefined." Because we called onTimeout with setTimeout, onTimeout is run within a new execution context. In that execution context, this no longer refers to an instance of ScopeTest, so this.message has no meaning.

The simplest way to deal with this problem of loss of scope is by making the Monitor class a special kind of class, called a singleton.

Singletons with JavaScript

A "singleton" is called that because only a "single" instance of that class exists at any time. Making a class into a singleton is surprisingly easy:

var ScopeTest = new function() {
this.message = "Greetings from ScopeTest!";
this.doTest = function() {
var onTimeout = function() {
alert(this.message);
};
setTimeout(onTimeout, 1000);
};
}

Using the keyword new before function creates a "one-shot" constructor. It creates a single instance of ScopeTest, and it's done: you can't use it to create any more ScopeTest objects.

To call the doTest method of this singleton object, you must use the actual name of the class (since there's only the one instance of it):

ScopeTest.doTest();

That's all well and good, but we haven't solved our loss of scope problem. If you were to try the code now, you'd get the same "undefined" message you saw before, because this doesn't refer to an instance of ScopeTest. However, using a singleton gives us an easy way to fix the problem. All we have to do is use the actual name of the object -- instead of the keyword this -- inside onTimeout:

var ScopeTest = new function() {
this.message = "Greetings from ScopeTest!";
this.doTest = function() {
var onTimeout = function() {
alert(ScopeTest.message);
};
setTimeout(onTimeout, 1000);
};
}

There's only one instance of ScopeTest, and we're using its actual name instead of this, so there's no confusion about which instance of ScopeTest is being referred to here.

When you execute this code, you'll see the expected value of "Greetings from ScopeTest!" in the JavaScript alert box.

Now, I get tired of using the actual object name throughout my object code, and I like to use a shortcut keyword like this wherever I possibly can. So, usually I create a variable self that I can use in place of this, and point it to the object name at the top of each method, like so:

var onTimeout = function() {
var self = ScopeTest;
alert(self.message);
};

This looks a bit silly in a method that's as short as that, but in longer chunks of code it's nice to have a shorthand solution similar to this that you can use to refer to your object. I use self, but you could use me, or heyYou, or darthVader if you wanted to.

Creating the Monitor Object

Now that we have a plan for code organization that will fix the loss-of-scope problem from setTimeout, it's time to create our base Monitor class:

Example 3.2. appmonitor2.js (excerpt)

var Monitor = new function(){
this.targetURL = null;
this.pollInterval = null;
this.maxPollEntries = null;
this.timeoutThreshold = null;
this.ajax = new Ajax();
this.start = 0;
this.pollArray = [];
this.pollHand = null;
this.timeoutHand = null;
this.reqStatus = Status;
}


Page 4

The first four properties, targetURL, pollInterval, maxPollEntries, and timeoutThreshold, will be initialized as part of the class's initialization. They will take on the values defined in the application's configuration, which we'll look at in the next section.

Here's a brief rundown on the other properties:

  • ajax - The instance of our Ajax class that makes the HTTP requests to the server we're monitoring.
  • start - Used to record the time at which the last request was sent.
  • pollArray - An array [43] that holds the server response times; the constant MAX_POLL_ENTRIES determines the number of items held in this array.
  • pollHand, timeoutHand - Interval IDs returned by the setTimeout calls for two different processes -- the main polling process, and the timeout watcher, which controls a user-defined timeout period for each request.
  • reqStatus - Used for the status animation that notifies the user when a request is in progress. The code that achieved this is fairly complicated, so we'll be writing another singleton class to take care of it. The reqStatus property points to the single instance of that class.
Configuring and Initializing our Application

A webmaster looking at this application may think that it was quite cool, but one of the first things he or she would want is an easy way to configure the app's polling interval, or the time that elapses between requests the app makes to the site it's monitoring. It's easy to configure the polling interval using a global constant.

To make it very simple for any user of this script to set the polling interval, we'll put this section of the code in a script element within the head of appmonitor2.html:

Example 3.3. appmonitor2.html (excerpt)

<script type="text/javascript">
// URL to monitor
var TARGET_URL = '/fakeserver.php';
// Seconds between requests
var POLL_INTERVAL = 5;
// How many entries bars to show in the bar graph
var MAX_POLL_ENTRIES = 10;
// Seconds to wait for server response
var TIMEOUT_THRESHOLD = 10;
</script>

You'll notice that these variable names are written in all-caps. This is an indication that they should act like constants -- values that are set early in the code, and do not change as the code executes. Constants are a feature of many programming languages but, unfortunately, JavaScript is not one of them. (Newer versions of JavaScript allow you to set real constants with the constkeyword, but this facility isn't widely supported (even by many modern browsers).) Note that these constants relate directly to the first four properties of our class: targetURL, pollInterval, maxPollEntries, and timeoutThreshold. These properties will be initialized in our class's init method:

Example 3.4. appmonitor2.js (excerpt)

this.init = function() {
var self = Monitor;
self.targetURL = TARGET_URL;
self.pollInterval = POLL_INTERVAL;
self.maxPollEntries = MAX_POLL_ENTRIES;
self.timeoutThreshold = TIMEOUT_THRESHOLD;
self.toggleAppStatus(true);
self.reqStatus.init();
};

As well as initializing some of the properties of our class, the init method also calls two methods: toggleAppStatus, which is responsible for starting and stopping the polling, and the init method of the reqStatus object. reqStatus is the instance of the Status singleton class that we discussed a moment ago.

This init method is tied to the window.onload event for the page, like so:

Example 3.5. appmonitor2.js (excerpt)

window.onload = Monitor.init;


Page 5
Setting Up the UI

The first version of this application started when the page loaded, and ran until the browser window was closed. In this version, we want to give users a button that they can use to toggle the polling process on or off. The toggleAppStatus method handles this for us:

Example 3.6. appmonitor2.js (excerpt)
 
this.toggleAppStatus = function(stopped) {
  var self = Monitor;
  self.toggleButton(stopped);
  self.toggleStatusMessage(stopped);
};

Okay, so toggleAppStatus doesn't really do the work, but it calls the methods that do: toggleButton, which changes Start buttons into Stop buttons and vice versa, and toggleStatusMessage, which updates the application's status message. Let's take a closer look at each of these methods.

The toggleButton Method

This method toggles the main application between its "Stop" and "Start" states. It uses DOM-manipulation methods to create the appropriate button dynamically, assigning it the correct text and an onclick event handler:

Example 3.7. appmonitor2.js (excerpt)
 
this.toggleButton = function(stopped) {
  var self = Monitor;
  var buttonDiv = document.getElementById('buttonArea');
  var but = document.createElement('input');
  but.type = 'button';
  but.className = 'inputButton';
  if (stopped) {
    but.value = 'Start';
    but.onclick = self.pollServerStart;
  }
  else {
    but.value = 'Stop';
    but.onclick = self.pollServerStop;
  }
  if (buttonDiv.firstChild) {
    buttonDiv.removeChild(buttonDiv.firstChild);
  }
  buttonDiv.appendChild(but);
  buttonDiv = null;
};

The only parameter to this method, stopped, can either be true, indicating that the polling has been stopped, or false, indicating that polling has started.

As you can see in the code for this method, the button is created, and is set to display Start if the application is stopped, or Stop if the application is currently polling the server. It also assigns either pollServerStart or pollServerStop as the button's onclick event handler. These event handlers will start or stop the polling process respectively.

When this method is called from init (via toggleAppStatus), stopped is set to true so the button will display Start when the application is started.

As this code calls for a div with the ID buttonArea, let's add that to our markup now:

Example 3.8. appmonitor2.html (excerpt)
 
<body>
  <div id="buttonArea"></div>
</body>

The toggleStatusMessage Method

Showing a button with the word "Start" or "Stop" on it might be all that programmers or engineers need to figure out the application's status, but most normal people need a message that's a little clearer and more obvious in order to work out what's going on with an application.

This upgraded version of the application will display a status message at the top of the page to tell the user about the overall state of the application (stopped or running), and the status of the polling process. To display the application status, we'll place a nice, clear message in the application's status bar that states App Status: Stopped or App Status: Running.

In our markup, let's insert the status message above where the button appears. We'll include only the "App Status" part of the message in our markup. The rest of the message will be inserted into a span with the ID currentAppState:

Example 3.9. appmonitor2.html (excerpt)
 
<body>
  <div id="statusMessage">App Status:  
      <span id="currentAppState"></span>
  </div>
  <div id="buttonArea"></div>
</body>

The toggleStatusMessage method toggles between the words that can display inside the currentAppState span:

Example 3.10. appmonitor2.js (excerpt)
 
this.toggleStatusMessage = function(stopped) {
  var statSpan = document.getElementById('currentAppState');
  var msg;
  if (stopped) {
    msg = 'Stopped';
  }
  else {
    msg = 'Running';
  }
  if (statSpan.firstChild) {
    statSpan.removeChild(statSpan.firstChild);
  }
  statSpan.appendChild(document.createTextNode(msg));
};

Once the UI is set up, the application is primed and ready to start polling and recording response times.


Page 6

Checking your Work In Progress

Now that you've come this far, it would be nice to be able to see your work in action, right? Well, unfortunately, we've still got a lot of loose ends in our application -- we've briefly mentioned a singleton class called Status but we haven't created it yet, and we still have event handlers left to write. But never fear! We can quickly get the application up and running with a few class and function stubs.

We'll start by creating that Status singleton class with one empty method.

Example 3.11. appmonitor2.js (excerpt)
 
var Status = new function() {
  this.init = function() {
    // don't mind me, I'm just a stub ...
  };
}

Since the Status class is used by the Monitor class, we must declare Status before Monitor.

Then, we'll add our button's onclick event handlers to the Monitor class. We'll have them display alert dialogs so that we know what would be going on if there was anything happening behind the scenes.

Example 3.12. appmonitor2.js (excerpt)
 
this.pollServerStart = function() {
  alert('This will start the application polling the server.');
};
this.pollServerStop = function() {
  alert('This will stop the application polling the server.');
};

With these two simple stubs in place, your application should now be ready for a test-drive.

1542_fig3.2
Figure 3.2. Humble beginnings

When you click the Start button in the display shown in Figure 3.2 you're presented with an alert box that promises greater things to come. Let's get started making good on those promises.

Polling the Server

The first step is to flesh out the Start button's onclick event handler, pollServerStart:

Example 3.13. appmonitor2.js (excerpt)
 
this.pollServerStart = function() {
  var self = Monitor;
  self.doPoll();
  self.toggleAppStatus(false);
};

This code immediately calls the doPoll method, which, like the app monitor we built in Chapter 2, Basic XMLHttpRequest, will be responsible for making an HTTP request to poll the server. Once the request has been sent, the code calls toggleAppStatus, passing it false to indicate that polling is underway.


Page 8

Where's the Poll Interval?

You might wonder why, after all this talk about setting a poll interval, our code jumps right in with a request to the server; where's the time delay? The answer is that we don't want a time delay on the very first request. If users click the button and there's a ten-second delay before anything happens, they'll think the app is broken. We want delays between all the subsequent requests that occur once the application is running, but when the user first clicks that button, we want the polling to start right away.

The only difference between doPoll in this version of our app monitor and the one we saw in the last chapter is the use of self to prefix the properties of the class, and the call to setTimeout. Take a look:

Example 3.14. appmonitor2.js (excerpt)
 
this.doPoll = function() {
  var self = Monitor;
  var url = self.targetURL;
  var start = new Date();
  self.reqStatus.startProc();
  self.start = start.getTime();
  self.ajax.doGet(self.targetURL + '?start=' + self.start,
      self.showPoll);
  self.timeoutHand = setTimeout(self.handleTimeout,
      self.timeoutThreshold * 1000);
};

Our call to setTimeout instructs the browser to call handleTimeout once the timeout threshold has passed. We're also keeping track of the interval ID that's returned, so we can cancel our call to handleTimeout when the response is received by showPoll.

Here's the code for the showPoll method, which handles the response from the server:

Example 3.15. appmonitor2.js (excerpt)
 
this.showPoll = function(str) {
  var self = Monitor;
  var diff = 0;
  var end = new Date();
  clearTimeout(self.timeoutHand);
  self.reqStatus.stopProc(true);
  if (str == 'ok') {
    end = end.getTime();
    diff = (end - self.start) / 1000;
  }
  if (self.updatePollArray(diff)) {
    self.printResult();
  }
  self.doPollDelay();
};

The first thing this method does is cancel the delayed call to handleTimeout that was made at the end of doPoll. After this, we tell our instance of the Status class to stop its animation (we'll be looking at the details of this a little later).

After these calls, showPoll checks to make sure that the response is ok, then calculates how long that response took to come back from the server. The error handling capabilities of the Ajax class should handle errors from the server, so our script shouldn't return anything other than ok ... though it never hurts to make sure!

Once it has calculated the response time, showPoll records that response time with updatePollArray, then displays the result with printResult. We'll look at both of these methods in the next section.

Finally, we schedule another poll in doPollDelay -- a very simple method that schedules another call to doPoll once the poll interval has passed:

Example 3.16. appmonitor2.js (excerpt)
 
this.doPollDelay = function() {
  var self = Monitor;
  self.pollHand = setTimeout(self.doPoll,
      self.pollInterval * 1000);
};

To check our progress up to this point, we'll need to add a few more stub methods. First, let's add startProc and stopProc to the Status class:

Example 3.17. appmonitor2.js (excerpt)
 
var Status = new function() {
  this.init = function() {
    // don't mind me, I'm just a stub ...
  };
  this.startProc = function() {
    // another stub function
  };
  this.stopProc = function() {
    // another stub function
  };
}

Let's also add a few stub methods to our Monitor class:

Example 3.18. appmonitor2.js (excerpt)
 
this.handleTimeout = function() {
  alert("Timeout!");
};
this.updatePollArray = function(responseTime) {
  alert("Recording response time: " + responseTime);
};
this.printResult = function() {
  // empty stub function
};

Now we're ready to test our progress. Open appmonitor2.html in your web browser, click Start, and wait for fakeserver.php to wake from its sleep and send ok back to your page.

You can expect one of two outcomes: either a response is received by your page, and you see a dialog similar to the one shown in Figure 3.3, or you see the timeout message shown in Figure 3.4.

1542_fig3.3
Figure 3.3. A response received by your AJAX application


Page 9

Don't worry if you receive the timeout message shown in Figure 3.4. Keep in mind that in our AJAX application, our timeout threshold is currently set to ten seconds, and that fakeserver.php is currently sleeping for a randomly selected number of seconds between three and 12. If the random number is ten or greater, the AJAX application will report a timeout.

1542_fig3.4
Figure 3.4. Your AJAX application giving up hope

At the moment, we haven't implemented a way to stop the polling, so you'll need to stop it either by reloading the page or closing your browser window.

Handling Timeouts

If you've run the code we've written so far, you've probably noticed that even when a timeout is reported, you see a message reporting the request's response time soon afterward. This occurs because handleTimeout is nothing but a simple stub at the moment. Let's look at building on that stub so we don't get this side-effect.

handleTimeout is basically a simplified version of showPoll: both methods are triggered by an asynchronous event (a call to setTimeout and an HTTP response received by an XMLHttpRequest object respectively), both methods need to record the response time (in a timeout's case, this will be 0), both methods need to update the user interface, and both methods need to trigger the next call to doPoll. Here's the code for handleTimeout:

Example 3.19. appmonitor2.js (excerpt)
 
this.handleTimeout = function() {
  var self = Monitor;
  if (self.stopPoll()) {
    self.reqStatus.stopProc(true);
    if (self.updatePollArray(0)) {
      self.printResult();
    }
    self.doPollDelay();
  }
};

Here, handleTimeout calls stopPoll to stop our application polling the server. It records that a timeout occurred, updates the user interface, and finally sets up another call to doPoll via doPollDelay. We moved the code that stops the polling into a separate function because we'll need to revisit it later and beef it up. At present, the stopPoll method merely aborts the HTTP request via the Ajax class's abort method; however, there are a few scenarios that this function doesn't handle. We'll address these later, when we create the complete code to stop the polling process, but for the purposes of handling the timeout, stopPoll is fine.

Example 3.20. appmonitor2.js (excerpt)
 
this.stopPoll = function() {
  var self = Monitor;
  if (self.ajax) {
    self.ajax.abort();
  }
  return true;
};

Now, when we reload our application, the timeouts perform exactly as we expect them to.

The Response Times Bar Graph

Now, to the meat of the new version of our monitoring app! We want the application to show a list of past response times, not just a single entry of the most recent one, and we want to show that list in a way that's quickly and easily readable. A running bar graph display is the perfect tool for the job.


Page 10

The Running List in pollArray

All the response times will go into an array that's stored in the pollArray property of the Monitor class. We keep this array updated with the intuitively named updatePollArray method. It's a very simple method that looks like this:

Example 3.21. appmonitor2.js (excerpt)
 
this.updatePollArray = function(pollResult) {
  var self = Monitor;
  self.pollArray.unshift(pollResult);
  if (self.pollArray.length > self.maxPollEntries) {
    self.pollArray.pop();
  }
  return true;
};

The code is very straightforward, although some of the functions we've used in it have slightly confusing names.

The unshift method of an Array object puts a new item in the very first element of the array, and shifts the rest of the array's contents over by one position, as shown in Figure 3.5.

1542_fig3.5
Figure 3.5. Inserting fruit using unshift

When the array exceeds the user-defined maximum length, updatePollArray truncates it by "popping" an item off the end. This is achieved by the pop method, which simply deletes the last item of an array. (Note that the method name pop may seem quite odd, but it makes more sense once you understand a data structure called a stack, which stores a number of items that can be accessed only in the reverse of the order in which they were added to the stack. We "push" an item onto a stack to add it, and "pop" an item from a stack to retrieve it. The pop  method was originally designed for developers who were using arrays [44] as stacks, but here we've repurposed it simply to delete the last item in an array.) The reason why we append items to the top and remove items from the bottom of the array is that, in our display, we want the most recent entries to appear at the top, and older entries to gradually move down to the bottom.

Displaying the Results

Once we've updated the results in pollArray, we can display them using the printResult method. This is actually the cool part: the user will experience first-hand the difference between our AJAX application and an older-style app that requires an entire page refresh to update content.

Rendering Page Partials

In AJAX jargon, the chunk of the page that holds the list of response times is called a page partial. This refers to an area of a web page that's updated separately from the rest of the page.

Updating a chunk of a web page in response to an asynchronous request to the server is called "rendering a page partial."

The printResult method iterates through pollArray, and uses DOM methods to draw the list of poll results inside a div with the ID pollResults. We'll start by adding that div to our markup:

Example 3.22. appmonitor2.html (excerpt)
 
<body>
  <div id="statusMessage">App Status:  
    <span id="currentAppState"></span>
  </div>
  <div id="pollResults"></div>
  <div id="buttonArea"></div>
</body>

Now we're ready for the printResult method:

Example 3.23. appmonitor2.js (excerpt)
 
this.printResult = function() {
  var self = Monitor;
  var polls = self.pollArray;
  var pollDiv = document.getElementById('pollResults');
  var entryDiv = null;
  var messageDiv = null;
  var barDiv = null;
  var clearAll = null;
  var msgStr = '';
  var txtNode = null;
  while (pollDiv.firstChild) {
    pollDiv.removeChild(pollDiv.firstChild);
  }
  for (var i = 0; i < polls.length; i++) {
    if (polls[i] == 0) {
      msgStr = '(Timeout)';
    }
    else {
      msgStr = polls[i] + ' sec.';
    }
    entryDiv = document.createElement('div');
    messageDiv = document.createElement('div');
    barDiv = document.createElement('div');
    clearAll = document.createElement('br');
    entryDiv.className = 'pollResult';
    messageDiv.className = 'time';
    barDiv.className = 'bar';
    clearAll.className = 'clearAll';
    if (polls[i] == 0) {
      messageDiv.style.color = '#933';
    }
    else {
      messageDiv.style.color = '#339';
    }
    barDiv.style.width = (parseInt(polls[i] * 20)) + 'px';
    messageDiv.appendChild(document.createTextNode(msgStr));
    barDiv.appendChild(document.createTextNode('\u00A0'));
    entryDiv.appendChild(messageDiv);
    entryDiv.appendChild(barDiv);
    entryDiv.appendChild(clearAll);
    pollDiv.appendChild(entryDiv);
  }
};


Page 11

There's quite a bit here, so let's look at this method step by step.

Example 3.24. appmonitor2.js (excerpt)
 
while (pollDiv.firstChild) {
  pollDiv.removeChild(pollDiv.firstChild);
}

After initializing some variables, this method removes everything from pollDiv: the while loop uses removeChild repeatedly to delete all the child nodes from pollDiv.

Next comes a simple for loop that jumps through the updated array of results and displays them.

We generate a message for the result of each item in this array. As you can see below, timeouts (which are recorded as a 0) generate a message of (Timeout).

Example 3.25. appmonitor2.js (excerpt)
 
if (polls[i] == 0) {
  msgStr = '(Timeout)';
}
else {
  msgStr = polls[i] + ' sec.';
}

Next, we use DOM methods to add the markup for each entry in the list dynamically. In effect, we construct the following HTML in JavaScript for each entry in the list:

<div class="pollResult">
    <div class="time" style="color: #339;">8.031 sec.</div>
    <div class="bar" style="width: 160px;"> </div>
    <br class="clearAll"/>
</div>

The width of the bar div changes to reflect the actual response time, and timeouts are shown in red, but otherwise all entries in this list are identical. Note that you have to put something in the div to cause its background color to display. Even if you give the div a fixed width, the background color will not show if the div is empty. This is annoying, but it's easy to fix: we can fill in the div with a non-breaking space character.

Let's take a look at the code we'll use to insert this markup:

Example 3.26. appmonitor2.js (excerpt)
 
entryDiv = document.createElement('div');
messageDiv = document.createElement('div');
barDiv = document.createElement('div');
clearAll = document.createElement('br');
entryDiv.className = 'pollResult';
messageDiv.className = 'time';
barDiv.className = 'bar';
clearAll.className = 'clearAll';
if (polls[i] == 0) {
  messageDiv.style.color = '#933';
}
else {
  messageDiv.style.color = '#339';
}
barDiv.style.width = (parseInt(polls[i] * 20)) + 'px';
messageDiv.appendChild(document.createTextNode(msgStr));
barDiv.appendChild(document.createTextNode('\u00A0'));
entryDiv.appendChild(messageDiv);
entryDiv.appendChild(barDiv);
entryDiv.appendChild(clearAll);
pollDiv.appendChild(entryDiv);

This code may seem complicated if you've never used DOM manipulation functions, but it's really quite simple. We use the well-named createElement method to create elements; then we assign values to the properties of each of those element objects.

Just after the if statement, we can see the code that sets the pixel width of the bar div according to the number of seconds taken to generate each response. We multiply that time figure by 20 to get a reasonable width, but you may want to use a higher or lower number depending on how much horizontal space is available on the page.

To add text to elements, we use createTextNode in conjunction with appendChild, which is also used to place elements inside other elements.

createTextNode and Non-breaking Spaces

In the code above, we create a non-breaking space using \u00A0. If we try to use the normal   entity here, createTextNode will attempt to be "helpful" by converting the ampersand to &; the result of this is that   is displayed on your page. The workaround is to use the escaped unicode non-breaking space: \u00A0.

1542_fig3.6
Figure 3.6. The application starting to take shape


Page 12

The last piece of the code puts all the div elements together, then places the pollResult div inside the pollResults div. Figure 3.6 shows the running application.

"Hold on a second," you may well be thinking. "Where's the bar graph we're supposed to be seeing?"

The first bar is there, but it's displayed in white on white, which is pretty useless. Let's make it visible through our application's CSS:

Example 3.27. appmonitor2.css (excerpt)
 
.time {
  width: 6em;
  float: left;
}
.bar {
  background: #ddf;
  float: left;
}
.clearBoth {
  clear: both;
}

The main point of interest in the CSS is the float: left declarations for the time and bar div elements, which make up the time listing and the colored bar in the bar graph. Floating them to the left is what makes them appear side by side. However, for this positioning technique to work, an element with the clearBoth class must appear immediately after these two divs.

This is where you can see AJAX in action. It uses bits and pieces of all these different technologies -- XMLHttpRequest, the W3C DOM, and CSS -- wired together and controlled with JavaScript. Programmers often experience the biggest problems with CSS and with the practicalities of building interface elements in their code.

As an AJAX programmer, you can either try to depend on a library to take care of the CSS for you, or you can learn enough to get the job done. It's handy to know someone smart who's happy to answer lots of questions on the topic, or to have a good book on CSS (for example, SitePoint's The CSS Anthology: 101 Essential Tips, Tricks & Hacks).

1542_fig3.7
Figure 3.7. The beginnings of our bar graph

Now that our CSS is in place, we can see the bar graph in our application display, as Figure 3.7 illustrates.

Stopping the Application

The final action of the pollServerStart method, after getting the app running, is to call toggleAppStatus to toggle the appearance of the application. toggleAppStatus changes the status display to App Status: Running, switches the Start button to a Stop button, and attaches the pollServerStop method to the button's onclick event.

The pollServerStop method stops the ongoing polling process, then toggles the application back so that it looks like it's properly stopped:

Example 3.28. appmonitor2.js (excerpt)
 
this.pollServerStop = function() {
  var self = Monitor;
  if (self.stopPoll()) {
    self.toggleAppStatus(true);
  }
  self.reqStatus.stopProc(false);
};

This code reuses the stopPoll method we added earlier in the chapter. At the moment, all that method does is abort the current HTTP request, which is fine while we're handling a timeout. However, this method needs to handle two other scenarios as well.

The first of these scenarios occurs when the method is called during the poll interval (that is, after we receive a response to an HTTP request, but before the next request is sent). In this scenario, we need to cancel the delayed call to doPoll.

The second scenario that this method must be able to handle arises when stopPoll is called after it has sent a request, but before it receives the response. In this scenario, the timeout handler needs to be canceled.


Page 13

As we keep track of the interval IDs of both calls, we can modify stopPoll to handle these scenarios with two calls to clearTimeout:

Example 3.29. appmonitor2.js (excerpt)
 
this.stopPoll = function() {
  var self = Monitor;
  clearTimeout(self.pollHand);
  if (self.ajax) {
    self.ajax.abort();
  }
  clearTimeout(self.timeoutHand);
  return true;
};

Now, you should be able to stop and start the polling process just by clicking the Start/Stop button beneath the bar graph.

Status Notifications

The ability of AJAX to update content asynchronously, and the fact that updates may affect only small areas of the page, make the display of status notifications a critical part of an AJAX app's design and development. After all, your app's users need to know what the app is doing.

Back in the old days of web development, when an entire page had to reload in order to reflect any changes to its content, it was perfectly clear to end users when the application was communicating with the server. But our AJAX web apps can talk to the server in the background, which means that users don't see the complete page reload that would otherwise indicate that something was happening.

So, how will users of your AJAX app know that the page is communicating with the server? Well, instead of the old spinning globe or waving flag animations that display in the browser chrome, AJAX applications typically notify users that processing is under way with the aid of small animations or visual transitions. Usually achieved with CSS, these transitions catch users' eyes -- without being distracting! -- and provide hints about what the application is doing. An important aspect of the good AJAX app design is the development of these kinds of notifications.

The Status Animation

Since we already have at the top of our application a small bar that tells the user if the app is running or stopped, this is a fairly logical place to display a little more status information.

Animations like twirling balls or running dogs are a nice way to indicate that an application is busy -- generally, you'll want to display an image that uses movement to indicate activity. However, we don't want to use a cue that's going to draw users' attention away from the list, or drive people to distraction as they're trying to read the results, so we'll just go with the slow, pulsing animation shown in Figure 3.8.

This animation has the added advantages of being lightweight and easy to implement in CSS -- no Flash player is required, and there's no bulky GIF [45] image to download frame by tedious frame.

The far right-hand side of the white bar is unused space, which makes it an ideal place for this kind of notification: it's at the top of the user interface, so it's easy to see, but it's off to the right, so it's out of the way of people who are trying to read the list of results.

1542_fig3.8
Figure 3.8. Our pulsing status animation

To host this animation, we'll add a div with the ID pollingMessage just below the status message div in our document:

Example 3.30. appmonitor2.html (excerpt)
 
<body>
  <div id="statusMessage">App Status:  
    <span id="currentAppState"></span>
  </div>
  <div id="pollingMessage"></div>
  <div id="pollResults"></div>
  <div id="buttonArea"></div>
</body>

Add a CSS rule to your style sheet to position this div:

Example 3.31. appmonitor2.css (excerpt)
 
#pollingMessage {  
  float: right;
  width: 80px;
  padding: 0.2em;
  text-align: center;
}

This animation is now positioned to the right of the page.


Page 14

Page 15

Example 3.35. appmonitor2.js (excerpt)
 
this.cleanup = function() {
  var self = Monitor;
  self.reqStatus.cleanup();
  self.reqStatus = null;
};

The cleanup method in the Status class does the IE housekeeping:

Example 3.36. appmonitor2.js (excerpt)
 
this.cleanup = function() {
  Status.div = null;
};

If we don't set that div reference to null, Internet Explorer will keep the memory it allocated to that variable in a death grip, and you'll see memory use balloon each time you reload the page.

In reality, this wouldn't be much of a problem for our tiny application, but it can become a serious issue in large web apps that have a lot of DHTML. It's good to get into the habit of cleaning up DOM references in your code so that this doesn't become an issue for you.

The displayOpacity Method

The central piece of code in the Status class lives in the displayOpacity method. This contains the browser-specific code that's necessary to change the appropriate CSS properties of the pollingMessage div. Here's the code:

Example 3.37. appmonitor2.js (excerpt)
 
this.displayOpacity = function() {
  var self = Status;
  var decOpac = self.currOpacity / 100;
  if (document.all && typeof window.opera == 'undefined') {
    self.div.filters.alpha.opacity = self.currOpacity;
  }
  else {
    self.div.style.MozOpacity = decOpac;
  }
  self.div.style.opacity = decOpac;
};

The currOpacity property of the object represents the opacity to which the pollingMessage div should be set. Our implementation uses an integer scale ranging from 0 to 100, which is employed by Internet Explorer, rather than the fractional scale from zero to one that's expected by Mozilla and Safari. This choice is just a personal preference; if you prefer to use fractional values, by all means do.

In the method, you'll see a test for document.all -- a property that's supported only by IE and Opera -- and a test for window.opera, which, unsurprisingly, is supported only by Opera. As such, only IE should execute the if clause of this if statement. Inside this IE branch of the if statement, the proprietary alpha.opacity property is used to set opacity, while in the else clause, we use the older MozOpacity property, which is supported by older Mozilla-based browsers.

Finally, this method sets the opacity in the standards-compliant way: using the opacity property, which should ultimately be supported in all standards-compliant browsers.

IE Gotchas

Internet Explorer version 6, being an older browser, suffers a couple of issues when trying to render opacity-based CSS changes.

Fortunately, the first of these is easily solved by an addition to our pollingMessage CSS rule:

Example 3.38. appmonitor2.css (excerpt)
 
#pollingMessage {
  float: right;
  width: 80px;
  padding: 0.2em;
  text-align: center;
  background: #fff;
}

The addition of the background property fixes the first specific problem with Internet Explorer. We must set the background color of an element if we want to change its opacity in IE, or the text will display with jagged edges. Note that setting background to transparent will not work: it must be set to a specific color.

The second problem is a little trickier if you want your CSS files to be valid. IE won't let you change the style.alpha.opacity unless it's declared in the style sheet first. Now, if you don't mind preventing your style sheets from being passed by the W3C validator, it's easy to fix this problem by adding another declaration:

Example 3.39. appmonitor2.css (excerpt)
 
#pollingMessage {
  float: right;
  width: 80px;
  padding: 0.2em;
  text-align: center;
  background: #fff;
  filter: alpha(opacity = 100);
}



Page 16

Unfortunately, this approach generates CSS warnings in browsers that don't support that proprietary property, such as Firefox 1.5, which displays CSS warnings in the JavaScript console by default. A solution that's better than inserting IE-specific style information into your global style sheet is to use JavaScript to add that declaration to the pollingMessage div's style attribute in IE only. That's what the setAlpha method that's called in init achieves. Here's the code for that method:

Example 3.40. appmonitor2.js (excerpt)
 
this.setAlpha = function() {
  var self = Status;
  if (document.all && typeof window.opera ==
      'undefined') {
    var styleSheets = document.styleSheets;
    for (var i = 0; i < styleSheets.length; i++) {
      var rules = styleSheets[i].rules;
      for (var j = 0; j < rules.length; j++) {
        if (rules[j].selectorText ==
            '#pollingMessage') {
          rules[j].style.filter =
              'alpha(opacity = 100)';
          return true;
        }
      }
    }
  }
  return false;
};

This code, which executes only in Internet Explorer, uses the document.styleSheets array to iterate through each style sheet that's linked to the current page. It accesses the rules in each of those style sheets using the rules property, and finds the style we want by looking at the selectorText property. Once it has the right style in the rules array, it gives the filter property the value it needs to change the opacity.

Opacity in Opera?

Unfortunately, at the time of writing, even the latest version of Opera (version 8.5) doesn't support CSS opacity, so such an animation does not work in that browser. However, this feature is planned for Opera version 9.

Running the Animation

The code for the processing animation consists of five methods: the first three control the "Processing ..." animation, while the remaining two control the "Done" animation. The three methods that control the "Processing ..." animation are:

       
  • startProc, which sets up the "Processing ..." animation and schedules repeated calls to doProc with setInterval
  •    
  • doProc, which monitors the properties of this class and sets the current frame of the "Processing ..." animation appropriately
  •    
  • stopProc, which signals that the "Processing ..." animation should cease

The two that control the "Done" animation are:

       
  • startDone sets up the "Done" animation and schedules repeated calls to doDone with setInterval
  •    
  • doDone sets the current frame of the "Done" animation and terminates the animation once it's completed

Starting it Up

Setting the animation up and starting it are jobs for the startProc method:

Example 3.41. appmonitor2.js (excerpt)
 
this.startProc = function() {
  var self = Status;
  self.proc = 'proc';
  if (self.setDisplay(false)) {
    self.currOpacity = 100;
    self.displayOpacity();
    self.procInterval = setInterval(self.doProc, 90);
  }
};

After setting the proc property to proc (processing), this code calls the setDisplay method, which sets the color and content of the pollingMessage div. We'll take a closer look at setDisplay next.

Once the code sets the color and content of the pollingMessage div, it initializes the div's opacity to 100 (completely opaque) and calls displayOpacity to make this setting take effect.

Finally, this method calls setInterval to schedule the next step of the animation process. Note that, as with setTimeout, the setInterval call returns an interval ID. We store this in the procInterval property so we can stop the process later.


Page 17

Both the "Processing ..." and "Done" animations share the setDisplay method:

Example 3.42. appmonitor2.js (excerpt)
 
this.setDisplay = function(done) {
  var self = Status;
  var msg = '';
  if (done) {
    msg = 'Done';
    self.div.className = 'done';
  }
  else {
    msg = 'Processing...';
    self.div.className = 'processing';
  }
  if (self.div.firstChild) {
    self.div.removeChild(self.div.firstChild);
  }
  self.div.appendChild(document.createTextNode(msg));
  return true;
};

Since the only differences between the "Processing ..." and "Done" states of the pollingMessage div are its color and text, it makes sense to use this common function to toggle between the two states of the pollingMessage div. The colors are controlled by assigning classes to the pollingMessage div, so we'll need to add CSS class rules for the done and processing classes to our style sheet:

Example 3.43. appmonitor2.css (excerpt)
 
.processing {
  color: #339;
  border: 1px solid #339;
}
.done {
  color:#393;
  border:1px solid #393;
}

Making it Stop

Stopping the animation smoothly requires some specific timing. We don't want the animation to stop abruptly right in the middle of a pulse. We want to stop it in the natural break, when the "Processing ..." image's opacity is down to zero.

So the stopProc method for stopping the animation doesn't actually stop it per se -- it just sets a flag to tell the animation process that it's time to stop when it reaches a convenient point. This is a lot like the phone calls received by many programmers at the end of the day from wives and husbands reminding them to come home when they get to a logical stopping point in their code.

Since very little action occurs here, the method is pretty short:

Example 3.44. appmonitor2.js (excerpt)
 
this.stopProc = function(done) {
  var self = Status;
  if (done) {
    self.proc = 'done';
  }
  else {
    self.proc = 'abort';
  }
};

This method does have to distinguish between two types of stopping: a successfully completed request (done) and a request from the user to stop the application (abort).

The doProc method uses this flag to figure out whether to display the "Done" message, or just to stop.

Running the Animation with doProc

The doProc method, which is invoked at 90 millisecond intervals, changes the opacity of the pollingMessage div to produce the pulsing effect of the processing animation. Here's the code:


Page 18

Example 3.45. appmonitor2.js (excerpt)
 
this.doProc = function() {
  var self = Status;
  if (self.currOpacity == 0) {
    if (self.proc == 'proc') {
      self.currOpacity = 100;
    }
    else {
      clearInterval(self.procInterval);
      if (self.proc == 'done') {
        self.startDone();
      }
      return false;
    }
  }
  self.currOpacity = self.currOpacity - 10;
  self.displayOpacity();
};

This method is dead simple -- its main purpose is simply to reduce the opacity of the pollingMessage div by 10% every time it's called.

The first if statement looks to see if the div has completely faded out. If it has, and the animation is still supposed to be running, it resets the opacity to 100 (fully opaque). Executing this code every 90 milliseconds produces a smooth effect in which the pollingMessage div fades out, reappears, and fades out again -- the familiar pulsing effect that shows that the application is busy doing something.

If the animation is not supposed to continue running, we stop the animation by calling clearInterval, then, if the proc property is done, we trigger the "Done" animation with a call to startDone.

Starting the "Done" Animation with startDone

The startDone method serves the same purpose for the "Done" animation that the startProc method serves for the "Processing ..." animation. It looks remarkably similar to startProc, too:

Example 3.46. appmonitor2.js (excerpt)
 
this.startDone = function() {
  var self = Status;
  if (self.setDisplay(true)) {
    self.currOpacity = 100;
    self.displayOpacity();
    self.procInterval = setInterval(self.doDone, 90);
  }
};

This time, we pass true to setDisplay, which will change the text to "Done" and the color to green.

We then set up calls to doDone with setInterval, which actually performs the fadeout.

The Final Fade

The code for doDone is significantly simpler than the code for doProc. It doesn't have to process continuously until told to stop, like doProc does. It just keeps on reducing the opacity of the pollingMessage div by 10% until it reaches zero, then stops itself. Pretty simple stuff:


Page 19

Example 3.47. appmonitor2.js (excerpt)
 
this.doDone = function() {
  var self = Status;
  if (self.currOpacity == 0) {
    clearInterval(self.procInterval);
  }
  self.currOpacity = self.currOpacity - 10;
  self.displayOpacity();
};

1542_fig3.9
Figure 3.9. The application with a pulsing status indicator

Finally, we're ready to test this code in our browser. Open appmonitor2.html in your browser, click the Start button, and you should see a pulsing Processing ... message near the top right-hand corner of the browser's viewport, like the one shown in Figure 3.9.

Be Careful with that Poll Interval!

Now that we have an animation running in the page, we need to be careful that we don't start the animation again before the previous one stops. For this reason, it's highly recommended that you don't set POLL_INTERVAL to anything less than two seconds.

Styling the Monitor

Now that we've got our application up and running, let's use CSS to make it look good. We'll need to add the following markup to achieve our desired layout:

Example 3.48. appmonitor2.html (excerpt)
 
<body>
  <div id="wrapper">
    <div id="main">
      <div id="status">
        <div id="statusMessage">App Status:  
          <span id="currentAppState"></span>
        </div>
        <div id="pollingMessage"></div>
        <br class="clearBoth" />
      </div>
      <div id="pollResults"></div>
      <div id="buttonArea"></div>
    </div>
  </div>
</body>

As you can see, we've added three divs from which we can hang our styles, and a line break to clear the floated application status message and animation. The completed CSS for this page is as follows; the styled interface is shown in Figure 3.10:

Example 3.49. appmonitor2.css
 
body, p, div, td, ul {
  font-family: verdana, arial, helvetica, sans-serif;
  font-size:12px;
}
#wrapper {
  padding-top: 24px;
}
#main {
  width: 360px;
  height: 280px;
  padding: 24px;
  text-align: left;
  background: #eee;
  border: 1px solid #ddd;
  margin:auto;
}
#status {
  width: 358px;
  height: 24px;
  padding: 2px;
  background: #fff;
  margin-bottom: 20px;
  border: 1px solid #ddd;
}
#statusMessage {
  font-size: 11px;
  float: left;
  height: 16px;
  padding: 4px;
  text-align: left;
  color: #999;
}
#pollingMessage {
  font-size: 11px;
  float: right;
  width: 80px;
  height: 14px;
  padding: 4px;
  text-align: center;
  background: #fff;
}
#pollResults {
  width: 360px;
  height: 210px;
}
#buttonArea {
  text-align: center;
}
.pollResult {  
  padding-bottom: 4px;
}
.time {
  font-size: 11px;
  width: 74px;
  float: left;
}
.processing {
  color: #339;
  border: 1px solid #333399;
}
.done {
  color: #393;
  border: 1px solid #393;
}
.bar {
  background: #ddf;
  float: left;
}
.inputButton {
  width: 8em;
  height: 2em;
}
.clearBoth {
  clear: both;
}


Page 20

1542_fig3.10
Figure 3.10. The completed App Monitor

Summary

Our first working application showed how AJAX can be used to make multiple requests to a server without the user ever leaving the currently loaded page. It also gave a fairly realistic picture of the kind of complexity we have to deal with when performing multiple tasks asynchronously. A good example of this complexity was our use of setTimeout to time the XMLHttpRequest requests. This example provided a good opportunity to explore some of the common problems you'll encounter as you develop AJAX apps, such as loss of scope and connection timeouts, and provided practical solutions to help you deal with them.

That's it for this excerpt from Build Your Own AJAX Web Applications [46] -- don't forget, you can download this article in .pdf format [47]. The book has eight chapters in total, and by the end of it, readers will have built numerous fully functioning web apps including an online chess game that multiple players can play in real time -- the book's Table of Contents [48] has the full details.

Summary

In this chapter, we had a quick overview of AJAX and the technologies that make it tick. We looked at some of the horrible coding contortions that developers had to endure back in the bad old days to create something resembling an interactive UI [25], and we saw how AJAX offers a huge improvement on those approaches. With a decent command of the building blocks of AJAX -- XML, the DOM, CSS, XMLHttpRequest, and JavaScript, which ties them all together -- you have everything you need to start building dynamic and accessible [26] AJAX sites.

About This Article

Build your own AJAX web applicationsThis Article Excerpted from: “Build Your Own Ajax Web Applications” published by Melbourne-based SitePoint.

Order online and get free shipping when you order a second book, plus a bonus video tutorial worth $9.95.

SitePoint

Glossary

[19] http://www.w3.org/MarkUp/2004/xhtml-faq
[20] http:/glossary.php?q=O#term_10
[21] http://www.sitepoint.com/glossary.php?q=U#term_67
[22] http://www.sitepoint.com/glossary.php?q=M#term_31
[23] http://www.sitepoint.com/glossary.php?q=F#term_45
[24] http://www.sitepoint.com/glossary.php?q=X#term_32
[25] http://www.sitepoint.com/glossary.php?q=U#term_67
[26] http://www.sitepoint.com/glossary.php?q=A#term_61
[27] http://www.sitepoint.com/glossary.php?q=J#term_65
[28] http://www.sitepoint.com/glossary.php?q=%23#term_2
[29] http://www.sitepoint.com/glossary.php?q=O#term_27
[30] http://www.sitepoint.com/glossary.php?q=I#term_30
[31] http://www.sitepoint.com/glossary.php?q=S#term_14
[32] http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10
[33] http://www.sitepoint.com/glossary.php?q=%23#term_2
[34] http://www.sitepoint.com/glossary.php?q=C#term_21
[35] http://docs.sun.com/source/816-6409-10/obj.htm
[36] http://docs.sun.com/source/816-6409-10/obj2.htm
[37] http://www.crockford.com/javascript/private.html
[38] http://developer.apple.com/internet/webcontent/xmlhttpreq.html
[39] http://jibbering.com/2002/4/httprequest.html
[40] http://www.xulplanet.com/references/objref/XMLHttpRequest.html
[41] http://kb.mozillazine.org/XMLHttpRequest
[42] http://msdn.microsoft.com/library/en-us/xmlsdk/html/xmobjxmlhttprequest.asp