Guía de referencia: Modelo | Vista | Datos tabulares | Mapeo objeto/relacional | Controladores | Aplicación | Personalización

Datos tabulares

Datos tabulares son aquellos que se visualizan en formato de tabla. Cuando creamos un módulo de OpenXava convencional el usuario puede gestionar la información sobre ese componente con una lista como ésta:
tab_es010.jpg
Esta lista permite al usuario:
  • Filtrar por cualquier columna o combinación de ellas.
  • Ordenar por cualquier columna con un simple click.
  • Visualizar los datos paginados, y así podemos leer eficientemente tablas de millones de registros.
  • Personalizar la lista: añadir, quitar y cambiar de orden las columnas (con el lapicito que hay en la parte superior izquierdas). Las personalizaciones se recuerdan por cada usuario.
  • Acciones genéricas para procesar la lista: Como la de generar un informe en PDF, exportar a Excel o borrar los registros seleccionados.
La lista por defecto suele ir bien, y además el usuario puede personalizarsela. Sin embargo, a veces conviene modificar el comportamiento de la lista. Esto se hace mediante la anotación @Tab dentro de la definición de la entidad.
La sintaxis de @Tab es:
@Tab(
    name="nombre",                           // 1
    filter=clase del filtro,                 // 2
    rowStyles=array de @RowStyle,            // 3
    properties="propiedades",                // 4
    baseCondition="condición base",          // 5
    defaultOrder="orden por defecto",        // 6
    editor="editor por defecto",             // 7  Nuevo en v4.6
    editors="todos los editores disponibles" // 8  Nuevo en v5.7
)
public class MiEntidad {
  1. name (opcional): Podemos definir varios tabs para una entidad (mediante la anotación @Tabs), y ponerle un nombre a cada uno. Este nombre se usará después para indicar que tab queremos usar (normalmente en aplicación.xml al definir un módulo).
  2. filter (opcional): Permite definir programáticamente un filtro a realizar sobre los valores que introduce el usuario cuando quiere filtrar.
  3. rowStyles (varios, opcional): Una forma sencilla de especificar una estilo de visualización diferente para ciertas filas. Normalmente para resaltar filas que cumplen cierta condición. Especificamos un array de @RowStyle, así podemos usar varios estilo por tab.
  4. properties (opcional): La lista de propiedades a visualizar inicialmente. Pueden ser calificadas. El sufijo + (nuevo en v4.1) se puede añadir a una propiedad para mostrar la suma de la columna abajo.
  5. baseCondition (opcional): Es una condición que aplicará siempre a los datos visualizados añadiendose a las que pueda poner el usuario.
  6. defaultOrder (opcional): Para especificar el orden en que aparece los datos en la lista inicialmente.
  7. editor (opcional): (Nuevo en v4.6) Editor de default-editors.xml o editores.xml usado para visualizar la lista. Se usa para el formato por defecto, si la lista tiene varios formatos los otros permanecen inalterados.
  8. editores (opcional): (Nuevo en v5.7) Lista de editores separados por coma usados para visualizar la lista. Cada editor corresponde a un formato disponible para los usuarios. Los editores se declaran en default-editors.xml o editores.xml.

Propiedades iniciales y resaltar filas

La personalización más simple es indicar las propiedades a visualizar inicialmente:
@Tab(
    rowStyles=@RowStyle(style="row-highlight", property="tipo", value="fijo"),
    properties="nombre, tipo, comercial.nombre, direccion.municipio," +
        "comercial.nivel.descripcion, direccion.estado.nombre"
)
Vemos como podemos poner propiedades calificadas (que pertenecen a referencias) hasta cualquier nivel. Estas serán las propiedades que salen la primera vez que se ejecuta el módulo, después cada usuario puede escoger cambiar las propiedades que quiere ver.
En este caso vemos también como se indica un @RowStyle; estamos diciendo que aquellos objetos cuya propiedad tipo tenga el valor fijo han de usar el estilo row-highlight. El estilo ha de definirse en la hoja de estilos CSS. El estilo row-highlight (highlight en versiones anteriores a la v4m3) ya viene predefinido con OpenXava, pero puedes definir tus propios estilos mediante el fichero custom.css en web/xava/style (nuevo en v4.5). El resultado visual del anterior tab es:
tab_es020.jpg

Filtros y condición base

Una técnica habitual es combinar un filtro con una condición base:
@Tab(name="Actuales",
    filter=FiltroAñoActual.class,
    properties="año, numero, sumaImportes, iva, cantidadLineas, pagada, cliente.nombre",
    baseCondition="${año} = ?"
)
 
La condición tiene la sintaxis SQL, ponemos ? para los argumentos y los nombres de propiedades entre ${}. En este caso usamos el filtro para dar valor al argumento. El código del filtro es:
package org.openxava.test.filtros;
 
import java.util.*;
 
import org.openxava.filters.*;
 
/**
 * @author Javier Paniza
 */
 
public class FiltroAñoActual implements IFilter {            // (1)
 
    public Object filter(Object o) throws FilterException {  // (2)
        Calendar cal = Calendar.getInstance();
        cal.setTime(new java.util.Date());
        Integer año = new Integer(cal.get(Calendar.YEAR));
        Object [] r = null;
        if (o == null) {                                    // (3)
            r = new Object[1];
            r[0] = año;
        }
        else if (o instanceof Object []) {                  // (4)
            Object [] a = (Object []) o;
            r = new Object[a.length + 1];
            r[0] = año;
            for (int i = 0; i < a.length; i++) {
                r[i+1]=a[i];
            }
        }
        else {                                              // (5)
            r = new Object[2];
            r[0] = año;
            r[1] = o;
        }
 
        return r;
    }
 
}
Un filtro recoge los argumentos que el usuario teclea para filtrar la lista y los procesa devolviendo lo que al final se envía a OpenXava para que haga la consulta. Como se ve ha de implementar IFilter (1) lo que lo obliga a tener un método llamado filter (2) que recibe un objeto que el valor de los argumentos y devuelve los argumentos que al final serán usados. Estos argumentos pueden ser nulo (3), si el usuario no ha metidos valores, un objeto simple (5), si el usuario a introducido solo un valor o un array de objetos (4), si el usuario a introducidos varios valores. El filtro ha de contemplar bien todos los casos. En el ejemplo lo que hacemos es añadir delante el año actual, y así se usa como argumento a la condición que hemos puesto en nuestro tab.
Resumiendo el tab que vemos arriba solo sacará las facturas correspondientes al año actual.
Podemos ver otro caso:
@Tab(name="AñoDefecto",
    filter=FiltroAñoDefecto.class,
    properties="año, numero, cliente.numero, cliente.nombre, sumaImportes, " +
        "iva, cantidadLineas, pagada, importancia",
    baseCondition="${año} = ?"
)
 
En este caso el filtro es:
package org.openxava.test.filtros;
 
import java.util.*;
 
import org.openxava.filters.*;
 
/**
 * @author Javier Paniza
 */
 
public class FiltroAñoDefecto extends BaseContextFilter {    // (1)
 
    public Object filter(Object o) throws FilterException {
        if (o == null) {
            return new Object [] { getAñoDefecto() };        // (2)
        }
        if (o instanceof Object []) {
            List c = new ArrayList(Arrays.asList((Object []) o));
            c.add(0, getAñoDefecto());                       // (2)
            return c.toArray();
        }
        else {
            return new Object [] { getAñoDefecto(), o };     // (2)
        }
    }
 
    private Integer getAñoDefecto() throws FilterException {
        try {
            return getInteger("xavatest_añoDefecto");        // (3)
        }
        catch (Exception ex) {
            ex.printStackTrace();
            throw new FilterException(
            "Imposible obtener año defecto asociado a esta sesión");
        }
    }
 
}
Este filtro desciende de BaseContextFilter, esto le permite acceder al valor de los objetos de sesión de OpenXava. Vemos como usa un método getAñoDefecto() (2) que a su vez llama a getInteger() (3) el cual (al igual que getString(), getLong() o el más genérico get()) nos permite acceder al valor del objeto xavatest_añoDefecto. Esto objeto lo definimos en nuestro archivo controladores.xml de esta forma:
<objeto nombre="xavatest_añoDefecto" clase="java.lang.Integer" valor="1999"/>
Las acciones lo pueden modificar y tiene como vida la sesión del usuario y es privado para cada módulo. De esto se habla más profundamente en el capítulo 7.
Esto es una buena técnica para que en modo lista aparezcan unos datos u otros según el usuario o la configuración que éste haya escogido.
También es posible acceder a variables de entorno dentro de un filtro de tipo BaseContextFilter, usando el método getEnvironment(), de esta forma:
new Integer(getEnvironment().getValue("XAVATEST_AÑO_DEFECTO"));
Para aprender más sobre variable de entorno ver el capítulo 7 sobre controladores.

Select parcial (nuevo en v5.6)

En baseCondition puedes escribir la sentencia select a partir de la clausula FROM, para hacerlo empieza la condición con from:
@Tab(name="DeValencia",
    baseCondition="from Cliente e, in (e.provincias) p where p.id = 'V'")
Usa la sintaxis de JPQL usando e como alias para la entidad principal.
Esta opción es mejor que usar un select íntegro porque la lista de propiedades la genera OpenXava, así el usuario puede personalizar la lista mientras que el desarrollador todavía tiene la opción de hacer consultas sofisticadas.

Select íntegro

Tenemos la opción de poner el select completo para obtener los datos del tab. Desde v4.5 se usa JPQL para la sintaxis:
@Tab(name="SelectIntegro",
    properties="codigo, descripcion, familia",
    baseCondition=
        "select e.codigo, e.descripcion, f.descripcion " +
        "from Subfamilia e, Familia f " +
        "where e.codigoFamilia  = f.codigo"
)
La implementación actual requiere que se use e como alias para la entidad principal.

Hasta v4.4.x se usaba SQL:
@Tab(name="SelectIntegro",
    properties="codigo, descripcion, familia",
    baseCondition=
        "select" +
        "    ${codigo}, ${descripcion}, XAVATEST.FAMILIA.DESCRIPCION " +
        "from " +
        "    XAVATEST.SUBFAMILIA, XAVATEST.FAMILIA " +
        "where " +
        "    XAVATEST.SUBFAMILIA.FAMILIA = " +
        "    XAVATEST.FAMILIA.CODIGO"
)
Esto es mejor usarlo solo en casos de extrema necesidad. No suele ser necesario, y al usarlo el usuario no podrá personalizarse la vista.

Orden por defecto

Por último, establecer un orden por defecto es harto sencillo:
@Tab(name="Simple", properties="año, numero, fecha",
    defaultOrder="${año} desc, ${numero} desc"
}
 
Este orden es solo el inicial, el usuario puede escoger otro con solo pulsar la cabecera de una columna.

Valores por defecto para los tabs a nivel de aplicación (new v4m4)

Puedes definir valor por defecto para todos (o seleccionados) @Tabs de tu aplicación de una vez. Para hacerlo, crea un archivo valores-defecto-tabs.xml en la carpeta xava de tu aplicación, tal como muestra el siguiente ejemplo:
<?xml version = "1.0" encoding = "ISO-8859-1"?>
 
<!DOCTYPE valores-defecto-tabs SYSTEM "dtds/valores-defecto-tabs.dtd">
 
<valores-defecto-tabs>
 
    <tab>
        <filtro clase="org.openxava.test.filtros.FiltroAnoActivo"/>
        <condicion-base>${ano} = ?</condicion-base>
 
        <para-modelo modelo="Albaran"/>
        <para-modelo modelo="Factura"/>
    </tab>
 
    <tab>
        <propiedades>ano, numero, fecha</propiedades>
        <orden-defecto>${numero} desc</orden-defecto>
 
        <para-modelo modelo="Albaran"/>
    </tab>
 
    <tab>
        <filtro clase="org.openxava.filters.UserFilter"/>
        <condicion-base>${usuario} = ?</condicion-base>
 
        <excepto-para-modelo modelo="Usuario"/>
    </tab>
 
    <tab>
        <orden-defecto>${oid} asc</orden-defecto>
        <para-todos-los-modelos/>
    </tab>
 
</valores-defecto-tabs>
El elemento <tab/> sigue la sintaxis de los tabs en los Componentes XML. Con la adición de <para-modelo/>, <excepto-para-modelo/> y <para-todos-los-modelos/> usados para aplicar los valores a las entidades deseadas.
Con estos elementos tab defines valores por defecto para los tabs de tus entidades, por lo tanto los valores usados en los @Tabs definidos en tus entidades siempre tienen preferencia sobre estos.

Sumatorio de columna (nuevo en v4.1)

Para mostrar la suma de todos los valores de una columna al final de la lista sólo has de añadir el símbolo + al nombre de la propiedad, como sigue:
@Tab( properties = "año, numero, descripcion, importe+" )
En este caso se mostrará la suma de la columna importe, tal como muestra la siguiente imagen:
tab_es030.jpg
Sólo se puede aplicar el sumatorio a las propiedades numéricas no calculadas.

Escoger un editor (nuevo en v4.6)

Un editor es el código real (normalmente un JSP) que visualiza la lista al usuario. Por defecto, el editor que OpenXava usa para visualizar los datos tabulares es una lista con paginación, filtrado, ordenación y búsqueda, pero podemos especificar nuestro propio editor para visualizar un tab concreto usando el atributo editor en @Tab.
Por ejemplo, si tenemos una lista de entidades Cliente y queremos visualizarla usando una interfaz de usuario personalizada, como una fila de fichas, lo puedes hacer así:
@Tab ( name ="Fichas", editor="ListaFichasCliente",
    properties="codigo, nombre, tipo, direccion.ciudad, direccion.provincia.nombre"
)
En este caso el editor ListaFichasCliente se usará para visualizar y editar los datos tabulares, en lugar de la de por defecto. Hemos de definir nuestro editor ListaFichasCliente en el archivo xava/editors.xml de nuestro proyecto:
<editor nombre="ListaFichasCliente" url="fichasClienteListEditor.jsp"/>
También hemos de escribir el código JSP para el editor en fichasClienteListEditor.jsp.
Esta característica es para cambiar el editor para un tab concreto de una entidad. Si lo que queremos es cambiar el editor para todos los tabs de cierta entidad a nivel de aplicación es mejor configurarlo usando el archivo xava/editores.xml.
Veáse la sección Editores para tabs del capítulo 9 para más detalles.

Inhabilitar personalización

El usuario puede personalizar la lista añadiendo, moviendo, quitando columnas y algunas cosas más:
tab_es040.jpg
Si no quieres que tus usuarios personalicen la lista puedes desactivarlo a nivel de aplicación añadiendo la siguiente entrada en xava.properties:
customizeList=false
Si quieres desactivar la personalización para una lista específica bajo determinadas circunstancias puedes hacerlo por código:
public class MiAccion extends TabBaseAction {
 
    public void execute() throws Exception {
        if (miCondicion) {
            getTab().setCustomizeAllowed(false);
        }
        ...
    }
 
}
Si quieres inhabilitar la personalización de la lista justo para un módulo, hay un controlador para eso, llamado NoCustomizeList (nuevo en v5.0). Úsalo cuando definas tu módulo en aplicacion.xml (mira el capítulo 8) como sigue:
<modulo nombre="Almacen">
    <modelo nombre="Almacen"/>
    <controlador nombre="Typical"/>
    <controlador nombre="NoCustomizeList"/>
</modulo>
De esa manera, el módulo Almacen no permite al usuario personalizar la lista.

Varios formatos de presentación usando editores (nuevo en v5.7)

Los mismos datos se pueden visualizar con diferentes formatos de presentación, por ejemplo, usando listas, gráficos, tarjetas, etc. El usuario puede escoger el formato usando los botones a la derecha de la barra de botones superior:
tab050.png
Los formatos disponibles son todo los editores que están asignado a tab usando <for-tabs/> en default-editors.xml o <para-tabs/> en editores.xml. Sin embargo, puedes cambiar los editores disponibles para un tab específico con el atributo editors (nuevo en v5.7) de @Tab. Por ejemplo, si escribes un @Tab como este:
@Tab ( name ="ConTarjetas", editors ="List, Charts, ListaTarjetasClientes",
    properties="numero, nombre, tipo, direccion.poblacion, direccion.provincia.nombre"
)
Este tab tendrá los formatos List y Charts, que son estándar, y un nuevo formato personalizado, ListaTarjetasClientes. ListaTarjetasClientes es un editor propio definido en editores.xml.
Para aprender como definir los editores para los tabs, lee la documentación sobre personalización.

Quitar los gráficos de modo lista (nuevo en v5.7)

Para quitar los gráficos de una lista concreta puedes usar @Tab(editors=), de esta manera:
@Tab ( name ="SoloLista", editors ="List",
    properties="numero, nombre, tipo, direccion.poblacion, direccion.provincia.nombre"
)
Así podemos tener un módulo sin gráficos, sólo la lista de OpenXava de toda la vida. Dado que hay solo un editor, los botones para seleccionar formato no aparecen.
La diferencia entre editor y editors, es que con editor indicamos el editor para el formato por defecto, mientras que con editors especificamos todos los formatos disponibles.
Para quitar los gráficos de todas las listas de tu aplicación de un solo golpe podemos usar valores-defecto-tabs.xml:
<?xml version = "1.0" encoding = "ISO-8859-1"?>
 
<!DOCTYPE valores-defecto-tabs SYSTEM "dtds/valores-defecto-tabs.dtd">
 
<valores-defecto-tabs>
 
    <tab editores="List">
        <para-todos-los-modelos/>
    </tab>
 
</valores-defecto-tabs>
Esta técnica no es sólo para quitar los gráficos, es para restringir la lista de formato para todos los módulos. Si no se usan todos los editores <para-tabs/>.