// ===================================================================

// Author: Matt Kruse <matt@ajaxtoolbox.com>

// WWW: http://www.AjaxToolbox.com/

//

// NOTICE: You may use this code for any purpose, commercial or

// private, without any further permission from the author. You may

// remove this notice from your final code if you wish, however it is

// appreciated by the author if at least my web site address is kept.

//

// You may *NOT* re-distribute this code in any way except through its

// use. That means, you can include it in your product, or your web

// site, or any other form where the code is actually being used. You

// may not put the plain javascript up on your site for download or

// include it in your javascript libraries for download. 

// If you wish to share this code with others, please just point them

// to the URL instead.

// Please DO NOT link directly to my .js files from your site. Copy

// the files to your server and use them there. Thank you.

// ===================================================================



/**

 * 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;




