Implementing an Ajax-like Interface: A Quick How-to

In the Web development world there's been a lot of buzz about an acronym many of us previously associated either with a brand of detergent or a Dutch football team. In a nutshell Asynchronous Javascript And XML means inserting new information into a Web page without reloading the whole page. Traditionally Dynamic HTML would use complex Javascripts to replace parts of a page with data that had already been downloaded or was based on user input. That's fine when managing small amounts of data, but if you needed more records from a large data source you had to make a call to the server and effectively reload the page. One common workaround used frames, but these break the unified concept of a seamlessly integrated Web interface and rely on Javascript to keep the disjointed parts together and prevent casual visitors from viewing only one frame. Whole books have been written on the wonders of Ajax, but given the ongoing state of flux in the evolution of Web browsers and competition from proprietary technologies requiring plug-ins such as Adobe's new Flex framework for Flash or Microsoft's Silverlight, many just think Ajax is more trouble than it's worth.

Surprisingly having read Christian Heilmann's excellent “Beginning JavaScript with DOM Scripting and Ajax” it took just a little experimentation to integrate Ajax into this site, so all internal links effectively load within the same page. Consider a 1000 word article or 6000 characters embedded in HTML. This would be unlikely to occupy more than 12KB the equivalent a small compressed jpeg image. By contrast reloading the whole page with linked header graphics, stylesheets and Javascripts may require way over 100Kb and although modern browsers cache such data, they still need to check for changes on every page reload.

The first challenge in deploying Ajax is capturing the correct variant of the XMLHttpRequest object, effectively XMLHttpRequest for Firefox, Safari, Konqueror, Opera and even IE7 and ActiveX for IE5.5 and IE6 (although the latter will still work with IE7, but is disabled by default due to ActiveX's inherit vulnerabilities on the Windows platform). Only users of IE Mac 5.2 (not updated since 2001), IE 4 and the old Netscape Navigator are left out, but we have a fallback solution for this dwindling pool of users. The second is to provide valid Web links that Search engines and Ajax-incapable browsers can use.

First the Ajax script:

The first module can be applied to many projects. Basically it takes two parameters the id attribute of target element (in quotes) and the URL of the script you wish to call in the background. This may be any script, static or dynamic supported by your server. It then simply replaces the content of the target element with the content returned by the script.

/* 
Adapted from Christian Heilmann's Beginning JavaScript with DOM Scripting and Ajax 
*/
simplexhr = {
doxhr : function( container, url ) {
   if( !document.getElementById || !document.createTextNode) {
      return;
   }
   simplexhr.outputContainer = document.getElementById( container );
   if( !simplexhr.outputContainer ){ return; }
   var request;
   try{
      request = new XMLHttpRequest();
   } catch ( error ) {
      try {
      request = new ActiveXObject( "Microsoft.XMLHTTP" );
      } catch ( error ) {
         return true;
      }
   }
   request.open( 'get', url );
   request.onreadystatechange = function() {
   if( request.readyState == 1 ) {
   simplexhr.outputContainer.innerHTML = '<h3>loading...</h3><hr /><p>Please wait while the server retrieves the requested information.</p>';
   }
   if( request.readyState == 4 ) {
      if ( /200|304/.test( request.status ) ) {
      simplexhr.retrieved(request);
      } else {
      simplexhr.failed(request);
      }
   }
   }
   request.send( null );
   return false;
   },
   
   failed : function( requester ) {
   simplexhr.outputContainer.innerHTML = '<p>Could not retrieve the requested data.</p>';
   return true;
   },
   retrieved : function( requester ) {
   var data = requester.responseText;
   simplexhr.outputContainer.innerHTML = data;
   return false;
   }
}

Strictly speaking we should use DOM scripting for the next bit, but we need workable links within the href attribute and an easily solution we can switch on and off dynamically, e.g. via browser detection to cater for those with non-Ajax-enabled browsers (practically IE5.2 Mac, IE Win < 5 and the old Netscape Navigator < 5. The simplest solution is to call the Ajax function within the onclick attribute. If Javascript is disabled the onclick attribute will be ignored. Moreover, the server can detect problematic browsers and, if the Ajax function is stored as a server-side variable, simply not include it for these browsers. In my PHP script I use a class variable $this->ajaxLink, which is set to ' onclick="ajaxLink();"' for compliant browsers and '' for others (a dwindling minority).

function ajaxLink(el) {
   remSelected();
   if (el.href!='#') {
      var script = el.href;
   }
   else { var script = el.title; }
   el.className='selected';
   var concat='&';
   if (!/?/.test(script)) { concat='?'; }
   script += concat + 'temp=1';
   simplexhr.doxhr('body-text',script);
   window.location='#head';
   return false;
}

function ajaxSearch(type) {
   if (type==2) {
      var f = document.asearch;
   }
   else {
      var f = document.search;
   }
   var w = f.searcht.value;
   var script = 'search.php?temp=1&w=' + escape(w);
   if (type==2) { 
      var sm='all';
      var rb=document.getElementsByName('smode');
      for (var i=0;i

This is called in compliant browsers with the following code:

<a onclick="ajaxLink(this);" href="/article.php?id=4567">Interesting Article</a>

The word this in parentheses refers to the current element and lets us grab and reset its attributes. First we get its href attribute and append the query string '&temp=1' so that the script only returns the article without the header, menu and footer. We also set its class to selected so it can be highlighted appropriately in the menu via the stylesheet. Lastly we set the return value to false.

Removing Inline onclick Event Handlers

We could convert this to a DOM-scripted version by adding a special attribute, ideally by assigning a special value to rel attribute such as "internal" to denote links we wish to load asynchronously in the targeted element.

<a rel="internal" href="/article.php?id=4567">Interesting Article</a>

Next we should replace our $this->ajaxLink (or the equivalent in your server-side language of choice with ' rel="internal' and use this DOM script to rewrite add the onclick event.


function addAjaxLinks() {
   var aTags=document.getElementsByTagName('a');
   for (var i=0;i<aTags.length;i++) {
      var rel=aTags[i].getAttribute('rel');
      if (rel=='internal') {
         aTags[i].onclick=function() {
         ajaxLink(this);
         return false;
      }
   }
}

window.onload=addAjaxLinks();

An additional function handles the search utility and I've yet to implement an Ajax solution for posting messages. Some might argue this solution avoids the X for XML, that would involve slightly more code and only really for long listings rather than simply pulling in data from scripts designed to return HTML.

Lastly the PHP script file would return either just the body text, if Ajax is enabled, or the whole page. Here's my solution:

<?php

include('/inc/blog.inc.php');

// If temp get variable is not set, set to 0
if (!isset($_GET['temp'])) { $_GET['temp']=0; }
// If temp equals 1 set $articleOnly parameter to true and remove
// header, menu and footer from returned script
$articleOnly = intval($_GET['temp'])===1 ? true:false;

// If mode is not set, use 'page' as default
if (!isset($_GET['mode'])) { $_GET['mode']='page'; }

// Cast the id to 0
if (!isset($_GET['id'])) { $_GET['id']=0; }
$_GET['id']=intval($_GET['id']);

// The 'Blog' class  calls all classes required 
// to build the page from a database query.
$page = new Blog($_GET['id'],$_GET['mode'],$articleOnly);


?>
Author: 
Neil Gardner
+