/* posts variables to server with JavaScript */
/* Author: Wes Jones. All Rights Reserved */
/* if script will only submit once it is most likely because you forgot to define 
the ajax_complete function that is called when the process is complete
*/

var EMPTY = '(empty)';

function AJAXEvent(id,result,callBack) {
	this.id = typeof(id) == 'string' ? id : null;
	this.result = result;
	this.callBack = callBack;
}

var AJAX = Base.extend({
	name: 'AJAX',
	wait_for_server: 20, // seconds
	abort_process: null,
	id: null,
	http_request: null,
	readystate: 1,
	previous_value: null, // if the value is the same as it was don't submit change,
	url: null,
	last_try: new Object({'url':null,'params':null}),// for backup request
	bkup_value: null,
	status_ary: new Array('Saving...','Loading...','Loading','Loading'),
	callBack: null,
	disabled: null,
	progressBar: null,
	failedTries: 0,
	
	constructor: null,

	makePOSTRequest: function(url, params) {
		typeof(url) != 'string' ? url = DIRECTOR_PATH : null;
		this.url = url;
		this.last_try.url = url;
		this.last_try.params = params;
		this.http_request = null;
		
		if (window.XMLHttpRequest) { // Mozilla, Safari,...
			this.http_request = new XMLHttpRequest();
			if (this.http_request.overrideMimeType) {
				this.http_request.overrideMimeType('text/html');
			}
		} else if (window.ActiveXObject) { // IE
			try {
				this.http_request = new ActiveXObject("Msxml2.XMLHTTP");
			} catch (e) {
				try {
					this.http_request = new ActiveXObject("Microsoft.XMLHTTP");
				} catch (e) {}
			}
		}
		if (!this.http_request) {
			alert('Cannot create XMLHTTP instance');
			return false;
		}
		
		this.resetLoadingBar();
		this.http_request.onreadystatechange = this.alertContents;
		typeof(this.http_request.onprogress) != 'undefined' ? this.http_request.onprogress = this.setProgress : null;
		this.http_request.open('POST', url, true);
		this.http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
		this.http_request.setRequestHeader("Content-length", params.length);
		this.http_request.setRequestHeader("Connection", "close");
		this.http_request.send(params);
		
		this.abort_process = setTimeout("AJAX.timeout()",this.wait_for_server*1000);
		this.intvCheck ? clearInterval(this.intvCheck) : null;
		this.intvCheck = setInterval(this.alertContents,1000);
	},

	alertContents: function() {
		/*
		* DO NOT USE. This is called from makePOSTRequest
		*/
		//if (window.DEBUG) { alert('readystate:'+http_request.readyState+'\nstatus:'+http_request.status); }
		if (typeof(AJAX.id) == 'string' && typeof(AJAX.abort_process) != null && typeof(AJAX.abort_process) != undefined) {
			if(document.getElementById(AJAX.id) && AJAX.failedTries<1) {
				document.getElementById(AJAX.id).innerHTML = AJAX.status_ary[AJAX.http_request.readyState] ? AJAX.status_ary[AJAX.http_request.readyState] : '';
			} else {
				document.getElementById(AJAX.id) ? document.getElementById(AJAX.id).innerHTML = 'Request '+AJAX.failedTries+' Failed. Retrying....' : null;
			}
		}
		try {
			if (AJAX.http_request.readyState == 4) {
				try {
					if (AJAX.http_request.status == 200) {
						var id = AJAX.id;
						var result = AJAX.http_request.responseText;
						var cb = AJAX.callBack;
						AJAX.abortAJAX();
						AJAX.dispatchEvent('COMPLETE',new AJAXEvent(id,result,cb));
					} else if (AJAX.http_request.status == 404){
						AJAXQue.addDebug('ERROR: 404 unable to reach '+AJAX.url);
						AJAX.dispatchEvent('AJAX_FAIL',new AJAXEvent(id,result));
					} else {
						if(AJAX.failedTries>10) {
							AJAX.failedTries = 0;
							clearTimeout(AJAX.abort_process);
							var retry = confirm("There was a problem with the request or your request has taken longer than "+AJAX.wait_for_server+" seconds.\nWould you like to Retry?");
							if (retry) {
								makePOSTRequest(AJAX.last_try.url,AJAX.last_try.parameters);
							} else {
								AJAXQue.clear();
								AJAXQue.abort();
							}
						} else {
							AJAX.failedTries += 1;
							
						}
					}
				} catch(e) {
					//AJAXQue.retry();
				}
			} else {
				//
			}
		} catch(e) {
			// http_request dissapeared. Page must have navigated
			AJAXQue.addDebug('AJAX::alertContents()::exception::'+e);
		}
   },
   
   timeout: function() {
   		AJAXQue.addDebug('TIMEOUT: unable to complete request. verify that url exists. '+this.url);
   		this.abortAJAX();
   		clearTimeout(this.abort_process);
   		this.dispatchEvent('AJAX_FAIL',new AJAXEvent(id,result));
   		if(confirm('Unable to complete your request. Would you like to retry?')) {
   			AJAXQue.retry();
   		}
   },
   
   resetLoadingBar: function() {
   		this.failedTries = 0;
   		var stat = document.getElementById('AJAX_status'); // defined in AJAXQue startDebugger
		if(stat && stat.style.display != 'none') {
			stat.innerHTML = '';
		}
   },
   
   setProgress: function(evt) {
   		var stat = document.getElementById('AJAX_status'); // defined in AJAXQue startDebugger
   		if(stat && stat.style.display != 'none') {
	   		if(this.progressBar == null) {
	   			this.progressBar = new Array();
	   			stat.innerHTML = '';
	   			for(var i = 0; i<10; ++i) {
	   				stat.innerHTML += '<div id="AJAX_prog_'+i+'" style="float:left;background-color:#009900;border:1px solid #ffffff;height:2px;">&nbsp;</div>';
	   				this.progressBar.push(document.getElementById('AJAX_prog_'+i));
	   			}
			}
			var i = 0;
			var end = (evt.position/evt.totalSize)*10;
			for(var i in this.progressBar) {
				this.progressBar[i].style.display = i<end ? 'block' : 'none';
			}
		}
	},

	abortAJAX: function() {
		/*
		 * DO NOT USE. This is used by the AJAXQue Object.
		 */
		this.abort_process ? clearTimeout(this.abort_process) : null;
		this.abort_process = null;
		if(this.http_request && typeof(this.http_request.abort) != 'undefined') {
			this.http_request.abort();
		}
		this.http_request = null;
		this.readystate = 1;
		this.previous_value = null;
		this.field_type = null;
		//this.complete(this.id);
		this.callBack = null;
		var disabledbtn = this.disabled ? document.getElementById(this.disabled) : null;
		if(disabledbtn) {
			disabledbtn.style.disabled = '';
		}
		this.disabled = null;
		if (this.bkup_value && this.id) {
			document.getElementById(this.id).innerHTML = this.bkup_value;
		}
		this.id = null;
		this.intvCheck ? clearInterval(this.intvCheck) : null;
	},

   // function to provide proper escaping of data as it is passed
   // to the server.

	getAjax: function(id,file,obj,returnTo,disable) {
		AJAXQue.addDebug('&nbsp;&nbsp;&nbsp;AJAX::getAjax(\''+id+'\',\''+file+'\',\''+obj+'\','+typeof(returnTo)+',disable='+(disable ? disable : 0)+')');
		if(this.disabled ==  null) { // if you make it disabled you have to wait for it to return before you can make another call
			var disabledbtn = disable ? document.getElementById(disable) : null;
			if(disabledbtn) {
				disabledbtn.style.disabled = 'disabled';
				this.disabled = disable;
			}
			this.abortAJAX();
			this.id = id;
			this.readystate = 0;
			this.callBack = returnTo;
			var poststr = '';
			if (typeof(obj) == 'object') {
				// this should never happen because add to ajax que encodes them
				// but just in case.
				poststr = PO.toString(obj);
				alert(poststr);
			} else if (typeof(obj) == 'string') {
				poststr = obj;
			}
			this.makePOSTRequest(file, poststr);
		}
	},

   refresh_file: function(field, file, id) {
		/*
		 * Refresh the Ajax file field to make sure you are editing the 
		 * most up to date data for file sharing.
		 */
		if (this.readystate) {
			this.id = id+'_div';
			this.readystate = 0; // disable onclick while processing
			this.previous_value = document.getElementById(this.id).innerHTML;
			this.field_type = field;
			document.getElementById(this.id).innerHTML = 'Refreshing . . .';
			var poststr = "type=file&filename=" + escape( file ) +
				"&refresh=1";
			makePOSTRequest(this.url, poststr);
			this.previous_value = this.previous_value.length ? this.previous_value : EMPTY;
		}
	},

// database functions

	editAJAX_db: function(field, id, content, db_table, db_id, db_field, rows, cols, callback,target,styleName) {
		/*
		 * An input field or a text area that is written to a div layer when the div
		 * is clicked on. This will put in this.saveto_db to save the change in data
		 * directly to the database.
		 */
		AJAXQue.addDebug('AJAX::editAJAX_db');
		if (this.readystate) {
			this.readystate = 0; // disable onclick while processing
			this.previous_value = document.getElementById(id+'_div').innerHTML;
			this.previous_value = this.previous_value.replace(/\<(.*)?\>?/,''); // no html tags.
			this.field_type = field;
			if (field == 'input') {
				document.getElementById(id+'_div').innerHTML = '<input type="text" id="'+id+'" name="'+id+'" value="" onKeyUp="AJAX.keyUpEdit(this,event)" onBlur="AJAX.finishEdit(this,event);'+this.name+'.saveto_db(\'input\',\''+db_table+'\', \''+db_id+'\', \''+db_field+'\', this.value, \''+id+'_div\','+(typeof(callback)=='string' ? '\''+callback+'\'' : callback)+','+(target ? target['class'] : 'null')+');" class="'+(styleName ? styleName : 'ajax_input')+'">';
				document.getElementById(id).value = unescape(this.previous_value) != EMPTY ? unescape(this.previous_value) : '';
			} else if (field == 'textarea') {
				document.getElementById(id+'_div').innerHTML = '<textarea id="'+id+'" name="'+id+'" rows="'+rows+'" cols="'+cols+'" onKeyUp="AJAX.keyUpEdit(this,event)" onBlur="AJAX.finishEdit(this,event);'+this.name+'.saveto_db(\'textarea\',\''+db_table+'\', \''+db_id+'\', \''+db_field+'\', this.value, \''+id+'_div\','+(typeof(callback)=='string' ? '\''+callback+'\'' : callback)+','+(target ? target['class'] : 'null')+');" class="'+(styleName ? styleName : 'ajax_textarea')+'">'+this.previous_value+'</textarea>';
			}
			document.getElementById(id).focus();
		}
	},

	saveto_db: function(editType, db_table, db_id, db_field, db_field_value, id, callback, target) {
		/*
		 * Save the data that is passed to a specific field in the database.
		 * this requires you to pass the table, id, field, value, and you can
		 * specify a call back function to receive the data when it is saved.
		 */
		AJAXQue.addDebug('enter this.saveto_db');
		if (this.previous_value != db_field_value) {
			document.getElementById(id).innerHTML = 'Saving . . .';
			var ur = new URLRequest('type','db', 'db_table',db_table, 'db_id',db_id, 'db_field',db_field, 'db_field_value',db_field_value, 'action','update');
			AJAXQue.add(id,this.url,ur,callback,false,target);
		} else {
			var value = unescape(this.previous_value);
			document.getElementById(id).innerHTML = (editType == 'textarea' ? value : value);
			this.readystate = 1;
			this.previous_value = null;
			this.bkup_value = null;
		}
	},
   
	keyUpEdit: function(field,evt) {
		var kc = getKeyCode(evt);
		if((kc == 13 || kc == 9) && field.type != 'textarea') { field.blur(); } // ENTER
		this.dispatchEvent('DBINPUT_KEY_UP',{id:field.id,charcode:kc});
	},
	
	finishEdit: function(field,evt) {
		this.dispatchEvent('DBINPUT_FINISH',{id:field.id});
	},
	
	writeEditText: function (divid,db_table,id,field,value,callBackStr,target,style,inputStyleName,rows,cols) { // if you pass rows or cols it will think it is a textarea
		var str = (rows||cols?'<pre>':'')+'<div id="'+divid+'_div" '+(style ? 'style="'+style+'"' : '')+' onClick="editAJAX_db(\''+(rows||cols ? 'textarea' : 'input')+'\', \''+divid+'\', \''+escape(value)+'\', \''+db_table+'\', '+id+', \''+field+'\', '+(rows?rows:'null')+', '+(cols?cols:'null')+', \''+callBackStr+'\','+(target ? target : 'null')+(inputStyleName ? ',\''+inputStyleName+'\'' : '')+');">'+(value == null || value == undefined || value == '0' ? EMPTY : value)+'</div>'+(rows||cols?'</pre>':'');
		return str; 
	}
	
});

function abortAJAX() { AJAX.abortAJAX(); }
function editAJAX_db(field, id, content, db_table, db_id, db_field, rows, cols, callback, target, inputStyleName) { AJAX.editAJAX_db(field,id,content,db_table,db_id,db_field,rows,cols,callback,target,inputStyleName); }
function getAjax(id,file,obj,returnTo,disable) { AJAX.getAjax(id,file,obj,returnTo,disable); }
function getIt(id,file,obj,returnTo,disable) { AJAXQue.add(id,file,obj,returnTo,disable); }

function AJAXQueObj() {
	/*
	 * This Object does all of the ajax request processing. Calls to getIt 
	 * add to a queue that is managed by a timer. This queue will process
	 * all ajax calls in the order they are received. If an ajax call failes
	 * this will wait X seconds and try again until it is successful.
	 * This also controls the ajax debug queue to show all ajax calls that 
	 * have been made.
	 *
	 * if you want to start the debugger. See the examples.
	 * if you want to force the queue to stop use AJAXQue.abort();
	 */
	this.name = 'AJAXQue';
	this.debug = null;
	this.debugary = new Array();
	this.delay = AJAX.wait_for_server*1000;
	this.reset_count = 0;
	this.que = new Array();
	this.running = true; // automatically running
	this.total_calls = 0;
	this.illegalChars = new Array();
	this.replacements = new Array();
	this.queFinished = null;
	this.queMaxLength = 50;
	this.inCall = false;
	this.enableTimerRetry = true;

	this.addIllegalChar = function(r,w) {
		this.illegalChars.push({'r':r,'w':w});
	}

	this.onKeyUp = function(fld,event) {
		for(var i in this.illegalChars) {
			fld.value = (fld.value+'').replace(this.illegalChars[i].r,this.illegalChars[i].w);
		}
	}
	
	this.setDelay = function (n) {
		try {
			this.delay = n;
			this.timer = new WTimer('AJAXQue.timer',(this.delay/10),10);
			this.timer.onTimerFinished = function () {
				typeof(AJAX_ENABLE_TIMER_RETRY) == 'undefined' || AJAX_ENABLE_TIMER_RETRY != 0 ? AJAXQue.retry() : AJAXQue.addDebug('Retry() disabled. AJAX_ENABLE_TIMER_RETRY is false');
			}
		} catch(e) {
			//alert('missing methods.js');
		}
	}
	this.add = function (divid,url,poststr,callBack,disable,target) {
		poststr = typeof(poststr) == 'object' ? PO.toString(poststr) : poststr;
		//this.addDebug(this.name+'.add('+divid+','+url+','+poststr+','+typeof(callBack)+','+(disable ? disable : 0)+')');
		//poststr = this.runReplace(poststr);
		var item = {'divid':divid,'url':url,'poststr':poststr,'callBack':callBack,'disabled':disable,'target':target};
		var c = 0;
		var dup = 0;
		for(var i in this.que) {
			if(this.que[i].poststr == item.poststr) {
				if(c) { 
					// do not delete the first one because it is already in call.
					this.addDebug('<div style="background-color:#ff0000;color:#ffffff;">Duplicate Deleting from que</div>');
					delete this.que[i];
				} else {
					// same call back to back. Do not add this one.
					dup = 1;
				}
			}
			++c;
		}
		if(!dup) {
			this.que.push(item);
			if(this.running && this.que.length==1 && this.inCall == false) {
				this.callNext();
			}
		} else {
			this.addDebug('<div style="background-color:#ff0000;color:#ffffff;">Duplicate call already in progress. Call not added to que.</div>');
		}
	}
	this.addDebug = function (str) {
		this.debugary.push(str);
		if(this.debugary.length>this.queMaxLength){ this.debugary.shift(); }
		if(this.debug) {
			this.debug_div.innerHTML = '';
			var str = '';
			for(var i in this.debugary) {
				str = this.debugary[i].replace(/id=\"*.?\"/gi,'id=""')+'<br>'+str;
			}
			this.debug_div.innerHTML = str;
		}
	}
	
	this.start = function () {
		// starts the que
		// starts a flag to process anything as soon as it is added to the que.
		this.que = new Array();
		this.running = true;
		this.callNext();
	}
	this.stop = function () {
		this.addDebug('stop');
		this.running = false;
	}
	this.retry = function () {
		this.addDebug('<font style="color:#ff0000;">RETRY</font>');
		AJAX.abortAJAX();
		this.timer.stop();
		this.callNext();
	}
	this.reset = function () {
		this.addDebug('reset');
		this.reset_count+=1;
		this.abort();
	}
	this.abort = function () {
		this.addDebug('abort');
		this.timer.stop();
		AJAX.abortAJAX();
		this.que = new Array();
		if(this.que.length == 0 && this.queFinished) { this.queFinished(); }
	}
	this.clear = function () {
		this.timer.stop();
		if(this.debug) {
			this.addDebug(this.que.length ? '<font style="color:#336699;">&nbsp;&nbsp;&nbsp;'+this.que.length+' CALLS LEFT</font>' : '<font style="color:#336699;">AJAXQue IS EMPTY</font>');
		}
		if(this.que.length == 0 && this.queFinished) { this.queFinished(); }
	}
	this.callNext = function () {
		this.total_calls += 1;
		if(this.que.length && this.que[0]) {
			this.inCall = true;
			this.addDebug('<font style="color:#336699;">CALL NEXT</font>');
			this.timer.stop();
			this.timer.start();
			this.setDisabled(this.que[0].disabled);
			AJAX.getAjax(this.que[0].divid,this.que[0].url,this.que[0].poststr);
		} else {
			this.abort();
		}
	}
	this.setDisabled = function (dis,enabled) {
		if(dis) {
			if(typeof(dis) == 'array') {
				for(var i in dis) {
					var div = document.getElementById(dis[i]);
					if(div) {
						div.disabled = enabled ? false : true;
						this.addDebug(div.id+'.disabled='+div.disabled);
					}
		   		}
			} else if (typeof(dis) == 'string') {
				var div = document.getElementById(dis);
				if(div) {
					div.disabled = enabled ? false : true;
					this.addDebug(div.id+'.disabled='+div.disabled);
				}
			}
		}
	}
	this.startDebugger = function (divid) {
		if(this.debug) {
			this.debug = false;
			var div = document.getElementById(divid).innerHTML = '<div id="AJAX_status" style="display:block;height:4px;"></div><div id="AJAX_queue"></div>';
			div.style.display = 'none';
			this.debug_div = document.getElementById('AJAX_queue');
			this.debug_div.style.display = 'none';
		} else {
			this.debug = true;
			document.getElementById(divid).innerHTML = '<div id="AJAX_status" style="display:block;height:4px;"></div><div id="AJAX_queue"></div>';
			this.debug_div = document.getElementById('AJAX_queue');
			this.debug_div.style.display = 'block';
			this.debug_div.style.backgroundColor = '#CCCCCC';
			this.debug_div.style.width='100%';
			this.debug_div.style.height='100px';
			this.debug_div.style.fontFamily = 'arial';
			this.debug_div.style.fontSize = '7pt';
			this.debug_div.style.overflow = 'auto';
			this.timer.onTimerStatus = function () {
				AJAXQue.addDebug('timer='+AJAXQue.timer.getLoop()+' reset='+AJAXQue.reset_count+' total calls='+AJAXQue.total_calls);
			}
			this.addDebug('<b>AJAX 2.0 - debugger started</b>');
		}
	}
	this.stopDebugger = function () {
		this.debug = null;
	}
	this.setDelay(this.delay);
	this.complete = function (ajaxEvt) {
		this.addDebug('&nbsp;&nbsp;&nbsp;<font style="color:#009900;">Return:'+traceAry(ajaxEvt.params)+'</font>');
		if(this.que.length) {
			//this.finish(this.que.shift(),ajaxEvt.params.id,ajaxEvt.params.result);
			this.addDebug('&nbsp;&nbsp;&nbsp;AJAX.finish');
			var itm = this.que.shift();
			var str = ajaxEvt.params.result;
			this.setDisabled(itm.disabled,true);
			this.clear();
			ajax_id = itm.divid;
			if(ajaxEvt.params.id != null && document.getElementById(ajaxEvt.params.id)){
				document.getElementById(ajaxEvt.params.id).innerHTML = ajaxEvt.params.result.replace(/\\/g,'');
			}
			try {
				if(typeof(itm.callBack) == 'string' && itm.target != null && typeof(itm.target) != 'undefined') {
					itm.target[itm.callBack](str);
				} else if(typeof(itm.callBack) == 'function'){
					itm.callBack(str);
				} else if(typeof(itm.callBack) == 'string') {
					eval(itm.callBack+'(\''+str+'\')');
				}
			} catch (err) {
				this.addDebug("<font color=\"#FF0000\">AJAX CALLBACK FATAL ERROR: "+err+" for function "+(itm.target ? itm.target['className']+"." : "")+itm.callBack+"( "+str+" )</font>");
			}
			this.inCall = false;
			this.callNext();
		} else { // probabally suggest or something bypassing the queue
			ajax_id = ajaxEvt.params.id;
			ajaxEvt.params.callBack != null ? ajaxEvt.params.callBack(ajaxEvt.params.result) : null;
		}
	}
	AJAX.addEventListener('COMPLETE',this,'complete');
}
var AJAXQue = new AJAXQueObj();
AJAXQue.addIllegalChar('\\','');
   
/*EXAMPLE 
// this is an example of how to setup the ajax debugger que on a page.
<script type="text/javascript">
   AJAXQue.startDebugger('dummy');
</script>

*/
