/**
 * Encapsulates an xmlHttpRequest in a proper Javascript class
 */
function AjaxRequest()
{
	/** Keep a snapshot of the object's self-reference **/
	var self = this;

	/** The requested URL **/
	this.url = false;

	/** The raw xmlHttpRequest object **/
	this.xmlHttpRequest = false;

	/** The function to call once the request has completed **/
	this.onLoading = false;

	/** The function to call as the request is processed **/
	this.onLoaded = false;

	/** Additional field that be used to attach arbitrary user data to the request **/
	this.userData = null;

	/** Stores post data for the request **/
	this.postData = new Array();

	/**
	 * Adds postData to the request.  Note that the post data will be cleared once the request has been sent.
	 * @param name The name of the post data item
	 * @param value The item of post data
	 */
	this.addPostData = function(name, value)
	{
		var encodedValue = escape(value).replace(/\+/g, '%2B');
		var postDataText = name + '=' + encodedValue;
		this.postData.push(postDataText);
	}

	/**
	 * Clears the postData for the request.  Note that the post data will automatically be cleared once the request has been sent.
	 */
	this.clearPostData = function()
	{
		this.postData = new Array();
	}

	/**
	 * Aborts any HTTP request that is currently in progress
	 */
	this.abort = function()
	{
		if (this.xmlHttpRequest)
		{
			this.xmlHttpRequest.abort();
		}
	}

	/**
	 * Sends the request.  If this object already has an xmlHttpRequest in progress, it will be cancelled first.
	 * @param url The URL to request (including any GET parameters)
	 * @param onLoaded Function to be called once the request has completed [optional]
	 * @param onLoading Function to be called as the request is processed [optional]
	 * @param userData Arbitrary user data that will get passed with the AjaxRequest object [optional]
	 */
	this.send = function(url, onLoaded, onLoading, userData)
	{
		// Only allow one request at a time off the same object, to prevent concurrency issues
		this.abort();

		// Fetch the right XMLHttpRequest object for the browser
		if (window.XMLHttpRequest)
		{
			this.xmlHttpRequest = new XMLHttpRequest();
		}
		else if (window.ActiveXObject)
		{
			try
			{
				this.xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
			}
			catch (exception)
			{
				try
				{
					this.xmlHttpRequest = new ActiveXObject("Microsoft.XMLHTTP");
				}
				catch (exception) {}
			}
		}

		// Save the request parameters and callback functions, then send the request...
		if (this.xmlHttpRequest)
		{
			this.url = url;
			this.onLoaded = onLoaded;
			this.onLoading = onLoading;

			// Only override the current userData if new data has been specified
			this.userData = (userData != null) ? userData : this.userData;

			this.xmlHttpRequest.onreadystatechange = this.processStateChanged;
			if (this.postData.length == 0)
			{
				this.xmlHttpRequest.open('GET', this.url, true);
				this.xmlHttpRequest.send(null);
			}
			else
			{
				// Fetch a snapshot of the current post-data, then clear the post-data array (so that fresh data will be used for the next send)
				var postData = this.postData;
				this.postData = new Array();
				var postDataText = '';

				// Build the POST request string
				var prefix = '';
				for (var index = 0; index < postData.length; index++)
				{
					postDataText += prefix + postData[index];
					prefix = '&';
				}

				// Send the POST request
				this.xmlHttpRequest.open('POST', this.url, true);
				this.xmlHttpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
				this.xmlHttpRequest.send(postDataText);
			}
		}
	}

	/**
	 * Internal function used to convert xmlHttpRequest onreadystatechange calls into onLoaded/onLoading parameterised function calls
	 */
	this.processStateChanged = function()
	{
		// Need to use 'self', as 'this' will eventually refer to the raw xmlHttpRequest object that this function gets attached to!
		if (self.xmlHttpRequest.readyState == 4 && self.onLoaded)
		{
			self.onLoaded(self, self.xmlHttpRequest.status, self.xmlHttpRequest.statusText);
		}
		else if (self.onLoading)
		{
			self.onLoading(self, self.xmlHttpRequest.readyState);
		}
	}
}

/**
 * Example onLoading function, simply displays alert boxes as the request is processed
 * Sample usage: new AjaxRequest.send('/example.jsp', null, ajaxLogLoading);
 * @param ajaxRequest The calling AjaxRequest object
 * @param state The current state of the xmlHttpRequest (will be the same as to ajaxRequest.xmlHttpRequest.readyState)
 */
var ajaxLogLoading = function(ajaxRequest, state)
{
	var message = new Date() + '\nMaking request to ' + ajaxRequest.url + '\nState is ' + state;
	alert(message);
}

/**
 * Example onLoaded function, simply displays alert boxes once the request has completed
 * Sample usage: new AjaxRequest.send('/example.jsp', ajaxLogLoaded);
 * @param ajaxRequest The calling AjaxRequest object
 * @param statusCode The final HTTP status code of the xmlHttpRequest (will be the same as to ajaxRequest.xmlHttpRequest.status)
 * @param statusText The final HTTP status text of the xmlHttpRequest (will be the same as to ajaxRequest.xmlHttpRequest.statusText)
 */
var ajaxLogLoaded = function(ajaxRequest, statusCode, statusText)
{
	var message = new Date() + '\nCompleted request to ' + ajaxRequest.url + '\nStatus is ' + statusCode;
	if (statusText)
	{
		message += ' (' + statusText + ')';
	}
	if (ajaxRequest.userData)
	{
		message += '\nUser data: ' + ajaxRequest.userData;
	}
	alert(message);
}

