// X(ml)H(ttp)R(equest)Wrapper Object
// define minimalist wrapper object to manage XMLHttpRequest object for
// AJAX processing to read URLs as simple, plain text
// author: richarduie[at]yahoo[dot]com
function XhrWrapper()
{
	// private methods - - - - - - - - - - - - - - - - - - - - - - - - -

		// utility method to abbreviate calls to getElementById() to get 
		// element with id {eid}
		function get(eid) { return document.getElementById(eid); }

		// create an instance of the XMLHttpRequest object
		function createRequestObject() {
			// initialize return object in function-global scope - if neither
			// approach to creation succeeds, return {request} will be null
			var request = null;
			// minimal browser sniff - STILL can't trust native implementation
			// of XMLHttpRequest object in IE (7 or otherwise) - would prefer
			// try/catch on attempt to create native object, but that would
			// succeed in IE7+, even though the object would be "lame"
			var browser = navigator.appName;
			if('Microsoft Internet Explorer' == browser) {
				// attempt to get ActiveX versions from latest to oldest

/*				request = new ActiveXObject(
					'Msxml2.XMLHTTP.6.0' ||	'Msxml2.XMLHTTP.3.0' ||
					'Msxml2.XMLHTTP' 		||	'Microsoft.XMLHTTP'
				);
*/
				request = new ActiveXObject('Msxml2.XMLHTTP.3.0');
			}
			// use non-ActiveX, native JS implementation of XMLHttpRequest
			// object for compliant browsers, e.g., Firefox, Opera, Safari.
			else request = new XMLHttpRequest();
			return request;
		}

		// callback for all changes of ready state
		function handleStateChange() {
			switch (ajaxRequest.readyState) {
				case 0: {	// created, uninitialized - open() not yet called
					break;	// no handling at present
				}
				case 1: {	// open() called - send() method not yet called
					break;	// no handling at present
				}
				case 2: {	// send() called - responseText and -Body unavailable
					break;	// no handling at present
				}
				case 3: {	// receiving - responseText and -Body still unavailable
					break;	// no handling at present
				}
				case 4: {	// completed - all data available
					doComplete(ajaxRequest.responseText || null);
					break;
				}
				default: {
					throw name + '.handleStateChange() could not identify a valid ready state';
				}
			}
		}

		// specific handling for request completed ready state
		function doComplete(c) {
		// c...String content returned as AJAX response 
			// intitialize optional, additional arguments to external function named
			// in {fnComplete} for String concatenation of arguments if they exist
			var args = '';
			// if arguments available, construct String to embed in eval() call
			if (null != fnCompleteArgs) {
				var last = fnCompleteArgs.length;
				for (var i = 0; i < last; i++) {
					args += ',' + fnCompleteArgs[i];
				}
			}
			try {
				// pass response contents and any additional arguments to external
				// function named in object variable {fnComplete}
				eval(fnComplete + '(c' + args + ')');
			}
			catch (e) {
			// since this method is private, instance name is referenced privately
				throw name + '.doComplete() failed to interpret expression:\n' +
					fnComplete + '(' + c + args + ')\n' + e.toString();
			}
		}

	// private fields - - - - - - - - - - - - - - - - - - - - - - - - -

		// object global XMLHttpRequest instance
		var ajaxRequest = null;

		// name of callback function (as seen from page scope) for onComplete
		// state - can be a reference to a public function of this object
		var fnComplete;

		// optional argument-list to be included after text contents fetched
		// by AJAX processing in call to {fnComplete} - array in argument order 
		// function named in {fnComplete}
		var fnCompleteArgs = null;

		var name = 'xhr';	// set default name for instance

	// public methods - - - - - - - - - - - - - - - - - - - - - - - - -

		// get text from a URL, passing results to page-scope function
		// named in {complete}, once response processing is complete
		this.doGetText = function(url, complete, completeArgs) {
		// url............service provider address - can be a file
		// complete.......name of function in page namespace to which to pass 
		//                response of AJAX request
		// completeArgs...additional arguments required by function named in 
		//                {complete} - optional
			// create request object, if not yet done
			if (null == ajaxRequest) ajaxRequest = createRequestObject();
			// if still null, throw exception and abandon process
			if (null == ajaxRequest) {
				throw this.getName() + '.doGetText() failed to create valid request object';
			}
			// assign name of onComplete handler to function global holder
			fnComplete = complete;
			// if argument-list passed for call to function named in {complete}
			// assign those to holder
			if (completeArgs && null != completeArgs) fnCompleteArgs = completeArgs;
			// initiate fetch dialog
			ajaxRequest.open('get', url);
			// force content type to be plain text
			ajaxRequest.setRequestHeader('Content-Type', 'text/plain;charset=UTF-8');
			// if not IE, overrideMimeType allows further insistence that server 
			// sends plain text mime type in response
			if (ajaxRequest.overrideMimeType)
				ajaxRequest.overrideMimeType('text/plain;charset=UTF-8');
			// do this after open() to avoid IE initialization problems -
			// allows reuse of same XHR object...silly IE bug
			ajaxRequest.onreadystatechange = handleStateChange;
			ajaxRequest.send(null);	// fetch!
			return false;
		}

		// append String (in new element) to an existing element of current document
		this.appendToElement = function(c, pId, tag) {
		// c.....any String (with or without markup)
		// pId...id of parent element in page to which to append content -
		//       optional with default of body
		// tag...tag name of element to contain content to be appended -
		//       optional with default of div
			// if argument is missing or null, offer error message
			if (!c || null == c) {
				// since this method is public, instance name is referenced publicly
				throw this.getName() + '.appendToElement(): missing content to append';
			}
			// ...otherwise (implicit else), add content to page
			// default to body, if no parent element id given
			var p = (!pId || null == pId)?document.body:get(pId);
			// default to div, if no container tag name given
			var tag = (!tag || null == tag)?'DIV':tag;
			var el = document.createElement(tag);	// create new element of type {tag}
			el.innerHTML = c;								// insert content into element
			p.appendChild(el);							// append element to parent
		}

	// public accessors and mutators for private fields - - - - - - -

		// get AJAX request object for this instance
		this.getRequest = function() { return ajaxRequest; }

		// get String name of function in page namespace to be applied as callback 
		// for completed ready state of AJAX request
		this.getFnComplete = function() { return fnComplete; }
		// set String name of function in page namespace to be applied as callback 
		// for completed ready state of AJAX request
		this.setFnComplete = function(f) { fnComplete = f; }

		// get arguments for function in page namespace to be applied as callback 
		// for completed ready state of AJAX request - array in argument order of 
		// callback function
		this.getFnCompleteArgs = function() { return fnCompleteArgs; }
		// set arguments for function in page namespace to be applied as callback 
		// for completed ready state of AJAX request - array in argument order of 
		// callback function
		this.setFnCompleteArgs = function(a) { fnCompleteArgs = a; }

		// set instance name of object - default name is "xhr"
		this.getName = function() { return name; }
		// set instance name of object, if default name "xhr" is not to be used
		this.setName = function(n) { name = n; }

	// public fields - - - - - - - - - - - - - - - - - - - - - - - - -
		// none at present
}