Monday, February 11, 2008

AJAX boiler-plate - asynchronous/synchronous, GET/POST, allows multiple concurrent requests

Update. 16/12/2008 5:58:37 PM. Updated formatting.

Here is my template AJAX code. It allows for the following:

  • Asynchronous or synchronous calls.
  • GET or POST request - ensuring that if it is a GET request, the URL is unique.
  • POST data sent as part of the POST request.
  • Multiple concurrent requests.

When I looked around for boiler-plate AJAX code, I didn't find any decent examples of code that allowed multiple concurrent requests. At the time I was writing a search page that would search files in mutliple locations. For a single search, I wanted to launch a different request to the server for each location. This way, I could update the page separately each time a location specific search finished and make the page look much more responsive.

One issue I came accross is that by default, IE only lets two concurrent requests occur at a time. Firefox was not so restrictive.

Examples of usage.

// url, callBackFunction, async flag, GET flag, post data
// Asynchronous GET request.
sendAjaxRequest("myScript?arg1=22&arg2=4", ajaxReqReturned, true, true, null);

// url, callBackFunction, async flag, GET flag, post data
// Asynchronous POST request.
var postData = "field1=value1&field2=value2";
sendAjaxRequest("myPage.jsp", ajaxReqReturned, true, false, postData);

/**
 * Call back function for when the AJAX call to the server returns.
 *
 * Param request - request object we are responding to
 */
function ajaxReqReturned(request) {
  var response = request.responseText;
  debug("updatePageForEnvironments with response [" + response + "].");
  // Take appropriate actions...
}

Here is the boiler-plate code.

/**
 * Writes debug to firebug console
 *
 * Param message - message to write out
 */
function debug(message) {
  if (console) {
    console.debug(message);
  } else {
    alert(message);
  }
}

/**
 * Send an AJAX request.
 *
 * Inspiration: http://saloon.javaranch.com/cgi-bin/ubb/ultimatebb.cgi?ubb=get_topic&f=20&t=005211
 *
 * Param url - URL to request
 * Param callBackFunction - function to call when the request has returned from
 *     the server
 * Param async - true if the call is asynchronous, false otherwise
 * Param get - true if this is a GET request, false if it is a POST
 * Param postPayload - post data. Ignore if get is true.
 */
function sendAjaxRequest(url, callBackFunction, async, get, postPayload) {
  var method = "GET";
  if (get) {
    // Prevent browser caching by sending always sending a unique url.
    url += "&randomnumber=" + new Date().getTime();
    postPayload = null;
  } else {
    method = "POST"
  }
  console.info("Sending [" + method + "] request "
      + (async ? "a" : "")  + "synchronously to url [" + url + "].");
  // Obtain an XMLHttpRequest instance
  var req = newXMLHttpRequest();
  // Set handler function to receive callback notifications from request.
  var handlerFunction = getReadyStateHandler(req, callBackFunction);
  req.onreadystatechange = handlerFunction;
  req.open(method, url, async);
  if (!get) {
    req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    req.setRequestHeader("Content-length", postPayload.length);
    req.setRequestHeader("Connection", "close");
  }
  req.send(postPayload);
}

/**
 *  Return: a new XMLHttpRequest object, or false if this browser doesn't
 *    support it
 */
function newXMLHttpRequest() {
  var xmlreq = false;
  if (window.XMLHttpRequest) {
    // Create XMLHttpRequest object in non-Microsoft browsers
    xmlreq = new XMLHttpRequest();
  } else if (window.ActiveXObject) {
    var avers = [
      "Microsoft.XmlHttp",
      "MSXML2.XmlHttp",
      "MSXML2.XmlHttp.3.0",
      "MSXML2.XmlHttp.4.0",
      "MSXML2.XmlHttp.5.0"
    ];
    for (var i = avers.length -1; i >= 0; i--) {
      try {
        xmlreq = new ActiveXObject(avers[i]);
      } catch(e) {}
    }
    if (!xmlreq) {
      alert("Unable to create AJAX request object.");
    }
  }
  return xmlreq;
}

/**
 * Create and return a call back function that checks for server
 * success/failure.
 *
 * Param req - The XMLHttpRequest whose state is changing
 * Param responseHandler - Function to pass the response to
 * Return: a function that waits for the specified XMLHttpRequest
 *     to complete, then passes its XML response to the given handler function.
 */
function getReadyStateHandler(req, responseHandler) {
  // Return an anonymous function that listens to the XMLHttpRequest instance
  return function () {
    // If the request's status is "complete"
    if (req.readyState == 4) {
      var message = req.getResponseHeader("status");
      // Check that a successful server response was received
      if (req.status == 200) {          //todo: catch 404 error.
        // Pass the payload of the response to the handler function
        responseHandler(req);
      } else if (message != undefined && message != null
            && message.length > 0) {
          debug("Error status  [" + req.status + "] with message ["
              + message + "].");
      }
    }
  }
}

No comments: