Juanma Santoyo

En ocasiones me llaman friki

Manual básico de Ajax (Parte 2)

| No hay comentarios

¡Pasemos a la acción!

Bueno, la teoría es necesaria pero lo bonito, y lo que a todos nos gusta es pringarnos las manos. Vamos allá con un ejemplo simple de Ajax.

El ejemplo consistirá en programar dos select. En el primero, tendremos una lista de países, y en el segundo una lista de ciudades. Lo que haremos, será modificar el select de ciudades en función del país seleccionado en el select de países.

El ejemplo tendrá tres partes: primero, programaremos la página HTML que contendrá los dos selects. Después programaremos un script de servidor en PHP, que será la petición Ajax y que generará la respuesta que contendrá toda la información sobre ciudades. Por último, programaremos un archivo JS que realizará la petición Ajax y que hará todas las gestiones pertinentes con el fin de presentar los resultados dentro del select de ciudades.

El código fuente

Este es el código fuente para este ejemplo. Te recomiendo que vayas mirando los archivos mientas los voy comentando:

Nociones básicas de Ajax (código fuente).

La página HTML

No nos complicaremos. Cumpliremos los requisitos básicos para generar un XHTML válido y fácil de maquetar con CSS; y que conste que es por orgullo propio, porque realmente al Ajax le va a dar igual si nuestro HTML valida o no.

En resumen, dos campos select y poco más.

El Script PHP.

En este caso, vamos a hacerlo fácil: guardaremos en arrays información sobre países y ciudades; y el script se limitará a recibir el país por GET y montar una salida XML que contenga los resultados.

Destacar que lo mismo que yo uso arrays, se podría hacer, por ejemplo; un acceso a base de datos. Y, obviamente, se podría usar cualquier otro lenguaje de servidor para generar el XML, como jsp o asp.

El XML generado será más o menos algo como esto:

<?xml version="1.0" encoding="utf-8"?>
<pais codigo="1" nombre="España">
	<ciudad codigo="101">Madrid</ciudad>
	<ciudad codigo="102">Barcelona</ciudad>
	<ciudad codigo="103">Palma de Mallorca</ciudad>
	<ciudad codigo="104">Sevilla</ciudad>
	<ciudad codigo="105">Valencia</ciudad>
	<ciudad codigo="106">Bilbao</ciudad>
</pais>

Destacar dos cosas: los códigos de las ciudades se han generado mediante una sencilla fórmula:
(código de la ciudad) = ((índice del país) * 100) + (índice de la ciudad)

Por otra parte, el código XML se ha montado en una línea única. Esto se hace así para no tener que lidiar con los saltos de línea cuando analicemos el XML con JavaScript (cada navegador los interpreta a su manera).

El script JavaScript

Ahora viene lo bueno. Este script hará tres cosas: gestionará la petición Ajax a ciudades.php, procesará el objeto XML resultante, y mostrará el resultado en pantalla.

Esta es con toda seguridad la parte más compleja de la pequeña aplicación que estamos programando.

A partir de ahora, el código merece una digna explicación, por lo que comentaré una a una todas las funciones del archivo js.

Empecemos con dos funciones sencillas: dos escuchadores de eventos.

function paginaCargada()
{
	var pais = document.getElementById("pais");
	var ciudad = document.getElementById("ciudad");

	pais.disabled = false;
	pais.value = 0;

	ciudad.disabled = false;
	ciudad.value = 0;
}

function paisCambiado(select)
{
	var ciudades = document.getElementById("ciudad");
	ciudades.disabled = true;

	_actualizarCiudades(select.value);
}

El escuchador paginaCargada, responde al evento de body onload y simplemente inicia los selectores de país y ciudad. Esto es para que al refrescar con F5 no conserven el valor anterior, y por si alguno hubiese quedado desactivado (durante el ejemplo, se desactivaran mientras se realiza la petición Ajax).

El escuchador paisCambiado responde al evento onchange del select de países. El único parámetro que recibe es el propio select de países.

La función de paisCambiado es simple: por una parte, deshabilita el select de ciudades (ya que se están cargando sus nuevos valores) y por otra, inicia la petición Ajax mediante la función _actualizarCiudades.

La siguiente función cumple la labor más importante de todas: crear un nuevo objeto Ajax y retornarlo.

function ajax()
{
	var httpRequest = null;

	if (window.XMLHttpRequest)
	{
		//El explorador implementa el interfaz de forma nativa
		httpRequest = new XMLHttpRequest();
	}
	else if (window.ActiveXObject)
	{
		//El explorador permite crear objetos ActiveX
		try
		{
			httpRequest = new ActiveXObject("MSXML2.XMLHTTP");
		}
		catch (e)
		{
			try
			{
				httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
			}
			catch (e)
			{

			}
		}
	}

	return httpRequest;
}

Una de las perticularidades del objeto XMLHTTPRequest (o ajax) es que está implementado de forma distinta en cada navegador. Firefox y la mayoría, lo traen implementado junto al resto de funcionalidades JavaScript, pero Internet Explorer lo trae implementado como un control ActiveX. Esta función hace exactamente eso: hace una detección de las funcionalidades del navegador y actúa en consecuencia para obtener una instancia del objeto XMLHTTPRequest y retornarla. Esta es una función muy típica, que de hecho ni siquiera es mía… en realidad la he copiado de la wikipedia: XMLHttpRequest en Wikipedia.

Este objeto será el que realice las peticiones asíncronas y el que nos permita obtener los resultados.

Y todas estas cosas se hacen en la siguiente función: _actualizarCiudades. Mención especial a dos detalles: por una parte, fijaos en que a esta función la acompaña una variable global ajx. Por otra, fijaos en que el nombre de esta función empieza con un underscore. En este script, las funciones que empiezan con underscore son “teóricamente” privadas, o dicho de otra forma; son funciones que en teoría no deberían ser usadas desde fuera del script.

var ajx = null;
function _actualizarCiudades(pais)
{
	if(ajx != null && ajx.readyState != 4 && ajx.readyState != 0)
	{
		ajx.abort();
	}

	ajx = ajax();
	var url = "ciudades.php?pais=" + pais;

	ajx.open("GET", url);
	ajx.send();

	ajx.onreadystatechange = function()
	{
		if(ajx.readyState == 4 && ajx.status == 200 && ajx.responseXML != null)
		{
			_ciudadesCargadas(ajx.responseXML);
			ajx = null;
		}
	}
}

Veamos, lo primero a comentar de esta función es el primer if, y la razón de que el objeto XMLHTTPRequest se defina sobre una variable global ajx.

La idea es que las peticiones Ajax puedan ser canceladas si se intenta enviar una petición mientras hay otra en curso. Para ello, se basa en el valor de la propiedad readyState. Si el objeto ajx tiene la propiedad readyState en valores distintos a 4 (finalizado) o 0 (sin iniciar); es que existe una petición en curso y debe abortarse antes de que se inicie una nueva. Creo que esto se entenderá mejor con un pequeño diagrama de flujo:

Ahora que estamos seguros de que no hay ninguna petición en curso, creamos un nuevo objeto Ajax.

Y lo que toca ahora, es crear la petición al servidor propiamente dicha. Lo primero, montar la URL a la que se accederá. Fijaos en que en este momento, concatenamos las variables que enviaremos por GET. Si quisiéramos hacer un POST, enviaríamos las variables en el método ajx.send (lo veremos en breve).

Ahora que tenemos la URL, y sabemos cómo enviaremos la información; podemos inicializar dichos valores en el objeto Ajax. Para ello usamos el método ajx.open.

Ahora el objeto XMLHTTPRequest sabe todo lo necesario para realizar una petición: la dirección, los parámetros a enviar, y la forma de enviarlos. Sólo nos queda lanzarla, y lo hacemos con el método ajx.send. Este método podría enviar también las variables POST en un objeto JavaScript. Algo como:

ajx.send({
		var1 : “valor 1”
		, var2 : “valor2”
});

Por último, necesitaremos controlar el estado de la petición para saber cuándo está lista, y actuar en consecuencia. Eso lo hacemos con un controlador de eventos, y el evento en cuestión es: ajx.onreadystatechange.

Este controlador se ejecutará cada vez que el valor de la propiedad ajx.readyState cambie, por lo que necesitaremos realizar una serie de validaciones antes de asumir que la petición ha finalizado:

Por una parte, necesitamos controlar que el valor de ajx.readyState sea 4 (que quiere decir que la petición ha finalizado), y por otra, necesitamos controlar que el valor de ajx.status sea 200 (este es el valor que retorna el servidor y que significa que no ha habido errores al servir la petición). No está de más comprobar que la propiedad ajx.responseXML contenga algún valor, ya que es donde esperamos encontrar el resultado.

Y ahora que sabemos que nuestra petición ha finalizado de forma satisfactoria, debemos analizar el resultado. Esto lo hará la función _ciudadesCargadas.

function _ciudadesCargadas(xml)
{
	var ciudad = document.getElementById("ciudad");	

	var ciudadesDat = _parseaCiudades(xml);

	_actualizarSelectCiudades(ciudad, ciudadesDat);

	ciudad.disabled = false;
}

Y el funcionamiento de esta función es muy simple. Se limita a hacer dos cosas: por una parte, procesa el resultado en la propiedad ajx.responseXML mediante la función _parseaCiudades y obtiene toda la información del xml en un objeto de JavaScript (bastante más manejable que el objeto XML que obtenemos del ajx), y por otra parte, envía el objeto al método _actualizarSelectCiudades, que se encarga de modificar el select de ciudades con los nuevos valores.

Además, una vez que todo el proceso ha terminado; esta función activará el select de ciudad (que se desactivó al lanzar la petición).

Esta sería la función que parsea el XML:

function _parseaCiudades(xml)
{
	var data = {
		pais: null
		, ciudades: []
	};

	var pais = {
		codigo: null
		, nombre: null
	};

	var mainNode = null;

	//buscamos el nodo principal. no podemos asegurar su posición en el objeto xml por razones de crossbrowsing.
	for(var i = 0; i < xml.childNodes.length; i++)
	{
		var node = xml.childNodes[i];
		if (node.nodeName == "pais")
		{
			mainNode = node;
			break;
		}
	}

	//rellenamos la estructura pais y lo añadimos a la estructura data.
	pais.codigo = mainNode.getAttribute("codigo");
	pais.nombre = mainNode.getAttribute("nombre");

	data.pais = pais;

	var ciudades = new Array();
	//creamos un array de estructuras ciudad y lo añadimos a la estructura data.
	for(var i = 0; i < mainNode.childNodes.length; i++)
	{
		var node = mainNode.childNodes[i];

		if(node.nodeName == "ciudad")
		{
			ciudades[ciudades.length] = {
				codigo: node.getAttribute("codigo")
				, nombre: node.firstChild.nodeValue
			};
		}
	}

	data.ciudades = ciudades;

	return data;
}

Lo cierto es que él código que encontramos en esta función merece un tutorial aparte. Os invito a que consideréis el código por vuestra cuenta porque de hecho no es demasiado complejo.

Sólo destacar un par de detalles: el objeto mainNode es el nodo principal del XML. En nuestro caso sería el nodo
, y por razones de crosbrowsing tenemos que hacer una búsqueda para encontrarlo (es decir, no podemos asumir que lo encontraremos en una posición determinada).

Por otra parte, notar que lo que hace el objeto en realidad es montar un objeto JavaScript con toda la información y retornarlo. Un objeto más o menos así:

data = {
	país : {
		codigo : ""
		, nombre : ""
	}
	, ciudades : [
		{
            codigo : ""
			, nombre : ""
		}
		, {
            codigo : ""
			, nombre : ""
		}
		, …
	]
}

Y este objeto llegará a la función _actualizarSelectCiudades, que cambiará la información que contiene el select de ciudades:

function _actualizarSelectCiudades(ciudad, data)
{
	var isIe = 'v' == 'v'; //true si estamos en internet explorer

	//vaciamos el option
	var i = 0;
	while(ciudad.options.length > 0)
	{
		ciudad.options[0] = null;
	}

	//añadimos los nuevos elementos
	for(var i = 0; i < data.ciudades.length; i++)
	{
		var el = document.createElement("option");
		el.value = data.ciudades[i].codigo;
		el.text = data.ciudades[i].nombre;

		if(!isIe)
		{
			ciudad.add(el, null);
		}
		else
		{
			ciudad.add(el);
		}
	}
}

Lo que se hace en esta función, es HTML Dom puro y duro. Es decir, modificar los elementos HTML mediante JavaScript. Primero vacía todo lo que hay en el select, y después añade los nuevos valores. Fijaos en la variable isIe: Detecta la funcionalidad del navegador para saber si estamos en Internet Explorer. Necesitamos saberlo por motivos de crossbrowsing, ya que los option se añaden de forma diferente en Internet Explorer y en el resto de navegadores.

¡Hemos terminado!

Después de este ejemplo, ya deberíamos tener más o menos claro como funciona una aplicación basada en Ajax. Hay que tener en cuenta que esto se puede complicar o simplificar tanto como uno quiera. Nuestro ejemplo ha sido muy sencillo, pero Ajax tiene mucha más potencia que esto si se usa bien.

Por otra parte, frameworks como JQuery o Mootools pueden llegar a simplificar muchísimo el trabajo con Ajax. Si realmente te interesa programar basado en Ajax, es obligatorio que les dediques unos minutos.

Share on FacebookTweet about this on TwitterShare on Google+Share on LinkedInEmail this to someone

Deja un comentario

Los campos obligatorios están marcados con *.