/*
UltimateAjax por Fco. Javier Marín Ros
Librería Javascript para el manejo de la clase XMLHttpRequest

Basada en la libreria VeryTinyAJAX de Pablo Rodríguez Rey y Javier Gil Motos  [http://www.inertinc.org],
distribuida bajo licencia GPL


Ejemplo de uso:

<script type="text/javascript" src="UltimateAjax.js"></script>
<script type="text/javascript">
ajax=AjaxClass(); //Carga la clase
if(ajax.checkHashLocation()==false) //Comprueba la ruta de dirección para comprobar si tiene datos para cargar una pagina u otra. Esto es útil para permitir el bookmarking de páginas ajax.
ajax.Updater('resultado','pagina2.htm',hGET,'texto=Hola Mundo',true,true);

</script>
<body>
<div id="resultado"></div>
</body>
*/

function Ajax()
{
	this.http= httpObject();//Objeto XMLHttpRequest usado para la comunicación
	this.waitingResponse=false;//Indica si la clase esta esperando respuesta del servidor
	this.loaderEvent='';//Se invoca cuando se esta cargando un nuevo contenido con la funcion Updater
	this.errorEvent='';//Se invoca cuando aparece un error
	this.cache=false;//Indica si se cachean los resultados de la funcion Updater
	this.dataCache=Array();//Cache de las paginas visitadas
	this.cacheExpireTime=900;//Tiempo en segundos en el que expira los datos de la cache. 0 para que no expire nunca.
	this.cacheTime=Array();//Almacena todos los tiempos de los elementos almacenados en cache
	this.httpError=	function () {// Comprobar que la respuesta del servidor es la 200 (HTTP OK)

		if (this.http.readyState==4) {
			try {
				var ok=(this.http.status!=200);
			}
			catch(e) {
				return(true);
			}

			return(ok);
		}
	};
	this.httpErrorShow=function () {// Muestra un mensaje de error dependiendo del tipo de error encontrado
		if (this.httpError()) {
			if (this.httpStatus()) alert("Se ha encontrado el error "+this.httpStatus()+": "+this.httpStatusString()+" en el servidor.");
			else alert("El servidor no responde a la petición!\nPruebe dentro de unos instantes.");
		}
	};
	this.Abort=	function() {// Cancelar la peticion actual
		this.http.abort();
	};
	this.httpComplete=	function () {// Indicar si se ha completado la operación
		if (this.http.readyState==4)
		return(true);
		else
		return(false);
	};
	this.httpData=	function() {// Devolver los datos recibidos
		return(this.http.responseText);
	};
	this.httpXML=function() {	// Devolver los datos recibidos en formato documento XML
		return(this.http.responseXML);
	};
	this.httpStatus=function () {// Devolver el estado del servidor, si se detecta error, el servidor no estará disponible

		try {
			return(this.http.status);
		}
		catch(e) {
			return(0);
		}
	};
	this.httpStatusString=function () {// Devolver el estado del servidor, en formato de texto

		try {
			return(this.http.statusText);
		}
		catch(e) {
			return(0);
		}
	};
	this.httpSend=function (method, url, data, eventfunction) {	// Realizar un envío de datos http
		var sdata=(data?data:"");
		var async=(eventfunction?true:false);

		switch (method) {

			case 0:
			this.http.open("GET",url+"?"+sdata,async);
			sdata=null;
			break;

			case 1:
			this.http.open("POST",url,async);
			break;

			default:
			return(false);
		}

		if (async)
		this.http.onreadystatechange=eventfunction;

		this.http.setRequestHeader('Content-Type','application/x-www-form-urlencoded; charset=ISO-8859-1"');

		this.http.send(sdata);

		return(true);
	};
	//Actualiza el contenido de un elemento a partir de la respuesta ajax
	//fade: indica si se muestra un efecto fade entre la transición de datos. Por defecto es true.
	//execLoader: indica si se carga el evento loaderEvent mientras se espera la respuesta del server. Util para colocar una funcion que reemplace el contenido por una imagen de carga. Por defecto es true.
	//finishEvent: evento llamando al acabar la actualización
	//registerToBackButton: indica si esta peticion se registra para que al pulsar el boton atras se vuelva a ella. Por defecto es true.
	this.Updater=function (containerID,url,method,data,fade,execLoader,finishEvent,registerToBackButton) {
		if(this.waitingResponse)
		return;

		this.waitingResponse=true;

		if(fade!=false)
		{
			if(execLoader!=false)
			fadeEffects.hide(containerID,this.loaderEvent);
			else
			fadeEffects.hide(containerID,null);
		}
		if(fade==false && execLoader!=false)
		eval(this.loaderEvent);

		if(method==null || typeof method=='undefined')
		method=hGET;


		if(this.cache==true || registerToBackButton!=false)
		{
			var datos=Array();//Datos que se almacenan en el hash de la URL
			datos['url']=url;
			datos['data']=data;
			datos['id']=containerID;
			if(method!=0)
			datos['method']=method;
			if(fade!=true)
			datos['fade']=fade;
			if(execLoader!=true)
			datos['execLoader']=execLoader;
			datos['finishEvent']=finishEvent;
			dataId=backButton.serializeData(datos);

			if(registerToBackButton!=false)//Añadir el registro del cambio de pagina
			{
				if(backButton.inicialized==false)
				backButton.initialize(this.backButtonClick,this);
				backButton.registerNewLocationID(dataId);
			}

			if(this.cache==true)
			{
				for (var key in this.dataCache)
				{
					if(key==dataId)//Ya esta cacheada la pagina, establecer el contenido y salir
					{
						//Comprobar el tiempo en cache
						difSegundo=(new Date().getTime()/1000)-this.cacheTime[key];
						if(this.cacheExpireTime!=0 && difSegundo<this.cacheExpireTime)
						{
							this.updaterResponse(this.dataCache[key],containerID,fade,finishEvent,false);
							return;
						}
					}
				}
			}
		}

		var thisObj=this;
		this.httpSend(method,url,data,function() {
			if (thisObj.httpError())//Se ha encontrado un error en la petición
			{
				if(thisObj.errorEvent!=null)
				eval(thisObj.errorEvent);
				else
				thisObj.httpErrorShow();

				thisObj.waitingResponse=false;
			}
			if(!thisObj.httpError() && thisObj.httpComplete())
			{
				thisObj.updaterResponse(thisObj.httpData(),containerID,fade,finishEvent,true);
			}
		});
	};
	this.updaterResponse=function(data,containerID,fade,finishEvent,addToCache){//Se llama cuando se tiene ya el resultado de la petición realizada por updater
		var thisObj=this;
		var setHtml=function(){//Funcion llamada al acabar la peticion
			SetContainerHTML(containerID,data,true);
		};
		var finishFunction=function(){//Funcion llamada al acabar la peticion
			thisObj.waitingResponse=false;

			if(thisObj.cache==true && addToCache==true)//Añadir datos a la cache
			{
				thisObj.cacheTime[dataId]=new Date().getTime()/1000;//Fecha en la que se añade a la cache
				thisObj.dataCache[dataId]=data;//Datos almacenados de la cache
			}
			if(finishEvent!=null)
			eval(finishEvent);
		};

		if(fade!=false)
		fadeEffects.show(containerID,finishFunction,setHtml);
		else
		{
			setHtml();
			finishFunction();
		}
	};
	this.backButtonClick=function(datos,id,classObject)//Invocado cuando se pulsa el boton atras del navegador
	{
		if(classObject.cache==true)
		{
			for (var key in classObject.dataCache)
			{
				if(key==id)//Ya esta cacheada la pagina
				{
					//Comprobar el tiempo en cache
					difSegundo=(new Date().getTime()/1000)-classObject.cacheTime[key];
					if(classObject.cacheExpireTime!=0 && difSegundo<classObject.cacheExpireTime)
					{
						classObject.updaterResponse(classObject.dataCache[key],datos['id'],datos['fade'],datos['finishEvent'],false);
						return;
					}

				}
			}
		}
		//No existe en cache, recargar
		classObject.Updater(datos['id'],datos['url'],datos['method'],datos['data'],datos['fade'],
		datos['execLoader'],datos['finishEvent'],false);
	};
	this.checkHashLocation=function(){//Comprueba el parametro hash de la url y carga su contenido si existe. Util para crear marcadores ajax. Devuelve true si se ha cargado contenido de la barra hash y false si no
		if(backButton.inicialized==false)
		backButton.initialize(this.backButtonClick,this);

		return backButton.goToHashLocation();
	}
	
	return this;
}




/* Funciones y constantes estáticas empleadas por la clase ajax */

// constantes para httpRequest
var hGET=0;
var hPOST=1;

//Funciones varias
function gid(id) { return(document.getElementById(id)); }
function $(id) {return document.getElementById(id);	}
function isIE()	{		return (navigator.userAgent.toLowerCase().indexOf("msie")!=-1)?true:false;	}

// crea el objeto XML-HTTP
function httpObject() {
	try {
		return new XMLHttpRequest();
	}
	catch (e) {
		try {
			return new ActiveXObject("Msxml2.XMLHTTP");
		}
		catch (e) {
			try {
				return  new ActiveXObject("Microsoft.XMLHTTP");
			}
			catch (e) {
				return false;
			}
		}
	}
}

// Devuelve todos los campos y datos
// de un formulario tipo campo1=dato1&campo2=dato2&...
function httpFormFields(formObject) {
	var fields="";
	for (x=0;x<formObject.length;x++) {
		var val="";
		if (formObject[x].name) {
			switch (formObject[x].type) {
				case "checkbox": val=(formObject[x].checked?"1":"0"); break;
				case "button": case "select-one": case "text": case "textarea":
				default: val=formObject[x].value;
			}
			fields=fields+(x>0?"&":"")+formObject[x].name+"="+escape(val);
		}
	}
	return(fields);
}

function SetContainerHTML(containerID,html,processScripts)
{
	$(containerID).innerHTML = html;

	if(processScripts!=false)//Eliminar y poner elementos javascript para que el navegador los reconozca
	{
		var elementos=$(containerID).getElementsByTagName('script');
		for(i=0;i<elementos.length;i++) {
			var elemento=elementos[i];

			nuevoScript=document.createElement('script');
			nuevoScript.text=elemento.innerHTML;
			nuevoScript.type = 'text/javascript';
			if(elemento.src!=null && elemento.src.length>0)
			nuevoScript.src=elemento.src;

			elemento.parentNode.replaceChild(nuevoScript,elemento);
		}
	}
}

//Efectos de degradado de opacidad
var fadeEffects = {
	fading:false,
	show:function(containerID,event,startupEvent)
	{
		if(fadeEffects.fading==true && isIE()==false)//Esperar a que acabe
		{
			window.setTimeout(fadeEffects.show,250,containerID,event,startupEvent);
			return;
		}
		//Establecer el nuevo contenido antes de empezar a mostrar el fade
		fadeEffects.changeOpac(0,containerID);
		if(typeof startupEvent== 'function')
		window.setTimeout(startupEvent,10);

		fadeEffects.fade(containerID,0,100,1,event);
	},
	hide:function(containerID,event)
	{
		fadeEffects.fading=true;
		fadeEffects.fade(containerID,100,0,-1,event);
	},
	fade:function(containerID,initial,fin,incr,event){
		var contador = 0;
		var velocidad = Math.round(400 / 100);//Tiempo empleado / 100
		for(i = initial; i >=0 && i<101; i+=incr) {
			window.setTimeout("fadeEffects.changeOpac(" + i + ",'" + containerID + "')", (contador * velocidad));
			contador++;
		}
		window.setTimeout(event,(contador * velocidad));//Llamar al evento al acabar
		window.setTimeout("fadeEffects.fading=false;",(contador * velocidad));
	},
	changeOpac:function (opacity, id) {
		var ids = document.getElementById(id).style;
		ids.opacity = (opacity / 100);
		ids.MozOpacity = (opacity / 100);
		ids.KhtmlOpacity = (opacity / 100);
		ids.filter = "alpha(opacity=" + opacity + ")";
	}
};

//Administracion del boton atras del navegar y bookmarking
var backButton={
	backButtonEvent:null,//Evento invocado cuando se cambia la pagina, con tres parametros: array de datos, la Id del array y la instancia de clase
	classObject:Object,//Instancia de clase pasada como argumento al invocar el evento backButtonEvent
	lastBackPage:0,//Ultima pagina accedida por ajax, interna
	inicialized:false,//Indica si se ha inicializado la clase
	initialize :function(event,classObject)//Inicializa la funcionalidad del script
	{
		this.backButtonEvent=event;
		this.classObject=classObject;

		if(isIE())
		{
			var iframe=document.createElement("iframe");
			iframe.setAttribute("id", "ajaxback");
			iframe.style.display="none";
			iframe.style.visibility="hidden";
			document.getElementsByTagName("body")[0].appendChild(iframe);
		}
		setInterval("backButton.checkLocation()", 350);
		this.inicialized=true;
	},
	checkLocation:function ()//Comprueba si se ha pulsado el boton atras
	{
		var hash=(typeof((hash=location.href.split("#")[1]))!="undefined"?hash:"");
		if(hash!=this.lastBackPage)
		{
			this.lastBackPage=hash;
			this.backButtonEvent(this.unserializeData(hash),hash,this.classObject);
		}
	},
	registerNewLocation:function (data)//Añade a la lista una nueva pagina que sera llamada cuando se pulse el boton atras
	{
		id=this.serializeData(data);
		return this.registerNewLocationID(id);
	},
	registerNewLocationID:function (id)//Añade a la lista una nueva pagina que sera llamada cuando se pulse el boton atras
	{
		if(isIE())
		{
			document.getElementById('ajaxback').src="control.htm?id="+id;
		}
		location.hash=id;
		this.lastBackPage=id;
		return id;
	},
	goToHashLocation:function()//Carga directamente la url encontrada en el hash de la direccion
	{
		var hash=(typeof((hash=location.href.split("#")[1]))!="undefined"?hash:"");
		if(hash.length>0)
		{
			this.lastBackPage=hash;
			this.backButtonEvent(this.unserializeData(hash),hash,this.classObject);
			return true;
		}
		else return false;
	},
	serializeData:function(dataArray){//Serializa el array pasado como argumento
		var str='';
		for (var key in dataArray)
		{
			if(typeof dataArray[key] == 'function' || typeof dataArray[key] == 'undefined' || dataArray[key]==null)
			continue;

			valor=dataArray[key].toString();
			if(valor.length<=0)
			continue;

			str +='&'+key+'='+ valor.replace('=','%3D').replace('&','%26');
		}
		return str.substr(1);
	},
	unserializeData:function(dataString){//Deserializa el array
		var split=dataString.split('&');
		var res=Array();
		for (var i=0; i < split.length; i++)
		{
			index=split[i].indexOf('=');
			valor=split[i].substr(index+1).replace('%3D','=').replace('%26','&');

			if(valor.length==0)
			valor=null;
			else if(isNaN(parseInt(valor))==false)
			valor=parseInt(valor);

			res[split[i].substr(0,index).replace('%3D','=').replace('%26','&')]=valor;
		}
		return res;
	}
};