Nivo Slider, un slideshow con jQuery

Para los que nos dedicamos al desarrollo y diseño de aplicaciones para Internet nos ha sorprendido la rapidez con la que se han propagado los slideshows (también llamados sliders). No hay web actual que se precie que no tenga uno. ¿Y qué es y para qué sirve? Se trata de un componente que presenta una serie de imágenes ordenadas con información seleccionada, que se van presentando una tras otra manteniendo cada una visible un breve periodo de tiempo. Estas imágenes representan una información seleccionada que queremos destacar y pueden incluir un enlace a dicha información ampliada e incluso una etiqueta con una breve descripción. Es muy útil en páginas muy densas con mucha información para, en el mismo espacio, destacar más de un tema.

Existen multitud de estos componentes dependiendo de la funcionalidad que incorporan. En nuestro caso, para una nueva web, el usuario nos pidió incorporar un sencillo slideshow y nos hemos decantado por Nivo Slider. Incorpora 16 efectos de transición, soporta enlaces a imágenes y navegación entre imágenes mediante teclado. Su utilización es sencilla y la versión empaquetada sólo ocupa 15Kb. por lo que se agradece su ligereza. Es un código de libre uso bajo la licencia MIT. Funciona en los siguientes navegadores:

  • Internet Explorer v7+
  • Firefox v3+
  • Google Chrome v4+
  • Safari v4+
  • Opera v10+

Para usarlo en nuestros proyectos necesitamos incluir jQuery, el script de Nivo Slider (jquery.nivo.slider.pack.js) y el CSS de Nivo Slider (nivo-slider.css). Además necesitamos añadir un poco de código HTML  a nuestra página. Suele ser un div con imágenes (images), de hecho, sólo imágenes o imágenes dentro de enlaces están permitidas dentro del div que representa el slider. Cualquier otro código HTML hará que no funcione el control.

<!-- Dentro de la sección <body> de la página --> 
<div id="slider"> 
 <img src="images/slide1.jpg" alt="" /> 
 <a href="http://dev7studios.com">
 <img src="images/slide2.jpg" alt="" title="#htmlcaption"/>
 </a> 
 <img src="images/slide3.jpg" alt="" title="Este es un ejemplo de un caption"/> 
 <img src="images/slide4.jpg" alt="" /> 
</div> 
<div id="htmlcaption" class="nivo-html-caption"> 
 <strong>Esto</strong> es un ejemplo de un caption <em>HTML</em> con <a href="#">un enlace</a>. 
</div> 

Para que el slider se vea correctamente mientras está cargando añadiremos algo de estilos CSS:

#slider { position:relative; width:618px; /* Change this to your images width */ 
 height:246px; /* Change this to your images height */ 
 background:url(images/loading.gif) no-repeat 50% 50%; 
 } 
#slider img { position:absolute; 
 top:0px; left:0px; display:none; 
 } 
#slider a { border:0; display:block; }

Para enlazar la funcionalidad del slider con la capa que hemos cargado previamente cuando cargamos la página, llamamos al método nivoSlider() de la siguiente forma:

<script type="text/javascript"> 
 $(window).load(function() { 
 $('#slider').nivoSlider(); 
 }); 
</script> 

Este método puede invocarse sin parámetros o bien con alguna de las opciones disponibles:

$('#slider').nivoSlider({
 effect:'random', // Efecto de transición: 'fold,fade,sliceDown'
 slices:15, // Para animaciones 'slice'
 boxCols: 8, // Para animaciones 'box'
 boxRows: 4, // Para animaciones 'box'
 animSpeed:500, // Velocidad de la transición
 pauseTime:3000, // Cuánto tiempo en milisegundos se visualiza una imagen
 startSlide:0, // Imagen de comienzo (empezando por cero)
 directionNav:true, // Si queremos que aparezcan controles para siguiente y anterior
 directionNavHide:true, // Sólo cuando el cursor se sitúa sobre el componente
                            se visualizará la navegación
 controlNav:true, // 1,2,3... navegación
 controlNavThumbs:false, // Usar miniaturas para el control de la navegación
 controlNavThumbsFromRel:false, // Usar imagen rel para miniaturas
 controlNavThumbsSearch: '.jpg', // Reemplazar este patrón del nombre de la imagen...
 controlNavThumbsReplace: '_thumb.jpg', // ...con esto para las imágenes miniatura
 keyboardNav:true, // Usar las flechas izquierda y derecha para navegación
 pauseOnHover:true, // Parar navegación al pasar el ratón sobre el componente
 manualAdvance:false, // Forzar transiciones manuales
 captionOpacity:0.8, // Opacidad para los caption o títulos de las imágenes
 prevText: 'Ant', // Texto de navegación hacia la izquierda
 nextText: 'Sig', // Texto de navegación hacia la derecha
 beforeChange: function(){}, // Función que se ejecuta antes de una transición
 afterChange: function(){}, // Función que se ejecuta después de una transición
 slideshowEnd: function(){}, // Función que se ejecuta después de que todas
                                las imágenes se han mostrado
 lastSlide: function(){}, // Función que se ejecuta cuando la última imagen se visualiza
 afterLoad: function(){} // Función que se ejecuta cuando se carga el componente
});

 

Hay gran variedad de efectos para las transiciones. Los valores posibles para la opción effect son: sliceDown, sliceDownLeft, sliceUp, sliceUpLeft, sliceUpDown, sliceUpDownLeft, fold, fade, random, slideInRight, slideInLeft, boxRandom, boxRain, boxRainReverse,boxRainGrow, boxRainGrowReverse. Para un uso avanzado, incluir Nivo Slider en WordPress o el uso de imágenes miniatura, podemos visitar la página oficial http://nivo.dev7studios.com/.

Deshabilitar el botón Submit manteniendo el nombre/valor en el formulario

Botón Submit

Cuando desarrollamos aplicaciones web en las que se muestra uno o varios formularios, en ocasiones nos encontramos con que al enviar éstos, el tiempo de proceso o consulta a la Base de Datos es mayor de lo deseable. Cómo todavía no conozco un usuario paciente, lo más probable es que mientras espera haya pulsado el botón enviar el formulario varias veces más, saturando el servidor con más peticiones y haciendo que el tiempo de proceso sea aún mayor.

Lo primero que se nos ocurre es deshabilitar el botón Submit para que el usuario no vuelva a pulsar y de esa manera evitamos recibir en el servidor varias peticiones idénticas. Esta solución funciona en la mayoría de los casos pero puede darse el caso en el que tengamos varios botones de tipo Submit dentro del mismo formulario (buscar, imprimir, ordenar, completar desplegable), y nos interese consultar en el servidor cuál de ellos ha pulsado mediante una sentencia similar a:

if (request.getParameter("nombre_del_input_submit_pulsado") != null) {     ... }

En el caso de que hayamos deshabilitado el botón no es posible recuperar si ha pulsado dicho botón puesto que los elementos deshabilitados de un formulario no se envían en la petición.

Existe un plugin en jQuery llamado Lock Submit que deshabilita el botón submit después de su pulsación manteniendo el par nombre / valor pasado en el formulario. Como dice en el título de la página del plugin:

Stop Double Clicks on form submits… without losing the submits name/value

Para usarlo necesitas tener jQuery, del que ya os he hablado en más de una ocasión y seguir estos pasos:

  1. Descarga el plugin
  2. Incluye tanto jQuery como el plugin en tu página
  3. Incluye el siguiente código adaptándolo al selector de tu botón:
<script type="text/javascript"> jQuery(document).ready(function() { jQuery('id_boton_submit').lockSubmit({ submitText: "Please wait" }); }); </script>

Internamente no deshabilita el botón, realmente el botón original se encuentra oculto y el que se muestra en la página deshabilitado es una copia del primero. La propiedad submitText permite modificar y personalizar el texto que se muestra dentro del botón. También tiene propiedades para modificar el estilo css.

Visor de Imágenes con jQuery y el plugin iViewer

Anteriormente ya he publicado varios posts hablando sobre la librería jQuery, en concreto como resaltar una búsqueda con jQuery y cómo actualizar tablas displaytag con ajax y jQuery. Gracias a esta librería y el plugin iViewer podemos cargar una imagen, visualizarla en pantalla dentro de un contenedor con la posibilidad de realizar zoom de la imagen con el ratón dentro del contenedor.

Puedes ver una demo pulsando sobre el enlace: http://joseantoniosaiz.es/samples/iviewer

¿Cómo se usa? Obviamente debemos de partir de una página que posea un contenedor en el que cargar la imagen, y al que daremos un ancho y un alto.

<html>
<head>
<style>
.viewer
{
width: 768px;
height: 573px;
border: 1px solid black;
position: relative;
}
.wrapper
{
overflow: hidden;
}
</style>
</head>
<body>
<h1>jQuery Zoom</h1>
<br/>
<div id="viewer"></div>
</body>
</html>

También debemos incluir las referencias a las librerías javascript de jQuery y del plugin iViewer, así como el enlace al fichero de estilos del plugin (yo lo inserté entre los tags head de la página)

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" ></script>
<script type="text/javascript" src="jquery.iviewer.js" ></script>
<link rel="stylesheet" href="jquery.iviewer.css" />

Para terminar debemos añadir el código javascript para invocar al plugin y cargue la imagen con los parámetros que deseemos. Al insertar el código dentro de la función ready nos aseguramos que se ejecutará cuando el documento HTML esté listo para visualizarse.

<script type="text/javascript">
var $ = jQuery;
$(document).ready(function(){
var iviewer = {};
$("#viewer").iviewer(
{
src: "CIMG0498.JPG",
initCallback: function()
{
iviewer = this;
}
});
});
</script>

En este ejemplo únicamente hemos añadido el parámetro src con la ruta a la imagen. Para ver más parámetros podemos consultar la página oficial del plugin. También he probado a cargar una imagen añadiendo una URL de una imagen publicada en cualquier web en vez de una ruta al fichero y funciona correctamente.

Una vez que se ha cargado la imagen, aparecerá una barra con un enlace para ampliar el zoom de la imagen, otro para reducirlo, otro para ver la imagen a tamaño real y otro para ajustarla al área de visión. También se ofrece el porcentaje de zoom. Pulsando el botón izquierdo del ratón podemos arrastrar la imagen para desplazarnos por ella. Si deseamos realizar zoom con la rueda del ratón podemos insertar el plugin mouse wheel extension:

<script type="text/javascript" src="jquery.mousewheel.min.js" ></script>

Resaltar una búsqueda con jQuery

Después de algunos meses sin publicar ningún post, y ya pasadas las vacaciones de verano, vuelvo con un tema que seguro que a los que trabajamos diseñando / desarrollando aplicaciones web nos han pedido los usuarios en más de alguna ocasión: Resaltar en la pantalla las palabras introducidas en una búsqueda.

En nuestro caso tenemos una página con un formulario de búsqueda con diferentes campos: una caja de texto (búsqueda por texto), dos campos de fechas (búsqueda por fechas) y una lista desplegable de países. Todos ellos se combinan en una consulta a la Base de datos y muestra un listado con los resultados. Dichos resultados se pueden ampliar en otra página pulsando sobre el registro correspondiente. Cómo os imagináis es en esta página de detalle donde se nos requirió que se resaltaran las palabras introducidas en la búsqueda.

Después de manejar varias opciones (resaltar en BBDD o en la propia aplicación, de manera que al navegador le llegaba el trabajo hecho), me decanté por hacerlo mediante javascript una vez que haya cargado todo el documento en la página. Para ello hemos incluido dentro del proyecto la librería jQuery (de la que ya os he hablado anteriormente) y de un plugin para ella que podéis consultar en http://bartaz.github.com/sandbox.js/jquery.highlight.html.

Básicamente esta librería contiene una función que se encarga de recorrer todas las palabras del contenedor (documento, div, table, etc.) que le especifiquemos y si encuentra alguna de las palabras utilizadas en la búsqueda y que le pasamos como parámetro la envuelve en una etiqueta <span class=”highlight”>:

<script type="text/javascript" src="/js/jquery-1.4.2.js"></script>
<script type="text/javascript" src="/js/jquery-highlight.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$("html").highlight(divide_string("<c:out value='${modelo.form.textoLibre}'/>"),{ wordsOnly: true });
});
</script>

Este código se ejecuta una vez que se ha cargado el documento y llama a la función highlight para que resalte las palabras dentro del elemento HTML del DOM. Asímismo le pasamos la opción wordsOnly:true para que resalte palabras completas (si en la búsqueda se introdujo ‘este’, que no resalte parcialmente Esteban). La función divide_string recibe el texto introducido en la caja de búsqueda y retorna un array de palabras, obviando palabras comunes como artículos y preposiciones. Aquí os paso el código:

function divide_string(cadena)
{
var pattern =                       "\\b(Sr|Sra|Sres|Sta|...más_palabras_comunes...|vuestro|vuestros|y|yo|él|en)\\b";
var re = new RegExp(pattern, "gi");
cadena = cadena.replace(re,"");
var mytool_array = cadena.split(" ");
return mytool_array;
}

Por último indicar que fue necesario realizar modificaciones del plugin ya que no resaltaba las palabras que tenían acento si en la caja de texto buscabas sin acento. Justo antes de crear la expresión regular a evaluar, modificamos el patrón para incluir las vocales acentuadas:

pattern = pattern.replace(/a/gi,"[a|á]");pattern = pattern.replace(/e/gi,"[e|é]");pattern = pattern.replace(/i/gi,"[i|í]");pattern = pattern.replace(/o/gi,"[o|ó]");pattern = pattern.replace(/u/gi,"[u|ú]");
if (settings.wordsOnly) {
pattern = "\\b" + pattern + "\\b";
}
var re = new RegExp(pattern, flag);

Seguro que alguna vez os resulta útil.

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