function AjaxRequest() {
  var req = new Object();
  
  // -------------------
  // Instance properties
  // -------------------

  /**
   * Timeout period (in ms) until an async request will be aborted, and
   * the onTimeout function will be called
   */
  req.timeout = null;
  
  /**
   *  Since some browsers cache GET requests via XMLHttpRequest, an
   * additional parameter called AjaxRequestUniqueId will be added to
   * the request URI with a unique numeric value appended so that the requested
   * URL will not be cached.
   */
  req.generateUniqueUrl = true;
  
  /**
   * The url that the request will be made to, which defaults to the current 
   * url of the window
   */
  req.url = window.location.href;
  
  /**
   * The method of the request, either GET (default), POST, or HEAD
   */
  req.method = "GET";
  
  /**
   * Whether or not the request will be asynchronous. In general, synchronous 
   * requests should not be used so this should rarely be changed from true
   */
  req.async = true;
  
  /**
   * The username used to access the URL
   */
  req.username = null;
  
  /**
   * The password used to access the URL
   */
  req.password = null;
  
  /**
   * The parameters is an object holding name/value pairs which will be 
   * added to the url for a GET request or the request content for a POST request
   */
  req.parameters = new Object();
  
  /**
   * The sequential index number of this request, updated internally
   */
  req.requestIndex = AjaxRequest.numAjaxRequests++;
  
  /**
   * Indicates whether a response has been received yet from the server
   */
  req.responseReceived = false;
  
  /**
   * The name of the group that this request belongs to, for activity 
   * monitoring purposes
   */
  req.groupName = null;
  
  /**
   * The query string to be added to the end of a GET request, in proper 
   * URIEncoded format
   */
  req.queryString = "";
  
  /**
   * After a response has been received, this will hold the text contents of 
   * the response - even in case of error
   */
  req.responseText = null;
  
  /**
   * After a response has been received, this will hold the XML content
   */
  req.responseXML = null;
  
  /**
   * After a response has been received, this will hold the status code of 
   * the response as returned by the server.
   */
  req.status = null;
  
  /**
   * After a response has been received, this will hold the text description 
   * of the response code
   */
  req.statusText = null;

  /**
   * An internal flag to indicate whether the request has been aborted
   */
  req.aborted = false;
  
  /**
   * The XMLHttpRequest object used internally
   */
  req.xmlHttpRequest = null;

  // --------------
  // Event handlers
  // --------------
  
  /**
   * If a timeout period is set, and it is reached before a response is 
   * received, a function reference assigned to onTimeout will be called
   */
  req.onTimeout = null; 
  
  /**
   * A function reference assigned will be called when readyState=1
   */
  req.onLoading = null;

  /**
   * A function reference assigned will be called when readyState=2
   */
  req.onLoaded = null;

  /**
   * A function reference assigned will be called when readyState=3
   */
  req.onInteractive = null;

  /**
   * A function reference assigned will be called when readyState=4
   */
  req.onComplete = null;

  /**
   * A function reference assigned will be called after onComplete, if 
   * the statusCode=200
   */
  req.onSuccess = null;

  /**
   * A function reference assigned will be called after onComplete, if 
   * the statusCode != 200
   */
  req.onError = null;
  
  /**
   * If this request has a group name, this function reference will be called 
   * and passed the group name if this is the first request in the group to 
   * become active
   */
  req.onGroupBegin = null;

  /**
   * If this request has a group name, and this request is the last request 
   * in the group to complete, this function reference will be called
   */
  req.onGroupEnd = null;

  // Get the XMLHttpRequest object itself
  req.xmlHttpRequest = AjaxRequest.getXmlHttpRequest();
  if (req.xmlHttpRequest==null) { return null; }
  
  // -------------------------------------------------------
  // Attach the event handlers for the XMLHttpRequest object
  // -------------------------------------------------------
  req.xmlHttpRequest.onreadystatechange = 
  function() {
    if (req==null || req.xmlHttpRequest==null) { return; }
    if (req.xmlHttpRequest.readyState==1) { req.onLoadingInternal(req); }
    if (req.xmlHttpRequest.readyState==2) { req.onLoadedInternal(req); }
    if (req.xmlHttpRequest.readyState==3) { req.onInteractiveInternal(req); }
    if (req.xmlHttpRequest.readyState==4) { req.onCompleteInternal(req); }
  };
  
  // ---------------------------------------------------------------------------
  // Internal event handlers that fire, and in turn fire the user event handlers
  // ---------------------------------------------------------------------------
  // Flags to keep track if each event has been handled, in case of 
  // multiple calls (some browsers may call the onreadystatechange 
  // multiple times for the same state)
  req.onLoadingInternalHandled = false;
  req.onLoadedInternalHandled = false;
  req.onInteractiveInternalHandled = false;
  req.onCompleteInternalHandled = false;
  req.onLoadingInternal = 
    function() {
      if (req.onLoadingInternalHandled) { return; }
      AjaxRequest.numActiveAjaxRequests++;
      if (AjaxRequest.numActiveAjaxRequests==1 && typeof(window['AjaxRequestBegin'])=="function") {
        AjaxRequestBegin();
      }
      if (req.groupName!=null) {
        if (typeof(AjaxRequest.numActiveAjaxGroupRequests[req.groupName])=="undefined") {
          AjaxRequest.numActiveAjaxGroupRequests[req.groupName] = 0;
        }
        AjaxRequest.numActiveAjaxGroupRequests[req.groupName]++;
        if (AjaxRequest.numActiveAjaxGroupRequests[req.groupName]==1 && typeof(req.onGroupBegin)=="function") {
          req.onGroupBegin(req.groupName);
        }
      }
      if (typeof(req.onLoading)=="function") {
        req.onLoading(req);
      }
      req.onLoadingInternalHandled = true;
    };
  req.onLoadedInternal = 
    function() {
      if (req.onLoadedInternalHandled) { return; }
      if (typeof(req.onLoaded)=="function") {
        req.onLoaded(req);
      }
      req.onLoadedInternalHandled = true;
    };
  req.onInteractiveInternal = 
    function() {
      if (req.onInteractiveInternalHandled) { return; }
      if (typeof(req.onInteractive)=="function") {
        req.onInteractive(req);
      }
      req.onInteractiveInternalHandled = true;
    };
  req.onCompleteInternal = 
    function() {
      if (req.onCompleteInternalHandled || req.aborted) { return; }
      req.onCompleteInternalHandled = true;
      AjaxRequest.numActiveAjaxRequests--;
      if (AjaxRequest.numActiveAjaxRequests==0 && typeof(window['AjaxRequestEnd'])=="function") {
        AjaxRequestEnd(req.groupName);
      }
      if (req.groupName!=null) {
        AjaxRequest.numActiveAjaxGroupRequests[req.groupName]--;
        if (AjaxRequest.numActiveAjaxGroupRequests[req.groupName]==0 && typeof(req.onGroupEnd)=="function") {
          req.onGroupEnd(req.groupName);
        }
      }
      req.responseReceived = true;
      req.status = req.xmlHttpRequest.status;
      req.statusText = req.xmlHttpRequest.statusText;
      req.responseText = req.xmlHttpRequest.responseText;
      req.responseXML = req.xmlHttpRequest.responseXML;
      if (typeof(req.onComplete)=="function") {
        req.onComplete(req);
      }
      if (req.xmlHttpRequest.status==200 && typeof(req.onSuccess)=="function") {
        req.onSuccess(req);
      }
      else if (typeof(req.onError)=="function") {
        req.onError(req);
      }

      // Clean up so IE doesn't leak memory
      delete req.xmlHttpRequest['onreadystatechange'];
      req.xmlHttpRequest = null;
    };
  req.onTimeoutInternal = 
    function() {
      if (req!=null && req.xmlHttpRequest!=null && !req.onCompleteInternalHandled) {
        req.aborted = true;
        req.xmlHttpRequest.abort();
        AjaxRequest.numActiveAjaxRequests--;
        if (AjaxRequest.numActiveAjaxRequests==0 && typeof(window['AjaxRequestEnd'])=="function") {
          AjaxRequestEnd(req.groupName);
        }
        if (req.groupName!=null) {
          AjaxRequest.numActiveAjaxGroupRequests[req.groupName]--;
          if (AjaxRequest.numActiveAjaxGroupRequests[req.groupName]==0 && typeof(req.onGroupEnd)=="function") {
            req.onGroupEnd(req.groupName);
          }
        }
        if (typeof(req.onTimeout)=="function") {
          req.onTimeout(req);
        }
      // Opera won't fire onreadystatechange after abort, but other browsers do. 
      // So we can't rely on the onreadystate function getting called. Clean up here!
      delete req.xmlHttpRequest['onreadystatechange'];
      req.xmlHttpRequest = null;
      }
    };

  // ----------------
  // Instance methods
  // ----------------
  /**
   * The process method is called to actually make the request. It builds the
   * querystring for GET requests (the content for POST requests), sets the
   * appropriate headers if necessary, and calls the 
   * XMLHttpRequest.send() method
  */
  req.process = 
    function() {
      if (req.xmlHttpRequest!=null) {
        // Some logic to get the real request URL
        if (req.generateUniqueUrl && req.method=="GET") {
          req.parameters["AjaxRequestUniqueId"] = new Date().getTime() + "" + req.requestIndex;
        }
        var content = null; // For POST requests, to hold query string
        for (var i in req.parameters) {
          if (req.queryString.length>0) { req.queryString += "&"; }
          req.queryString += encodeURIComponent(i) + "=" + encodeURIComponent(req.parameters[i]);
        }
        if (req.method=="GET") {
          if (req.queryString.length>0) {
            req.url += ((req.url.indexOf("?")>-1)?"&":"?") + req.queryString;
          }
        }
        req.xmlHttpRequest.open(req.method,req.url,req.async,req.username,req.password);
        if (req.method=="POST") {
          if (typeof(req.xmlHttpRequest.setRequestHeader)!="undefined") {
            req.xmlHttpRequest.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
          }
          content = req.queryString;
        }
        if (req.timeout>0) {
          setTimeout(req.onTimeoutInternal,req.timeout);
        }
        req.xmlHttpRequest.send(content);
      }
    };

  /**
   * An internal function to handle an Object argument, which may contain
   * either AjaxRequest field values or parameter name/values
   */
  req.handleArguments = 
    function(args) {
      for (var i in args) {
        // If the AjaxRequest object doesn't have a property which was passed, treat it as a url parameter
        if (typeof(req[i])=="undefined") {
          req.parameters[i] = args[i];
        }
        else {
          req[i] = args[i];
        }
      }
    };

  /**
   * Returns the results of XMLHttpRequest.getAllResponseHeaders().
   * Only available after a response has been returned
   */
  req.getAllResponseHeaders =
    function() {
      if (req.xmlHttpRequest!=null) {
        if (req.responseReceived) {
          return req.xmlHttpRequest.getAllResponseHeaders();
        }
        alert("Cannot getAllResponseHeaders because a response has not yet been received");
      }
    };

  /**
   * Returns the the value of a response header as returned by 
   * XMLHttpRequest,getResponseHeader().
   * Only available after a response has been returned
   */
  req.getResponseHeader =
    function(headerName) {
      if (req.xmlHttpRequest!=null) {
        if (req.responseReceived) {
          return req.xmlHttpRequest.getResponseHeader(headerName);
        }
        alert("Cannot getResponseHeader because a response has not yet been received");
      }
    };

  return req;
}
The AjaxRequest class is a wrapper for the XMLHttpRequest objects which 
are available in most modern browsers. It simplifies the interfaces for
making Ajax requests, adds commonly-used convenience methods, and makes 
the process of handling state changes more intuitive.
An object may be instantiated and used, or the Class methods may be used 
which internally create an AjaxRequest object.