Mensaje Nº 1 | 5:51 PM

Muchas páginas web usan una recarga automática para que los usuarios dispongan de la última actualización de la misma. Un ejemplo son las portadas de los periódicos digitales o las retransmisiones en vivo. Sin embargo, en muchas ocasiones la recarga no ofrece una página nueva al usuario. Este artículo intenta ofrecer una manera que permita recargar la página sólo si ésta ha cambiado. Asumo conocimientos elevados de Javascript y básicos sobre el funcionamiento de los servidores web y la arquitectura cliente/servidor.

Requisitos
Para lograr este objetivo utilizaremos la cabecera Last-Modified que el servidor envía a los navegadores junto con el contenido de las páginas. Hay que indicar que esto no lo hace siempre: las páginas dinámicas no suelen llevar esta cabecera ni tampoco muchos servidores que admiten el mecanismo SSI (Server Side Includes) de Apache, que permite incluir ficheros del servidor como parte de las páginas. Si ese es nuestro caso, tendremos que utilizar alguna de las siguientes opciones:

• Proporcionar desde el lenguaje de servidor que estemos utilizando para las páginas dinámicas la cabecera Last-Modified.
• Emplear SSI con la directiva XBitHack puesta a Full.
• Si no es posible hacer nada de esto, tendremos que hacer alguna chapuza, como generar un fichero de texto con la fecha del servidor y emplear las técnicas que veremos a continuación

Para estar seguros de si disponemos de esa cabecera, podemos crear un fichero HTML que contenga el siguiente script:

Quote
<script language="Javascript"><!--
alert(document.lastModified);
// --></script>

Si la ventana que nos aparece contiene una fecha, es que vamos bien. Otra manera de hacerlo es utilizar Firefox con la extensión WebDeveloper. Esta extensión muestra las cabeceras a través del menú Information > View Response Headers. Todo desarrollador que se precie debería instalarse esa extensión, no sólo por eso. Es casi la opción menos útil de todos los documentos.

El usuario deberá tener un navegador que soporte el uso del objeto XMLHttpRequest que permita llamadas de tipo HEAD. Explorer 5 y superiores, Mozilla/Firefox, y las versiones preliminares de Opera 7.6 disponen de esa característica. En los demás navegadores, simplemente no se producirá recarga alguna.

Creación del objeto XMLHttpRequest
El objeto XMLHttpRequest permite pedir páginas web y otro tipo de informaciones al servidor desde el navegador. Las posibilidades que abre su uso son tremendas y el mejor ejemplo del que disponemos a la hora de escribir este artículo es Gmail, el correo web ofrecido por Google. Esta aplicación lee al arrancar un interfaz escrito enteramente en Javascript y luego lee del servidor tan sólo los datos que necesita en cada momento. El pequeño truco que estamos estudiando es tan sólo un atisbo de su potencial.

Microsoft lo introdujo como un control ActiveX y Mozilla, consciente de su utilidad, lo copió (no existe estándar del W3C al respecto) para procurar facilitar un uso compatible entre ambos. Sin embargo, dado que excepto Internet Explorer ningún navegador apoya la tecnología ActiveX, fuente de agujeros de seguridad sin fin y anclada a la plataforma Windows, la forma de crear el objeto es distinta. En Explorer es tal que así:

Quote

xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");

No obstante, el nombre del objeto ActiveX ha cambiado según la versión de Explorer. De modo que tendremos que comprobar uno a uno cual de ellos es el más adecuado.

Quote
var ids = ["Msxml2.XMLHTTP.5.0","Msxml2.XMLHTTP.4.0",
"Msxml2.XMLHTTP.3.0","Msxml2.XMLHTTP",
"Microsoft.XMLHTTP"];
for(var i=0; !xmlhttp && i<ids.length; i++) {
try { xmlhttp = new ActiveXObject(ids[i]); }
catch(ex) { xmlhttp = false; }
}

Utilizamos excepciones (try...catch) como mecanismo para controlar si se ha creado correctamente el objeto ActiveX o no. Esta construcción del lenguaje no estaba disponible en versiones antiguas de Javascript, incluyendo la incorporada en la versión 4 del Explorer. Por tanto, este código daría error. Pero este navegador soporta la compilación condicional.

Quote
/*@cc_on @*/
/*@if (@_jscript_version >= 5)
// Usamos compilación condicional para evitar errores en
// versiones de Internet Explorer antiguas.
var ids = ["Msxml2.XMLHTTP.5.0","Msxml2.XMLHTTP.4.0",
"Msxml2.XMLHTTP.3.0","Msxml2.XMLHTTP",
"Microsoft.XMLHTTP"];
for(var i=0; !xmlhttp && i<ids.length; i++) {
try{ xmlhttp = new ActiveXObject(ids[i]); }
catch(ex) { xmlhttp = false; }
}
@end @*/

Los navegadores que no sean Explorer interpretarán todo este código como un comentario. Explorer examinará la condición y la ejecutará si la versión de Jscript es mayor o igual a 5, es decir, si la versión de Explorer es la 5 o superior. Nos queda por solucionar los demás navegadores

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


Si el objeto aún no ha sido creado y el objeto existe, lo creamos. De este modo, no intentaremos crearlo ni en Explorer (pues en ese caso ya existirán) ni en navegadores que ya soporten el objeto.

No obstante, ese trabajo puede ahorrarse si se usa alguna de las librerías disponibles como Sarissa o la de Andrew Gregory, que ya hacen el trabajo de ofrecer un interfaz común para todos los navegadores.

Uso del objeto XMLHttpRequest
El objeto XMLHttpRequest permite hacer peticiones de varios tipos al servidor, como GET, POST o HEAD. Esta última permite obtener las cabeceras de un documento. Por tanto, es la que usaremos para averiguar la fecha de la última modificación. También hay que indicar que una conexión con este objeto puede ser síncrona o asíncrona. En el primer caso, la ejecución se para hasta que se recibe respuesta. En el segundo se continúa la ejecución y, si la hemos definido, ejecutará una función cuando se reciban los datos.

En nuestro caso, resultaría ilógico que la ejecución de Javascript se paralizara a la espera de saber si hay que refrescar o no. Por tanto, emplearemos comunicación asíncrona. La llamada será:

Quote
xmlhttp.open("HEAD", url, true);

donde true indica que la llamada será asíncrona y url será la dirección de nuestra página, que estará definida previamente.
El objeto tiene un evento llamado onreadystatechange que salta cuando se produce un cambio de estado en la carga del documento. Por ejemplo, si ya está recibiendo datos o si ha completado la carga. El objeto dispone de una propiedad llamada readyState que indica cual es ese estado, exactamente. Puede tomar estos valores:

A nosotros nos interesa el último, de modo que el controlador del evento actuará sólo en ese caso. Comprobará si hay fecha nueva y si es así recargará la página. En cambio, si la página sigue siendo la misma, pondrá en marcha un temporizador que volverá a pedir la fecha de modificación de la página después de un cierto tiempo. Esta es la función:

Quote
function recarga(url, f, t) {
xmlhttp.open("HEAD", url, true);
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4) {
fecha_nueva = Date.parse(
xmlhttp.getResponseHeader("Last-Modified"));
if (f>=fecha_nueva)
setTimeout("recarga('"+url+"',"+f+","+t+")", t);
else
location.href = location.href;
}
}
xmlhttp.send(null);
}

Vemos que recibe tres parámetros: la dirección a comprobar, la fecha del documento actual y el tiempo que se aplicará al temporizador para volver a ejecutar la función.

Inicialización
Ahora sólo nos queda inicializar todo el mecanismo este. Para ello debemos obtener la fecha la última modificación del documento y llamar al método que ya hemos escrito. Usaremos un temporizador porque no tiene mucho sentido hacer una comprobación de la fecha nada más cargar el documento y comprobaremos cada 5 segundos, porque esto es una prueba (en el mundo real conviene que pongamos tiempos mucho más largos):

Quote
var fecha = Date.parse(document.lastModified);
var timeout = 5000;
if (xmlhttp)
setTimeout(
"recarga('/minibackoffice/modificacion.html',"+
fecha+", timeout)", timeout);

Hay que poner la comprobación por si tenemos un navegador sin el objeto XMLHttpRequest.


Las vírgenes tienen muchas navidades pero ninguna Nochebuena.