/************************************************************************************/
/*
/*	Filename: mocc_lib_cls_ajax.js
/*	current version: 1.0
/*	date: 2009-05-18
/*	description:		This file handles all aspects of ajax on the javascript side including:
/*					queueing ajax requests
/*					creating and opening an XHTML connection
/*					sending a GET or POST to the specified location
/*					receiving a response
/*					setting a specified HTML element to the output received
/*					setting a specified HTML element to a custom "now loading" text
/*					setting the value of a clicked object (if specified) to specified text
/*					setting an output element to the ajax response if set to do so
/*					calling a function with the ajax response if set to do so (function can be in local code)
/*					looping back to the start of the queue if set to do so
/*
/*				This file also has its own getElem, which takes a string (HTML element ID) and
/*					returns a handle to the object that has that HTML element ID
/*
/*	Dependencies: none
/*	Contributors (chronological order, starting with originating author):
/*					1. Matt Olinger (MO) and Cliff Chambers (CC)
/*
/*
/*
/*	Usage Example 1 (basic request, 1 IN QUEUE):
/* //use all original defaults
/* //perform a get request to http://www.foo.net/bar.php?foo=bar&bar=foo)
/*		var ajxQ=new mocc_ajax();
/*		ajxQ.addRequest('get','http://www.foo.net/bar.php','foo=bar&bar=foo');
/*
/*	Usage Example 2 (output element, loading element, clicked element, 1 IN QUEUE):
/* //change the method of output to 'outputelem', so results go into an element on the page and no function is called
/*	//change id of output element to 'div_results' (in the html, there should be something like a <div id="outputelem">...</div>; if not it will be created as a child to the body)
/*	//change id and text of loading element to 'div_results' and '<img src="images/loading.gif">' respectively.  Also set the loading text to be appended to the current contents of the loading elem
/* //	-note that the loading element will be the same as output element, but this is NOT required
/* //change id and text of clicked object to 'btnSubmit' and 'Please Wait...' respectively (in the html, there should be something like a <input type="button" name="btnSubmit" value="Send Request" />)
/*		var ajxQ=new mocc_ajax();
/*		ajxQ.setResultsElem('outputelem','div_results');
/*		ajxQ.setLoadOb('div_results','<img src="images/loading.gif">','append'); //make loading element same as output element (not required)
/*		ajxQ.setClickOb('btnSubmit','Please Wait...');
/*		ajxQ.addRequest('get','http://www.foo.net/bar.php','foo=bar&bar=foo');
/*
/*	Usage Example 3 (output function, loading element, 1 IN QUEUE):
/* //change the method of output to 'callfunction' so a function (showRes(p)) on the local page is called with the results as the parameter and no element on the page is modified
/*	//change output function to 'showRes(p);' (in the html, there should be something like a <div id="outputelem">...</div>; if not it will be created as a child to the body)
/*	//change id and text of loading element to 'div_loading' and '<img src="images/loading.gif">' respectively.  Also set the loading text to be appended to the current contents of the loading elem
/* //change id and text of clicked object to 'btnSubmit' and 'Please Wait...' respectively (in the html, there should be something like a <input type="button" name="btnSubmit" value="Send Request" />)
/*		var ajxQ=new mocc_ajax();
/*		ajxQ.setResultsFunction('callfunction','showRes(ajaxResponseText);');
/*		ajxQ.setLoadOb('div_loading','<img src="images/loading.gif">','append');
/*		ajxQ.addRequest('get','http://www.foo.net/bar.php','foo=bar&bar=foo');
/*
/*	Usage Example 4 (output function and output element, 1 IN QUEUE):
/* //change the method of output to 'outputandcall' so:
/*		- a function (showRes(p)) on the local page is called with the results as the parameter AND
/* 	- the results go into an element on the page
/*	//change id of output element to 'div_results' (in the html, there should be something like a <div id="outputelem">...</div>; if not it will be created as a child to the body)
/*	//change output function to 'showRes(p);' (in the html, there should be something like a <div id="outputelem">...</div>; if not it will be created as a child to the body)
/*	//change id and text of loading element to 'div_loading' and '<img src="images/loading.gif">' respectively.  Also set the loading text to be appended to the current contents of the loading elem
/* //change id and text of clicked object to 'btnSubmit' and 'Please Wait...' respectively (in the html, there should be something like a <input type="button" name="btnSubmit" value="Send Request" />)
/*		var ajxQ=new mocc_ajax();
/*		ajxQ.setResultsElem('outputandcall','div_results');
/*		ajxQ.setResultsFunction('outputandcall','showRes(ajaxResponseText);');
/*		ajxQ.addRequest('get','http://www.foo.net/bar.php','foo=bar&bar=foo');
/*
/*	Usage Example 5 (different output elements, different loading elements and texts, POST, loop, 3 IN QUEUE):
/*	//set the queue to loop with a delay of 10 seconds between finishing the queue and restarting the queue
/* //change the method of output to 'outputelem', so results go into an element on the page and no function is called
/*	//change id of output element to 'div_results_n' where n is in integer between 0 and 2 (3 elements for 5 calls)
/*	//change id and text of loading elements for each request
/*		var ajxQ=new mocc_ajax();
/*		ajxQ.setLoop(true,10000);
/*
/*		ajxQ.setResultsElem('outputelem','div_results_0');
/*		ajxQ.setLoadOb('div_results_0','<img src="images/loading.gif">','overwrite'); //make loading element same as output element (not required)
/*		ajxQ.addRequest('post','http://www.foo.net/bar.php','foo=bar&bar=foo');
/*
/*		ajxQ.setResultsElem('outputelem','div_results_1');
/*		ajxQ.setLoadOb(null,'<img src="images/loading.gif">','overwrite'); //null = no "loading object" for this call - renders other 2 parameters useless
/*		ajxQ.addRequest('get','http://www.foo.net/bar.php','foo=bar&bar=rockin');
/*
/*		ajxQ.setResultsElem('outputelem','div_results_2');
/*		ajxQ.setLoadOb('div_results_2','<img src="images/loading.gif">','overwrite'); //make loading element same as output element (not required)
/*		ajxQ.addRequest('post','http://www.bar.net/foo.php','id=32');
/*
/*	There are no examples for the cancel routines or the specified idx in addRequest as those
/* have not been tested, but assuming they work, you should be able to figure out how to use
/* them based on the previous 5 examples.
/*
/************************************************************************************/
/*	version history:
/************************************************************************************/
/*	Version 1.0 (2009-05-18):
/*	Version 1.0 was the creation of the ajax class and included the following 
/* functions and functionality:
/*		-mocc_ajax(): the class itself including all private variables and public functions
/*		-me.createRequestObject(): creates and returns an XML request object
/*		-me.sendRequest(): sends a request for data as specified by parameters
/*		-me.addRequest(): adds a request with its own parameters to the queue of ajax requests
/*		-me.setLoadOb(f_ob,f_val,f_method): sets the default parameters for the "loading object" (f_ob)
/*			-the "Loading Object" is the object that is generally used to display a loading graphic or text
/*		-me.setClickOb(f_ob,f_val): sets the default value while loading (f_val) for the "clicked object" (f_ob)
/*			-the "clicked object" is generally a button that was clicked to initiate the ajax request
/*		-me.setResultsElem(f_method,f_elem): sets the id of the default output element (if any) where results will be placed
/*			-also sets the method of handling the result: 'outputelem' or 'callfunction' or 'outputandcall'
/*		-me.setResultsFunction(f_method,f_func): sets the default code to run or function to call using the results as the parameter
/*			-also sets the method of handling the result: 'outputelem' or 'callfunction' or 'outputandcall'
/*		-me.getCurrentIndex(): returns the index in the queue that is currently being processed
/*		-me.getQueueLength(): returns the last index in the queue (so actually, queue length is 1 greater than this return)
/*		-me.setLoading(elem,txt,txtMode): overwrites, prepends, or appends, based on txtMode, the "loading object" (elem) with the loading html (txt)
/*		-me.handleResponse(): completes an ajax request by setting the output element and/or calling the results function and proceeding to next in queue
/*		-me.setLoop(f_bln,f_tim): sets the parameters for looping through ajax calls: f_bln (do or do not loop) and f_tim (delay in ms between finishing the queue and restarting the queue)
/*		-me.cancelAll(): cancel all requests in the queue
/*		-me.cancelRemaining(): cancel all remaining requests in the queue (from currentQueueIndex through lastQueueIndex)
/*		-me.cancelRequest(idx): cancel only the request with index idx
/*		-me.getElem(obID): returns a handle to the element with id=ob
/*		-me.elemExists(obID): returns boolean true if an element with id=ob exists, false otherwise
/*		
/************************************************************************************/

function mocc_ajax() {
	var me = this;			//I like using me.xxx instead of this.xxx - it gives the class a more personal touch :)
	var inQueue=false;	//set to true only when an ajax call is waiting for completion
	var traceElement='';	//element in which to show the trace.  '' (empty string) means no trace

	var lastQueueIndex=-1;		//initialize the queue length to 0 (no indices)
	var currentQueueIndex=0;	//initialize the current queue to 0 (would do index 0 first if there were any in queue)

	me.createRequestObject = function() {
		var ro;
		var browser = navigator.appName;
		if(browser == "Microsoft Internet Explorer"){ ro = new ActiveXObject("Microsoft.XMLHTTP"); }
		else { ro = new XMLHttpRequest(); }
		return ro;
	}

	var loop=false;		// true=start queue over at beginning after completing the queue
	var loopDelay=5000;	// time to wait (ms) between completing the queue and starting it over at index 0
	var cancel=false;		// boolean flag used to cancel the entire queue upon completion of the current request

	var requester = me.createRequestObject();		// initialize the object that does the sending/receiving

	// the request information
	var action='get';
		var actions=new Array();
	var file=null;
		var files=new Array();
	var queryString='';
		var queryStrings=new Array();

	// the processing and results information
	var method='outputelem';  //this can be 'outputelem' or 'callfunction' or 'outputandcall'
		var methods=new Array();
	var outputElem='newajaxelem';
		var outputElems=new Array();
	var callFunction='void();';
		var callFunctions=new Array();
	var paramFormat='text';
		var paramFormats=new Array();

	// the clickob information
	var clickedObject=null;			//this is the object (not the ID of the object)
		var clickedObjects=new Array();
	var clickedValue='Please wait...';
		var clickedValues=new Array();
	var clickedPrevValue='';

	// the loading element information
	var loadingElem=outputElem;
		var loadingElems=new Array();
	var whileLoadingHTML=null;
		var whileLoadingHTMLs=new Array();
	var whileLoadingMode='overwrite';	//this can be 'overwrite', 'append', or 'prepend' (defaults to overwrite in function also)
		var whileLoadingModes=new Array();

	var completeCallbackFunctions=new Array();	//a queue of functions to call when the entire queue completes or is cancelled (will not be called when looping is enabled)

	me.sendRequest = function() {
		var traceTxt='';
		if (inQueue || currentQueueIndex>lastQueueIndex) return;
		while ((actions[currentQueueIndex]==null || actions[currentQueueIndex]==undefined) && 
					currentQueueIndex<=lastQueueIndex) {
			if (traceElement!='') { me.appendToTrace('addReq, skipping '+currentQueueIndex+' of '+lastQueueIndex); }
			currentQueueIndex++;
		}
		inQueue=true;

		if (loadingElems[currentQueueIndex]!=null && whileLoadingHTMLs[currentQueueIndex] != undefined) {
			me.setLoading(loadingElems[currentQueueIndex],whileLoadingHTMLs[currentQueueIndex],whileLoadingModes[currentQueueIndex]);
		}
	
		if (clickedObjects[currentQueueIndex]!=null && clickedObjects[currentQueueIndex]!=undefined) {
			clickedPrevValue=clickedObjects[currentQueueIndex].value;
			clickedObjects[currentQueueIndex].value=clickedValues[currentQueueIndex];
		}
	
		//"ensure" that no cached responses are received
		//"ensure" because it is still possible but very unlikely
		randparam=parseInt(Math.random()*99999999);
		param = queryStrings[currentQueueIndex] + '&cacherand='+randparam + '&ajxIdx='+currentQueueIndex;

		if(actions[currentQueueIndex]=='post') {
			requester.open(actions[currentQueueIndex], files[currentQueueIndex], true);
			requester.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
			requester.setRequestHeader("Content-length", param.length);
			requester.setRequestHeader("Connection", "close");
			requester.onreadystatechange = function() { me.handleResponse(); }
			requester.send(param);
			traceTxt+=','+files[currentQueueIndex]+','+param;
		}
		else if(actions[currentQueueIndex]=='get') {
			if (param!='') var page=files[currentQueueIndex] + '?' + param;
			requester.open(actions[currentQueueIndex], page);
			requester.onreadystatechange =  function() { me.handleResponse(); }
			requester.send(null);
			traceTxt+=','+page;
		}

		if (traceElement!='') { me.appendToTrace('sendReq,'+currentQueueIndex+traceTxt); }
	}

	me.addRequest = function(f_action,f_file,f_params,f_idx) { //f_idx is an optional parameter.  insert request into slot f_idx of the queue if appropriate.  f_idx=-1 means "do next", null or undefined means put at end of current queue
		var traceTxt="";
		if (f_file==null || f_file==undefined || f_file=='') {
			alert('invalid file path/name');
			return -1;
		}
		f_action=f_action.toLowerCase();
		if (!(f_action=='get' || f_action=='post')) {
			alert('invalid action');
			return -1;
		}
		
		if (f_idx==-1) f_idx=currentQueueIndex+1;		// -1 means "DO NEXT"

		if (f_idx==null || f_idx==undefined || f_idx>lastQueueIndex) f_idx=lastQueueIndex+1;

		actions.splice(f_idx,0,f_action);
		files.splice(f_idx,0,f_file);
		queryStrings.splice(f_idx,0,f_params);

		methods.splice(f_idx,0,method);
		outputElems.splice(f_idx,0,outputElem);
		callFunctions.splice(f_idx,0,callFunction);
		paramFormats.splice(f_idx,0,paramFormat);

		clickedObjects.splice(f_idx,0,clickedObject);
		clickedValues.splice(f_idx,0,clickedValue);

		loadingElems.splice(f_idx,0,loadingElem);
		whileLoadingHTMLs.splice(f_idx,0,whileLoadingHTML);
		whileLoadingModes.splice(f_idx,0,whileLoadingMode);

		lastQueueIndex++;
		setTimeout(function() {me.sendRequest();},1);

		if (traceElement!='') { me.appendToTrace('addReq,'+f_idx+' of '+lastQueueIndex+','+f_action+','+f_file+','+f_params+','+method+','+outputElem+','+callFunction+','+paramFormat+','+clickedObject+','+clickedValue+','+loadingElem+','+traceTxt); }
		return f_idx;
	}

	me.setLoadOb = function(f_ob,f_val,f_method) {
		if (me.elemExists(f_ob)) {
			loadingElem=me.getElem(f_ob);
			whileLoadingHTML=f_val;
			if (f_method=='append') whileLoadingMode='append';
			else if (f_method=='prepend') whileLoadingMode='prepend';
			else f_method='overwrite';
		} else if (f_ob==null) {
			loadingElem=null;
		} else {
			alert('The element "'+f_ob+'" could not be found');
		}
	}

	me.setClickOb = function(f_ob,f_val) {
		if (me.elemExists(f_ob)) {
			clickedObject=me.getElem(f_ob);
			clickedValue=f_val;
		} else if (f_ob==null) {
			clickedObject=null;
		} else {
			alert('The element "'+f_ob+'" could not be found');
		}
	}

	me.setResultsElem = function(f_method,f_elem) {
		method=f_method;
		outputElem=f_elem;
	}

	me.setResultsFunction = function(f_method,f_func,f_paramFormat) {
		if (f_paramFormat==undefined) f_paramFormat='text';
		method=f_method;
		callFunction=f_func;
		paramFormat=f_paramFormat;
	}

	me.isComplete = function() {
		if (currentQueueIndex>=lastQueueIndex && !inQueue) return true;
		return false;
	}

	me.getCurrentIndex = function () {
		return currentQueueIndex;
	}

	me.getQueueLength = function () {
		return lastQueueIndex;
	}

	me.setLoading = function(elem,txt,txtMode) {
		if (txt!=undefined && txt!=null) {
			if (txtMode=='append') elem.innerHTML+=txt;
			else if (txtMode=='prepend') elem.innerHTML=txt+elem.innerHTML;
			else	elem.innerHTML = txt;
		}
	}

	me.handleResponse = function() {
		var traceTxt="";
		if (cancel) {
			cancel=false;
			if (traceElement!='') { me.appendToTrace('finalize cancel all,'+currentQueueIndex+' of '+lastQueueIndex+',skipping: '+files[currentQueueIndex]+' ?'+queryStrings[currentQueueIndex]+','+methods[currentQueueIndex]); }
		} else {
			if (requester.readyState==3) {
				//alert('3');
			} else if (requester.readyState==4) {	// response was received, process it
	//			if (traceElement!='') { me.appendToTrace('handleResponse pre,'+currentQueueIndex+' of '+lastQueueIndex+','+methods[currentQueueIndex]+','+traceTxt); }
				// me.method can be 'outputelem' or 'callfunction' or 'outputandcall'
				if (methods[currentQueueIndex]=='outputelem' || methods[currentQueueIndex]=='outputandcall') {	//output to the specified element
					if (!me.elemExists(outputElems[currentQueueIndex])) 	{
						traceTxt+='created elem: '+outputElems[currentQueueIndex]+',';
						var elem = document.createElement(outputElems[currentQueueIndex]);
						var parentelem = document.body; //get body element
						parentelem.appendChild(elem);
					} else {
						var elem = me.getElem(outputElems[currentQueueIndex]);
					}
					elem.innerHTML=requester.responseText;
					traceTxt+='output to elem: '+outputElems[currentQueueIndex]+',';
				}
				if (methods[currentQueueIndex]=='callfunction' || methods[currentQueueIndex]=='outputandcall') {
					fcall=requester.responseText;
					if (paramFormats[currentQueueIndex]=='json') {
//						if (fcall=='') fcall={"results":["set":"empty"]};
						if (fcall!='') {
							var json=eval('('+fcall+')');
							fcall=callFunctions[currentQueueIndex].replace('ajaxResponseText','json');
						}
					} else {
						fcall=fcall.replace(/\n|\r/g,"");
						fcall=fcall.replace(/\\'/g,"'+String.fromCharCode(39)+'");
						fcall=fcall.replace(/'/g,"\\'");
						fcall=fcall.replace(/"/g,"'+String.fromCharCode(34)+'");
						fcall=callFunctions[currentQueueIndex].replace('ajaxResponseText',"'"+fcall+"'");
					}
	//				fcall='"'+fcall+'"';
					if (fcall!='') {
						if (traceElement!='') { me.appendToTrace('call function,'+fcall); }
						eval(fcall);
						traceTxt+='called function: '+callFunctions[currentQueueIndex]+',format: '+paramFormats[currentQueueIndex]+','+fcall+',';
					} else {
						if (traceElement!='') { me.appendToTrace('call function, (empty string)'); }
					}
				}
	
				if (clickedObjects[currentQueueIndex]!=null && clickedObjects[currentQueueIndex]!=undefined) {
					clickedObjects[currentQueueIndex].value=clickedPrevValue;
				}
	
				if (traceElement!='') { me.appendToTrace('handleResponse,'+currentQueueIndex+' of '+lastQueueIndex+','+files[currentQueueIndex]+' ?'+queryStrings[currentQueueIndex]+','+methods[currentQueueIndex]+','+traceTxt); }
	
				currentQueueIndex++;
				inQueue=false;
				if (currentQueueIndex<=lastQueueIndex) me.sendRequest();
				else if (loop) {
					currentQueueIndex=0;
					setTimeout(function() {me.sendRequest();},loopDelay);
				} else {
					for (var i=0;i<completeCallbackFunctions.length;i++) {
						eval(completeCallbackFunctions[i]);
					}
				}
			}
		}
	}

	me.setLoop = function(f_bln,f_time) {
		if (f_time.match(/^[0-9]+$/)) {
			loop=f_bln;
			loopDelay=f_time;
			if (loop && currentQueueIndex>lastQueueIndex) {
				currentQueueIndex=0;
				me.sendRequest();
			}
		} else {
			alert('invalid time: '+f_time);
		}
	}

	me.cancelAll = function() {
		lastQueueIndex=-1;
		currentQueueIndex=0;
		inQueue=false;
		cancel=true;
		if (traceElement!='') { me.appendToTrace('cancel all'); }
	}

	me.cancelRemaining = function() {
		var idx=lastQueueIndex;
		if (inQueue) lastQueueIndex=currentQueueIndex;
		if (traceElement!='') { me.appendToTrace('cancel remaining,'+currentQueueIndex+','+idx+','+lastQueueIndex); }
	}

	me.cancelRequest = function(idx,verbose) {
		var cancelled=false;
		var traceTxt='';
		if (verbose==undefined) verbose=true;
		if (inQueue && currentQueueIndex==idx) {
			if (verbose) alert('The request to be cancelled is already in progress and could not be cancelled.');
			cancelled=false;
			traceTxt+='<span class="fail">failed</span>';
		} else {
			actions[idx]=null;
			cancelled=true;
			traceTxt+='succeeded';
		}
		if (traceElement!='') { me.appendToTrace('cancel one,'+idx+','+traceTxt); }
		return cancelled;
	}

	me.getElem = function(obID) {
		if (document.getElementById) elem = document.getElementById(obID);
		else if (document.all) elem = document.all[obID];
		else if (document.layers) elem = document.layers[obID];
		return elem;
	}

	me.elemExists = function(obID) {
		var pgt = new Array();
		pgt = document.getElementsByTagName("*");
		for (i=0; i<pgt.length; i++) {
			if (pgt[i].id==obID) {
				return true;
			}
		}
		return false;
	}

	me.addQueueCompleteCallback = function(fc) {
		if (fc!=undefined && fc!=null && fc!="") {
			completeCallbackFunctions.push(fc);
		}
	}
	
	me.enableTrace = function(obID) {
		if (obID==undefined) obID='div_ajaxClassTrace';
		if (!me.elemExists(obID)) {
			var elem = document.createElement('div');
			var elemtxt = document.createTextNode('<h3>Ajax Class Trace</h3>');
			var parentelem = document.body; //get body element
			elem.setAttribute("id",obID);
			elem.style.margin="5em";
			parentelem.appendChild(elem);
			//elem.appendChild(elemtxt);
			elem.innerHTML='<h3>Ajax Class Trace</h3>';
		}
		traceElement=obID;
		me.appendToTrace('trace enabled');
	}
	
	me.appendToTrace = function(txt) {
		var elem=me.getElem(traceElement);
		elem.innerHTML+='<p>'+txt+'</p>';
	}

	me.clearTrace = function() {
		var elem=me.getElem(traceElement);
		elem.innerHTML='<h3>Ajax Class Trace</h3>';
	}

	me.disableTrace = function(obID) {
		traceElement='';
		me.appendToTrace('trace disabled');
	}
}
