Table of Contents

Capítulo 9: Personalización
Editores
Configuración de editores
Editores para valores múltiples
Editores personalizables y estereotipos para crear combos
Vistas JSP propias y taglibs de OpenXava
Ejemplo
xava:editor
xava:action, xava:link, xava:image, xava:button
xava:message (nuevo en v2.0.3)
xava:descriptionsList (nuevo en v2.0.3)

Capítulo 9: Personalización

La interfaz de usuario generada por OpenXava es buena para la mayoría de los casos, pero a veces puede que necesitemos personalizar alguna parte de la interfaz de usuario (creando nuestros propios editores) o crear nuestra interfaz de usuario íntegramente a mano (usando vistas personalizadas con JSP).

Editores

Configuración de editores

Vemos como el nivel de abstracción usado para definir las vista es alto, nosotros especificamos las propiedades que aparecen y como se distribuyen, pero no cómo se visualizan. Para visualizar las propiedades OpenXava utiliza editores.
Un editor indica como visualizar una propiedad. Consiste en una definición XML junto con un fragmento de código JSP.
Para refinar el comportamiento de los editores de OpenXava o añadir los nuestros podemos crear en el directorio xava de nuestro proyecto un archivo llamado editores.xml. Este archivo es como sigue:
<?xml version = "1.0" encoding = "ISO-8859-1"?>
 
<!DOCTYPE editores SYSTEM "dtds/editores.dtd">
 
<editores>
    <editor .../> ...
</editores>
Simplemente contiene la definición de un conjunto de editores, y un editor se define así:
<editor
    url="url"                                <!-- 1 -->
    formatear="true|false"                   <!-- 2 -->
    depende-de-estereotipos="estereotipos"   <!-- 3 -->
    depende-de-propiedades="propiedades"     <!-- 4 -->
    enmarcable="true|false"                  <!-- 5 -->
>
    <propiedad ... /> ...                    <!-- 6 -->
    <formateador ... />                      <!-- 7 -->
    <para-estereotipo ... /> ...             <!-- 8 -->
    <para-tipo ... /> ...                    <!-- 8 -->
    <para-propiedad-modelo ... /> ...        <!-- 8 -->
</editor>
  1. url (obligado): URL de la página JSP que implementa el editor.
  2. formatear (opcional): Si es true es OpenXava el que tiene la responsabilidad de formatear los datos desde HTML hasta Java y viceversa, si vale false tiene que hacerlo el propio editor (generalmente recogiendo información del request y asignandolo a org.openxava.view.View y viceversa). Por defecto vale true.
  3. depende-de-estereotipos (opcional): Lista de estereotipos separados por comas de los cuales depende este editor. Si en la misma vista hay algún editor para estos estereotipos éstos lanzarán un evento de cambio si cambian.
  4. depende-de-propiedades (opcional): Lista de propiedades separadas por comas de los cuales depende este editor. Si en la misma vista se está visualizando alguna de estas propiedades éstas lanzarán un evento de cambio si cambian.
  5. enmarcable (opcional): Si vale true enmarca visualmente el editor. Por defecto vale false. Es útil para cuando hacemos editores grandes (de más de una línea) que pueden quedar más bonitos de esta manera.
  6. propiedad (varias, opcional): Permite enviar valores al editor, de esta forma podemos configurar un editor y poder usarlo en diferente situaciones.
  7. formateador (uno, opcional): Clase java para definir la conversión de Java a HTML y de HTML a Java.
  8. para-estereotipo o para-tipo o para-propiedad-modelo (obligada una de ellas, y solo una): Asocia este editor a un estereotipo, tipo o a una propiedad concreta de un modelo. Tiene preferencia cuando asociamos un editor a una propiedad de un modelo, después por estereotipo y como último por tipo.
Podemos ver un ejemplo de definición de editor, este ejemplo es uno de los editores que vienen incluidos con OpenXava, pero es un buen ejemplo para aprender como hacer nuestros propios editores:
<editor url="textEditor.jsp">
    <for-type type="java.lang.String"/>
    <for-type type="java.math.BigDecimal"/>
    <for-type type="int"/>
    <for-type type="java.lang.Integer"/>
    <for-type type="long"/>
    <for-type type="java.lang.Long"/>
</editor>
Aquí asignamos a un grupo de tipos básicos el editor textEditor.jsp. El código JSP de este editor es:
<%@ page import="org.openxava.model.meta.MetaProperty" %>
 
<%
String propertyKey = request.getParameter("propertyKey");                             // 1
MetaProperty p = (MetaProperty) request.getAttribute(propertyKey);                    // 2
String fvalue = (String) request.getAttribute(propertyKey + ".fvalue");               // 3
String align = p.isNumber()?"right":"left";                                           // 4
boolean editable="true".equals(request.getParameter("editable"));                     // 5
String disabled=editable?"":"disabled";                                               // 5
String script = request.getParameter("script");                                       // 6
boolean label = org.openxava.util.XavaPreferences.getInstance().isReadOnlyAsLabel();
if (editable || !label) {                                                             // 5
%>
<input name="<%=propertyKey%>" class=editor                                         <!-- 1 -->
    type="text"
    title="<%=p.getDescription(request)%>"
    align='<%=align%>'                                                              <!-- 4 -->
    maxlength="<%=p.getSize()%>"
    size="<%=p.getSize()%>"
    value="<%=fvalue%>"                                                             <!-- 3 -->
    <%=disabled%>                                                                   <!-- 5 -->
    <%=script%>                                                                     <!-- 6 -->
    />
<%
} else {
%>
<%=fvalue%>&nbsp;
<%
}
%>
<% if (!editable) { %>
    <input type="hidden" name="<%=propertyKey%>" value="<%=fvalue%>">
<% } %>
Un editor JSP recibe un conjunto de parámetros y tiene accesos a atributos que le permiten configurarse adecuadamente para encajar bien en una vista OpenXava. En primer lugar vemos como cogemos propertyKey (1) que después usaremos como id HTML. A partir de ese id podemos acceder a la MetaProperty (2) (que contiene toda la meta información de la propiedad a editar). El atributo fvalue (3) contiene el valor ya formateado y listo para visualizar. Averiguamos también la alineación (4) y si es o no editable (5). También recibimos el trozo de script de javascript (6) que hemos de poner en el editor.
Aunque crear un editor directamente con JSP es sencillo no es una tarea muy habitual, es más habitual configurar JSPs ya existentes. Por ejemplo si en nuestro xava/editores.xml ponemos:
<editor url="textEditor.jsp">
    <formatedor clase="org.openxava.formatters.UpperCaseFormatter"/>
    <para-tipo tipo="java.lang.String"/>
</editor>
Estaremos sobreescribiendo el comportamiento de OpenXava para las propiedades de tipo String, ahora todas las cadenas se visualizaran y aceptaran en mayúsculas. Podemos ver el código del formateador:
package org.openxava.formatters;
 
import javax.servlet.http.*;
 
/**
 * @author Javier Paniza
 */
 
public class UpperCaseFormatter implements IFormatter {                // 1
 
    public String format(HttpServletRequest request, Object string) {  // 2
        return string==null?"":string.toString().toUpperCase();
    }
 
    public Object parse(HttpServletRequest request, String string) {   // 3
        return string==null?"":string.toString().toUpperCase();
    }
 
}
Un formateado ha de implementar IFormatter (1) lo que lo obliga a tener un método format() (2) que convierte el valor de la propiedad que puede ser un objeto Java cualquiera en una cadena para ser visualizada en un documento HTML; y un método parse() (3) que convierte la cadena recibida de un submit del formulario HTML en un objeto Java listo para asignar a la propiedad.

Editores para valores múltiples

Definir un editor para editar valores múltiples es parecido a hacerlo para valores simples. Veamos.
Por ejemplo, si queremos definir un estereotipo REGIONES que permita al usuario seleccionar más de una región para una propiedad. Ese estereotipo se puede usar de esta manera:
@Stereotype("REGIONES")
private String [] regiones;
Entonces podemos añadir una entrada en el archivo tipo-estereotipo-defecto.xml como sigue:
<para estereotipo="REGIONES" tipo="String []"/>
Y definir nuestro editor en el editores.xml de nuestro proyecto:
<editor url="editorRegiones.jsp">                                                     <!-- 1 -->
    <propiedad nombre="cantidadRegiones" valor="3"/>                              <!-- 2 -->
    <formateador clase="org.openxava.formatters.MultipleValuesByPassFormatter"/>  <!-- 3 -->
    <para-estereotipo estereotipo="REGIONES"/>
</editor>
editorRegiones.jsp (1) es el archivo JSP que dibuja nuestro editor. Podemos definir propiedades que serán enviada al JSP como parámetros del request (2). El formateador tiene que implementar IMultipleValuesFormatter, que es similar a IFormatter pero usa String [] en vez de String. En este caso usamos un formateador genérico que simplemente deja pasar el dato.
Y para terminar escribimos nuestro editor JSP:
<%@ page import="java.util.Collection" %>
<%@ page import="java.util.Collections" %>
<%@ page import="java.util.Arrays" %>
<%@ page import="org.openxava.util.Labels" %>
 
<jsp:useBean id="style" class="org.openxava.web.style.Style" scope="request"/>
 
<%
String propertyKey = request.getParameter("propertyKey");
String [] fvalues = (String []) request.getAttribute(propertyKey + ".fvalue"); // (1)
boolean editable="true".equals(request.getParameter("editable"));
String disabled=editable?"":"disabled";
String script = request.getParameter("script");
boolean label = org.openxava.util.XavaPreferences.getInstance().isReadOnlyAsLabel();
if (editable || !label) {
    String sregionsCount = request.getParameter("cantidadRegiones");
    int regionsCount = sregionsCount == null?5:Integer.parseInt(sregionsCount);
    Collection regions = fvalues==null?Collections.EMPTY_LIST:Arrays.asList(fvalues);
%>
<select name="<%=propertyKey%>" multiple="multiple"
    class=<%=style.getEditor()%>
    <%=disabled%>
    <%=script%>>
    <%
    for (int i=1; i<regionsCount+1; i++) {
        String selected = regions.contains(Integer.toString(i))?"selected":"";
    %>
    <option
        value="<%=i%>" <%=selected%>>
        <%=Labels.get("regions." + i, request.getLocale())%>
    </option>
    <%
    }
    %>
</select>
<%
}
else {
    for (int i=0; i<fvalues.length; i++) {
%>
<%=Labels.get("regions." + fvalues[i], request.getLocale())%>
<%
    }
}
%>
 
<%
if (!editable) {
    for (int i=0; i<fvalues.length; i++) {
%>
        <input type="hidden" name="<%=propertyKey%>" value="<%=fvalues[i]%>">
<%
    }
}
%>
Como se puede ver es como definir un editor para un valor simple, la principal diferencia es que el valor formateado (1) es un array de cadenas (String []) y no una cadena simple (String).

Editores personalizables y estereotipos para crear combos

Podemos hacer que propiedades simples que se visualicen como combos que rellenen sus datos desde la base datos. Veámoslo.
Definimos las propiedades así en nuestro componente:
@Stereotype("FAMILY")
private int familyNumber;
 
@Stereotype("SUBFAMILY")
private int subfamilyNumber;
Y en nuestro editores.xml ponemos:
<editor url="descriptionsEditor.jsp">                                               <!-- 10 -->
    <propiedad nombre="modelo" valor="Familia"/>                                    <!--  1 -->
    <propiedad nombre="propiedadClave" valor="codigo"/>                             <!--  2 -->
    <propiedad nombre="propiedadDescripcion" valor="descripcion"/>                  <!--  3 -->
    <propiedad nombre="ordenadoPorClave" valor="true"/>                             <!--  4 -->
    <propiedad nombre="readOnlyAsLabel" valor="true"/>                              <!--  5 -->
    <para-estereotipo estereotipo="FAMILIA"/>                                       <!-- 11 -->
</editor>
 
<!--  Es posible especificar dependencias de estereotipos o propiedades -->
<editor url="descriptionsEditor.jsp"                                                <!-- 10 -->
    depende-de-estereotipos="FAMILIA">                                              <!-- 12 -->
<!--
<editor url="descriptionsEditor.jsp" depende-de-propiedades="codigoFamilia">        <!-- 13 -->
-->
    <propiedad nombre="modelo" valor="Subfamilia"/>                                 <!--  1 -->
    <propiedad nombre="propiedadClave" valor="codigo"/>                             <!--  2 -->
    <propiedad nombre="propiedadesDescripcion" valor="codigo, descripcion"/>        <!--  3 -->
    <propiedad nombre="condicion" value="${codigoFamilia} = ?"/>                    <!--  6 -->
    <propiedad nombre="estereotiposValoresParametros" valor="FAMILIA"/>             <!--  7 -->
    <!--
    <propiedad nombre="propiedadesValoresParametros" valor="codigoFamilia"/>        <!--  8 -->
    -->
    <propiedad nombre="formateadorDescripciones"                                    <!--  9 -->
        valor="org.openxava.test.formatters.FormateadorDescripcionesFamilia"/>
    <para-estereotipo estereotipo="SUBFAMILIA"/>                                    <!-- 11 -->
</editor>
Al visualizar una vista con estas dos propiedades codigoFamilia y codigoSubfamilia sacará un combo para cada una de ellas, el de familias con todas las familias disponible y el de subfamilias estará vacío y al escoger una familia se rellenará con sus subfamilias correspondientes.
Para hacer eso asignamos a los estereotipos (FAMILIA y SUBFAMILIA en este caso(11)) el editor descriptionsEditor.jsp (10) y lo configuramos asignandole valores a sus propiedades. Algunas propiedades con las que podemos configurar estos editores son:
  1. modelo: Modelo del que se obtiene los datos. Puede ser el nombre de una entidad (Factura) o el nombre de un modelo usado en una colección incrustada (Factura.LineaFactura).
  2. propiedadClave o propiedadesClave: Propiedad clave o lista de propiedades clave que es lo que se va a usar para asignar valor a la propiedad actual. No es obligado que sean las propiedades clave del modelo, aunque sí que suele ser así.
  3. propiedadDescripcion o propiedadesDescripcion: Propiedad o lista de propiedades a visualizar en el combo.
  4. ordenadoPorClave: Si ha de estar ordenador por clave, por defecto sale ordenado por descripción. También se puede usar order con un orden al estilo SQL, si lo necesitas.
  5. readOnlyAsLabel: Si cuando es de solo lectura se ha de visualizar como una etiqueta. Por defecto es false.
  6. condicion: Condición para restringir los datos a obtener. Tiene formato SQL, pero podemos poner nombres de propiedades con ${}, incluso calificadas. Podemos poner argumentos con ?. En ese caso es cuando dependemos de otras propiedades y solo se obtienen los datos cuando estas propiedades cambian.
  7. estereotiposValoresParametros: Lista de estereotipos de cuyas propiedades dependemos. Sirven para rellenar los argumentos de la condición y deben coincidir con el atributo depende-de-estereotipos. (12)
  8. propiedadesValoresParametros: Lista de propiedades de las que dependemos. Sirven para rellenar los argumentos de la condición y deben coincidir con el atributo depende-de-propiedades. (13)
  9. formateadorDescripciones: Formateador para las descripciones visualizadas en el combo. Ha de implementar IFormatter.
Siguiendo este ejemplo podemos hacer fácilmente nuestro propios estereotipos que visualicen una propiedad simple con un combo con datos dinámicos. Sin embargo, en la mayoría de los casos es más conveniente usar referencias visualizadas como @DescriptionsList; pero siempre tenemos la opción de los estereotipos disponible.

Vistas JSP propias y taglibs de OpenXava

Obviamente la mejor forma de crear interfaces de usuario es usando las anotaciones de vista que se ven en el capítulo 4. Pero, en casos extremos quizás necesitemos definir nuestra propia vista usando JSP. OpenXava nos permite hacerlo. Y para hacer más fácil la labor podemos usar algunas taglibs JSP provistas por OpenXava. Veamos un ejemplo.

Ejemplo

Lo primero es indicar en nuestro módulo que queremos usar nuestro propio JSP, en aplicacion.xml:
<modulo nombre="ComercialJSP" carpeta="facturacion.variaciones">
    <modelo nombre="Comercial"/>
    <vista nombre="ParaJSPPropio"/>                               <!-- 1 -->
    <vista-web url="jsp-propios/comercial.jsp"/>                  <!-- 2 -->
    <controlador nombre="Typical"/>
</modulo>
Si usamos vista-web (2) al definir el módulo, OpenXava usa nuestro JSP para dibujar el detalle, en vez de usar la vista generada automáticamente. Opcionalmente podemos definir una vista OpenXava con vista (1), esta vista es usada para saber que eventos lanzar y que propiedades llenar, si no se especifica se usa la vista por defecto de la entidad; aunque es aconsejable crear una vista OpenXava explícita para nuestra vista JSP, de esta manera podemos controlar los eventos, las propiedades a rellenar, el orden del foco, etc explicitamente. Podemos poner nuestro JSP dentro de la carpeta web/jsp-propios (u otra de nuestra elección) de nuestro proyecto, y este JSP puede ser así:
<%@ include file="../xava/imports.jsp"%>
 
<table>
<tr>
    <td>C&oacute;digo: </td>
    <td>
        <xava:editor property="codigo"/>
    </td>
</tr>
<tr>
    <td>Nombre: </td>
    <td>
        <xava:editor property="nombre"/>
    </td>
</tr>
 
<tr>
    <td>Nivel: </td>
    <td>
        <xava:editor property="nivel.id"/>
        <xava:editor property="nivel.descripcion"/>
    </td>
</tr>
</table>
Somos libres de crear el archivo JSP como queramos, pero puede ser práctico usar las taglibs de OpenXava, en este caso, por ejemplo, se usa <xava:editor/>, esto dibuja un editor apto para la propiedad indicada, además añade el JavaScript necesario para lanzar los eventos. Si usamos <xava:editor/>, podemos manejar la información visualizada usando el objeto xava_view (del tipo org.openxava.view.View), por lo tanto todos los controladores estándar de OpenXava (CRUD incluido) funcionan.
Podemos observar como las propiedades cualificadas están soportadas (como nivel.id o nivel.descripcion) (nuevo en v2.0.1), además cuando el usuario rellena nivel.id, nivel.descripcion se llena con su valor correspondiente. Sí, todo el comportamiento de una vista OpenXava está disponible dentro de nuestros JSPs si usamos las taglibs de OpenXava.
Veamos las taglib de OpenXava.

xava:editor

La marca (tag) <xava:editor/> permite visualizar un editor (un control HTML) para nuestra propiedad, de la misma forma que lo hace OpenXava cuando genera automáticamente la interfaz de usuario.
<xava:editor
    property="nombrePropiedad"         <!-- 1 -->
    editable="true|false"              <!-- 2  Nuevo en v2.0.1 -->
    throwPropertyChanged="true|false"  <!-- 3  Nuevo en v2.0.1 -->
/>
  1. property (obligado): Propiedad del modelo asociado al módulo actual.
  2. editable (opcional): Nuevo en v2.0.1. Fuerza a este editor a ser editable, de otra forma se asume un valor por defecto apropiado.
  3. throwPropertyChanged (opcional): Nuevo en v2.0.1. Fuerza a este editor a lanzar el evento de propiedad cambiada, de otra forma se asume un valor por defecto apropiado.
Esta marca genera el JavaScript para permitir a nuestra vista trabajar de la misma forma que una vista automática. Las propiedades calificadas (propiedades de referencias) están soportadas (nuevo en v2.0.1).

xava:action, xava:link, xava:image, xava:button

La marca (tag) <xava:action/> permite dibujar una acción (un botón o una imagen que el usuario puede pulsar).
<xava:action action="controlador.accion" argv="argv"/>
El atributo action indica la acción a ejecutar, y el atributo argv (opcional) nos permite establecer valores a algunas propiedades de la acción antes de ejecutarla. Un ejemplo:
<xava:action action="CRUD.save" argv="resetAfter=true"/>
Cuando el usuario pulse en la acción se ejecutará CRUD.save, antes pone a true la propiedad resetAfter de la acción.
La acción se visualiza como una imagen si tiene una imagen asociada y como un botón si no tiene imagen asociada. Si queremos detereminar el estilo de visualización podemos usar directamente las siguientes marcas: <xava:button/>, <xava:image/> o <xava:link/> similares a <xava:action/>.
Podemos especificar una cadena vacía para la acción (nuevo en v2.2.1), como sigue:
<xava:action action=""/>
En este caso la marca (tag) no tiene efecto y no se produce error. Esta característica puede ser útil cuando el nombre de la acción lo obtenemos dinámicamente (es decir action=”<%=micodigo()%>”), y el valor pueda estar vacío en ciertos casos.

xava:message (nuevo en v2.0.3)

La marca (tag) <xava:message/> permite mostrar en HTML un mensaje de los archivos de recursos i18n de OpenXava.
<xava:message key="clave_mensaje" param="parametroMensaje" intParam="paramMensaje"/>
El mensaje es buscado primero en los archivos de recursos de nuestro proyecto (MiProyecto/i18n/MensajesMiProyecto.properties) y si no se encuentra ahí es buscado en los mensajes por defecto de OpenXava (OpenXava/i18n/Messages.properties).
Los atributos param y intParam son opcionales. El atributo intParam es usado cuando el valor a enviar como parametro es de tipo int. Si usamos Java 5 podemos usar siempre param porque int es automáticamente convertido por autoboxing.
Esta marca solo genera el texto del mensaje, sin ningun tipo de formateo HTML.
Un ejemplo:
<xava:message key="cantidad_lista" intParam="<%=cantidadTotal%>"/>

xava:descriptionsList (nuevo en v2.0.3)

La marca (tab) <xava:descriptionsList/> permite visualizar una lista descripciones (un combo HTML) para una referencia, del mismo modo que lo hace OpenXava cuando genera la interfaz de usuario automáticamente.
<xava:descriptionsList
    reference="nombreReferencia"  <!-- 1 -->
/>
  1. reference (obligado): Una referencia del modelo asociado con el módulo actual.
Esta marca genera el JavaScript necesario para permitir a la vista personalizada trabajar de la misma forma que una automática.
Un ejemplo:
<tr>
    <td>Nivel: </td>
    <td>
        <xava:descriptionsList reference="nivel"/>
    </td>
</tr>
En este caso nivel es una referencia al modelo actual (por ejemplo Comercial). Un combo es mostrado con todos los niveles disponibles.