A simple AJAX example

1. Introduction

AJAX is a method of allowing your browser to communicate with a host in a way that does not involve completely re-drawing the page you are looking at; instead, it gives the browser the opportunity, if it wants, to modify some part of the page as appropriate. Broadly speaking the sequence of events is:

  1. Your browser makes an AJAX request to a host, asking it to run a particular script, and sending some data for the script.
  2. The script on that host receives the data and uses it to generate some results to return to your browser.
  3. Your browser uses those results to decide how to update your page.

Let's look at that in more detail.

2. The AJAX Request

A webpage makes an AJAX request via JavaScript as a result of taking a number of separate steps. I have encapsulated these in a function that it may be convenient to show now, along with an example of its use.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>ajax-test</title>

<script type="text/javascript">

var  delim0 = String.fromCharCode(0);                       // Create three delimiters
var  delim1 = String.fromCharCode(1);
var  delim2 = String.fromCharCode(2);

var  colour = 0xf02020;                                     // A hex number
var  tmarg  = 20;                                           // Decimal number
var  string = "A string passed through to a PHP script.";   // String

function ajax (url, data, callbackFunction)
     {

     var request = window.XMLHttpRequest ? new XMLHttpRequest () : new ActiveXObject ("MSXML2.XMLHTTP.3.0");

     request.open ("post", url, true);
     request.setRequestHeader ("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); 
 
     request.onreadystatechange = function ()
          {

          if  (request.readyState!=4)  return;

          if  (request.status==200 && request.responseText!="")  callbackFunction (request.responseText);
          if  (request.status!=200)  alert ("Bad ajax status: " + request.status);

          request = null;

          };

     request.send (data);

     }


function getColour (results)
     {

     var  items;

     items  = results.split (delim2);                       // Cleave again to get the data

     return  parseInt (items[0]) + parseInt (items[1]) + parseInt (items[2]);

     }

function initialise ()
     {

     var  mydata;

     mydata = "colour=" + colour + "&margin=" + tmarg + "&string=" + encodeURIComponent(string);

     ajax ("ajax-test.php", mydata, initialiseContinue);

     }


function initialiseContinue (results)
     {

     var  i, items, subItems, ptr, newcol;

     ptr = document.getElementById("put_it_here");

     items  = results.split (delim0);                       // Cleave the major data items

     for  (i=0;  i<items.length;  i++)                      // Treat each major item
          {

          subItems = items[i].split (delim1);               // Cleave the item to obtain the data

          switch  (subItems[0])                             // Handle each separate set of data
               {

               case "S":                                    
                    ptr.style.marginTop = subItems[1] + "px";
                    break;

               case "T":                                    // A more complex structure
                    ptr.textContent = subItems[1];
                    newcol          = getColour (subItems[2]);
                    ptr.style.color = "#" + newcol.toString(16);
                    break;

               case "Z":                                    // Just a placeholder
                    break;

               default:
                    alert ("initialiseContinue: " + subItems);
                    return;

               }

          }

     }

</script>

</head>

<body onload="initialise();">

<p id="put_it_here"></p>

</body>
</html>

You may want to resize your browser window and scroll so that you can see the whole of the function ajax (), which has three arguments. Let's go through that next. The first thing it does is to create an XMLHttpRequest object (q.v.). This object will manage the AJAX transfer, once told to do so via its send method. Our instance of the object will be called request.

If you're wondering about the ?: syntax on that line, we're having to test whether your browser can create an XMLHttpRequest object in the way most browsers can. Some versions of Explorer can't, so for that we need to do something different. The ?: syntax is called a conditional operator (q.v.).

Next, we execute the open method on the request object. There are three arguments to the open method. The first indicates which request type is to be used. I use "post" so that the data to be sent can be encoded separately from the URL (we'll see how that works in a minute). The second argument to open is the URL of the script to be run by the server; this URL has been passed in as the first argument to the ajax function. The third argument to open indicates that you want the request to be sent asynchronously, that is, the ajax function should return as soon as the request is sent, and should not wait until all the data is returned. I set this to true because I often want to have more than one request going on at once. After the call to the open method, these three parameters are stored in the request object, ready for use later

After the open comes a call to the setRequestHeader method (q.v.). The form of this call appears to be mandatory when using the "post" request type.

Skipping forward for the moment to the end of the ajax function, we see a call to the send method. Unsurprisingly, this is where the request is actually initiated and data sent to the remote script. Note that the data being sent has been passed into the ajax function as its second argument.

Now the request has been sent, and soon data will be being returned to your browser. How is this data obtained so it can be made available to the caller of the ajax function? Return now to the middle of the ajax function, where we see the onreadystatechange event handler (q.v.) being set to something, an anonymous function (q.v.). This function will be called each time the readyState of the request object changes. You can look up the various possible values, but the only really interesting one is 4, which means that the request is complete. Notice that for other values, the function does nothing.

Once the request is complete, we check whether it succeeded or not. You will observe that status having value 200 is the test here. If so, then the callbackFunction, supplied as the third argument to the ajax function, will be called, with the responseText (the results returned from the remote script) of the request as its argument. That is where you will process what is returned.

We also check in case the returned status is not 200, so that any corrective action or fixup can be performed. In our case, a simple alert suffices.

Finally, having processed the returned results (if any), notice that we set the XMLHttpRequest obect variable, request, to NULL. To understand why this is necessary, read up about JavaScript's inner functions and closures.

3. Using the ajax function

Now that we've hidden away the innards of ajax, we can see how it's used. In our example, there's just the one call, in function initialise (). So scroll down to that and take a look. The three arguments for the call are:

  1. The URL of the script to be run - a string: "ajax-test.php". We'll look at that in a minute.
  2. A data string. We'll see how that's made up next.
  3. The name of the callback function, in this case its function initialiseContinue (...). We'll look at that after examining the PHP script.

The data string in our example was as follows:

"colour=" + colour + "&margin=" + tmarg + "&string=" + string

You can see how the variables: colour, tmarg, and string are initialised at the top of the example, and so the resulting string to be passed as data will be:

"colour=16719904&margin=20&string=A%20string%20passed%20through%20to%20a%20PHP%20script."

We've used encodeURIComponent (q.v.) to ensure that the content of the variable string, which may in general contain special characters, does not present a problem. You'll notice, for example, that the spaces in it get replaced by %20. Notice also that the hex value used to initialise colour appears as a decimal value.

You can see from this how to pass data to the script. Let's look now to how the script receives it and passes data back. Our example PHP script looks like this:

<?php

// An example of how to get the parameters supplied from the other side in an
// AJAX request, how to create a response, and how to send the response back.
//
// We use the $delimx to structure the data, so it's easy to generate, and easy to
// break apart again in JavaScript. This works quite satisfactorily and effectively.

header ("Content-Type: text/plain; charset=utf-8");    // Ensure multi-byte utf-8 returned OK

$colour = $_POST["colour"];                  // Parameters passed through
$margin = $_POST["margin"];
$string = $_POST["string"];

$delim0 = chr (0);                           // Delimiters for returned data
$delim1 = chr (1);
$delim2 = chr (2);

$num1 = $colour & 0xFF;                      // Process the argument and so derive some
$num2 = $colour & 0xFF00;                    // values to be returned
$num3 = $colour & 0xFF0000;

$outstr = "Z";                               // Start to build up $outstr as the response string

$outstr .= $delim0 . "T" . $delim1 . $string . $delim1 . $num1 . $delim2 . $num2 . $delim2 . $num3;
$outstr .= $delim0 . "S" . $delim1 . $margin;

echo $outstr;                                // Send the response string back

?>

Notice how the three names in the data string being sent to the script appear as members of the $_POST array in the PHP script, where they may be readily accessed. Notice also that there is an echo statement at the end of the script. This is how data is returned - as a string made up of all the data output by one or more echo statements. So you see that getting data from and returning it to the browser is quite straightforward. It's up to you to decide what data needs to be transmitted back and forth, of course.

The header () function call avoids problems if you are using multi-byte UTF-8 characters.

4. How to structure the data

So far, we've seen the outlines of how to make an AJAX request and receive returned data. You will note that the data - all of it - is in the form of a string. When I started using AJAX, the first thing I did was think about how to pass structured data back, such as an error messages and an array of numbers. I kept reading that XML was what got passed back, and that may certainly be used for structured data. However, I couldn't see where the XML was generated or decoded, and after a while I realised that it was up to me to arrange the encoding and decoding.

That all seemed unnecessarily complicated for my purposes, so I designed a simple protocol wherein data is delimited. Given that all that one is allowed to pass back is a string, I chose as delimiters characters that would not form part of the data. It would then be my responsibility to ensure that these characters never did, in fact, appear in the data stream. The characters I chose were:

These have values 0, 1, and 2 respectively and are the first three characters in the 7-bit ASCII table. In PHP they may be conveniently generated by the chr() function, as seen in the PHP script above. In JavaScript, String.fromCharCode() does much the same thing. The way these are used is as follows.

delim0 is used as a major separator between data items. In our example, there are three data items, each of which is tagged with a letter. delim1 is then used to separate each piece of data within that data item. The major data items are sorted and treated in initialiseContinue. It's a simple matter to cleave the data at the major item boundaries using string.split(), (rather as DNA is cleaved by enzymes in the cell), and then treat each piece separately. initialiseContinue is set up to handle that in a clear way.

In the case of the "T" major item, we see it has two data items. The first is the text string and the second is a set of numbers to represent the text-colour. The numbers in that set are delimited by the third of our delimiters, delim2. An extra level of cleaving is required to access these.

5. Points to note

5.1 Those letters

I always put a single letter as the first thing in a major data item, so that it's easily visible when doing the decoding in JavaScript, and can be easily spotted in the switch statement. In the application where I'm using this technique, there are about 100 separate calls to the ajax function. The letters get reused as necessary, but I try to use a letter consistently. Thus, "L" is always used to indicate that the particular PHP script wants to output a log message for the user.

5.2 initialiseContinue

Personally, I always call these callback-functions xxxxxxxContinue just to emphasise that they are callback functions. I also always structure them this way, even if there is only one item of data to return. For one thing, that may get expanded later, for another it allows for the case where an unexpected item is returned; this latter is then highlighted by the alert(). Usually this has meant an unexpected situation has arisen. This can then be coded for and the problem eliminated.

I also try to keep the callback functions compact, with any large amount of code for a data item relegated to another function. This is illustrated here by the getColour function, which is obviously small in our example. This is also when the next level of delimiter, delim2 comes into play.

5.3 What is the "Z" item for?

It's a convenience item. Sometimes one is building the response data in a loop, and you'd like to put out the separator first. In our example it's not really required, but in a more complex case, this is a simple way to avoid bugs from having one too many separators. As it's low-cost in terms of processing, I routinely add it in all cases.

5.4 Do I need to do all this?

Not if you have a really simple case or use for AJAX where you just want to get a single piece of information from a script back to the JavaScript. I developed this approach because I knew at the outset that my application was going to become quite large, and I'd have a variety of data to return.

6. Finally ...

You may be a novice programmer, or an experienced programmer who happens to be new to PHP, JavaScript, or HTML. Either way, I'd recommend working through this example.