Contenido

Carga asíncrona de grandes cantidades de dátos con Ajax

22 Mar

+ 29

Ajax es una técnica para cargar asíncronamiente datos que podremos usar en nuestras páginas web. Esta técnica ha permitido a las aplicaciones web que evolucionen hasta el nivel actual y darán lugar a nuevas aplicaciones más potentes y complejas.

Esta técnica se basa en el uso de xmlhttprequest() para lanzar llamadas de forma asíncrona al servidor, pudiendo así devolver datos cargados posteriormente a la carga de la página. De esta forma la carga inicial de la página puede ser menor y dejar a elección del usuario cargar los datos a medida que los vayan necesitando.

El problema no lo encontramos con la cantidad de datos. Si el número de datos devueltos por el servidor es muy alto esto suele convertirse en una larga espera, con el fichero .gif de loading reglamentario.

Para intentar mitigar este problema he estado haciendo una serie de pruebas, con fín de dejar los ficheros XML que hasta ahora he estado usando en algunos proyectos.

XML

Durante mucho tiempo ha sido el sistema usado en muchas implementaciones para obtener datos de forma asíncrona. De ahí el propio nombre de AjaX (Asynchronous JavaScript And XML). Estos ficheros, generalmente ofrecen mayor interconexión con multiples herramientas.

En un uso para carga asíncrona, nos encontramos como príncipal problema el peso del fichero y tiempo invertido en recorrer el fichero XML para obtener los datos.

....
<hotel>
	<code>0000</code>
	<name>Hotel0</name>
	<direction>Calle0</direction>
	<telef>Telf0</telef>
	<geo>
		<lat>00</lat>
		<lng>00</lng>
	</geo>
</hotel>
....
// Javascript
var hotelDescription = data, hotelList = [];
var data = data.getElementsByTagName("hotel");
for (var x in data) {
	var hotel = data[x];
	if (!hotel.getElementsByTagName) continue;
	hotelList.push(hotel.getElementsByTagName("name")[0].firstChild.data);

		hotelDescription[hotel.getElementsByTagName("name")[0].firstChild.data] = {
			code: hotel.getElementsByTagName("code")[0].firstChild.data,
			name: hotel.getElementsByTagName("name")[0].firstChild.data,
			direction: hotel.getElementsByTagName("direction")[0].firstChild.data,
			telf: hotel.getElementsByTagName("telef")[0].firstChild.data,
			geo: {
				lat: hotel.getElementsByTagName("lat")[0].firstChild.data,
				lng: hotel.getElementsByTagName("lng")[0].firstChild.data
			}
		};

}

JSON

La primera alternativa, fué JSON, una implementación muy clara y natural de mostrar datos. Al tratarse de un fichero TXT podemos hacer que ocupe menos espacio y al evitar la estructura XML conseguimos que el tiempo invertido en recorrer el XML para obtener los datos.

Pero por contra, para poder usar los datos obtenidos hemos de usar la función eval() lo que nos penaliza la carga del fichero. Una vez evaluado el código el tiempo empleado en preparar los datos para poder usarlos es mínimo y sin duda, el más rápido de los sistemas exáminados.

....
{
	code: 0000,
	name: 'Hotel0',
	direction: 'Calle0',
	telef: 'Telf0',
	geo: {
		lat: 00,
		lng: 00
	}
}
....
// Javascript
var hotelDescription = data, hotelList = [];
for (var x in data) hotelList.push(data[x].name);

TXT

La gente de Flickr comentaba hace poco como hicieron para devolver más de 10.000 resultados en menos de 200ms. Y obviamente me ví obligado a contemplar esta técnica.

Se basa en usar una estructura preestablecida en un fichero de texto plano. Usando un separador por registro y otro por campos dentro de cada registro. De esta forma a la hora de cargar el fichero simplemente tendremos que hacer una serie de split() para cortar la cadena y convertirla en una variable que podamos usar.

Como mayor ventaja, esta técnica ofrece un tamaño de fichero mucho más pequeño que los comentados anteriormente. Por contra el tiempo de proceso sobre los datos para obtener una variable que pdamos usar es mucho mayor que la necesaria para procesar el fichero JSON, pero bastante menor que el empleado para un fichero XML.

0000:Hotel0:Calle0:Telf0:00:00|...
// Javascript
var tmp = data.split("|");
var hotelList = [];
var hotelDescription = [];
for (var x = 0, len = tmp.length; x<len; x++) {
	var hotel = tmp[x].split(":");

	hotelList.push(hotel[1]);

	hotelDescription[hotel[1]] = {
		code: hotel[0],
		name: hotel[1],
		direction: hotel[2],
		telf: hotel[3],
		geo: {
			lat: hotel[4],
			lng: hotel[5]
		}
	};
}

Comparativas

Para las pruebas, he realizado una pequeña aplicacion que nos permite cargar 500, 1000, 2000, 5000 o 10000 registros en los formatos anteriores (XML, JSON o TXT). Para los gráficos he usado los resultados obtenidos en Firefox 3.0.7 en Mac Os X.

Si queréis podéis probar por vosotros mismos los resultados que obtendríais en diferentes navegadores directamente aqui.

500 registros

500_registros

1000 registros

10000_registros

2000 registros

2000_registros2

5000 registros

5000_registros

10000 registros

10000_registros

Parece que los gráficos hablan por si solos, y podemos sacar una información interesante.

Conclusiones

bigajax

De los resultados obtenemos las siguientes premisas:

XML

  • Son los ficheros más pesados
  • El tiempo de procesamiento es mayor que los demás.

JSON

  • Son los ficheros que más tiempo tardan en cargar completamente.
  • El tiempo de procesamiento es el más rápido de los testeados.

TXT

  • Son los ficheros menos pesados.
  • El tiempo de proceso y carga son bajos aunque no destacan frente a los demás.
  • El tiempo total es el menor de los testeados.

Descargar el proyecto

He subido el proyecto a Github para que los descargueis y usais si quereis.

Preloaders.net, imagenes de loading en 3D

2 Feb

+ 2

Jesus Moreno me muestra Preloaders.net, un sitio al estilo ajaxload.info que nos permite generar imagenes de “Cargando” para nuestras aplicaciones, pero en esta podemos generar imagenes con animaciones en 3D.

Pseudo-ajax para leer ficheros locales

12 Ene

+ 6

CSSGalery.info nos muestra una solución para leer fichero cuando ejecutamos HTML desde nuestro local. Detectando si la página está siendo ejecutado en un entorno local mediante el protocolo file: de la URL decide como actuar ante una llamada a la función get_content().

function get_content(url,update) {
	if ( document.location.protocol == "file:" ) {
		// console.log("local");
		var ifr = new IFrame({
		src:url,
		events:{
		load: function() {
				$(update).set("html", this.contentWindow.document.body.innerHTML );
			}
		}
		}).inject(document.body);
	}else {
		// console.log("server");
		var myHTMLRequest = new Request.HTML({
		update:update,
		}).get(url);
	}
}

La función get_content(), a la que le pasamos la ruta y donde queremos descargar el cotenido se encarga de hacer todo el trabajo por nosotros. Para los que no usamos MooTools he montado una versión en JS sin necesidad de trabajar con ningún framework.

function get_content(url,update) {
  if ( document.location.protocol == "file:" ) {
    var ifr = document.createElement("iframe");
    ifr.src = url;
    ifr.onload = function() {
	document.getElementById(update).innerHTML = this.contentWindow.document.body.innerHTML;
    }
    document.getElementsByTagName("body")[0].appendChild(ifr);
  }else {
    var ajax = (XMLHttpRequest)?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');
    ajax.open('GET', URL);
    ajax.onreadystatechange = function(){
		document.getElementById(update).innerHTML = ajax.responseText;
    };
    ajax.send(null);
  }
}

Desde CSSGallery.info podemos testear la versión online y/o descargar un zip para probarlo en nuestro local.

CSSHttpRequest, ajax cross-domain con CSS

24 Oct

+ 4

El Ajax entre diferentes dominios es algo que veremos dentro de unos meses en las nuevas versiones de los navegadores actuales. Mientras tanto los desarrolladores están dando soluciones a esta necesidad y ahora, usando la manipulación del CSS desde Javascript solventan el problema

CSSHttpRequest.get(
        "http://www.nb.io/hacks/csshttprequest/hello-world/",
        function(response) { alert(response); }
    );

Si revisamos el código, vemos la magia de esta técnica:

var s = chr.getStyleSheet();

    // IE
    if(s.addImport) {
        index = s.addImport(url);
        item = s.imports(index);
    }

    // W3C
    else if(s.insertRule) {
        index = s.insertRule("@import url(" + url + ");", 0);
        item = s.cssRules[index];
    }

Me encanta ver como la necesidad despierta estas maravillas entre los desarrolladores.

16 formularios de contacto con Ajax

22 Oct

+ 1

Interesante recopilatorio de formularios de contacto que usan la tecnología Ajax para complementar funcionalidades y ofrecer al usuario una página atractiva y cómoda de usar.

Firefox3 integra opción nativa para gestionar la subida de ficheros

8 Jul

+ 3

Una de las nuevas funcionalidades de Firefox3 es la extensión de los elementos <input type="file" /> que nos permite utilizarlos previamente antes de subirlos a nuestro servidor mediante Javascript.

firefox3_files

Como podemos ver en Firebug, vemos que los elementos <input /> incorporan una propiedad DOM extra llamada files, que se compone de un array de elementos ordenados que hacen referencia a los ficheros subidos mediante ese elemento.

Esta propiedad, dispone de 3 métodos con las que podremos hacer uso de los elementos que subamos:

  • Atributos : fileSize y fileName
  • Métodos: getAsDataURL, getAsBinary y getAsText

Ejemplo de uso

function writeText() {
    var data = $('text').files.item(0).getAsBinary();
    $('result').update(data);
    $('textsize').update($('text').files.item(0).fileSize);
}
function writeImage() {
  var data = $('image').files.item(0).getAsDataURL();
  $('imageresult').src = 'data:' + data;
  $('imagesize').update($('image').files.item(0).fileSize);
}

Web Development Helper monitoriza ajax en tu Internet Explorer

25 May

+ 4

Gracias a edusanver descubro Web Development Helper es un plugin para Internet Explorer que nos permite, entre otras cosas, monitorizar las peticiones Ajax como si de Firebug en Firefox se tratara.

web_development_helper

Características

Entre las características más interesantes de este plugin se encuetran:

  • Inspector DOM de todos los elementos.
  • Capturas de imagenes de la página en la que estás.
  • Log de las peticiones HTTP (o HTTPS) solicitadas por el navegador o peticiones Ajax.
  • Ver las peticiones y respuestas en detalle.
  • Habilidad de filtrar por tipos de URL’s en los logs.
  • Traza de errores en detalle de las peticiones Javascript.
  • Consola con posibilidad de interacción Javascript usando (window.debugService).

Requerimientos

Descargar

Descargar

XDomainRequest() y como Microsoft hace lo que le da la gana

9 Mar

+ 2

Uno de los problemas con los que nos encontramos al usar Ajax en nuestras aplicaciones es que nos es imposible acceder, mediante javascript, a contenidos de otro servidor usando el el objeto XMLHTTPRequest(), debiendo recurrir a alternativas algo “sucias”, por ello la W3C decidió dotar a este objeto la posibilidad de trabajar en cross-domain, osea entre diferentes dominios. Esto facilitaría, por ejemplo la lectura de feeds, o cualquier tipo de contenido servido por páginas externas a nuestro dominio.

La aparición del primer borrador por parte de la W3C, hizo que los principales navegadores centraran su atención en esta nueva funcionalidad y se pusieran manos a la obra para dotar a sus navegadores de tan esperada y necesaria funcionalidad.

Con la aparición de un navegador que dije que no volvería a nombrar proximamente, apareció otra vez el dilema. XDomainRequest() ha sido el desencadenante, al alejarse del estandar ofrecido por la W3C.

Según la página del propio Microsoft, el uso de este nuevo objeto sería así:

// 1. Creamos el objeto XDR
xdr = new XDomainRequest();

// 2. Abrimos la conexión con el servidor usando el método POST
xdr.open("POST", "http://www.contoso.com/xdr.txt");

// 3. Enviamos información al servidor
xdr.send("data to be processed");

Como podemos ver, esto se parece a la actual forma de usar el objeto XMLHttpRequest() con la diferencia del nombre del objeto y de que la url a la que solicitamos la información se trata de una URL absoluta en lugar de una relativa.

Algo similar a lo propuesto por la W3C, que era extender al objeto XMLHttpRequest() con la capacidad de acceder a servidores remotos sin necesidad de modificar el código existente.

var xhr = new XMLHttpRequest();
xhr.open("GET", "http://www.anieto2k.com/.../datos.php", true);
xhr.onreadystatechange = function(){
  if ( xhr.readyState == 4 ) {
    if ( xhr.status == 200 ) {
      document.body.innerHTML = "Respuesta: " + xhr.responseText;
    } else {
      document.body.innerHTML = "ERROR";
    }
  }
};
xhr.send(null);


Como podemos ver la diferencia nos hace recordar el odiado ActiveX() y las condiciones tan feas que hemos de hacer para hacer compatibles nuestros navegadores. ¿Ahora con la nueva versión tendremos que añadir una línea más?

var ajax =  function() {
  if (windows.XDomainRequest) { //IE8
	return new XDomainRequest();
  } else if (window.XMLHttpRequest) { //FF, Opera, IE7
    	return new XMLHttpRequest();
  } else if (window.ActiveXObject) { //IE6
  	return new ActiveXObject('Microsoft.XMLHTTP')
  } else { //No Ajax Compatibles
    	alert("No hay ajax");
   	return null;
  }
}

¿Nos encontramos con un nuevo ActiveX() (versión exclusiva para Internet Explorer 6.0) o será el nuevo innerHTML (Aceptado como parte del estandar tras ser una invención de Microsoft)?

XMLHttpRequest entre servidores con Firefox 3

10 Ene

+ 5

Hace un mes vimos Ajax Cross Domain Script, un script que nos permite realizar una conexión entre servidores mediante Ajax, resolviendo una limitación del objeto XMLHttpRequest().

Hoy John Resig, anuncia que Firefox 3 integrará siguiente versión evolutiva del objeto XMLHttpRequest(), permitiendo realizar peticiones entre servidores de forma directa y nativa

Modo de uso

Para asegurar una cierta seguridad, intervendrán varios factores que harán posible que podamos personalizar nuestra propia lista de sitios permitidos o denegados.

Para ello deberemos hacer uso de la cabecera Access-Control, que forma parte del borrador Access Control for Cross-Site. Permitiendo o denegando el acceso al servidor.

Cabeceras Acces-Control 

 Las cabeceras Acces-Control, podrán ser usadas desde cualquier lenguaje de programación capaz de modificar las cabeceras salientes del servidor. Veamos unos ejemplos:

Ejemplo

//PHP
<?php header('Access-Control: allow <*>'); ?> //Permitimos todos
<?php header('Access-Control: deny <*>');?>  //Denegamos todos
<?php header('Access-Control: allow <mozilla.org>');?> //Permitimos solo mozilla.org
<?php header('Access-Control: allow <mozilla.org> exclude <developer.mozilla.org>');?> //Permitimos mozilla.org y denegamos developer.mozilla.org
<?php header('Access-Control: allow <developer.mozilla.org:80> method GET, POST');?> //Permitimos developer.mozilla.org para el puerto 80 en los métodos GET y POST

//XML
<?access-control allow="*"?> //Permitimos todos
<?access-control deny="*"?> //Denegamos todos
<?access-control allow="mozilla.org"?> //Permitimos solo mozilla.org
<?access-control allow="mozilla.org" exclude="developer.mozilla.org"?> //Permitimos mozilla.org y denegamos developer.mozilla.org
<?access-control allow="developer.mozilla.org" method="GET POST"?> //Permitimos developer.mozilla.org para el puerto 80 en los métodos GET y POST

Pese a que el borrador informa que no es posible usar variables GET entre servidores, Firefox 3 parece que si lo hará.  

Javascript

Gracias a una implementación limpia, las aplicaciones Ajax no necesitarán ser modificadas ya que el uso será exactamente igual del que estamos haciendo uso ahora. Simplemente que la URL de nuestras peticiones pueden comenzar por HTTP :D

La mejor forma de usar las llamadas asincronas

12 Dic

+ 3

Con este título la gente de IBM nos muestra un artículo bastante interesante en el que vemos una forma muy interesante de hacer nuestras llamadas asincronas mediante javascript (Ajax), principalmente se trata de una función que como veremos más adelante inteta cubrir todos los flancos que pueden hacer fallar la experiencia asincrona.

Función propuesta completa

function getResource(uri, data_callback, error_callback, timeout) {
    var tryAgain = function () {
      getResource(uri, data_callback, error_callback, timeout);
    }
    var r = new XMLHttpRequest();
    var timer = setTimeout(
        function() {
            r.abort();
            r.onreadystatechange = null;
            setTimeout(tryAgain, timeout);
        },
        timeout);
    r.open("GET", uri, true);
    r.onreadystatechange = function() {
        if (r.readyState != 4) {
            return;
        }
        clearTimeout(timer);  // readyState==4, borramos timer
        if (r.status==200) {  // "OK status"
              data_callback(r.responseText);
        }
        else if (r.status==304) {
            // "Not Modified": No modificamos la salida
        }
        else if (r.status >= 400 && r.status < 500) {
            // Posible error, posible URI erronea
            error_callback(r)
        }
        else if (r.status >= 500 && r.status < 600) {
            // Server error, volvemos a lanzar con un poco de demora
            setTimeout(tryAgain, timeout);
        }
        else {
            error_callback(r);
        }
    }
    r.send(null);
    return r;
}

Función detallada

Empezando de arriba a abajo, tenemos la variable tryAgain, que no es más que la definición de una función que hace la misma llamada que hemos hecho inicialmente. Esta variable será usada para lanzar la función en caso de necesitar relanzarla por algún motivo.

var tryAgain = function () {
     getResource(uri, data_callback, error_callback, timeout);
   }

Sobre la variable r, recae la responsabilidad de albergar el objeto XMLHttpRequest() que hará posible el asincronismo. Aunque yo propongo algo más completo para asegurarnos que IE6 no se quede fuera de juego.

var r = new XMLHttpRequest();
//Más completo
var r = (XMLHttpRequest)?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');

Ya con el objeto principal cargado, declaramos la variable timer, que al igual que tryAgain solo será llamada en el caso en se requiera. Esta función se encargará abortar la transacción asíncrona al cubrir el tiempo timeout dado.

var timer = setTimeout(
        function() {
            r.abort();
            r.onreadystatechange = null;
            setTimeout(tryAgain, timeout);
        },
        timeout);

Una vez preparado el camino con las variables necesarias para el funcionamineto, solo nos queda hacer la llamada, en este caso por GET, y controlar los valores retornados para accionar una funcionalidad u otra.

Tratando la respuesta

Al igual que en el ejemplo anterior, la respuesta tambien debería ser tratada teniendo en cuenta un timeout que nos evitará demoras innecesarias. Para ello definiremos nuestra función usando una estructura similar a esta.

var other_data = null;
function processData(this_data) {
    var delay = 1000;     // Esperamos 1 segundo
    if (other_data == null) {
        setTimeout(function() { processData(this_data); }, delay);
        return;
    }
    // Tenemos this_data y other_data
    displayThisAndThat(this_data, other_data);
    // Reseteamos other_data, ya la hemos usado
    other_data = null;
}
// Ejemplo de uso
getResource(uri2,processData,errorData, 5000);