At the Forge - Beginning Ajax

How to put the A (asynchronous) in Ajax.

Note that ajax-test.html, although simple, is a fully working Ajax program. In order for it to work, you need to have a file named atf.html in the DocumentRoot directory of your Web site. (Otherwise, you will get an HTTP response code of 404.) If you've ever wondered how hard it is to perform an Ajax call, you now can see that it's relatively simple.

Adding Ajax to Registration

Now that we have seen how an Ajax program works, let's use this knowledge to modify the registration program that we built last month. Our old registration page defined a list of user names in the JavaScript. If the user's requested user name was a member of that list, we alert the user to the error and forbid the user from actually registering.

I won't describe all of the problems with this approach, as there are many. As a simple alternative, what if we were to use Ajax to retrieve the list of user names? That way, we could be sure that the list was up to date.

What if, instead of having the array contents hard-coded, we were to download them from a Web page on the server? (This is admittedly not as sophisticated as getting a yes or no answer to a specific user name; we will get to that functionality in next month's column.) If the Ajax-retrieved list of user names was generated dynamically, we could have it grab appropriate data from the database and then return an XML document that easily could be turned into an array. To make the example easier in this month's column, we don't use a dynamic page, but rather a static one. However, if you have done any server-side Web programming in the past, you probably will understand how to take our file, usernames.txt (Listing 2), and turn it into a dynamic page.

A registration page that follows this principle is shown in Listing 3. That file, ajax-register.html, is similar to the registration form we created last month. In last month's non-Ajax version, we defined an array (usernames). We then defined a checkUsername function that is invoked by the onchange handler for the username text field. This had the effect of invoking checkUsername when the user completed the user name. If the requested user name was in the usernames array, the user was given a warning, and the submit button was disabled. Otherwise, the user was able to submit the form to the server-side registration program, presumably as a first step to participating in the site.

To turn last month's registration page into an Ajax-style one, we modify the checkUsername function, which is invoked when the user finishes entering his or her requested user name. Instead of defining the usernames array, we instead have checkUsername fire off an Ajax request to the server. Unlike last month's non-Ajax version, this is all that checkUsername will do. The updated function looks like this:

function checkUsername() {"GET", "usernames.txt", true);
xhr.onreadystatechange = parseUsernames;

As you can see, our function is requesting the file usernames.txt from the server. When xhr's state changes, we ask to invoke the parseUsernames function. It is in this function that we have put the serious logic, first turning the retrieved file contents into an array:

var usernames = [ ];

if (xhr.readyState == 4) {
if (xhr.status == 200) {
    usernames = xhr.responseText.split("\n");

Here, we see the standard Ajax pattern repeated from the previous example: wait for xhr.readyState to be 4, and then check that xhr.status (the HTTP response status code) is 200. At that point, we know we have received the contents of usernames.txt, which (as you can see from Listing 2) contains the existing user names, one user name per line. We use JavaScript's split function to turn this into an array, which we assign to usernames.

From this point on, we can reuse the logic from last month's non-Ajax version, first grabbing the various node IDs from the page, using DOM methods:

var new_username = document.forms[0].username.value;
var found = false;
var warning = document.getElementById("warning");
var submit_button = document.getElementById("submit-button");

Then, we check to see if the requested user name is in our array:

for (i=0 ; i<usernames.length; i++)
    if (usernames[i] == new_username)
        found = true;

If the user name is found in the list, we issue a warning at the top of the page. Otherwise, we clear out any warning that might be there:

if (found)
    setText(warning, "Warning: username '" + new_username +"' was taken!");
    submit_button.disabled = true;

    submit_button.disabled = false;

Now, is this a good way to handle the checking of user names? Not really—although now that we have the basic Ajax logic in place, we can modify it slightly to be more efficient and secure.

One problem is that the list of user names is in a static file. Perhaps our server is running a cron job that creates usernames.txt on a regular basis, but that seems a bit silly when we can instead use a server-side program to query the database dynamically. Switching from a static file to a dynamic page thus seems like a good idea, if only for performance reasons.

There are security reasons as well. As with last month's version, we are downloading the entire list of user names to the user's browser. This means that a potentially malicious user would have access to all of the user names and would be able to poke through them, either with the intention of trying to break into the site or spam the users.

One potential downside of using Ajax for this type of check is the speed issue. As I indicated previously, the core of Ajax is its asynchronous nature, which means that we cannot know how long it will take for the server to respond to our query. In my simple tests, the round trip from my browser to my server and back was nearly instantaneous, and it provided me with useful feedback right away. On a more heavily loaded server, or with a more sophisticated database query, or if users have slow Internet connections, asynchronous calls might begin to feel sluggish. That said, even the worst Ajax function will likely be faster than a page refresh, because of the reduced overhead that is involved.