

// ===================================================================


// Author: Matt Kruse <matt@ajaxtoolbox.com>


// WWW: http://www.AjaxToolbox.com/


// ===================================================================





/**


 * 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) { 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;











