This article gives a practical introduction to AJAX. We will describe two successful PHP libraries and create a small application, a Google Suggest-like search field.
To get the most out of this article, you should have good skills in object-oriented programming in PHP4 or PHP5. You should also have some knowledge of JavaScript.
AJAX, which stands for Asynchronous JavaScript And XML, is a programming technique that combines different technologies. It includes a standard user interface using (X)HTML and CSS, dynamic elements and interaction via the DOM (Document Object Model) and asynchronous data exchange using the XMLHttpRequest object. JavaScript is used to tie these elements together, adding logic and dynamically updating the user interface as needed.
While AJAX has "XML" in its name, it is not necessary to use XML for data exchange. Other common formats for data exchange include plain text, preformatted HTML (which is added to the current page using the innerHTML property) and JSON (a JavaScript notation that can be run through the eval function to turn it into native JavaScript types). You can also use any other data format that is compatible with JavaScript and PHP.
A simplified definition of AJAX is to use JavaScript to communicate with the server outside of the normal GET or POST flow. The important thing is not the specific set of technologies, but the richer user experience created by this development model.
AJAX is generally built on top of the XMLHttpRequest object, which is a standard in modern browsers. If you use a library to add AJAX support to your application, you do not need to learn much about XMLHttpRequest because the library will take care of the details. In Internet Explorer, XMLHttpRequest is a built-in ActiveX object, while in Firefox, Safari and most other browsers, it is a native JavaScript object.
XMLHttpRequest offers a simple API that enables you to make HTTP requests to your server. Those requests can use GET or POST. To the server they look like any other request from the user's browser, even including all cookies for that domain and HTTP auth (if it is turned on).
On the JavaScript side, XMLHttpRequest gives you access to the content and headers when sending or receiving a request. This allows you to set HTTP headers, which are commonly used to tell the server that the request was made by XMLHttpRequest and not by normal user interaction. Once content has been received, it can be treated as plain text, or if its content-type is "text/xml", as an XML DOM object. This DOM support has made XML a popular choice for moving data between client and server, but the plain text support permits you to use any format you can parse in JavaScript.
The main reason to use AJAX is to enhance the interactivity of a web application. With AJAX, users receive feedback from programs much faster. The normal cycle of "click-and-wait" is broken, and the interface acts and feels more like a native, client-side application.
In a workflow of a typical web application, a user fills out a form and submits it back to the server. The web server processes the form and then sends the response back to the user, who then has to wait. The page is reloaded with all its contents and structure. Lots of bandwidth is wasted, since the new page contains much of the HTML from the original page.
An AJAX application, on the other hand, can send requests to the web server to retrieve only the data that is needed, and uses JavaScript in the browser to process the web server response. Using this extra layer of JavaScript, the application does not slow down the user interface. It makes the application more responsive, because the amount of data exchanged between browser and server is vastly reduced. Furthermore, there is less processing to be done on the server side, because a lot of the tasks of the application are performed by the client.
Unlike with other tools used to make highly interactive applications (such as Flash), AJAX fits into everyday development processes. You can continue using your favorite editor or integrated development environment (IDE) without needing to learn any special tools.
Finally, there are many free, Open Source toolkits that aid the AJAX development process and reduce the amount of JavaScript code you have to write. In this article, we will show you how to include AJAX in your application using two of the best-known libraries.
As you do not know which browser the client uses, your application might not run on incompatible browsers or if JavaScript is switched off. Consequently, you should provide a fallback method. One way to accomplish this is to build a normal application first, and then extend it using optional AJAX actions.
You should also be aware that AJAX applications will not run on Internet Explorer when ActiveX is disabled, which is often the case in public settings such as internet cafes. You also have to deal with the quirks of different browsers and platforms when testing your new code, but you are probably used to this scenario from working with CSS.
AJAX offers a lot of added interactivity, but there are some tasks that it cannot do, such as drawing or animations. In that case, using Flash would be better, and though a mixed AJAX / Flash solution can be powerful, the increased development complexity may lead you to choose only one of them at a time.
There are many libraries or toolkits that provide easy integration (or at least try to) between JavaScript and PHP. All of them take care of sending the data in some way, but most toolkits offer something extra, from direct mapping of a PHP class's methods to a JavaScript proxy, to a framework for building widgets. Let us take a look at two popular packages, JPSpan and HTML_AJAX.
JPSpan is one of the more mature libraries for PHP and its first release was in November 2004. It provides cross-browser, XMLHttpRequest-based AJAX support, with synchronous or asynchronous operation. An object-oriented API is offered for JavaScript and PHP.
The default JPSpan server is the JPSpan_Server_PostOffice class. It can be used to map all classes to JavaScript, or to map individual classes.
Calls are made by creating an instance of the JavaScript class and then calling its methods. In asynchronous operation, a callback class is specified when the instance is created. Results are sent to methods with the same name as the called method.
If you are using PHP4, due to its limitations, all class and method names are lowercase.
JPSpan also provides complex datatypes like multi-dimensional arrays and objects, serialization and support for passing PHP errors to JavaScript, with configurable error handling on the JavaScript side.
We will now take a closer look at how JPSpan works by creating a simple "Hello World" example. It should show us a random string in response to a mouse click and let us add new random strings.
Here is the server-side file:
jpspan_server.php: <?php session_start(); // our hello world class class HelloWorld { function HelloWorld() { if (!isset($_SESSION['strings'])) { $_SESSION['strings'] = array('Hello','World','Hello World'); } } function addString($string) { $_SESSION['strings'][] = $string; return $this->stringCount(); } function randomString() { $index = rand(0,count($_SESSION['strings'])-1); return $_SESSION['strings'][$index]; } function stringCount() { return count($_SESSION['strings']); } } // Including this sets up the JPSPAN constant require_once 'jpspan-0.4.3/JPSpan.php'; // Load the PostOffice server require_once JPSPAN . 'Server/PostOffice.php'; // Create the PostOffice server $S = & new JPSpan_Server_PostOffice(); // Register your class with it... $S->addHandler(new HelloWorld()); // This allows the JavaScript to be seen by just // adding ?client to the end of the server's URL if (isset($_SERVER['QUERY_STRING']) && strcasecmp($_SERVER['QUERY_STRING'], 'client')==0) { // Compress the Javascript output (e.g. strip whitespace) // turn this off if it has performance problems define('JPSPAN_INCLUDE_COMPRESS',false); // Display the Javascript client $S->displayClient(); }else { //This is where the real serving happens... including error handler, PHP errors, //warnings and notices serialized to JS require_once JPSPAN. //'ErrorHandler.php'; // Start serving requests... $S->serve(); } ?>
First, we create a class called HelloWorld with some simple PHP methods to add strings to a session array, return its count, and return a random string from it. These methods are addString(), randomString() and stringCount(), respectively.
You can do anything you would with a normal class here, but remember that this class is being recreated on a JavaScript-side call. Therefore, if you want to keep class members between calls, you will need to put the class instance in the session.
Next, JPSpan and the relevant file for the PostOffice server is included, a new PostOffice server is created and our example class is registered. The following if statement decides whether to output the needed JavaScript client or to handle requests.
Next, we look at the client side of the application:
<html> <head> <title>JPSpan Hello World</title> <script type='text/javascript' src='jpspan_server.php?client'></script> <script> // create a javascript class to hold the callback methods var hwCallback = { randomstring: function(result) { document.getElementById('canvas').innerHTML += '<p>'+result+'</p>'; }, stringcount: function(result) { document.getElementById('count').innerHTML = result; }, addstring: function(result) { document.getElementById('count').innerHTML = result; } } // create our remote object. Note the lowercase mapping, this is because in // php4 php classes and functions have no case. On the server you can // register each function adding the case back var remoteHW = new helloworld(hwCallback); function do_addString() { remoteHW.addstring(document.getElementById('string').value); document.getElementById('string').value = ''; } </script> </head> <body onLoad="remoteHW.stringcount()"> <input type="button" name="check" value="Show Random String" onclick= "remoteHW.randomstring(); return false;"> <div>Number of Random Strings: <span id="count"></span></div> <div id="canvas" style="border: solid 1px black; margin: 1em; padding: 1em;"></div> <div> Add new String: <input type="text" name="string" id="string" size="20"> <input type="button" name="check" value="Add new String" onclick= "do_addString(); return false;"> </div> </body> </html>
Notice the clear separation between HTML and PHP. You only have to include the following code to call the file from the server-side example:
<script type='text/javascript' src='jpspan_server.php?client'></script>
Next we create a JavaScript class hwCallback to hold the callback methods.
They will replace the content of some <div> elements with the value submitted by the server, using the JavaScript method innerHTML.
After this, we only have to create the remote object:
var remoteHW=new helloworld (hwCallback);
The class helloworld is the exported PHP class we created on the server side. The class name is now all lowercase (as stated earlier) because we are using PHP4.
After adding the HTML form that includes the JavaScript handlers, our first AJAX application is now ready!
Both JPSpan and HTML_AJAX can be used asynchronously (using callbacks) or synchronously (returning values directly). Generally it is best to perform async callbacks, since sync calls can cause the user interface to freeze while waiting for feedback. Sometimes this is actually what you want, but you should post some kind of "please wait" message if you are transferring much of anything. Synchronous calls provide an easier programming model, but do not let that lull you into using them on a regular basis. While AJAX requests on a local network usually take less then 50ms, those going over the internet will rarely be less than 250ms. This means that the user will be unable to click on any other elements, or even switch to another browser tab for a quarter of a second to half a second.
HTML_AJAX has more features than JPSpan. However, to keep things simple, we will use a configuration similiar to the one we applied in the case of JPSpan: an external server page that generates a JavaScript proxy, which is included in our HTML page. HTML_AJAX can also be used with the proxy and server code generated inline, all in one single script. However, we do not recommend this as it removes your ability to cache the generated JavaScript.
An HTML_AJAX server page is generally very simple. It creates an HTML_AJAX_Server instance, registers all the classes you want exported (called stubs) and handles incoming requests. The incoming requests can be of three different types:
HTML_AJAX also provides AJAX functionality that can be used purely from the JavaScript side. This allows you to add some basic AJAX functionality to your site very quickly, or to tie HTML_AJAX into your current framework. One basic use is to update the contents of an HTML element with a fragment generated from another PHP page. This gives you the flexibility of an <iframe> element without many of its drawbacks:
<html> <head> <script type='text/javascript' src="server.php?client=main"></script> <script type='text/javascript' src="server.php?client=dispatcher"></script> <script type='text/javascript' src="server.php?client=HttpClient"></script> <script type='text/javascript' src="server.php?client=Request"></script> <script type='text/javascript' src="server.php?client=json"></script> </head> <body> <script type="text/javascript"> function clearTarget() { document.getElementById('target').innerHTML = 'clear'; } // grab is the simplest usage of HTML_AJAX. You use it to perform a request to // a page and get its results back. It can be used in either Sync mode where // it returns a directory or with a call back. Both methods are shown below var url = 'README'; function grabSync() { document.getElementById('target').innerHTML = HTML_AJAX.grab(url); } function grabAsync() { HTML_AJAX.grab(url,grabCallback); } function grabCallback(result) { document.getElementById('target').innerHTML = result; } // replace can operate either against a url like grab or against a remote // method if it is going to be used against a remote method. defaultServerUrl // needs to be set to a url that is exporting the class it is trying to call. // Note that replace currently always works using Sync AJAX calls. an option // to perform this with Async calls may become an option at some further // time. Both usages are shown below function replaceUrl() { HTML_AJAX.replace('target',url); } </script> <ul> <li><a href="javascript:clearTarget()">Clear Target</a></li> <li><a href="javascript:grabSync()">Run Sync Grab Example</a></li> <li><a href="javascript:grabAsync()">Run Async Grab Example</a></li> <li><a href="javascript:replaceUrl()">Replace with content from a URL</a></li> </ul> <div style="white-space: pre; padding: 1em; margin: 1em; width: 600px; height: 300px; border: solid 2px black; overflow: auto;" id="target">Target</div> </body> </html>
After including the needed JavaScript, you can grab content from a URL on your server either in sync mode using HTML_AJAX.grab(url) or in async mode using HTML_AJAX.grab(url,grabCallback), where grabCallback is the callback function HTML_AJAX automatically calls after grabbing.
Using HTML_AJAX.replace('target',url) replaces the HTML element that has the target ID with the content from the URL.
For security reasons, you can only grab from URLs on your server. This is not an HTML_AJAX-specific feature, but a limitation in your browser to prevent cross-site scripting (XSS) attacks.
For a more advanced example, we will create a search box similar to Google Suggest for searching PEAR packages.
Providing users with word and phrase suggestions on how to complete a request in an input box makes your site more attractive and easier to use. Normally this is done with a combination of some text input and a dropdown list that are kept synchronized. While the user types into the input form, an initially invisible <div> element is created to contain the suggestions as they appear. The input field needs an event handler to monitor the text it contains in order to ensure that the suggestion list is always relevant.
Back in the browser, the callback function picks up the suggestions and applies a DOM manipulation so the user can view and select them. Each entry will have an event handler to update the input field when clicked.
Using HTML_AJAX, we can manage the client-server interaction with a few lines of code. The class for handling the functionality on the PHP side is pretty simple:
suggest.class.php: class suggest { function suggest() { require_once 'pear_array.php'; $this->strings = $aPear; } function getString($input='') { if ($input == '') return ''; $input = strtolower($input); $suggestStrings=array(); foreach ($this->strings as $string) { if (strpos(strtolower($string),$input) === 0) { $suggestStrings[] = $string; } } return $suggestStrings; } }
In this example, we define an array of possible search terms. For normal use, you might get those values from a database. The only method needed for the search list is getString(). It expects a string and compares it with every possible entry of the array. If an element matches, it is copied to a result-set array and returned.
Next, we have to start up the server. For this example, we will use the AutoServer class:
auto_server.php: session_start(); require_once 'HTML/AJAX/Server.php'; class AutoServer extends HTML_AJAX_Server { // this flag must be set for your init methods to be used var $initMethods = true; // init method for my suggest class function initSuggest() { require_once 'suggest.class.php'; $suggest = new suggest(); $this->registerClass($suggest); } } $server = new AutoServer(); $server->handleRequest();
By extending the server class and adding an init method for each class, you can manage several PHP classes being exported using only one server. You just have to set the variable $initMethods as "true" and name the init methods in the format: init[className]. In our example it is called initSuggest.
The AutoServer class is more powerful than necessary for a simple example like this, but is also a very interesting function of HTML_AJAX and shows its usefulness in bigger projects.
If our method does not produce any errors, we can now go further to implement the Suggest example on the client side:
<html> <head> <title>HTML_AJAX Suggest</title> <script type='text/javascript' src='auto_server.php?client=all&stub=suggest'></script> <script> function do_suggest() { remoteSuggest.getstring(document.getElementById('string').value); } // create a javascript hash to hold our callback methods var suggestCallback = { getstring: function(result) { document.getElementById('suggestions').innerHTML = result; } } // create our remote object. Note the lowercase mapping. This is because in // php4, classes and functions have no case. in the server you can // register each function to add the case back var remoteSuggest = new suggest(suggestCallback); </script> </head> <body> <div> Type in a PEAR Package: <input type="text" name="string" id="string" size="20" onkeyup=" do_suggest(); return false;"> <input type="button" name="check" value="suggest..." onclick="do_suggest(); return false;"> </div> <div id="suggestions"> </div> </body> </html>
First, we include the JavaScript for the AutoServer class. Then, we create the JavaScript code for sending a request, do_suggest(), and the callback function for displaying the results. Finally, we create a new instance of our AJAX engine.
The rest of our first example is a simple form with a single input field and a <div> element for showing the results. After adding the event handler onkeyup="do_suggest(); return false;" to the input, the function do_suggest() is called after every keypress. The handler onkeypress would be too early.
After changing the value of the input field, the function do_suggest() is called. This in turn calls the remoteSuggest.getstring() method of the HTML_AJAX JavaScript class. HTML_AJAX manages the conversation with the server. Next, the server replies with an array of possible suggestions. Then, HTML_AJAX calls the callback function, passing the data from the server.
Then the callback function does the DOM manipulation and outputs all suggestions in the <div> element.
Now we have a working example, but it is not ideal. The auto-complete function of the client's browser will interfere with the application's usability. We can switch it off easily by adding the attribute autocomplete="off" to the input field. Second, the suggestion list is not visually appealing. We will improve our callback function as shown below:
var suggestCallback = { getstring: function(resultSet) { var resultDiv = document.getElementById('suggestions'); resultDiv.innerHTML = ''; for(var f=0; f<resultSet.length; ++f){ var result=document.createElement("span"); result.innerHTML = resultSet[f]; resultDiv.appendChild(result); } } }
After emptying the <div> element for results (resultDiv), each result is wrapped in a <span> element for better formatting and added to the <div> element in a for loop. This is done using the JavaScript methods result=document.createElement("span") and appendChild().
For better layout we define some CSS:
* { padding: 0; margin: 0; font-family : Arial, sans-serif; } #suggestions { max-height: 200px; width : 306px; border: 1px solid #000; overflow : auto; margin-top : -1px; float : left; } #string { width : 300px; font-size : 13px; padding-left : 4px; } #suggestions span { display: block; }
The last section is the most important, so that each suggestion span is shown one below the other.
The next step is to initially hide the <div> element for results by adding "display: none" in the CSS file, and to set it to visible when we receive results to display. Thus, we add the following callback method:
resultDiv.style.display='block'; if (!resultSet) resultDiv.style.display='none';
This will also hide our <div> element when the server does not know what to suggest.
How can we make the example more interactive? We can see the suggestions, but cannot choose from the list. To add more interactivity to the results, we can add event handlers to each span:
result.onmouseover = highlight; result.onmouseout = unHighlight; result.onmousedown = selectEntry;
This adds a JavaScript function to each event we want to define. highlight() and unHighlight() simply change the CSS class of the span:
function highlight (){ this.className='highlight'; }
Corresponding CSS class:
.highlight { background-color: 0000ff; color: fff; }
(We have not shown the unHighlight() function. It simply removes the styling applied by the highlight() function.)
At a minimum, we want to insert the suggestion automatically into the input field by clicking on it. Since the input field has the string id, we can set its value with the following function:
function selectEntry (){ document.getElementById('string') .value = this.innerHTML; }
This replaces the value of the input field with the content of the span, which is nothing more than one result served by our AJAX server.
The layout now looks like the screenshot below:
HTML_AJAX example: Suggest
The example works, but we have too many requests sent to the server. If the user types very fast, it is possible that Request 2 is sent before the result of Request 1 is back.
To prevent this, we will use a pattern called "submission throttling". Every 350 milliseconds, the browser checks if the value in the input field has changed and, if yes, a request is submitted. If a user types very fast, we save some bandwidth and do not disturb the user while he is typing. The server then produces an ordered list of suggestions. If the input field is empty, we do not send a request, but hide the resulting <div> element.
Here is the finished Suggest example:
<html> <head> <title>HTML_AJAX Suggest</title> <link rel="StyleSheet" type="text/css" href="suggest3.css" /> <script type='text/javascript' src='auto_server.php?client=all&stub=suggest'></script> <script> var string = ''; var oldstring = ''; var timeout= 1000; /*milliseconds to timeout; good value is 350*/ function do_suggest() { string = document.getElementById('string').value; if (string != oldstring) { /* don't send request when input field is empty */ if (string) { remoteSuggest.getstring(string); } /* hide div instead */ else { document.getElementById('suggestions').style.display = 'none'; } oldstring = string; } window.setTimeout('do_suggest()', timeout); } // create a javascript hash to hold the callback methods var suggestCallback = { getstring: function(resultSet) { var resultDiv = document.getElementById('suggestions'); resultDiv.innerHTML = ''; resultDiv.style.display = 'block'; if (!resultSet) resultDiv.style.display = 'none'; else{ for(var f=0; f<resultSet.length; ++f){ var result=document.createElement("span"); result.innerHTML = resultSet[f]; result.onmouseover = highlight; result.onmouseout = unHighlight; result.onmousedown = selectEntry; resultDiv.appendChild(result); } } } } // create our remote object var remoteSuggest = new suggest(suggestCallback); // functions for interactivity function highlight (){ this.className = 'highlight'; } function unHighlight () { this.className = ''; } function selectEntry () { document.getElementById('string').value = this.innerHTML; } </script> </head> <body onload="do_suggest()"> <h1>HTML_AJAX Example: Suggest</h1> <p>Note: timeout is set to 1000ms for better demonstration. A good choice for daily use is 350ms.</p> <div id="error"></div> Type in a PEAR Package: <form method="get" id="suggest"> <input type="text" name="string" id="string" size="20" autocomplete="off"> <input type="button" name="check" value="suggest..." onkeyup="do_suggest(); return false;"> <div id="suggestions"> </div> </form> </body> </html>
As you can see, it is pretty simple to add interactivity to your forms and applications. This example can be extended by handling key requests, letting the user navigate through result lists with the cursor, or by adding caching functionality to save bandwidth.
When trying out the examples using HTML_AJAX, you will notice a red box containing a "Loading..." notice each time an AJAX call is performed. This is automatically done by HTML_AJAX.
This notice is a <div> element that is created if it does not already exist. To show a different message, insert a <div> element with id="HTML_AJAX_LOADING" in your HTML, such as in the example below:
<div id="HTML_AJAX_LOADING" style= "background-color : blue; color : white; display : none; position : absolute; right : 50px; top : 50px;"> Loading new string...</div>
If you do not want this message to be shown on every request, you have to overwrite the JavaScript function that generates it:
HTML_AJAX.onOpen = function(){ // do nothing }
As you are experimenting with AJAX, you will find one of the biggest changes to be your debugging approach. Instead of having to care solely for PHP code debugging, now you have to debug JavaScript and the AJAX communication between the two. However, this can be quite easy to manage.
First, always test each piece separately. When working in JavaScript, make sure to create a debugging function. The easiest thing to do is to create your own JavaScript equivalent of the PHP print_r() function, as shown below:
function print_r(input) { var ret; ..for(var i in input) { ....ret += "["+i+"] = "+input[i]+"\n"; ..} ..alert(ret); }
JPSpan also offers logging capabilities through its observer functionality. You can use this to log AJAX call errors and successes.
The default server setup also passes PHP errors across as JavaScript alerts. You might also notice that you are seeing alerts from JavaScript errors; this happens because JPSpan is catching them and creating alerts as well.
When using HTML_AJAX, you can add your own error handler to change a <div> element with the error id:
HTML_AJAX.onError = function(e) { msg = "\n\n"; for(var i in e) { msg += i + ':' + e[i] +"\n"; } document.getElementById('error'). innerHTML += msg; }
This will catch all your AJAX errors, which are normally just PHP errors, but can also include 404s, timeouts, and so on.
Finally, we recommend developing on Firefox and then testing in Internet Explorer. Firefox's built-in development tools are generally better than anything available for Internet Explorer, and there are tons of great Firefox extensions.
Now that you know what AJAX is and how to use it, you can start adding it to your website. In most cases, this technology will prove useful (if used correctly, of course). This does not mean, however, that you should employ it on all sites. You should always take your goals into account. On some sites it is better to start with basic navigation or content presentation, then spend additional time introducing AJAX to add greater interaction. Another important factor is your target audience. For example, if the majority of your users have JavaScript disabled, AJAX is not a good idea.
As long as you keep the usability of your site as the central issue, you are bound to see good results with AJAX!