// JavaScript Document
/*
	AJAX class
	
	Last Update: December 7, 2007
	
	author: Flasto
*/


/* params are optional

*/
function Ajax(baseURL,baseMethod,baseLoadingObject) {
	
	
	/**** OBJECT CONSTRUCTOR       ******/
	
	//Base URL of Ajax Response Content
	this.baseURL = null;
	
	//True when request is in process
	this.isBusy = false;
	
	//Error message variable
	this.error = "";
	
	this.isSafari = navigator.userAgent.indexOf("Safari") != -1;
	this.isGecko = navigator.userAgent.indexOf("Gecko") != -1;
	this.isOpera = navigator.userAgent.indexOf("Opera") != -1;
	this.isIE = navigator.appName == "Microsoft Internet Explorer";
	if(this.isSafari)
		this.isGecko = false;
	
	//Request Method
	this.baseMethod = "GET";
	
	//HTML Element to show when loading content
	this.loadingObject = null;
	
	//Element where html content will be placed
	this.responseObject = null;
	
	//If true, clears any whitespace on XML. Default is TRUE
	this.ignoreWhiteSpace = true;
	
	//If true, escapes any variable for safe XML transfer. Default is FALSE
	this.xmlEscapeParams = false;
	
	//Can escape multiple times the & in case it's necessary. Default is 0 times
	this.ampEscapes = 0;
	
	//Request and response Object
	this.xmlHttp = null;
	
	
	//XML Object. The xml response will be placed on this object
	this.xmlObj = null;
	
	// Text Object. The text response will be placed on this object
	this.textObj = null;
	
	//JSON Object. The JSON response will be placed on this object
	this.jsonObj = null;
	
	//Any function that should be called after http response
	this.handler = null;	
	
	//Expression that recognize non Element Objects
	this.hashRE = /^#/;
	
	//Expression that recognize nodes with whitespace only
	this.emptyRE = /^[\r\t\n ]*$/;
	
	//Expression that cleans any text element's whitespace
	this.cleanRE = new RegExp("^[\r\t\n ]*|[\r\t\n ]*$",'g');
	
	this.historyEnabled = false;
	this.blankPage = null;
	this.historyExec = false;
	this.referer = null;
	
	this.iFrame = null;
	
	var self = this;
	
	this.currentHash = null;
	
	this.locationCheck = null;
	
	//Asigns values given on constructor
	if(baseURL)
		this.baseURL = baseURL;
	if(baseMethod)
		this.baseMethod = baseMethod;
	if(baseLoadingObject)
		this.loadingObject = baseLoadingObject;

	/*** END OF OBJECT CONSTRUCTOR ***/

	
	//Sets a new loading Object
	this.setLoadingObject = function (loadObj) {
		this.loadingObject = loadObj;	
	}
	
	//Sets a new Base URL
	this.setBaseURL = function (url) {
		this.baseURL = url;	
	}
	
	//Sets a new Request Method
	this.setMethod = function(method) {
		this.baseMethod = method;	
	}
	/*
		Enables browser history with ajax. Simulating the GET parameters using the Hash (#),
		Due to the nature of browser history, this may only work with GET method
		It could be used with POST, but POST should be used for larger data transfer rather than navigation purposes.
		
		History is currently not well supported on Safari, has strange behavior
		
		This method MUST be called AFTER the body has finished loading, otherwise the iframe cannot be appended into the body.
		
		Required parameter 'blankPage' is a blank dummy page in which the IFRAME will make the calls.
	*/
	this.enableHistory = function (blankPage) {
		

		//Currently not supported on Safari browsers
		if(!document.body)
			return false;
		/*if(this.isSafari)
			return false;*/
		
		if(blankPage == null)
			return false;
			
		this.blankPage = blankPage;
		this.historyEnabled = this.blankPage != null;
		
		//Creates the IFRAME (not necessary on Mozilla)
		if(this.iFrame == null) {
				this.iFrame = document.createElement("iframe");
				this.iFrame.style.display = "none";
				this.iFrame.style.visibility = "hidden";
				if(!this.isGecko) {
					try { this.iFrame.attachEvent("onload",setHash); } catch (e) 
						{ 
							try {this.iFrame.addEventListener("load",setHash,false) }
								catch(e) { return false; }
						}
					this.iFrame.src = this.blankPage+"?"+location.hash.substring(1);
				}
				document.body.appendChild(this.iFrame);
		
				// 'listens' to any URL hash changes
				this.locationCheck = window.setInterval(checkLocation,100);
				
			}
		
		return this.iFrame != null;
	}
	
	//Disables the history
	this.disableHistory = function() {
		this.blankPage = null;
		this.historyEnabled = false;
		document.body.removeChild(this.iFrame);
		this.iFrame = null;
		window.clearInterval(this.locationCheck);
		this.locationCheck = null;
	}
	
	/*
		Sets the URL has for non Gecko browsers.
		When the iFrame has finished loading the dummy page, sets its GET parameters into the URL hash
	*/
	var setHash = function () {
		
			location.hash = self.iFrame.contentWindow.location.search.substring(1); 
			currentHash = location.hash;
	}
	
	
	/*
		Listens to any change in the URL's HASH
		currentHash is the "location" in which Ajax currently is
		If the url hash is different from the currentHash, means that the user navigated backwards or forwards in the browser,
		hence a new Ajax call must be made with the new location
	*/
	var checkLocation = function () {
		//document.getElementById("debug").innerHTML = "currentHash: "+self.currentHash+", location.hash="+location.hash+"<br/>";
		if(location.hash != self.currentHash) {
			self.currentHash = location.hash;
			if(self.isIE) {
				self.iFrame.src = "blank.html?"+location.hash.substring(1); 	
			}
			self.historyExec = true;
			self.getResponse(location.hash.substring(1),self.responseObject,self.handler);
		}
		return;
	}
	
	
	//Sets visible the loadingObject
	this.showLoader = function(loading) {
		var ld = document.getElementById(this.loadingObject);
		if(ld != null) {
			if(loading)
				ld.style.display = "block";
			else
				ld.style.display = "none";
		}
	}
	
	
	// Creates the Http Object
	this.GetXmlHttpObject = function() {
		var objXMLHttp=null;
		if (window.XMLHttpRequest) {
			objXMLHttp=new XMLHttpRequest();
		} else if (window.ActiveXObject) {
			objXMLHttp=new ActiveXObject("Microsoft.XMLHTTP");
		}
		return objXMLHttp;
	}
	
	// Creates and returns a new empty XML Object
	this.getXMLDoc = function() {
		var objXMLDoc=null;
		
		//For mozilla compatible
		if (document.implementation && document.implementation.createDocument) {
			objXMLDoc=document.implementation.createDocument("","root",null);
		} else if (window.ActiveXObject) { //For Internet Explorer
			objXMLDoc=new ActiveXObject("Microsoft.XMLDOM");
			objXMLDoc.async=false;
		}
		return objXMLDoc;
	}
	/* 
		Param:
			string XML: string containing the xml
	Returns a parsed xml object
	Stores the XML on this object's XMLObj
	*/
	
	this.parseXML = function (xml) {
		
		//For Internet Explorer 
		if (window.ActiveXObject) {
	 	 	this.xmlObj=new ActiveXObject("Microsoft.XMLDOM");

			this.xmlObj.async="false";
  			this.xmlObj.loadXML(xml);
			this.xmlObj = this.xmlObj.documentElement;
						  			
  		} else  { // For Mozilla compatible
			//alert(xml);
	  		var parser=new DOMParser();
	  		this.xmlObj=parser.parseFromString(xml,"text/xml");
			this.xmlObj = this.xmlObj.documentElement;
			
  		}
		if(this.ignoreWhiteSpace) {
			removeWhiteSpace(this.xmlObj);	
		}
		return this.xmlObj;
		
	}
	
	//Tries to evaluate the given expression and returns the object, or null if failed
	this.parseJSON = function (json) {
		try { this.jsonObj = eval("("+json+")"); } catch(e) { this.jsonObj = null; this.error = e; }
		return this.jsonObj;
	}
	
	//Returns the response XML,
	this.getXMLObj = function () {
		return this.xmlObj;
	}
	
	//Returns the response Text
	this.getTextObj = function () {
		return this.textObj;
	}
	
	//Returns the response JSON Object
	this.getJSONObj = function () {
		return this.jsonObj;	
	}
	
	//Converts to String this object's XML
	this.getXMLString = function () {
		return this.xmlToString(this.xmlObj);	
	}
	
	//converts any string to be used safely on XML
	this.xmlEscape = function (string) {
		var re;
		var i;
		var nEscElements = new Array("&nbsp;","&","<",'"',"'");
		var escElements = new Array(" ","&amp;","&lt;","&quot;","&apos;");
		var escString = string;
		
		for(i=0;i<escElements.length;i++) {
			re = new RegExp(nEscElements[i],'g');
			escString = escString.replace(re,escElements[i]);
		}
		re = new RegExp("&",'g');
		for(i = 0;i<this.ampEscapes;i++) {
			escString = escString.replace(re,"&amp;");
		}
		return escString;
		
	}
	
	//Removes any whitespace from an XML Object
	this.removeWhiteSpace = function (xmlObj) {
		if(xmlObj == null)
			return;
		var node = xmlObj;
		var delNode = null;
		while(node != null) {
			delNode = node;
			node = node.nextSibling;
			if(delNode.nodeType == 3) {
				if(this.emptyRE.test(delNode.nodeValue)) {
					delNode.parentNode.removeChild(delNode);
				} else {
					delNode.nodeValue = delNode.nodeValue.replace(this.cleanRE,"");	
				}
			} else {
				this.removeWhiteSpace(delNode.firstChild);
			}
		}
		
		//Removes the <xml version ...> header if exists(only happens on IE)
		if (window.ActiveXObject) {
			if(xmlObj.nodeType == 9 && xmlObj.firstChild != null && xmlObj.firstChild.nodeName == "xml") {
				xmlObj.removeChild(xmlObj.firstChild);
			}
		}
		return xmlObj;
	}
	
	
	/* Returns a string representation of an XML Object 
		only the XML Object (xmlObj) parameter is used. The rest are for recursive actions
	*/
	this.xmlToString = function (xmlObj,root,indent) {
		if(xmlObj == null)
			return "";
		if(xmlObj.nodeType == 9) {
			xmlObj = xmlObj.firstChild;	
		}
		if(indent == null)
			indent = "";
		var node = xmlObj;
		if(root == null)
			root = xmlObj;
		var output = "";
		var attributes;
		var i=0;
		while(node != null) {
			if(node.nodeName.match(this.hashRE))
				output += indent+node.nodeValue+"\n";
			else {
				output += indent+"<"+node.nodeName;
				attributes =node.attributes;
				i = 0;
				while(attributes[i] != null) {
					output += " "+attributes[i].name+"=\""+attributes[i].value+"\"";
					i++;
				}
			output += ">\n";
			output += this.xmlToString(node.firstChild,root,indent+"\t");
			output += indent+"</"+node.nodeName+">\n";
			}
			if(node == root) {
				node = null;	
			} else
				node = node.nextSibling;
		}
		return output;	
	}
	
	//Returns the http object, contains the html response
	this.getXMLHttp = function () {
		return this.xmlHttp;	
	}
	
	
	/* Requests an xml document with the default parameters
		params: String containing the variables to be sent, null or "" if not used
		reponseObject: String of the HTML Element's ID where the response will be placed, null or "" if not used
		handler: String containing the function to execute after response
	*/
	this.getResponse = function (params,responseObject,handler) {
		this.getNewResponse(this.baseURL,this.baseMethod,params,responseObject,handler);
	}
	
	// Builds and sends the message
	// url: Destination address
	// method: GET or POST (Doesn't work with files)
	// params: string containing variables to be sent
	//			null or "" if no variables will be sent
	// respObj: String of the HTML Element's ID where the response will be placed
	//         null or "" if the response won't be dumped automatically
	// eventHandler: string containing the function to execute after response
	
	this.getNewResponse = function(url,method,params,respObj,eventHandler) {
		
		//If busy, exits
		if(this.isBusy) {
			this.error = "Object is Busy";
			return false;
		}
		if(url == null || url == "") {
			this.error = "No URL";
			return false;
		}
		if(method.toLowerCase() != "post" && method.toLowerCase() != "get") {
			this.error = "No method";
			return false;
		}
		
		
		
		//Sets the state to BUSY
		this.isBusy = true;
		
		//Sets the event Handler
		
		this.handler = eventHandler;
		
		//Sets the response HTML object
		this.responseObject = respObj;
		
		//For avoiding cached pages, adds timestamp
		var time = new Date();
		
		if(!params)
			params = "";
		if(params.charAt(0) == '&')
			params = params.slice(1);
		
		
		if(this.historyEnabled && this.blankPage != null) {
			
			//HistoryExec checks if getResponse was called by a user action or by the checkLocation routine.
			//If called by the checklocation routine, it does not store the action in history
			if(!this.historyExec) {
				if(this.isIE || this.isOpera) {
				
					//location.hash = params;
					this.iFrame.src = this.blankPage+"?"+params;
					this.currentHash = "#"+params;
				} else {
					location.hash = params;
					this.currentHash = location.hash;
				}
			} else {
				this.historyExec = false;
			}
		}
		
		params = "gt="+time.getTime()+"&"+params;
		
		//If params need to be escaped
		if(this.xmlEscapeParams) {
			var escParams = params.split("&");
			var i = 0;
			params = "";
			var param;
			while(escParams[i] != null) {
				param = escParams[i].split("=");
				params += param[0]+"="+encodeURIComponent(this.xmlEscape(param[1]));
				i++;
				if(escParams[i] != null)
					params += "&";
			}
		}
		
		
		
		//Creates the HTTP Object
		this.xmlHttp= this.GetXmlHttpObject();

		//If fails , returns
		if (this.xmlHttp==null) {
			this.error = "Browser does not support HTTP Request";
			return;
		} 
		
		//Sets the handler when response is ready.
		var object = this;
			
		//This function is executed after HTTP response is ready
		this.xmlHttp.onreadystatechange = function () {
			with(object) {
				
				if (xmlHttp.readyState==4 || xmlHttp.readyState=="complete") {

						//Places the XML on the xml object, removes whitespace if ignoreWhiteSpace is TRUE
						xmlObj = xmlHttp.responseXML;
						if(ignoreWhiteSpace) {
							removeWhiteSpace(xmlObj);	
						}
						
						//Places the response On the text Object
						textObj = xmlHttp.responseText;
						
						//Places the JSON response the json Object
						jsonObj = parseJSON(xmlHttp.responseText);
					
					
					//If there is a responseObject, places the result on the HTML element
					var rObj = document.getElementById(responseObject);
					if(rObj != null)
						rObj.innerHTML=xmlHttp.responseText;
						
					//Hides the loading Object
					if(loadingObject != null || loadingObject != "")
						showLoader(false);
						
					//Sets the state to NOT busy
					isBusy = false;
					
					//executes the handler (if any)
					if(handler != null)
						if(typeof handler == "string")
							eval(handler);
						else
							handler(referer);
					
					//Sets the HTTP object to null
					//xmlHttp = null;
					
					//Sets the handler to null
					//handler = null;
				} 
			}
		}


		//Shows the loading Object while waiting for response
		if(this.loadingObject != null || this.loadingObject != "")
				this.showLoader(true);
				
				
		//Sends the HTTP Request by GET or POST method
		if(method.toLowerCase() == "post") {
			this.xmlHttp.open("POST",url,true);
			
			this.xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
			this.xmlHttp.setRequestHeader("Content-length", params.length);
			this.xmlHttp.setRequestHeader("Connection", "close");
			this.xmlHttp.send(params);
		} else {
			this.xmlHttp.open("GET",url+"?"+params,true);
			this.xmlHttp.send(null);

		}

	}



	this.abort = function () {
		if(this.isBusy) {
			this.error = "Ajax Timed Out";
			if(this.loadingObject != null || this.loadingObject != "")
				this.showLoader(false);
						
			this.isBusy = false;
					
			if(this.handler != null)
				eval(this.handler); //executes the handler (if any)
					
			this.xmlHttp = null;
		}	
	}
	
	
	//submit  the given form using ajax
	this.submitAjax = function(fname,respObj,trigger) {
		var params = "";
		var sndfrm = document.getElementById(fname);
	
		//builds the parameters
		for(i=0;i<sndfrm.elements.length;i++) {
			
			if(sndfrm.elements[i].type == "radio" ) {
				if(	sndfrm.elements[i].checked) 
						params += "&"+sndfrm.elements[i].name+"="+sndfrm.elements[i].value;
			} else if (sndfrm.elements[i].type == "checkbox" ) {
				if(sndfrm.elements[i].checked)
					params += "&"+sndfrm.elements[i].name+"="+sndfrm.elements[i].value;
			} else if (sndfrm.elements[i].tagName == "SELECT" ) {
				if(sndfrm.elements[i].multiple) {
					for(j=0;j<sndfrm.elements[i].options.length;j++) {
						if(sndfrm.elements[i].options[j].selected)
							params += "&"+sndfrm.elements[i].name+"="+sndfrm.elements[i].options[j].value; 
					}
				} else
					params += "&"+sndfrm.elements[i].name+"="+sndfrm.elements[i].value;
			} else
				params += "&"+sndfrm.elements[i].name+"="+sndfrm.elements[i].value;
		}
		this.getNewResponse(sndfrm.action,sndfrm.method,params,respObj,trigger);
	}
}
