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:
We'll take a simple example of loading up a drop down list. Our list will be empty until you click a button, after which the list will be loaded with items obtained via AJAX. If you then make a selection from the list, an alert showing your selected item will appear.
You can try the example here in a new window.
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 ternary 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.
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 loadSelect (). So scroll down to that and take a look. The three arguments for the call are:
The data string consists of "name=value" pairs separated by ampersands (&). In our example, there are two such pairs, as follows:
mydata = "num=" + tmarg + "&string=" + encodeURIComponent(string);
You can see how the variables: tmarg and string are initialised at the top of the example, and so the resulting string to be passed as data will be:
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. The string to be sent may have many "name=value" pairs, separated by an & character; Ours has just the two.
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:
Notice how the two 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.
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:
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 loadContinue. 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. loadContinue is set up to handle that in a clear way.
In the case of the "N" and "M" major items, we see that each has one data item. For "N", this is a number, and for "M" it is a string. The "S" major item has just one subitem, which is a set of strings. The strings in that set are delimited by the third of our delimiters, delim2. An extra level of cleaving is required in loadContinue to access these.
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.
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.