/**
 * 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.
 */

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) {
      alert("Your browser is either too old to support this feature, or does not support ActiveX.");
      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;
}
// ---------------------------------------
// Static methods of the AjaxRequest class
// ---------------------------------------
/**
 * Returns an XMLHttpRequest object, either as a core object or an ActiveX
 * implementation. If an object cannot be instantiated, it will return null;
 */
AjaxRequest.getXmlHttpRequest = function() {
   if(window.XMLHttpRequest) {
      return new XMLHttpRequest();
   }
   else if(window.ActiveXObject) {
      // Based on http://jibbering.com/2002/4/httprequest.html
      /*@cc_on @*/
      /*@if(@_jscript_version >= 5)
	try {
	return new ActiveXObject("Msxml2.XMLHTTP");
	}
	catch(e) {
	try {
	return new ActiveXObject("Microsoft.XMLHTTP");
	}
	catch(E) {
	return null;
	}
	}
	@end @*/
   }
   else {
      return null;
   }
}
;
/**
 * See if any request is active in the background
 */
AjaxRequest.isActive = function() {
   return(AjaxRequest.numActiveAjaxRequests>0);
}
;
/**
 * Make a GET request. Pass an object containing parameters and arguments as
 * the second argument.
 * These areguments may be either AjaxRequest properties to set on the request
 * object or name/values to set in the request querystring.
 */
AjaxRequest.get = function(args) {
   AjaxRequest.doRequest("GET",args);
}
;
/**
 * Make a POST request. Pass an object containing parameters and arguments as
 * the second argument.
 * These areguments may be either AjaxRequest properties to set on the request
 * object or name/values to set in the request querystring.
 */
AjaxRequest.post = function(args) {
   AjaxRequest.doRequest("POST",args);
}
;
/**
 * The internal method used by the .get() and .post() methods
 */
AjaxRequest.doRequest = function(method,args) {
   if(typeof(args)!="undefined" && args!=null) {
      var myRequest = new AjaxRequest();
      myRequest.method = method;
      myRequest.handleArguments(args);
      myRequest.process();
   }
}
;
/**
 * Submit a form. The requested URL will be the form's ACTION, and the request
 * method will be the form's METHOD.
 * Returns true if the submittal was handled successfully, else false so it
 * can easily be used with an onSubmit event for a form, and fallback to
 * submitting the form normally.
 */
AjaxRequest.submit = function(theform, args) {
   var myRequest = new AjaxRequest();
   if(myRequest==null) {
      return false;
   }
   var serializedForm = AjaxRequest.serializeForm(theform);
   myRequest.method = theform.method.toUpperCase();
   myRequest.url = theform.action;
   myRequest.handleArguments(args);
   myRequest.queryString = serializedForm;
   myRequest.process();
   return true;
}
;
/**
 * Serialize a form into a format which can be sent as a GET string or a POST
 * content.It correctly ignores disabled fields, maintains order of the fields
 * as in the elements[] array. The 'file' input type is not supported, as
 * its content is not available to javascript. This method is used internally
 * by the submit class method.
 */
AjaxRequest.serializeForm = function(theform) {
   var els = theform.elements;
   var len = els.length;
   var queryString = "";
   this.addField =
   function(name,value) {
      if(queryString.length>0) {
	 queryString += "&";
      }
      queryString += encodeURIComponent(name) + "=" + encodeURIComponent(value);
   }
   ;
   for(var i=0;i<len;i++) {
      var el = els[i];
      if(!el.disabled) {
	 switch(el.type) {
	    case 'text': case 'password': case 'hidden': case 'textarea':
	       this.addField(el.name,el.value);
	       break;
	    case 'select-one':
	       if(el.selectedIndex>=0) {
		  this.addField(el.name,el.options[el.selectedIndex].value);
	       }
	       break;
	    case 'select-multiple':
	       for(var j=0;j<el.options.length;j++) {
		  if(el.options[j].selected) {
		     this.addField(el.name,el.options[j].value);
		  }
	       }
	       break;
	    case 'checkbox': case 'radio':
	       if(el.checked) {
		  this.addField(el.name,el.value);
	       }
	       break;
	 }
      }
   }
   return queryString;
}
;
// -----------------------
// Static Class variables
// -----------------------
/**
 * The number of total AjaxRequest objects currently active and running
 */
AjaxRequest.numActiveAjaxRequests = 0;
/**
 * An object holding the number of active requests for each group
 */
AjaxRequest.numActiveAjaxGroupRequests = new Object();
/**
 * The total number of AjaxRequest objects instantiated
 */
AjaxRequest.numAjaxRequests = 0;

