/*******************************************************************************
 * - AJAX Toolkit                                                              *
 *                                                                             *
 * Esta API implementa la función 'ajaxSendRequest', que permite ejecutar re-  *
 * motamente scripts en el servidor mediante peticiones originadas en el       *
 * cliente.                                                                    *
 *                                                                             *
 * La función 'ajaxSendRequest' recibe como único argumento un objeto, que de- *
 * termina todos los aspectos configurables de la función. Un ejemplo válido   *
 * de llamada es el siguiente:                                                 *
 *                                                                             *
 * function myHandler(response, v1, v2, vn)                                    *
 * {                                                                           *
 *     // ...                                                                  *
 * }                                                                           *
 *                                                                             *
 * var opts = {                                                                *
 *     method:	'get',                                                         *
 *	   url: '/scripts/js/php-ajax/script.php',                                 *
 *	   queryVars: {name1: 'value1', name2: 'value2', nameN: 'valueN'},         *
 *	   responseType: 'text',                                                   *
 *	   handler: myHandler,                                                     *
 *	   handlerVars: [mivar1, mivar2, mivarn],                                  *
 *	   async: true,                                                            *
 *	   showLoading: true,                                                      *
 *     loadingBg: '#000000',                                                   *
 *     loadingOp: 0.8,                                                         *
 *	   loadingImg: 'images/loading.gif',                                       *
 *     loadingImgW: 250,                                                       *
 *     loadingImgH: 40,                                                        *
 *	   debug: true                                                             *
 * };                                                                          *
 *                                                                             *
 * ajaxSendRequest(opts);                                                      *
 *                                                                             *
 * A continuación se detallan cada una de las opciones:                        *
 *                                                                             *
 * - method:		El método que se utilizará en la llamada ('get' o 'post'). *
 *					OPTIONAL. DEFAULT: 'get'.                                  *
 *                                                                             *
 * - url:			La URL RELATIVA del script que se desea ejecutar en el     *
 *					servidor. Es importante que la URL sea relativa, ya que si *
 *					se introduce con el dominio y el que introducimos cuando   *
 *					naveguemos por la página es diferente (poner www o no, por *
 *					ejemplo) el objeto xmlhttp lo tomará como dominios dife-   *
 *					rentes y en algunos navegadores se bloqueará por seguri-   *
 *					dad. REQUIRED.                                             *
 *                                                                             *
 * - queryVars:		Las variables que se van a pasar al script remoto. Las va- *
 *					riables son un objeto de la forma {name1: 'value1', nameN: *
 *					'valueN'}. OPTIONAL. DEFAULT: null.                        *
 *                                                                             *
 * - responseType:	El tipo de respuesta que nos devolverá el servidor ('text',*
 *					'xml' o 'json'. El tipo de respuesta 'json' es similar al  *
 *					'text', con la diferencia de que previamente a devolver la *
 *					respuesta, ésta se evalúa. DEFAULT 'xml'.                  *
 *                                                                             *
 * - handler:		La función que manejará la respuesta. No el nombre de la   *
 *					función, sino una referencia a la misma. Dicha función de- *
 *					berá estar definida por el usuario y deberá tener al menos *
 *					un argumento, que será la respuesta. Si no se define nin-  *
 *					gún manejador y se está realizando una llamada síncrona,   *
 *					la llamada a la función 'ajaxSendRequest' devolverá la     *
 *					respuesta. OPTIONAL. DEFAULT: null.                        *
 *                                                                             *
 * - handlerVars:	Variables adicionales que se le pasarán al handler cuando  *
 *					se haya obtenido la respuesta. Las variables son un array  *
 *					de valores de la forma [mivar1, mivar2, mivarn]. OPTIONAL. *
 *					DEFAULT: null.                                             *
 *                                                                             *
 * - async:			Determina si se va a realizar una llamada síncrona (false) *
 *					o asíncrona (true).                                        *
 *                                                                             *
 *					En una llamada asíncrona con handler la función que llame  *
 *					a 'ajaxSendRequest' seguirá su ejecución tras la llamada y *
 *					cuando la respuesta esté lista, se le pasará al handler.   *
 *                                                                             *
 *					En una llamada asíncrona sin handler la función sigue y    *
 *					nadie maneja la respuesta.                                 *
 *                                                                             *
 *					En una llamada síncrona con handler la función que realiza *
 *					la llamada se espera hasta tener la respuesta y se la pasa *
 *					al handler.                                                *
 *                                                                             *
 *					En una llamada síncrona sin handler la funcíon se espera y *
 *					cuando termina la llamada, 'ajaxSendRequest' devuelve la   *
 *					respuesta. OPTIONAL. DEFAULT: true.                        *
 *                                                                             *
 * - showLoading:   Indica si se debe mostrar un div bloqueante indicando que  *
 *					se está esperando la respuesta. OPTIONAL. DEFAULT: false.  *
 *                                                                             *
 * - loadingBg:     Color de fondo del div bloqueante de la forma #RRGGBB.     *
 *                  OPTIONAL. DEFAULT: #ffffff.                                *
 *                                                                             *
 * - loadingOp:     Opacity del div bloqueante. Real de 0 a 1. OPTIONAL. DE-   *
 *                  FAULT: 0.4.                                                *
 *                                                                             *
 * - loadingImg:	Ruta relativa de la imagen que se mostrará en el loading.  *
 *					OPTIONAL. DEFAULT: img/ajax-loader.gif.                    *
 *                                                                             *
 * - loadingImgW:   Anchura de la imagen que se mostrará en el loading. Sirve  *
 *                  para poder centrar la imagen en el div. OPTIONAL. DEFAULT: *
 *                  100.                                                       *
 *                                                                             *
 * - loadingImgH:   Altura de la imagen que se mostrará en el loading. Sirve   *
 *                  para poder centrar la imagen en el div. OPTIONAL. DEFAULT: *
 *                  100.                                                       *
 *                                                                             *
 * - debug:			Indica si se deben mostrar o no los mensajes de error.     *
 *					OPTIONAL. DEFAULT: false.                                  *
 *                                                                             *
 * @date    2008/01/11                                                         *
 * @version 0.4                                                                *
 * @author  Antonio Vilar Sánchez <tonivilar at gmail dot com>                 *
 *******************************************************************************/

/**
 * Determina si un objeto es de tipo array
 */
function isArray(object)
{
    return object.constructor == Array;
}

/**
 * Crea el objeto xmlhttp
 */
function ajaxCreateObject()
{
    var xmlhttp;

    try {
        xmlhttp = new ActiveXObject('Msxml2.XMLHTTP');
    } catch(e) {
        try {
            xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
        } catch(f) {
            xmlhttp = null;
        }
    }

    if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
        xmlhttp = new XMLHttpRequest();
    }

    return xmlhttp;
}

/**
 * Chequea las opciones
 */
function ajaxCheckParams(opts)
{
	var errorMsg = '';

	if (typeof opts.method == 'undefined') {
		opts.method = 'GET'; // default value
	} else {
		opts.method = opts.method.toUpperCase();

		if (opts.method != 'GET' && opts.method != 'POST') {
			errorMsg = '* method must be GET or POST\n';
		}
	}

	if (typeof opts.url == 'undefined') {
		errorMsg += '* url cannot be empty\n';
	}

	if (typeof opts.responseType == 'undefined') {
		opts.responseType = 'XML'; // default value
	} else {
		opts.responseType = opts.responseType.toUpperCase();

		if (opts.responseType != 'TEXT' && opts.responseType != 'XML' && opts.responseType != 'JSON') {
			errorMsg = '* responseType must be TEXT, XML or JSON\n';
		}
	}

	if (typeof opts.async == 'undefined') {
		opts.async = true; // default value
	} else if (typeof opts.async != 'boolean') {
		errorMsg += '* async must be boolean\n';
	}

	if (typeof opts.handler != 'undefined' && typeof opts.handler != 'function') {
		errorMsg += '* handler must be a function\n';
	} else if (typeof opts.handler == 'undefined' && opts.async) {
	    opts.responseType = 'TEXT';
	}

	if (typeof opts.handlerVars != 'undefined' && !isArray(opts.handlerVars)) {
		errorMsg += '* handlerVars must be an array\n';
	}

	if (typeof opts.debug == 'undefined') {
		opts.debug = false; // default value
	} else if (typeof opts.debug != 'boolean') {
		opts.debug = true;
		errorMsg += '* debug must be boolean\n';
	}

	if (typeof opts.showLoading == 'undefined') {
		opts.showLoading = false; // default value
	} else if (typeof opts.showLoading != 'boolean') {
		errorMsg += '* showLoading must be boolean\n';
	}

	if (typeof opts.loadingBg == 'undefined') {
		opts.loadingBg = '#ffffff'; // default value
	} else {
	    var re_hexcolor = /^#[0-9a-fA-F]{6}$/;

	    if (!re_hexcolor.test(opts.loadingBg)) {
	        errorMsg += '* loadingBg must be the form #RRGGBB\n';
	    }
	}

	if (typeof opts.loadingOp == 'undefined') {
		opts.loadingOp = 0.4; // default value
	} else if (typeof opts.loadingOp != 'number' || opts.loadingOp < 0 || opts.loadingOp > 1) {
	    errorMsg += '* loadingOp must be a number between 0 and 1\n';
	}

	if (typeof opts.loadingImgW == 'undefined') {
		opts.loadingImgW = 100; // default value
	} else if (typeof opts.loadingImgW != 'number') {
	    errorMsg += '* loadingImgW must be a number\n';
	}

	if (typeof opts.loadingImgH == 'undefined') {
		opts.loadingImgH = 100; // default value
	} else if (typeof opts.loadingImgH != 'number') {
	    errorMsg += '* loadingImgH must be a number\n';
	}

	if (errorMsg && opts.debug) {
		alert('ERROR:\n\n' + errorMsg);
		return false;
	}

	return true;
}

/**
 * Obtiene la respuesta del objeto xmlhttp
 */
function ajaxGetResponse(opts, xmlhttp)
{
	if (opts.responseType == 'XML') {
		response = xmlhttp.responseXML;
	} else {
		response = xmlhttp.responseText;
	}

	if (opts.responseType == 'JSON') {
		response = eval(response);
	}

	return response;
}

/**
 * Pasa la respuesta al manejador
 */
function ajaxCallHandler(opts, response)
{
	if (typeof opts.handlerVars == 'object') { // manejador con argumentos adicionales
		handlerArgs = '';
		for (var i = 0; i < opts.handlerVars.length; i++) {
			handlerArgs += 'opts.handlerVars[' + i + '], ';
		}
		handlerArgs = handlerArgs.replace(/, $/, '');

		eval("opts.handler(response, " + handlerArgs + ")");
	} else {
		opts.handler(response);
	}
}

/**
 * Muestra imagen de loading
 */
function ajaxCreateLoading(opts)
{
 	if (opts.showLoading) {
 		var blockerDiv = document.createElement('div');

 		blockerDiv.id               = 'ajaxBlockerDiv';
 		blockerDiv.style.position   = 'fixed';
 		blockerDiv.style.zIndex     = '3000';
 		blockerDiv.style.width      = '100%';
 		blockerDiv.style.height     = '100%';
 		blockerDiv.style.top        = '0px';
 		blockerDiv.style.left       = '0px';
 		blockerDiv.style.background = opts.loadingBg;
 		blockerDiv.style.opacity    = opts.loadingOp;
 		blockerDiv.style.filter     = 'alpha(opacity=' + opts.loadingOp * 100 + ')';

 		document.body.appendChild(blockerDiv);

 		var loadingImg = document.createElement('img');

 		if (typeof opts.loadingImg == 'undefined') {
 			// Obtenemos la URL del script ajax.js
 			var scripts = document.getElementsByTagName('script');

 			for (var i = 0; i < scripts.length; i++) {
 				if (scripts[i].src.search(/\/ajax.js$/) != -1) {
 					baseURL = scripts[i].src.replace('ajax.js', '');
 				}
 			}

 			loadingImg.src = baseURL + 'img/ajax-loader.gif';
 		} else {
 			loadingImg.src = opts.loadingImg;
 		}

 		loadingImg.border           = '0';
 		loadingImg.style.position   = 'absolute';
 		loadingImg.style.top        = '50%';
 		loadingImg.style.left       = '50%';
 		loadingImg.style.marginTop  = '-' + opts.loadingImgH / 2 + 'px';
 		loadingImg.style.marginLeft = '-' + opts.loadingImgW / 2 + 'px';

 		blockerDiv.appendChild(loadingImg)

 		document.body.appendChild(blockerDiv);
 	}
}

/**
 * Elimina la imagen de loading
 */
function ajaxDestroyLoading(opts)
{
	if (opts.showLoading) {
		document.body.removeChild(document.getElementById('ajaxBlockerDiv'));
	}
}

/**
 * Muestra la excepcion si está activado el debug
 */
function ajaxShowException(opts, e)
{
	if (opts.debug) {
		alert(e);
	}
}

/**
 * Función pricipal
 */
function ajaxSendRequest(opts)
{
	if (!ajaxCheckParams(opts)) {
		return false;
	}

    var xmlhttp, uri, vars, data, response, requestArgs, handlerArgs;

    xmlhttp = ajaxCreateObject();

    if (typeof opts.queryVars == 'object') {
        vars = '?';
        for (var varName in opts.queryVars) {
            vars += varName + '=' + opts.queryVars[varName] + '&';
        }
        vars = vars.replace(/&$/, '');
    } else {
        vars = '';
    }

    if (opts.method == 'GET') {
        uri  = opts.url + vars;
        data = null;
    } else {
        uri  = opts.url;
        data = vars.replace(/^\?/, '');
    }

    try {
        xmlhttp.open(opts.method, uri, opts.async);

        if (opts.responseType == 'XML') {
            xmlhttp.overrideMimeType('text/xml');
        }

        if (opts.method == 'POST') {
            xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        }

        if (opts.async == false) {  // Síncrono
       		ajaxCreateLoading(opts);
        	xmlhttp.send(data);

			response = ajaxGetResponse(opts, xmlhttp);

        	if (typeof opts.handler == 'function') {
				ajaxCallHandler(opts, response);
				ajaxDestroyLoading(opts);
        	} else {
        		ajaxDestroyLoading(opts);

        		return response;
        	}
        } else { // Asíncrono
            xmlhttp.onreadystatechange = function() {
            	try {
            		if (xmlhttp.readyState == 4) {
            			try {
            				if (xmlhttp.status == 200) {
            					if (typeof opts.handler == 'function') {
            						ajaxCallHandler(opts, ajaxGetResponse(opts, xmlhttp));
            					}

            					ajaxDestroyLoading(opts);
            				}
            			} catch(e) {}
            		}
            	} catch(e) {
            		ajaxShowException(opts, e);
            	}
            }

            ajaxCreateLoading(opts);
            xmlhttp.send(data);
        }
    } catch(e) {
		ajaxShowException(opts, e);
    }
}