Self Optimizing AJAX Object

In 2007 I wrote a self contained AJAX object that supports asynchronous and synchronous access with PUT and GET methods, text and XML and XSL tranforms. The impetous for making certain methods of the object self optimizing came from an blog post by Peter-Paul Koch on @media Ajax 2007 Day 2. The example used by Dan Webb is the following addEvent function.

function addEvent(obj, evt, fn) {
    if (document.addEventListener) {
        addEvent = function (obj, evt, fn) {
            obj.addEventListener(evt, fn, false);
        }
    } else if (document.attachEvent) {
        addEvent = function (obj, evt, fn) {
            obj.attachEvent('on' + evt, fn);
        }
    }
    addEvent(obj, evt, fn);
}

Rewriting a function at runtime is a wonderful way to avoid object detection and improve performance. From Peter-Paul Koch's blog post:

Basically, what happens is that the first time addEvent is called, the function determines whether the browser supports addEventListener or attachEvent. It then rewrites itself to a new function that uses the detected method to add the event. As a result, we only have to do the object detection once, which is much more elegant than doing it every time.

I realized that an AJAX object was a perfect place to use runtime function optimization. Consider the following createXMLHTTPObject function.

createXMLHTTPObject: function () {
    var req = null;

    // Go through each factory. Once you have a working factory,
    // overwrite this function to use the working factory.
    for (var i = 0; i < this.XMLHttpFactories.length; i++) {
        try {
            req = this.XMLHttpFactories[i]();

            // The XMLHTTP object was created, set which factory to use.
            this.XMLHttpFactory = this.XMLHttpFactories[i];

            // Overwrite the createXMLHTTPObject method.
            this.createXMLHTTPObject = function () {
                var req = null;
                try {
                    req = this.XMLHttpFactory();
                } catch (e) {
                    req = null;
                }

                return req;
            };

            // Return the XMLHTTP object.
            return req;
        } catch (e) {
            continue;
        }

        break;
    }

    // XMLHTTP is not supported.
    this.createXMLHTTPObject = function () {
        return null;
    };

    return req;
},

The first time the createXMLHTTPObject function is called, it walks through the XMLHttpFactories array of functions available for different browsers to create an XML HTTP object. The XMLHttpFactories array contains the following code.

XMLHttpFactories: [
    function () {
        return new XMLHttpRequest();
    },
    function () {
        return new ActiveXObject("Msxml2.XMLHTTP.6.0");
    },
    function () {
        return new ActiveXObject("Msxml2.XMLHTTP.3.0");
    },
    function () {
        return new ActiveXObject("Msxml2.XMLHTTP");
    },
    function () {
        return new ActiveXObject("Microsoft.XMLHTTP");
    }
],

Once a working function is found to create an XML HTTP object, the working function is written to the XMLHttpFactory variable and the createXMLHTTPObject function is rewritten to use the following code.

// Overwrite the createXMLHTTPObject method.
this.createXMLHTTPObject = function () {
    var req = null;
    try {
        req = this.XMLHttpFactory();
    } catch (e) {
        req = null;
    }

    return req;
};

Using object detection in the createXMLHTTPObject method allows the createXMLHTTPObject method to support modern browsers as well as older Microsoft IE browsers. Rewriting the function means that your code doesn't have to do object detection every time the function is called, therefore improving performance for AJAX heavy web sites.

AJAX Object Methods

init(bCache)

Used to set whether or not to add a noCache parameter to the URL to avoid caching the URL's data.

bCache

Boolean. Default value is true.

load(url, callback, data, userid, password, bText)

Loads a URL and returns true for successful asynchronous calls and a xmlDocument or text variable for synchronous calls.

url

Text. Required. URL to retrieve.

callback

Function. Optional. Function that is called for an asynchronous call. When a function is provided an asynchronous call is made. When a function is not provided a synchronous call is used.

data

Text. Optional. Contains name/value pairs of data to post to the provided URL. If provided the a POST method will be used to retrieve the URL data, otherwise the GET method will be used.

userid

Text. Optional. User ID if required by URL.

password

Text. Optional. Password if required by User ID.

bText

Boolean. Optional. Determines whether to return text or and xmlDocument. Default is false.

loadText(url, callback, data, userid, password)

Shortcut to call the load function with the the bText parameter set to true.

url

Text. Required. URL to retrieve.

callback

Function. Optional. Function that is called for an asynchronous call. When a function is provided an asynchronous call is made. When a function is not provided a synchronous call is used.

data

Text. Optional. Contains name/value pairs of data to post to the provided URL. If provided the a POST method will be used to retrieve the URL data, otherwise the GET method will be used.

userid

Text. Optional. User ID if required by URL.

password

Text. Optional. Password if required by User ID.

transform(el, xmlData, xslData)

Transform XML data using supplied XSL document and append the data to an HTML element.

el

Object. Required. Element that is to received the transformed data.

xmlData

xmlDocument. Required. XML data to transform.

xmlData

xmlDocument. Required. XSL data to transform the XML data.

getXML(req)

Used internally by the object to create an xmlDocument. This is used to work around certain browser errors when trying to access the xmlDocument of a request object.

req

Request Object. Required. Contains object returned by the send method of the XML HTTP Request object.

getXMLFromString(str)

Returns an xmlDocument object from an XML data string.

str

Text. Required. Text of XML data.

createXMLHTTPObject

Used internally to create an XML HTTP object.

getNodeValue(obj, tag)

Used to retrieve data from an XML document.

obj

xmlDocument. Required.

tag

Text. Required. Name of XML element to retrieve data.

Usage

Include the following lines in the head section of your web page.

<script type="text/javascript" src="js/ajax.js"></script>

In your javascript function add calls to the AJAX object. This example retrieves the internet address from the XML data and places it in the passed element.

function loadAsynchronousExample(el, url) {
    // Clear element first.
    el.innerHTML = "";
    ajax.load(url, function (xml) {
        el.innerHTML = ajax.getNodeValue(xml, "internetaddress");
    });
}

Samples and Download

  • dirajax.htm — Directory Lookup AJAX Demo that retrieves directory data into a table based on the data typed in the text input field. Try typing my name then hover the mouse cursor over the names in the table.
  • ajaxtest.htm — Test page to display the use of different AJAX object methods. Be sure to click the function buttons before and after the AJAX call to see the non-optimized and optimized version of the methods.
  • ajax.js. Right click to save. If you have Firefox or Safari, click to view.

3 Comments

Gravatar Image1. Posted at 5/12/2010 9:18:53 AM by Tanny O'Haley

I have tested this on the iPhone, iPad and Palm Pre. The Palm Pre currently doesn't support synchronous calls, however synchronous and asynchronous calls work on the the Apple products. If you have an Android based device, could you please test synchronous calls with the ajaxtest.htm page?

Gravatar Image2. Posted at 9/21/2010 2:13:58 PM by Nate Bundy

Just tested this on Android 2.1 on the Epic 4G. All asynchronous calls work; all synchronous calls fail silently except for xsl transform synchronous which produces:

Error: NOT_FOUND_ERR: DOM Exception 8

Gravatar Image3. Posted at 9/23/2010 11:15:52 PM by Tanny O'Haley

Hi Nate,

I appreciate your testing. It's not a surprise to me that the Android version of Webkit doesn't support synchronous calls since there does not seem to be a unified version of mobile webkit supported by all of the mobile browsers. Each version seems to be a little different, some support synchronous AJAX calls and some don't, some support orientation changes for web pages and others don't, ... , yet they all use Webkit.

Not being able to make a synchronous call means that the developer/programmer has to perform a work around in the onsubmit event. Instead of performing a lookup, validating the data and submitting a form by returning a true, the programmer has to make an asynchronous call and return false. When the call is returned, then validate the data, set a flag saying the data is valid and resubmit the form. On the other hand the developer could just submit the form and let the server validate the data though I believe it's a better experience for the user to validate the data from the client.

Add a comment

Discussion for this entry is now closed.