Ajax Application Design

Asynchronous is the operative word with Ajax, and here's what it's all about.
The Server Side

Ajax is almost exclusively a client-side paradigm. And, indeed, it is increasingly clear that we can use JavaScript in general, and Ajax in particular, to create new and interesting applications and interfaces. That said, server-side programs still have a major role to play in Web applications, including Ajax applications.

To begin with, only server-side programs can access the site's relational database. (And yes, it's theoretically possible to have JavaScript access the database directly, but that would be a security and performance nightmare.) This means everything you normally would store in a database, but want to have displayed in the browser, will need to be filtered through a server-side program. Almost any nontrivial application will thus benefit from being part of a larger Web framework, such as Zope, Ruby on Rails or even a roll-your-own system that encapsulates behavior in a set of related methods or functions. In other words, the server-side programs in an Ajax application become very specialized database query and reporting tools.

In the interests of time and space, we don't access a database this month. However, there is no way for the HTTP client to know whether the HTTP server is checking a database or returning a random result, and we will take advantage of this secrecy to fudge the lack of a database. If we decide at some point to modify our server-side program to retrieve a list of user names from a database instead of hard-coding the list in a hash, that will be just fine.

Our server-side program, check-name-exists.pl (Listing 3), is a simple CGI program written in Perl. We turn the POSTDATA parameter, which we have received from the Ajax request, and look inside it to see if we received a setting for username. If so, we then look for a match among the keys of the %usernames hash. If we find a match, it returns yes to the caller. If there is no match, it returns no.

Notice how we use a hash, rather than an array, to store the user names. This is a hack for the sake of efficiency; the time it takes to find an array element (and see if there is a match) is proportional to the number of elements in the array. By contrast, hash key lookups take constant time, regardless of how many elements there are. In a production setting, we obviously would expect to look for user names in a database or server-side disk file, rather than a hash or an array.

This example also demonstrates one way to mock up an Ajax application while development is still taking place—create a server-side program that produces results for a very small subset of the data, simulating the full range of database queries that you might normally want to use. In this way, development on the JavaScript side of the project will not have to wait for the server-side portion to be complete, allowing for more parallelized development.

Parsing the Response

When the response arrives from the server, our callback routine, parseResponse, is invoked. As always, we wait until the readyState of our XMLHttpRequest is 4 and for the HTTP status code to be 200. At that point, we can expect one of four different responses from the server:

  • A yes response indicates that the user name was taken. We disable the form's submit button and display a warning. If and when the user changes the text inside of the username text field, the warning will be removed and the submit button re-enabled.

  • A no response indicates that the user name is available. We remove any warning that might have been placed, and enable the submit button.

  • An empty response might come before the yes or no, in which case we ignore it.

  • Finally, it's possible that our program will not behave precisely as we might expect. If this happens, we display the unexpected response that we received for debugging purposes. This is the sort of thing you would probably want to remove from production code.

Notice how we used a switch statement to look at the different possibilities. Also notice how we were able to reduce the complexity of our JavaScript code by sharing the work with the server. This is the key to a good Ajax application. Rather than having the client or the server do all of the work itself, each of them shares in the burden, doing what it can do fastest and most cleanly.

Finally, you might notice that for all of our talk about XML—it is, after all, the x in Ajax—there was a distinct lack of XML in this application. True, we used the XMLHttpRequest to send HTTP requests to the server, but what happened to the XML?

The truth is that Ajax is a great name, but it doesn't quite describe the range of options the programming paradigm provides. The HTTP response, as I indicated above, can come in any MIME type, although XML and plain text are the most common. If this application were returning a more sophisticated set of data, such as a store inventory or points for a chart, XML might be more appropriate. Another format that is gaining in popularity is JSON, which resembles Perl's “Data::Dumper” in its representation of JavaScript objects. Ajax is merely a technique for dividing the work between the client and the server; you should not feel compelled to use XML for the data transfer if it is inappropriate for the task at hand.



Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Good article

gds's picture

Really good articles on ajax fundamentals. One comment I have is that it is not pointed out in Part 2 and 3 that the database access, register.pl, is still in effect. It is also easy to change check-name-exists.pl above to use similar database methods as register.pl users:

use strict;
use diagnostics;
use warnings;
use CGI;
use CGI::Carp;
use DBI;
# ------------------------------------------------------------
# # Connect to the database
# ------------------------------------------------------------
my $dbname = 'test';
my $dbuser = 'gene';
my $dbpassword = '';
my $dbh = DBI->connect("DBI:mysql:dbname=$dbname",
$dbuser, $dbpassword,
AutoCommit => 1, RaiseError => 1,
PrintError => 1, ChopBlanks => 1}) ||
print "Error connecting: '$DBI::errstr' ";

# Define the usernames that are taken
# (Use a hash for lookup efficiency)
#my %usernames = ('abc' => 1,
# 'def' => 1,
# 'ghi' => 1,
# 'jkl' => 1);
# ------------------------------------------------------------
my $query = new CGI;
print $query->header("text/plain");
# Get the POST data
my $postdata = $query->param("POSTDATA");
# Get the username
my ($name, $value) = split /=/, $postdata;
my $username = '';
if ($name eq 'username')
$username = $value;
my $select_sql = "SELECT COUNT(*) FROM Users WHERE username = ?";
my $select_sth = $dbh->prepare($select_sql);
my ($username_is_taken) = $select_sth->fetchrow_array();

# If this username is defined, say "yes"!
if ($username_is_taken)
print "yes";
# Otherwise, say "no"!
print "no";

I also change it to use onblur instead of onchange but had to pass a parameter to checkUserName():

function checkUsername(val) {
var username = val; //document.forms[0].username.value;
xhr.send("username=" + escape(username));