var ajaxCaller = {

  shouldDebug: false,
  shouldEscapeVars: false,
  shouldMakeHeaderMap: true,

  calls : new Array(),
  pendingResponseCount : 0,

   /**************************************************************************
      PUBLIC METHODS
   *************************************************************************/

  getXML: function(url, callbackFunction) {
    this.get(url, null, callbackFunction, true, null);
  },

  getPlainText: function(url, callbackFunction) {
    this.get(url, null, callbackFunction, false, null);
  },

  postForPlainText: function(url, vars, callbackFunction) {
    this.postVars(url, vars, null, callbackFunction, false,
                    null, "POST", null, null, null);
  },

  postForXML: function(url, vars, callbackFunction) {
    this.postVars(url, vars, null, callbackFunction, true,
                    null, "POST", null, null, null);
  },

  post: function(url, urlVars, callbackFunction, expectingXML, callingContext) {
    this._callServer(url, urlVars, callbackFunction, expectingXML,
                    callingContext, "POST", null, null, null);
  },
  _callServer: function(url, urlVars, callbackFunction, expectingXML,
                       callingContext, requestMethod, bodyVars,
                       explicitBodyType, explicitBody) {

    if (urlVars==null) {
      urlVars = new Array();
    }

    this._debug("_callServer() called. About to request URL\n"
                + "call key: [" + this.calls.length + "]\n"
                + "url: [" + url + "]\n"
                + "callback function: [" + callbackFunction + "]\n"
                + "treat response as xml?: [" + expectingXML + "]\n"
                + "Request method?: [" + requestMethod + "]\n"
                + "calling context: [" + callingContext + "]\n"
                + "explicit body type: [" + explicitBodyType + "]\n"
                + "explicit body: [" + explicitBody + "]\n"
                + "urlVars: [" + util.describe(urlVars) + "]\n"
                + "bodyVars: [" + util.describe(bodyVars) + "]"
              );


    var xReq = this._createXMLHttpRequest();
    xReq.onreadystatechange = function() {
      ajaxCaller._onResponseStateChange(call);
    }

    var call = {xReq: xReq,
                callbackFunction: callbackFunction,
                expectingXML: expectingXML,
                callingContext: callingContext,
                url: url};

    if (urlVars!=null) {
      var urlVarsString = this._createHTTPVarSpec(urlVars);
      if (urlVarsString.length > 0) { // TODO check if appending with & instead
        url += "?" + urlVarsString;
      }
    }

    xReq.open(requestMethod, url, true);    if (   requestMethod=="GET"        || requestMethod=="HEAD"        || requestMethod=="DELETE") {      this._debug("Body-less request to URL " + url);      xReq.send(null);      return;    }    if (   requestMethod=="POST"        || requestMethod=="PUT"        || requestMethod=="OPTIONS"        || requestMethod=="TRACE") {      bodyType = null;      body = null;      if (explicitBodyType==null) { // It's a form        bodyType = 'application/x-www-form-urlencoded; charset=UTF-8';        body = this._createHTTPVarSpec(bodyVars);      } else {        bodyType = explicitBodyType;        body = explicitBody;      }      this._debug("Content-Type: [" + bodyType + "]\nBody: [" + body + "].");      xReq.setRequestHeader('Content-Type',  bodyType);      xReq.send(body);      return;    }    this._debug("ERROR: Unknown Request Method: " + requestMethod);  },  _onResponseStateChange: function(call) {    xReq = call.xReq;    if (xReq.readyState < 4) { //Still waiting      return;    }    if (xReq.readyState == 4) { //Transmit to actual callback      this._debug("Call " + util.describe(call)                + " with context [" + call.callingContext+"]"                + " to " + call.url + " has returned.");      callbackFunction = call.callbackFunction;      if (!callbackFunction) { // Maybe still loading, e.g. in another JS file        setTimeout(function() {_onResponseStateChange(call);}, 100);      }      var content = call.expectingXML ? xReq.responseXML : xReq.responseText;      responseHeaders = xReq.getAllResponseHeaders();      headersForCaller = this.shouldMakeHeaderMap ?	this._createHeaderMap(responseHeaders) : responseHeaders;      callbackFunction(content, headersForCaller, call.callingContext);    }    call = null; // Technically the responsibility of GC    this.pendingResponseCount--;  },  // Browser-agnostic factory function  _createXMLHttpRequest: function() {    if (window.XMLHttpRequest) {      return new XMLHttpRequest();    } else if (window.ActiveXObject) {      return new ActiveXObject('Microsoft.XMLHTTP')    } else {      _error("Could not create XMLHttpRequest on this browser");      return null;    }  },  _createHTTPVarSpec: function(vars) {      var varsString = "";      for( key in vars ) {        var value = vars[key];        if (this.shouldEscapeVars) {          escapePlusRE =  new RegExp("\\\+");          value = value.replace(escapePlusRE, "%2B");        }        varsString += '&' + key + '=' + value;      }      if (varsString.length > 0) {        varsString = varsString.substring(1); // chomp initial '&'      }      this._debug("Built var String: " + varsString)      return varsString;   },  /* Creates associative array from header type to header */  _createHeaderMap: function(headersText) {    extractedHeaders = headersText.split("\n");    delete extractedHeaders[extractedHeaders.length]; // Del blank line at end    headerMap = new Array();    for (i=0; i<extractedHeaders.length-2; i++) {      head = extractedHeaders[i];      fieldNameEnding = head.indexOf(":");      field = head.substring(0, fieldNameEnding);      value = head.substring(fieldNameEnding + 2, head.length);      value = value.replace(/\s$/, "");      headerMap[field] = value;    }    return headerMap;  },  _debug: function(message) {      if (this.shouldDebug) {        alert("AjaxJS Message:\n\n" + message);      }  },  _error: function(message) {      if (this.shouldDebug) {        alert("AjaxJS ERROR:\n\n" + message);      }  }};
