/*
####################################################################################################
# HTTP BUS OBJECT
# Version: 1.0.6 | Last Update: 09 June 2009 | Created On: 27 May 2008
####################################################################################################
# Copyright (C) by E-Muze (www.E-Muze.net)
# Copying the content of this file or fragments of this file's content with individual 
# functionality is strictly forbidden without the written consent of the author. Using this code
# as an example in order to understand and learn from it, with the purpose of improving your own
# work and/or writing your own code with similar functionality, is allowed and encouraged.
####################################################################################################
# NO DEPENDENCIES
####################################################################################################
# NO DOCUMENTATION
####################################################################################################
# KNOWN, UNFIXED PROBLEMS
# 1. Some configuration parameters have no effect since their associated features have not been implemented.
# 2. Lacking some features.
####################################################################################################
*/

function httpBus (objName, CFG)
{
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// OBJECT PROPERTIES
	
	this.objName = objName; // the name under which this object is instanced
	
	this.debugLog = objName + " [httpBus] Debug Log:\n\n"; // just a string used as debug log
	this.comInt = null; // the server communication interface, in our case the xml http request object available to the browser
	this.usable = false; // switch that determines if the instanced object is ready to be used or not
	this.busy = false; // while the object is busy a new operation cannot be called without calling the cancel method first
	
	this.outcome = null; // the code returned by the last operation
	this.returnData = { text : null, XML : null }; // container object for bus return data
	this.previousRequest = null; // POST or GET based on the type of the previous request
	this.lastURL = null; // last url called through the getURL method
	this.auxData = null; // container for auxiliary data which needs to be passed on to the callback
	
	this.CFG = null; // container for local configuration parameters
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// OBJECT INIT
	
	this.init = function (CFG)
	{
		// load configuration params
		this.loadConfig (CFG);
		
		// init the server communication interface
		this.comInt = this.getComInt ();
		
		// enable this object if initialization was successful
		if (this.comInt) this.usable = true;
		// else fail with error
		else
		{
			alert (this.objName + ' could not be properly initiated.');
			return false;
		}
	};
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// GET COM INT
	
	/*
		Returns the xml http request object available to the current browser.
	*/
	
	this.getComInt = function ()
	{
		// register available objects
		var versions =
		[
			function () { return new XMLHttpRequest () },
			function () { return new ActiveXObject ("Msxml2.XMLHTTP") },
			function () { return new ActiveXObject ("Msxml3.XMLHTTP") },
			function () { return new ActiveXObject ("Microsoft.XMLHTTP") }
		];
		
		var xmlhttp = null;
		
		// check for the version available on this browser and select it
		for (var i = 0; i < versions.length; i ++)
		{
			try
			{
				xmlhttp = versions[i] ();
			}
			catch (e)
			{
				continue;
			}
			
			break;
		}
		
		// return selected object
		return xmlhttp;
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// PREP FOR REQUEST
	
	/*
		Just a method used before all new requests to run some checks and resets.
	*/
	
	this.prepForRequest = function ()
	{
		// fail if not usable
		if (!this.usable)
		{
			alert (this.objName + ' not ready for usage.');
			return false;
		}
		
		// cancel previous request (will run reset too) before starting again
		this.cancel ();
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// GET URL
	
	/*
		Will issue the bus with a HTTP GET request.
		
		PARAMS:
			URL - full URL address to which to make the request
			modifiedSince - date format [Fri, 16 May 2008 17:34:32 GMT], will return the data IF it has been modified since the given data, otheriwse, it will return null
	*/
	
	this.getURL = function (URL, modifiedSince, AUX)
	{
		// prepare for new request
		this.prepForRequest ();
		
		// set busy status
		this.busy = true;
		
		// set request type
		this.previousRequest = "GET";
		
		// store aux data
		this.auxData = (AUX ? AUX : null);
		
		// open get request and set http header
		this.lastURL = URL;
		this.comInt.open ("GET", URL, true);
		this.comInt.setRequestHeader ('User-Agent', 'XMLHTTP/1.0');
		if (modifiedSince) this.comInt.setRequestHeader ("If-Modified-Since", modifiedSince);
		
		// set the callback function using an intermediate function to avoid this. problems
		eval ("this.comInt.onreadystatechange = function () { " + this.objName + ".stateChange (); }; ");
		
		// send request
		this.comInt.send (null);
	};
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// GET
	
	/*
		Will issue the bus with a HTTP GET request. This method is to be used only when the bus object is used with a custom destination.
		
		PARAMS:
			params - optional - object containing parameters to be passed through the URL.
			customSeparator - optional - character to be used to separate the varname + value pairs in the URL, default is &
			customAssignator - optional - character to be used to separate the varname from value in the URL, default is =
			customStarter - optional - character to be used at the beginning  of the list of variables in the URL, default is ?
	*/
	
	this.get = function (params, customSeparator, customAssignator, customStarter)
	{
		// prepare for new request
		this.prepForRequest ();
		
		// return error if no destination was entered for the bus
		if (!this.CFG.destination)
		{
			alert (this.objName + ' cannot run get method without a registered destination.');
			return false;
		}
		
		// set busy status
		this.busy = true;
		
		// set request type
		this.previousRequest = "GET";
		
		// get the URL to which we connect
		var URL = this.CFG.destination;
		
		// if we have parameters
		if (params)
		{
			var separator = customSeparator ? customSeparator : '&';
			var assignator = customAssignator ? customAssignator : '=';
			var starter = customStarter ? customStarter : '?';
			
			// if our destination url already contains parameters, we append the separator to the URL, otherwise we append the starter
			URL += (this.CFG.urlParams) ? separator : starter;
			
			// append varname + value pairs to the URL
			var counter = 0;
			for (var idx in params)
			{
				URL += (counter ? separator : '') + escape (idx) + assignator + escape (params[idx]);
				counter ++;
			}
		}
		
		// open get request and set http header
		this.comInt.open ("GET", URL, true);
		this.comInt.setRequestHeader ('User-Agent', 'XMLHTTP/1.0');
		
		// set the callback function using an intermediate function to avoid this. problems
		eval ("this.comInt.onreadystatechange = function () { " + this.objName + ".stateChange (); }; ");
		
		// send request
		this.comInt.send (null);
	};
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// POST URL
	
	/*
		Will issue the bus with a HTTP POST request.
		
		PARAMS:
			URL - full URL address to which to make the request
			DATA - optional - object containing data to be passed through POST.
	*/
	
	this.postURL = function (URL, DATA, AUX)
	{
		// prepare for new request
		this.prepForRequest ();
		
		// set busy status
		this.busy = true;
		
		// set request type
		this.previousRequest = "POST";
		
		// store aux data
		this.auxData = (AUX ? AUX : null);
		
		// open get request and set http header
		this.comInt.open ("POST", URL, true);
		this.comInt.setRequestHeader ('User-Agent', 'XMLHTTP/1.0');
		this.comInt.setRequestHeader ('Content-type', 'application/x-www-form-urlencoded');
		
		// set the callback function using an intermediate function to avoid this. problems
		eval ("this.comInt.onreadystatechange = function () { " + this.objName + ".stateChange (); }; ");
		
		// init empty post data
		var postData = '';
		
		// if we have post data
		if (DATA)
		{
			// append varname + value pairs to the URL
			var queryString = new Array ();
			for (var idx in DATA)
			{
				if (this.checkIsArray (DATA[idx]))
				{
					for (var idxx in DATA[idx])
						queryString.push (encodeURIComponent (idx) + '=' + encodeURIComponent (DATA[idx][idxx]));
				}
				else queryString.push (encodeURIComponent (idx) + '=' + encodeURIComponent (DATA[idx]));
			}
			postData = queryString.join ('&');
		}
		
		// send request
		this.comInt.send (postData);
	};
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// POST
	
	/*
		Will issue the bus with a HTTP POST request. This method is to be used only when the bus object is used with a custom destination.
		
		PARAMS:
			params - optional - object containing parameters to be passed through the URL.
			DATA - optional - object containing data to be passed through POST.
			customSeparator - optional - character to be used to separate the varname + value pairs in the URL, default is &
			customAssignator - optional - character to be used to separate the varname from value in the URL, default is =
			customStarter - optional - character to be used at the beginning  of the list of variables in the URL, default is ?
	*/
	
	this.post = function (params, DATA, customSeparator, customAssignator, customStarter)
	{
		// prepare for new request
		this.prepForRequest ();
		
		// return error if no destination was entered for the bus
		if (!this.CFG.destination)
		{
			alert (this.objName + ' cannot run get method without a registered destination.');
			return false;
		}
		
		// set busy status
		this.busy = true;
		
		// set request type
		this.previousRequest = "POST";
		
		// get the URL to which we connect
		var URL = this.CFG.destination;
		
		// if we have parameters
		if (params)
		{
			var separator = customSeparator ? customSeparator : '&';
			var assignator = customAssignator ? customAssignator : '=';
			var starter = customStarter ? customStarter : '?';
			
			// if our destination url already contains parameters, we append the separator to the URL, otherwise we append the starter
			URL += (this.CFG.urlParams) ? separator : starter;
			
			// append varname + value pairs to the URL
			var counter = 0;
			for (var idx in params)
			{
				URL += (counter ? separator : '') + escape (idx) + assignator + escape (params[idx]);
				counter ++;
			}
		}
		
		// open get request and set http header
		this.comInt.open ("POST", URL, true);
		this.comInt.setRequestHeader ('User-Agent', 'XMLHTTP/1.0');
		this.comInt.setRequestHeader ('Content-type', 'application/x-www-form-urlencoded');
		
		// set the callback function using an intermediate function to avoid this. problems
		eval ("this.comInt.onreadystatechange = function () { " + this.objName + ".stateChange (); }; ");
		
		// init empty post data
		var postData = '';
		
		// if we have post data
		if (DATA)
		{
			// append varname + value pairs to the URL
			var counter = 0;
			for (var idx in DATA)
			{
				postData += (counter ? '&' : '') + escape (idx) + '=' + escape (DATA[idx]);
				counter ++;
			}
		}
		
		// send request
		this.comInt.send (postData);
	};
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// STATE CHANGE
	
	/*
		Will be automatically called when the state of the request changes.
	*/
	
	this.stateChange = function ()
	{
		// silent fail if not busy
		if (!this.busy) return null;
		
		/* Request State List:
		0 - Uninitialized - The object has been created, but not initialized (the open method has not been called).
		1 - Open - The object has been created, but the send method has not been called.
		2 - Sent - The send method has been called, but the status and headers are not yet available.
		3 - Receiving - Some data has been received.
		4 - Loaded - All the data has been received, and is available.
		*/
		
		/* HTTP Status List:
		100 - Continue
		101 - Switching protocols
		200 - OK
		201 -  Created
		202 - Accepted
		203 - Non-Authoritative Information
		204 - No Content
		205 - Reset Content
		206 - Partial Content
		300 - Multiple Choices
		301 - Moved Permanently
		302 - Found
		303 - See Other
		304 - Not Modified
		305 - Use Proxy
		307 - Temporary Redirect
		400 - Bad Request
		401 - Unauthorized
		402 - Payment Required
		403 - Forbidden
		404 - Not Found
		405 - Method Not Allowed
		406 - Not Acceptable
		407 - Proxy Authentication Required
		408 - Request Timeout
		409 - Conflict
		410 - Gone
		411 - Length Required
		412 - Precondition Failed
		413 - Request Entity Too Large
		414 - Request-URI Too Long
		415 - Unsupported Media Type
		416 - Requested Range Not Suitable
		417 - Expectation Failed
		500 - Internal Server Error
		501 - Not Implemented
		502 - Bad Gateway
		503 - Service Unavailable
		504 - Gateway Timeout
		505 - HTTP Version Not Supported
		*/
		
		// if state is not loaded, don't do anything
		if (this.comInt.readyState != 4) return null;
		
		// check returned HTTP status, fail if error code returned
		if (this.comInt.status != 200 && this.comInt.status != 304)
		{
			// set operation outcome to error
			this.outcome = [ 'HTTP error', this.comInt.status ];
			
			// pass outcome to callback function, if any
			if (this.CFG.callback) this.CFG.callback (this);
			
			// return null
			return null;
		}
		
		// store return data
		this.returnData = { text : this.comInt.responseText, XML : this.comInt.responseXML };
		
		// generate success outcome
		this.outcome = [ 'success' ];
		
		// call callback, if any set
		if (this.CFG.callback) this.CFG.callback (this);
		
		// clear busy status
		this.busy = false;
	};
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// CANCEL
	
	/*
		Only usable while the object is busy, will cancel the current ongoing operation and then run an object reset.
	*/
	
	this.cancel = function ()
	{
		// abort current operation
		this.comInt.abort ();
		
		// reset object
		this.reset ();
	};
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// RESET
	
	/*
		Will reset all request-related properties of the object and make the object available for a fresh request. This method does not reset
		the initial configuration parameters too.
	*/
	
	this.reset = function ()
	{
		// clear previous request type
		this.previousRequest = null;
		
		// clear previous bus return data
		this.returnData = { text : null, XML : null };
		
		// reset outcome
		this.outcome = null;
		
		// set object not busy
		this.busy = false;
	};
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// CHECK IS ARRAY
	
	this.checkIsArray = function (obj)
	{
		if (!obj || !obj.constructor) return false;
		else if (obj.constructor.toString ().indexOf ("Array") == -1) return false;
		else return true;
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// LOAD CONFIG
	
	/*
		This method loads the configuration parameters from the given configuration array and uses defaults where values are not set.
	*/
	
	this.loadConfig = function (CFG)
	{
		if (!CFG) CFG = new Object ();
		
		this.CFG = 
		{
			destination : this.def (CFG.destination, null), // http address to which the bus is going
			callback : this.def (CFG.callback, null), // callback method for bus return event
			
			urlParams : this.def (CFG.urlParams, false) // tell the object if the destination url already includes parameters or if it is just a base url
		};
	};
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// DEF
	
	/*
		Method used to return the value if it is valid (including zeros) or return the default when value is undefined or non existing
	*/
	
	this.def = function (value, def)
	{
		if (value === 0 || (value && value != undefined)) return value;
		else return def;
	}
	
	//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	// INIT THE OBJECT AUTOMATICALLY
	
	this.init (CFG);
}
	
/*
####################################################################################################
*/
