viernes, 28 de enero de 2011

Componente lista para Tapestry 5 (paginable y anidable)

Tapestry 5 viene con muchos componetes útiles,  varios tipos de enlaces, campos de formulario, tabla paginada, bucles, gestión de errores y otros muchos otros que se le pueden
añadir mediante librerías externas. Sin embargo, hay uno que he echado en falta que es una lista de elementos paginada y que podamos usarla de forma anidada. Lo más parecido que hay es el componente Grid sin embargo este presenta los datos en una tabla y puede que no nos sirva.

Una de las cosas buenas de Tapestry es que es muy sencillo crear un nuevo componente. Asi que tomando como bse el código del componente Grid, ya que tiene muchas similitudes con él, he desarrollado un componente lista paginada.

El componente Lista está formado por las siguientes clases:

com.blogspot.elblogdepicodev.tapestry.components.Lista: representa la clase principal del componente.
com.blogspot.elblogdepicodev.tapestry.components.ListaPager: genera una lista de enlaces a las páginas de la lista según los datos devueltos por el objeto ListaDataSource y emite los eventos de cambio de página.
com.blogspot.elblogdepicodev.tapestry.components.ListaRows: procesa los elementos de la lista para la página que se está visualizando.

A partir del componente Gird, he eliminado lo que no era necesario y añadido el soporte para poder anidar varios componentes Lista. Su uso podría ser tan sencillo como lo siguiente, aunque se pueden pasar otros parámetros como el número de elementos en cada página y algunos otros parámetros que también existen en el componente Grid como inPlace, rowIndex,y rowClass:

<t:lista source="list1" row="x1" rowindex="i1" rowsperpage="literal:3" inplace="true">
  <t:lista source="list2" row="x2" rowindex="i2" rowsperpage="literal:10"> 
    ${x1} x ${x2} = ${multiplica(x1, x2)}
  </t:lista>
</t:lista>

Sencillo para todo lo que hace, ¿no?. Piensa como lo podrías hacer con otro framework ¿tendrías que copiar y pegar código para poder reutilizar la funcionalidad? ¿tendrías que manejar parámetros de la request y variables en sesión? Pues eso es una de las cosas que me gusta de Tapestry que te puedes olvidar de todo esto es usar el componente y listo no hace falta saber como funciona internamente de eso ya se encarga el componente. El resultado sería este.


Otra clase importante es ListaDataSource en la cual se apoya la clase Lista para obtener los datos a mostrar.

com.blogspot.elblogdepicodev.tapestry.misc.ListaDataSource: es una interfaz que permitirá al componente lista obtener los datos a mostrar.

package com.blogspot.elblogdepicodev.tapestry.misc;

import org.apache.tapestry5.ValueEncoder;

public interface ListaDataSource {
 /**
  * Devuelve el número de elementos totales en la lista.
  */
    int getAvailableRows();

 /**
  * Permite preparar el modelo para mostrar los elementos desde el índice de inicio hasta el índice de fin.
  */
    void prepare(int startIndex, int endIndex);

 /**
  * Obtiene el objeto de índice indicado.
  */
    Object getRowValue(int index);

 /**
  * Devuelve la clase de los objetos de la lista.
  */
    @SuppressWarnings("rawtypes")
    Class getRowType();
    
    @SuppressWarnings("rawtypes")
 /**
  * Encoder para el objeto devuelto por el método getContext().
  */
    ValueEncoder getEncoder();

 /**
  * Dato asociado a los elementos lista, usado como clave para persistir la página actual de la lista a visualizar y permitir el anidamiento de componentes Lista.
  */
    Object getContext();
}

La clase ListaDataSource es una interfaz y por tanto necesitamos alguna implementación de ella para poder usar el componente Lista. Con las siguientes dos abarcamos muchos de los casos que podemos necesitar.

com.blogspot.elblogdepicodev.tapestry.misc.CollectionListaDataSource: crea un objeto ListaDataSource a partir de una colección de objetos.
com.blogspot.elblogdepicodev.tapestry.misc.HibernateListaDataSource: crea un objeto ListaDataSource que obtendrá los datos de una sesión de hibernate.

Otra de las buenas características de Tapestry es el concepto de Coercer que básicamente es una forma que tiene Tapestry de convertir un objeto en otro, por ejemplo un int a un String, un String con cierto formato a una lista, etc... en el caso que nos ocupa una Collection en un CollectionDataSource, de esta forma en el parámetro source del componente Lista podremos pasarle también un objeto que implemente la interfaz Collection y Tapestry buscará una forma de convertir esa Collection a un objeto que implemente el tipo del parámetro (ListaDataSource). Para ello debemos indicarle a Tapestry en la clase del módulo de la aplicación el coercer necesario para ello.

package com.blogspot.elblogdepicodev.tapestry.services;

public class AppModule {
...
@SuppressWarnings("rawtypes")
public static void contributeTypeCoercer(Configuration configuration) {
    Coercion coercion = new Coercion() {
        public CollectionListaDataSource coerce(Collection input) {
            return new CollectionListaDataSource(input);
        }
    };

    configuration.add(new CoercionTuple(Collection.class, CollectionListaDataSource.class, coercion));

    add(configuration, ListaPagerPosition.class);
}
...
}

El código del componente lista es demasiado como para ponerlo en esta entrada pero podéis descargarlo desde el apartado referencia. Si a alguien le parece interesante puede sentirse libre de usarlo y modificarlo.

Referencia:
Documentación sobre Apache Tapestry
Código fuente del componente lista paginada para Tapestry 5 (source code)