Refresco Automático de un Div Cada X Segundos con jQuery

Tanto si desarrollas una aplicación web en la que se muestra una colección de datos dinámica, que se actualiza cada poco tiempo, como si quieres que el contenido de una parte de tu web se actualice o refresque periódicamente, aquí tienes una solución a tu problema utilizando jQuery.

Tanto si desarrollas una aplicación web en la que se muestra una colección de datos dinámica, que se actualiza cada poco tiempo, como si quieres que el contenido de una parte de tu web se actualice o refresque periódicamente, aquí tienes una solución a tu problema utilizando jQuery.

En las antiguas webs, sobre todo en la de noticias o periódicos, cada cierto tiempo se refrescaba todo su contenido, actualizando la página completa. Para eso utilizamos el tag Meta refresh de HTML. Lo que hace es refrescar la página o frame cada cierto tiempo. Para ello, entre los tags <HEAD></HEAD> de la página incluíamos el siguiente código:

<meta http-equiv="refresh" content="5">

Lo que hace es refrescar todo el contenido de la página cada 5 segundos. Este comportamiento está desaconsejado por el World Wide Web Consortium (W3C) ya que puede desorientar al usuario.

Durante el desarrollo de una aplicación web, necesitábamos que una tabla de datos (utilizábamos displaytag, de la que ya os he hablado anteriormente) se actualizara cada cierto tiempo, definido por el usuario), debido a que el estado de los datos mostrados en esta displaytag tenían un comportamiento bastante dinámico y cambiante.

Para ello me creé en la página un div con id  salastatus y éste contenía otro div con id resulttable que contenía la displaytag y algún control más que deseaba actualizar, a continuación introduje el siguiente código que se ejecuta tras la carga de la página:

 $(document).ready(function(){
   var refreshId = setInterval(refrescarTablaEstadoSala, 30000);
   $.ajaxSetup({ cache: false });
 });

Lo que hace el código es, cada 30 segundos (el valor del parámetro va en milisegundos), se ejecuta la función javascript refrescarTablaEstadoSala. Comentaros que aquí para simplificar he indicado el número de milisegundos de forma directa, pero realmente lo enviaba el servidor, a partir de los datos de configuración del usuario.

A continuación muestro el contenido de la función refrescarTablaEstadoSala:

function refrescarTablaEstadoSala() {
  $("#rolling").toggle();
  $("#salastatus").css('opacity', 0.4);
  $("#salastatus").load('estadoSala.do?randval='+ Math.random() + " #resulttable", function(){
      //aquí puedes meter más código si necesitas;
      $("#salastatus").css('opacity', 1);
      $("#rolling").toggle();
  });
}

Lo primero que hago es mostrar un icono de cargando cuyo id es “rolling” ( $(“#rolling”).toggle(); ) y pongo la capa que quiero actualizar transparente ( $(“salastatus”).css(‘opacity’, 0.4); ), totalmente operativa, pero el usuario ya detecta que ha comenzado una actualización. A continuación mediante el método load, de la que ya os hablé en esta publicación anterior, realizo una petición de la página con un número aleatorio como parámetro (para que el navegador no me devuelva la página de caché) seguido de un espacio y el div que quiero cargar en salastatus precedido de un #. Tras la carga, oculto el icono cargando y quito la transparecia a la capa. Antes de esto puedes completar la función con el código que necesites o el tratamiento de errores que desees.

No olvides incluir el enlace al fichero js de jquery o no te funcionará. Cómo ves una solución bastante elegante de resolver el problema ocasionado con el meta refresh y su manera, mucho más intrusiva de refrescar la página.

Internacionalización (I18n) con DisplayTag y Spring

La librería de tags Displaytag nos permite mostrar listas de objetos en forma de tabla, facilitandónos la ordenación, la paginación de los resultados, el agrupamiento, la exportación de datos, etc., tal cómo ya habíamos comentado en un post anterior. La configuración se realiza mediante un fichero de propiedades que debe localizarse en el CLASSPATH de la aplicación, llamado display-tag.properties. En él se definen, tanto los estilos y apariencia, como los mensajes que aparecerán cuando se muestran los controles de paginación, o no hay datos, etc. La labor de configuración es bastante sencilla, pero cuando tu aplicación es multiidioma y necesitas configurar los mensajes o etiquetas que aparecen en la tabla en función del idioma del usuario, el tema no es tan trivial.

Displaytag

En nuestra aplicación desarrollamos con Spring framework y tenemos configurada la internacionalización en el fichero xml de configuración de Spring de la siguiente forma:
<!-- Bean para recoger el archivo de propiedades con los mensajes -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>/WEB-INF/properties/messages</value>
</list>
</property>
<!-- Tiempo para la actualización del archivo properties -->
<property name="cacheSeconds" value="60"></property>
</bean>
<!-- Bean para cambiar el idioma (permite cambiar el “Locale” actual en cada petición al servidor, mediante el uso del parámetro 'siteLanguage') -->
<bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName">
<value>siteLanguage</value>
</property>
</bean>
<!-- Bean que especifica el "locale" actual utilizando la sesión del usuario -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean>

Por defecto la aplicación recoge el idioma en el que esté configurado el navegador, pero una vez que hemos iniciado la aplicación podemos modificar el idioma durante la sesión del usuario. Para esto se realizará una petición al servidor con el parámetro “siteLanguage” y el nuevo locale. Hemos tenido que realizar una tarea previa en las páginas para que no inserten los mensajes de forma literal, sino que contengan una clave con la que se obtengan los mensajes almacenados en el fichero de propiedades. En nuestro caso utilizamos el fichero messages.properties para los mensajes en castellano y messages_en.properties para los mensajes en inglés, por ejemplo (el sufijo representa el idioma, en, es, de, it,fr,etc.).

Tan sólo nos falta la configuración del displaytag para que tanto los mensajes como los títulos de las columnas de la tabla se configuren siguiendo el idioma seleccionado. En el caso de los encabezados de las columnas actuamos de la misma manera que en las demás etiquetas de la página, es decir, en la cabecera hacemos referencia a una clave que recogerá el mensaje del fichero de mensajes con el idioma correspondiente:

<display:column titleKey="etiq.tabla.titulo" property="titulo" sortable="true" sortProperty="titulo"></display:column>

Los demás mensajes de la tabla se recogen del fichero de configuración displaytag.properties. En él se deben añadir estas dos líneas:
# locale provider (Jstl provider by default)
locale.provider=org.displaytag.localization.I18nSpringAdapter
# locale.resolver (nothing by default, simply use locale from request)
locale.resolver=org.displaytag.localization.I18nSpringAdapter

Una vez hecho esto tenemos que configurar un nuevo fichero displaytag.properties con el sufijo correspondiente al idioma seleccionado (displaytag_en.properties, displaytag_de.properties, etc.), pero únicamente con las claves de los mensajes que queremos cambiar con el idioma, no es necesario duplicar todo el fichero, ya que los valores que no se encuentren los recoge del original. Este nuevo fichero se debe localizar en la misma carpeta, siempre en el classpath de la aplicación, como por ejemplo en la carpeta donde residan las clases java.

Tablas con DisplayTag y Ajax

Durante los dos últimos años, mi equipo y yo hemos introducido en los proyectos J2EE una librería de tags JSP para la publicación de datos de forma tabular. Se trata de DisplayTag, una librería open source muy potente que nos facilita la visualización de tablas de datos y la ordenación y paginación de los mismos empleando muy poco código. Para incluirla basta con añadir en la página JSP el tag <display:table>, indicarle el nombre de las columnas, así como el nombre de la colección de datos que enviamos desde el servidor en la respuesta. La librería se encarga de generar el código html para presentar los datos en la tabla, generar los controles de paginación y enlaces de ordenación de las columnas.

Una de las funcionalidades que no incluye esta librería es la utilización de AJAX,  que nos acerca una serie de técnicas de programación para la creación de aplicaciones interactivas. Cada vez que pulsamos para ordenar la tabla o para pasar a la siguiente página de resultados se realiza una petición al servidor y éste devuelve una nueva página recargando la anterior con la nueva información. Sería conveniente que por cada petición de ordenar o cambiar la página de resultados se recargase únicamente la información de la tabla de forma asíncrona y no toda la página.

Solución con Ajax Tags

La librería Ajax Tags es un conjunto de tags JSP que simplifica el uso de la tecnología AJAX en las páginas JSP. Además incorpora un tag específico para permitir y habilitar AJAX en las Displaytags comentadas más arriba. Para utilizarlo basta con envolver la tabla con el tag <ajax:displayTag> de manera que sólo se refresque el área contenida en éste y no toda la página:

<ajax:displayTag id="displayTagFrame" ajaxFlag="displayAjax">

    <display:table name="service.allCars" pagesize="10" scope="page"
        defaultsort="1" defaultorder="descending" export="true" id="row"
        excludedParams="ajax">
        <display:column property="make" title="Make" sortable="true"
            headerClass="sortable" />
        <display:column property="model" title="Model" sortable="true"
            headerClass="sortable" />
        <display:column title="Link" media="html">
            <a href="http://www.${row.make}.com">${row.make} Web Page</a>
        </display:column>
        <display:column title="Link" media="excel xml"> www.${row.make}.com </display:column>
    </display:table>

</ajax:displayTag>

¿Cómo funciona? Engloba todo el código html de la tabla en un div con el id indicado como parámetro, sobreescribe el método onclick de cada enlace de la tabla por un código que se encarga de realizar la llamada al servidor y recargar únicamente el div indicado con el código de la tabla (obviando todo el código que no sea la tabla).

Aun resultando así de fácil nos hemos encontrado dos problemas:

  1. Nosotros utilizamos Struts/Tiles para componer la maquetación de las páginas (aunque nuestro framework sea Spring), de manera que dividimos la página en varias JSPs (cabecera, menú, pie y contenido). Cómo hemos dicho anteriormente ajaxtags obvia el código que no sea de la tabla pero únicamente lo hacía para el contenido, por lo que en el div volvía a aparecer la cabecera, el pie, etc. La solución fue crear una definición en la configuración de Tiles que sólo incluyese el contenido y si en la petición venía cierto parámetro, redirigir desde el servidor a ésta.
  2. Ajaxtags sobreescribe el evento onclick de los enlaces, pero también sobreescribe el atributo href con un javascript://nop/. Esto nos supone un problema si el sitio tiene alguna restricción de accesibilidad, puesto que en navegadores que no tengan javascript activo (o no soporten js) no funcionará la paginación y ordenación de la tabla.

Solución con jQuery

jQuery es una librería Javascript que simplifica el desplazamiento entre documentos, manipulación del DOM, la gestión de eventos, los efectos y animaciones y la interacción mediante Ajax para un rápido desarrollo web. Esta solución se basa en la funcionalidad load de jQuery, la cual permite cargar html externo desde una fuente remota e inyectarlo en el DOM.

Vamos a verlo más fácil con el siguiente ejemplo:

<div id="tablaresultados">

    <display:table name="modelo.resultado"
        requestURI="/catalog/busquedaTexto.do" pagesize="10" cellspacing="0"
        decorator="es.displaytag.decorator.BuscaTextoTableDecorator"
        id="resultados">
        <display:column title="Título" property="titulo" sortable="true" />
        <display:column title="Signatura" property="signatura" sortable="true" />
        <display:column title="Fechas" property="fecha" style="text-align:center" sortable="true" />
    </display:table>

</div>

El único requisito en la tabla será que la contenga una capa con el id que deseemos. En esta solución sí que es necesario introducir código javascript en la página:

<script>

    if (!com) var com = {};
    es.displaytag= {
            onPeopleTableLoad: function() {

                // Se llama cuando se cargan los datos
                $("table#resultados th.sortable").each(function() {
                    // Itera sobre cada cabecera de columna conteniendo la clase sortable, de manera
                    // que
                    // podemos sobreescribir la gestión de click via ajax, en lugar de
                    // permitir al navegador seguir un enlace normal
                    $(this).click(function() {
                        // "this" es el elemento th ordenable
                        var link = $(this).find("a").attr("href") + " #tablaresultados";
                        $("div#tablaresultados").load(link, {}, es.displaytag.onPeopleTableLoad);
                        // Paramos la propagación de eventos, sin permitir que el navegador ejecute el
                        // href
                        return false;
                    });
                });
                $("div#tablaresultados .pagelinks a").each(function() {
                    // Itera sobre cada enlace de paginación para sobreescribirlo
                    $(this).click(function() {
                        var link = $(this).attr("href") + " #tablaresultados";
                        $("div#tablaresultados").load(link, {}, es.displaytag.onPeopleTableLoad);
                        return false;
                    });
                });
            }
    };

    $(document).ready(function() {
        // Carga inicial cuando el DOM está preparado. Se asume que vas a inyectar
        // dentro de un div
        // con el id "tablaresultados" que existe en la página.
        $("div#tablaresultados").load("/path/remoto/que/genera/tabla/arriba", {}, es.displaytag.onPeopleTableLoad);
    });

</script>

Para evitar el problema 1 comentado en la solución con ajaxtags, añadimos a los enlaces el id “#tablaresultados” para indicarle que recargue únicamente la capa que contiene la tabla. El problema 2 no lo tenemos con esta solución puesto que no se sobreescribe el atributo href. En el caso de mostrar la tabla en un navegador que no interprete javascript la tabla funcionará normalmente recargando toda la página.

Conclusiones

Personalmente me inclino por la solución con jQuery, ya que en nuestro caso no es necesario modificar código en el servidor y porque necesitamos cumplir con criterios de accesibilidad.

Seguramente existen multitud de componentes o tablas con Ajax para incorporar a las aplicaciones web, pero si ya tienes una página con displaytag, el código en el servidor que la alimenta y deseas incorporar ajax y mantener lo que tenías, estas dos soluciones son unas muy buenas opciones.

Actualización

(25-03-2011) Posterior a esta entrada se desarrolló un plugin jQuery (displaytag-ajax) que provee la capacidad de refresco AJAX a tablas generadas mediante Display Tag. Debo dar las gracias a Yajairo87 ya que gracias a su comentario pude descargármelo y probarlo (funciona perfectamente). Si tenéis curiosidad y miráis el código comprobaréis que realiza el mismo proceso comentado en esta entrada. Podéis descargarlo en la siguiente dirección: http://joseantoniosaiz.es/samples/displaytag-ajax/displaytag-ajax-1.2_0.zip