Openxava 3.1.4 Reference Guide - Es
Openxava 3.1.4 Reference Guide - Es
VERSIÓN 3.1.4
OpenXava 3.1.4 1
Contenido
° ° ° ° °
Contenido
OpenXava 3.1.4 2
Contenido
° ° ° ° °
OpenXava 3.1.4 3
° ° ° ° °
Presentación
OpenXava es un marco de trabajo para desarrollar aplicaciones JavaEE/J2EE
rápida y fácilmente.
La filosofía subyacente es definir con anotaciones de Java o con XML y
programar con Java, pero cuanto más definimos y menos programamos mejor.
El objetivo principal es hacer que las cosas más típicas en una aplicación de
gestión sean fáciles de hacer, mientras que ofrecemos la flexibilidad
suficiente para desarrollar las funciones más avanzadas y especificas.
Componente de negocio
Las piezas fundamentales para crear una aplicación OpenXava son los
componentes, en el contexto de OpenXava un componente de negocio es una
clase Java (aunque existe también una versión XML) que contiene toda la
información necesaria sobre un concepto de negocio para poder crear
aplicaciones sobre eso. Es decir toda la información que el sistema ha de
saber sobre el concepto de factura se define en un archivo Factura.java. En
un componente de negocio se define:
• La estructura de datos.
• Las validaciones, cálculos y en general toda la lógica de negocio
asociada a ese concepto.
• Las posibles vista, esto es, la configuración de todos las posibles
interfaces gráficas para este componente.
• Se define las posibilidades para la presentación tabular de los datos.
Esto se usa para el modo lista (consultar y navegar por los datos), los
listados, exportación a excel, etc.
• Mapeo objeto-relacional, lo que incluye información sobre las tablas de
la base de datos y la forma de convertir a objetos la información que
en ellas hay.
OpenXava 3.1.4 4
Capítulo 1: Visión general
Controladores
Los componentes de negocio no definen lo que un usuario puede hacer con
la aplicación; esto se define con los controladores. Los controladores están en
el archivo xava/controladores.xml de cada proyecto; además OpenXava tiene
un conjunto de controladores predefinidos en OpenXava/xava/
default-controllers.xml.
Un controlador es un conjunto de acciones. Una acción es un botón o vínculo
que el usuario puede pulsar.
Los controladores están separados de los componentes de negocio porque un
mismo controlador puede ser asignado a diferentes componentes de negocio.
Por ejemplo, un controlador para hacer un mantenimiento, imprimir en PDF, o
exportar a archivos planos, etc. puede ser usado y reusado para facturas,
clientes, proveedores, etc.
Aplicación
Una aplicación OpenXava es un conjunto de módulos. Un módulo une un
componente de negocio con uno o más controladores. Cada módulo de la
aplicación es lo que al final utiliza el usuario, y generalmente se configura
como un portlet dentro de un portal.
Estructura de un proyecto
Un proyecto OpenXava típico suele contener las siguientes carpetas:
• [raiz]: En la raíz del proyecto nos encontraremos el build.xml (con las
tareas Ant).
• src[carpeta fuente]: contiene el código fuente Java escrito por
nosotros.
• xava: Los archivos XML para configurar nuestras aplicaciones
OpenXava. Los principales son aplicacion.xml y controladores.xml.
• i18n: Archivos de recursos con las etiquetas y mensajes en varios
idiomas.
OpenXava 3.1.4 5
Capítulo 1: Visión general
OpenXava 3.1.4 6
Capítulo 1: Visión general
° ° ° ° °
OpenXava 3.1.4 7
° ° ° ° °
OpenXava 3.1.4 8
Capítulo 2: Mi primer proyecto OX
import javax.persistence.*;
import org.openxava.annotations.*;
@Entity
public class Almacen {
@Column(length=40) @Required
private String nombre;
OpenXava 3.1.4 9
Capítulo 2: Mi primer proyecto OX
Este es su significado:
1. @Id: Indica si esta propiedad forma parte de la clave. La clave
identifica a cada objeto de forma única y normalmente coincide con
la clave en la tabla de base de datos.
2. @Column(length= ): Longitud de los datos visualizados. Es opcional,
pero suele ser útil para hacer mejores interfaces gráficos y generar
las tablas de la base de datos.
3. @Required: Indica si hay que validar la existencia de información en
esta propiedad antes de crear o modificar.
4. La propiedad definida de la forma usual para una clase Java. Todo
tipo válido para una propiedad Java se puede poner, lo que incluye
tipos integrados, clases del JDK, clases propias, etc.
OpenXava 3.1.4 10
Capítulo 2: Mi primer proyecto OX
Las posibilidades de una propiedad van mucho más allá de lo que aquí se
muestra, se puede ver una explicación más completa en el capitulo sobre el
modelo.
La tabla
Antes de poder probar la aplicación hemos de crear la tabla en la base de
datos:
• Arrancamos la base de datos: Desde la línea de órdenes vamos a la
carpeta openxava-3.x/tomcat/bin y ejecutamos:
• En Linux/Unix: ./start-hsqldb.sh gestion-db 1666
• En Windows: start-hsqldb gestion-db 1666
• Creamos la tabla:
• Editamos Gestion/build.xml. Buscamos la tarea ant
actualizarEsquema.
• Ponemos el valor correcto para schema.path, en este caso “../
OpenXavaTest/lib/hsqldb.jar”.
• Ejecutamos la tarea ant actualizarEsquema.
• Arrancamos el Tomcat, si ya está arrancado lo paramos y lo
rearrancamos, y ya está todo listo.
OpenXava 3.1.4 11
Capítulo 2: Mi primer proyecto OX
import org.openxava.tests.*;
/**
* @author Javier Paniza
*/
// Leeemos
setValue("codigo", "7");
execute("CRUD.search");
assertValue("codigo", "7");
assertValue("nombre", "Almacen JUNIT");
OpenXava 3.1.4 12
Capítulo 2: Mi primer proyecto OX
// Modificamos
setValue("nombre", "Almacen JUNIT MODIFICADO");
execute("CRUD.save");
assertNoErrors();
assertValue("codigo", "");
assertValue("nombre", "");
// Comprobamos modificado
setValue("codigo", "7");
execute("CRUD.search");
assertValue("codigo", "7");
assertValue("nombre", "Almacen JUNIT MODIFICADO");
// Borramos
execute("CRUD.delete");
assertMessage("Almacen borrado satisfactoriamente"); // 6
}
OpenXava 3.1.4 13
Capítulo 2: Mi primer proyecto OX
Esto para probar contra el portal Liferay. También es posible probar contra el
portal JetSpeed2, mira en OpenXavaTest/properties/xava-junit.properties para
ver cómo.
Las etiquetas
Ya nos funciona, pero hay un pequeño detalle que se ha quedado suelto.
Posiblemente queramos definir las etiquetas que se mostrarán al usuario. La
forma de hacerlo es escribiendo un archivo con todas las etiquetas, y así
podemos traducir nuestro producto a otro idioma con facilidad.
Para definir las etiqueta solo tenemos que editar el archivo
EtiquetasGestion_es.properties en la carpeta i18n. Editar ese archivo y añadir:
Almacen=Almacén
No es necesario poner todas las propiedades, porque los casos más comunes
(codigo, nombre, descripcion y un largo etc) ya los tiene OpenXava incluidos
en inglés, español, chino, alemán, polaco, indonesio, francés, italiano y
catalán.
Si queremos una versión en otro idioma (inglés, por ejemplo) solo tenemos
que copiar y pegar con el sufijo apropiado. Por ejemplo, podemos tener un
EtiquetasGestion_en.properties con el siguiente contenido:
Almacen=Warehouse
OpenXava 3.1.4 14
Capítulo 2: Mi primer proyecto OX
Ahora, nuestra aplicación mostrará “Hay 23663 objetos en la lista” en vez del
mensaje por defecto de OpenXava “'Hay 23663 registros en la lista”.
Para saber más sobre como definir las etiquetas de nuestros elementos
OpenXava podemos echar un vistazo a los archivos de OpenXavaTest/i18n.
OpenXava 3.1.4 15
Capítulo 2: Mi primer proyecto OX
° ° ° ° °
OpenXava 3.1.4 16
° ° ° ° °
Capítulo 3: Modelo
Componente de negocio
La unidad básica para crear aplicaciones OpenXava es el componente de
negocio. Un componente de negocio se define usando una clase Java llamada
Entity. Esta clase es una entidad EJB3 convencional, o con otras palabras, un
POJO con anotaciones que sigue el estándar Java Persistence API (JPA).
JPA es el estándar de Java para la persistencia, es decir, para objetos que
guardan su estado en una base de datos. Si sabes desarrollar usando POJOs
con JPA, ya sabes como desarrollar aplicaciones OpenXava.
Usando una simple clase Java podemos definir un Componente de Negocio
con:
• Modelo: Estrutura de datos, validaciones, calculos, etc.
• Vista: Cómo se puede mostrar el modelo al usuario.
• Datos tabulares: Cómo se muestra los datos de este componentes
en modo lista (en formato tabular).
• Mapeo objeto/relacional: Cómo grabar y leer el estado de los
objetos desde la base de datos.
Este capítulo explica cómo definir la parte del modelo, es decir, todo sobre la
estructura, las validaciones, los cálculos, etc.
OpenXava 3.1.4 17
Capítulo 3: Modelo
Entidad
Para define la parte del modelo hemos de definir a clase Java con
anotaciones. Además de sus propias anotaciones, OpenXava sporta
anotaciones de JPA, Hibernate Validator e Hibernate Annotations. Esta clase
Java es una entidad, es decir, una clase persistente que representa concepto
de negocio.
En este capítulo JPA se usa para indicar que es una anotación estándar de
Java Persistent API, HV para indicar que es una anotación de Hibernate
Validator, HA para indicar que es una anotación de Hibernate Annotations y
OX para indicar que es una anotación de OpenXava.
Ésta es la sintáxis para una entidad:
@Entity // 1
@EntityValidator // 2
@RemoveValidator // 3
public class NombreEntidad { // 4
// Propiedades // 5
// Referencias // 6
// Colecciones // 7
// Métodos // 8
// Buscadores // 9
// Métodos de retrollamada // 10
}
1. @Entity (JPA, uno, obligado): Indica que esta clase es una entidad JPA,
con otras palabras, sus instancias serán objetos persistentes.
2. @EntityValidator (OX, varios, opcional): Ejecuta una validación a
nivel de modelo. Este validador puede recibir el valor de varias
propiedades del modelo. Para validar una sola propiedad es preferible
poner el validador a nivel de propiedad.
3. @RemoveValidator (OX, varios, opcional): Se ejecuta antes de borrar,
y tiene la posibilidad de vetar el borrado del objeto.
4. Declaración de la clase: Como en un clase de Java convencional.
Podemos usar extends e implements.
5. Propiedades: Propiedades de Java convencionales. Representan el
estado principal del objeto.
6. Referencias: Referencias a otras entidades.
7. Colecciones: Colecciones de referencias a otras entidades.
8. Métodos: Métodos Java con lógica de negocio.
9. Buscadores: Los buscadores son métodos estáticos que hacen
búsquedas usando las prestaciones de consulta de JPA.
OpenXava 3.1.4 18
Capítulo 3: Modelo
Propiedades
Una propiedad representa parte del estado de un objeto que se puede
consultar y en algunos casos cambiar. El objeto no tiene la obligación de
guardar físicamente la información de la propiedad, solo de devolverla cuando
se le pregunte.
La sintaxis para definir una propiedad es:
@Stereotype // 1
@Column(length=) @Max @Length(max=) @Digits(integerDigits=) // 2
@Digits(fractionalDigits=) // 3
@Required @Min @Range(min=) @Length(min=) // 4
@Id // 5
@Hidden // 6
@SearchKey // 7
@Version // 8
@Formula // 9 Nuevo in v3.1.4
@DefaultValueCalculator // 10
@PropertyValidator // 11
private tipo nombrePropiedad; // 12
public tipo getNombrePropiedad() { ... } // 12
public void setNombrePropiedad(tipo nuevoValor) { ... } // 12
OpenXava 3.1.4 19
Capítulo 3: Modelo
OpenXava 3.1.4 20
Capítulo 3: Modelo
Estereotipo
Un estereotipo (@Stereotype) es la forma de determinar un comportamiento
especifico dentro de un tipo. Por ejemplo, un nombre, un comentario, una
descripción, etc. todos corresponden al tipo Java java.lang.String pero si
queremos que los validadores, logitud por defecto, editores visuales, etc. sean
diferente en cada caso y necesitamos afinar más; lo podemos hacer
asignando un esterotipo a cada uno de estos casos. Es decir, podemos tener
los estereotipos NOMBRE, TEXTO_GRANDE o DESCRIPCION y asignarlos a
nuestras propiedades.
El OpenXava viene configurado con los siguientes estereotipos:
• DINERO, MONEY
• FOTO, PHOTO, IMAGEN, IMAGE
• TEXTO_GRANDE, MEMO, TEXT_AREA
• ETIQUETA, LABEL
• ETIQUETA_NEGRITA, BOLD_LABEL
• HORA, TIME
• FECHAHORA, DATETIME
• GALERIA_IMAGENES, IMAGES_GALLERY (instrucciones)
• RELLENADO_CON_CEROS, ZEROS_FILLED
• TEXTO_HTML, HTML_TEXT (texto con formato editable)
• ETIQUETA_IMAGEN, IMAGE_LABEL (imagen que depende del contenido
de la propiedad)
• EMAIL
• TELEFONO, TELEPHONE
• WEBURL
OpenXava 3.1.4 21
Capítulo 3: Modelo
• IP
• ISBN
• TARJETA_CREDITO, CREDIT_CARD
• LISTA_EMAIL, EMAIL_LIST
Vamos a ver como definiríamos un estereotipo propio. Crearemos uno
llamado NOMBRE_PERSONA para representar nombres de persona.
Editamos (o creamos) el archivo editors.xml o editores.xml en nuestra carpeta
xava. Y añadimos
<editor url="editorNombrePersona.jsp">
<para-estereotipo estereotipo="NOMBRE_PERSONA"/>
</editor>
En este caso asume 40 longitud y tipo String, así como ejecutar el validador
NotBlankCharacterValidator
para comprobar que es requerido.
Estereotipo GALERIA_IMAGENES
Si queremos que una propiedad de nuestro componente almacene una galería
de imágenes. Solo necesitamos declarar que nuestra propiedad sea del
estereotipo GALERIA_IMAGENES. De esta manera:
OpenXava 3.1.4 22
Capítulo 3: Modelo
@Stereotype("GALERIA_IMAGENES")
private String fotos;
OpenXava 3.1.4 23
Capítulo 3: Modelo
Enums
OpenXava suporta enums de Java 5. Un enum permite definir una propiedad
que solo puede contener los valores indicados.
Es fácil de usar, veamos un ejemplo:
private Distancia distancia;
public enum Distancia { LOCAL, NACIONAL, INTERNACIONAL };
Propiedades calculadas
Las propiedades calculadas son de solo lectura (solo tienen getter) y no
persistentes (no se almacenan en ninguna columna de la tabla de base de
datos).
Una propiedad calculada se define de esta manera:
@Depends("precioUnitario") // 1
@Max(9999999999L) // 2
public BigDecimal getPrecioUnitarioEnPesetas() {
if (precioUnitario == null) return null;
return precioUnitario.multiply(new BigDecimal("166.386"))
.setScale(0, BigDecimal.ROUND_HALF_UP);
OpenXava 3.1.4 24
Capítulo 3: Modelo
OpenXava 3.1.4 25
Capítulo 3: Modelo
OpenXava 3.1.4 26
Capítulo 3: Modelo
En este caso cuando el usuario intenta crear una nueva factura (por ejemplo)
se encontrará con que el campo de año ya tiene valor, que él puede cambiar
si quiere. La lógica para generar este valor está en la clase
CurrentYearCalculator class, así:
package org.openxava.calculators;
import java.util.*;
/**
* @author Javier Paniza
*/
public class CurrentYearCalculator implements ICalculator {
OpenXava 3.1.4 27
Capítulo 3: Modelo
)
private String relacionConComercial;
OpenXava 3.1.4 28
Capítulo 3: Modelo
package org.openxava.test.calculadores;
import java.sql.*;
import org.openxava.calculators.*;
import org.openxava.util.*;
/**
* @author Javier Paniza
*/
public class CalculadorCantidadLineas implements IJDBCCalculator { // 1
OpenXava 3.1.4 29
Capítulo 3: Modelo
Para usar JDBC nuestro calculador tiene que implementar IJDBCCalculator (1)
y entonces recibirá un IConnectionProvider (2) que podemos usar dentro de
calculate().
OpenXava dispone de un conjunto de calculadores incluidos de uso genérico,
que se pueden encontrar en org.openxava.calculators.
OpenXava 3.1.4 30
Capítulo 3: Modelo
@PrePersist
private void calcularContador() {
contador = new Long(System.currentTimeMillis()).intValue();
}
Validador de propiedad
Un validador de propiedad (@PropertyValidator) ejecuta la lógica de validación
sobre el valor que se vaya a asignar a esa propiedad antes de grabar. Una
propiedad puede tener varios validadores:
@PropertyValidators ({
@PropertyValidator(value=ValidadorExcluirCadena.class, properties=
@PropertyValue(name="cadena", value="MOTO")
),
@PropertyValidator(value=ValidadorExcluirCadena.class, properties=
@PropertyValue(name="cadena", value="COCHE"),
onlyOnCreate=true
)
})
private String descripcion;
import org.openxava.util.*;
import org.openxava.validators.*;
/**
* @author Javier Paniza
*/
OpenXava 3.1.4 31
Capítulo 3: Modelo
OpenXava 3.1.4 32
Capítulo 3: Modelo
OpenXava 3.1.4 33
Capítulo 3: Modelo
Referencias
Una referencia hace que desde una entidad o agregado se pueda acceder
otra entidad o agregado. Una referencia se traduce a código Java como una
propiedad (con su getter y su setter) cuyo tipo es el del modelo al que se
referencia. Por ejemplo un Cliente puede tener una referencia a su Comercial,
y así podemos escribir código Java como éste:
Cliente cliente = ...
cliente.getComercial().getNombre();
OpenXava 3.1.4 34
Capítulo 3: Modelo
@ManyToOne(fetch=FetchType.LAZY)
private Comercial comercialAlternativo; // 2
public Comercial getComercialAlternativo() {
return comercialAlternativo;
}
public void setComercialAlternativo(Comercial comercialAlternativa) {
this.comercialAlternativo = comercialAlternativo;
}
OpenXava 3.1.4 35
Capítulo 3: Modelo
Como se puede ver se devuelve un entero, es decir, el valor para familia por
defecto es la familia cuyo código es el 2.
En el caso de clave compuesta sería así:
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumns({
@JoinColumn(name="ZONA", referencedColumnName="ZONA"),
@JoinColumn(name="ALMACEN", referencedColumnName="CODIGO")
})
@DefaultValueCalculator(CalculadorDefectoAlmacen.class)
private Almacen almacen;
import org.openxava.calculators.*;
/**
* @author Javier Paniza
*/
public class CalculadorDefectoAlmacen implements ICalculator {
OpenXava 3.1.4 36
Capítulo 3: Modelo
@Id @Hidden
private int contador;
...
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="SERVICIO")
private Servicio servicio;
@Hidden
private int contador;
Necesitamos escribir la clase clave aunque la clave sea solo una referencia
con una sola columna clave.
Es mejor usar esta característica sólo cuando estemos trabajando contra
bases de datos legadas, si tenemos control sobre el esquema es mejor usar
un id autogenerado.
OpenXava 3.1.4 37
Capítulo 3: Modelo
Colecciones
Podemos definir colecciones de referencias a entidades. Una colección es una
propiedad Java que devuelve java.util.Collection.
Aquí la sintaxis para definir una colección:
@Size // 1
@Condition // 2
@OrderBy // 3
@XOrderBy // 4
@OneToMany/@ManyToMany // 5
private Collection<tipo> nombreColeccion; // 5
public Collection<tipo> getNombreColeccion() { ... } // 5
public void setNombreColeccion(Collection<tipo> nuevoValor) { ... } // 5
OpenXava 3.1.4 38
Capítulo 3: Modelo
Para hacer algo con todos los albaranes asociados a una factura.
Vamos a ver otro ejemplo más complejo, también dentro de Factura:
@OneToMany (mappedBy="factura", cascade=CascadeType.REMOVE) // 1
@OrderBy("tipoServicio desc") // 2
@org.hibernate.validator.Size(min=1) // 3
private Collection<LineaFactural> facturas;
1. Usar REMOVE como tipo de cascadaas cascade type hace que cuando
el usuario borra una factura sus líneas también se borran.
2. Con @OrderBy obligamos a que las lineas se devuelvan ordenadas
por tipoServicio.
3. La restricción de @Size(min=1) hace que sea obligado que haya al
menos una línea para que la factura sea válida.
Tenemos libertad completa para definir como se obtienen los datos de una
colección, con @Condition podemos sobreescribir la condición por defecto:
@Condition(
"${almacen.codigoZona} = ${this.almacen.codigoZona} AND " +
"${almacen.codigo} = ${this.almacen.codigo} AND " +
"NOT (${codigo} = ${this.codigo})"
)
public Collection<Transportista> getCompañeros() {
return null;
}
OpenXava 3.1.4 39
Capítulo 3: Modelo
query.setParameter("zona", getAlmacen().getCodigoZona());
query.setParameter("codigoAlmacen", getAlmacen().getCodigo());
query.setParameter("codigo", getCodigo());
return query.getResultList();
}
En este caso un cliente tiene una colección de provincias, pero una misma
provincia puede estar presente en varios clientes.
Métodos
Los métodos se definen en una entidad OpenXava (mejor dicho, en una
entidad JPA) como una clase de Java convencional. Por ejemplo:
public void incrementarPrecio() {
setPrecioUnitario(getPrecioUnitario().multiply(new BigDecimal("1.02")).setScale(2));
}
Los métodos son la salsa de los objetos, sin ellos solo serían caparazones
tontos alrededor de los datos. Cuando sea posible es mejor poner la lógica
OpenXava 3.1.4 40
Capítulo 3: Modelo
de negocio en los métodos (capa del modelo) que en las acciones (capa del
controlador).
Buscadores
Un buscador es método estático especial que nos permite buscar un objeto o
una colección de objetos que sigue algún criterio.
Algunos ejemplos:
public static Cliente findByCodigo(int codigo) throws NoResultException {
Query query = XPersistence.getManager().createQuery(
"from Cliente as o where o.codigo = :codigo");
query.setParameter("codigo", codigo);
return (Cliente) query.getSingleResult();
}
Como se ve, usar método buscadores produce un código más legible que
usando la verbosa API de JPA. Pero esto es solo una recomendación de estilo,
podemos escoger no escribir métodos buscadores y usar directamente
consultas de JPA.
Validador de entidad
Este validador (@EntityValidator) permite poner una validación a nivel de
modelo. Cuando necesitamos hacer una validación sobre varias propiedades
del modelo, y esta validación no corresponde lógicamente a ninguna de ellas
se puede usar este tipo de validación.
Su sintaxis es:
OpenXava 3.1.4 41
Capítulo 3: Modelo
@EntityValidator(
value=clase, // 1
onlyOnCreate=(true|false), // 2
properties={ @PropertyValue ... } // 3
)
import java.math.*;
/**
* @author Javier Paniza
*/
OpenXava 3.1.4 42
Capítulo 3: Modelo
OpenXava 3.1.4 43
Capítulo 3: Modelo
}),
@EntityValidator(value=org.openxava.test.validadores.ValidadorPrecioProhibido.class,
properties= {
@PropertyValue(name="precioProhibido", value="555"),
@PropertyValue(name="precioUnitario")
},
onlyOnCreate=true
)
})
public class Product {
Validador al borrar
El @RemoveValidator también es un validador a nivel de modelo, la
diferencia es que se ejecuta antes de borrar el objeto, y tiene la posibilidad
de vetar el borrado.
Su sintaxis es:
@RemoveValidator(
value=clase, // 1
properties={ @PropertyValue ... } // 2
)
Y el validador:
package org.openxava.test.validadores;
import org.openxava.test.model.*;
import org.openxava.util.*;
import org.openxava.validators.*;
/**
* @author Javier Paniza
OpenXava 3.1.4 44
Capítulo 3: Modelo
*/
public class ValidadorBorrarTipoAlbaran implements IRemoveValidator { // 1
OpenXava 3.1.4 45
Capítulo 3: Modelo
En este caso cada vez que se graba por primera vez un TipoAlbaran se
añade un sufijo a su descripción.
Como se ve es exactamente igual que cualquier otro método solo que este
se ejecuta automáticamente antes de crear.
Con @PreUpdate podemos indicar que se ejecute cierta lógica justo después
de modificar un objeto y justo antes de actualizar su contenido en la base de
dato, esto es justo antes de hacer el UPDATE.
Como sigue:
@PreUpdate
private void antesDeModificar() {
setDescripcion(getDescripcion() + " MODIFICADO");
}
OpenXava 3.1.4 46
Capítulo 3: Modelo
@Embeddable // 1
public class NombreIncrustada { // 2
// Propiedades // 3
// Referencias // 4
// Metodos // 5
}
Referencias incrustadas
Este ejemplo es una Direccion incrustada (anotada con @Embedded) que es
referenciada desde la entidad principal.
En la entidad principal podemos escribir:
@Embedded
private Direccion direccion;
import javax.persistence.*;
import org.openxava.annotations.*;
/**
*
* @author Javier Paniza
*/
@Embeddable
public class Direccion implements IConPoblacion {
@Required @Column(length=30)
private String calle;
@Required @Column(length=5)
private int codigoPostal;
OpenXava 3.1.4 47
Capítulo 3: Modelo
@Required @Column(length=20)
private String poblacion;
OpenXava 3.1.4 48
Capítulo 3: Modelo
Colecciones incrustadas
Las colecciones incrustadas no se soportan en JPA 1.0. Pero podemos
simularlas usando colecciones a entidades con tipo de cascada REMOVE o
ALL. OpenXava trata estas colecciones de una manera especial, como si
fueran colecciones incrustadas.
Ahora un ejemplo de una colección incrustada. En la entidad principal (por
ejemplo de Factura) podemos poner:
@OneToMany (mappedBy="factura", cascade=CascadeType.REMOVE)
private Collection<LineaFactura> lineas;
import java.math.*;
import javax.persistence.*;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.GenericGenerator;
import org.openxava.annotations.*;
import org.openxava.calculators.*;
import org.openxava.test.validators.*;
/**
*
* @author Javier Paniza
*/
@Entity
OpenXava 3.1.4 49
Capítulo 3: Modelo
@EntityValidator(value=ValidadorLineaFactura.class,
properties= {
@PropertyValue(name="factura"),
@PropertyValue(name="oid"),
@PropertyValue(name="producto"),
@PropertyValue(name="precioUnitario")
}
)
public class LineaFactura {
@ManyToOne // 'Lazy fetching' produce un falla al borrar una linea desde la factura
private Factura factura;
@Column(length=4) @Required
private int cantidad;
@Stereotype("DINERO") @Required
private BigDecimal precioUnitario;
@ManyToOne(fetch=FetchType.LAZY, optional=false)
private Producto producto;
@DefaultValueCalculator(CurrentDateCalculator.class)
private java.util.Date fechaEntrega;
@ManyToOne(fetch=FetchType.LAZY)
private Comercial vendidoPor;
@Stereotype("MEMO")
private String observaciones;
@PostRemove
private void postRemove() {
factura.setComentario(factura.getComentario() + "DETALLE BORRADO");
}
OpenXava 3.1.4 50
Capítulo 3: Modelo
OpenXava 3.1.4 51
Capítulo 3: Modelo
return factura;
}
Herencia
OpenXava soporta la herencia de herencia de JPA y Java.
Por ejemplo podemos definer una superclase mapeada (@MappedSuperclass)
de esta manera:
package org.openxava.test.model;
import javax.persistence.*;
import org.hibernate.annotations.*;
import org.openxava.annotations.*;
/**
* Clase base para definir entidades con un oid UUID. <p>
*
* @author Javier Paniza
*/
@MappedSuperclass
public class Identificable {
OpenXava 3.1.4 52
Capítulo 3: Modelo
import javax.persistence.*;
import org.openxava.annotations.*;
/**
* Clase base para entidades con una propiedad 'nombre'. <p>
*
* @author Javier Paniza
*/
@MappedSuperclass
public class ConNombre extends Identifiable {
@Column(length=50) @Required
private String nombre;
import javax.persistence.*;
/**
*
* @author Javier Paniza
*/
@Entity
@DiscriminatorColumn(name="TYPE")
@DiscriminatorValue("HUM")
@Table(name="PERSONA")
@AttributeOverrides(
@AttributeOverride(name="name", column=@Column(name="PNOMBRE"))
)
public class Humano extends ConNombre {
OpenXava 3.1.4 53
Capítulo 3: Modelo
@Enumerated(EnumType.STRING)
private Sexo sexo;
public enum Sexo { MASCULINO, FEMENINO };
import javax.persistence.*;
/**
*
* @author Javier Paniza
*/
@Entity
@DiscriminatorValue("PRO")
public class Programador extends Humano {
@Column(length=20)
private String lenguajePrincipal;
OpenXava 3.1.4 54
Capítulo 3: Modelo
Clave múltiple
La forma preferida para definir la clave de una entidad es una clave única
autogenerada (anotada con @Id y @GeneratedValue), pero a veces, por
ejemplo cuando vamos contra bases de datos legadas, necesitamos tener una
entidad mapeada a una tabla que usa varias columnas como clave. Este caso
se pude resolver con JPA (y por tanto con OpenXava) de dos formas, usando
@IdClass o usando @EmbeddedId
Clase id
En este caso usamos @IdClass en nuestra entidad para indicar una clase
clave, y marcamos las propiedades clave como @Id en nuestra entidad:
package org.openxava.test.model;
import javax.persistence.*;
import org.openxava.annotations.*;
import org.openxava.jpa.*;
/**
*
* @author Javier Paniza
*/
@Entity
@IdClass(AlmacenKey.class)
public class Almacen {
@Id
// Column también se especifica en AlmacenKey por un bug en Hibernate, ver
// http://opensource.atlassian.com/projects/hibernate/browse/ANN-361
@Column(length=3, name="ZONA")
private int codigoZona;
@Id @Column(length=3)
private int codigo;
@Column(length=40) @Required
private String nombre;
OpenXava 3.1.4 55
Capítulo 3: Modelo
También necesitamos declarar una clase id, una clase serializable normal y
corriente con todas las propiedades clave de la entidad:
package org.openxava.test.model;
import java.io.*;
import javax.persistence.*;
/**
*
* @author Javier Paniza
*/
@Column(name="ZONE")
private int codigoZona;
private int codigo;
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
return obj.toString().equals(this.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
OpenXava 3.1.4 56
Capítulo 3: Modelo
@Override
public String toString() {
return "AlmacenKey::" + codigoZona + ":" + codigo;
}
Id inscrustado
En este case tenemos una referencia a un objeto incrustado (@Embeddable)
marcada como @EmbeddedId:
package org.openxava.test.model;
import javax.persistence.*;
import org.openxava.annotations.*;
/**
*
* @author Javier Paniza
*/
@Entity
public class Almacen {
@EmbeddedId
private AlmacenKey clave;
@Column(length=40) @Required
private String nombre;
OpenXava 3.1.4 57
Capítulo 3: Modelo
Y nuestra clave es una clase incrustable que contiene las propiedades clave:
package org.openxava.test.model;
import javax.persistence.*;
/**
*
* @author Javier Paniza
*/
@Embeddable
public class AlmacenKey implements java.io.Serializable {
@Column(length=3, name="ZONA")
private int codigoZona;
@Column(length=3)
private int codigo;
OpenXava 3.1.4 58
Capítulo 3: Modelo
OpenXava 3.1.4 59
Capítulo 3: Modelo
° ° ° ° °
OpenXava 3.1.4 60
° ° ° ° °
Capítulo 4: Vista
OpenXava genera a partir del modelo una interfaz gráfica de usuario por
defecto. Para muchos casos sencillos esto es suficiente, pero muchas veces
es necesario modelar con más precisión la forma de la interfaz de usuario o
vista. En este capítulo vamos a ver cómo.
Disposición
La anotación @View se puede usar en una entidad o una clase incrustable
para definir la disposición de sus miembros en la interfaz de usuario.
La sintaxis para definir una vista (@View) es:
@View(
name="nombre", // 1
members="miembros", // 2
extendsView="view" // 3 Nuevo en v3.1.2
)
public class MiEntidad {
OpenXava 3.1.4 61
Capítulo 4: Vista
@Id @Required
@Column(length=3, name="ZONA")
private int codigoZona;
@Id @Required
@Column(length=3, name="OFICINA")
private int codigoOficina;
@Id @Required
@Column(length=3, name="CODIGO")
private int codigo;
@Required @Column(length=40)
private String nombre;
OpenXava 3.1.4 62
Capítulo 4: Vista
Grupos
Con los grupos podemos agrupar un conjunto de propiedades relacionadas, y
esto tiene un efecto visual. Para definir un grupo solo necesitamos poner el
nombre del grupo y después sus miembros entre corchetes. Justo de esta
forma:
@View(members=
"id [ codigoZona, codigoOficina, codigo ];" +
"nombre"
)
OpenXava 3.1.4 63
Capítulo 4: Vista
" tipo;" +
" nombre;" +
"]" +
"contacto [" +
" telefono;" +
" correoElectronico;" +
" sitioWeb;" +
"]"
)
Si queremos que aparezca uno debajo del otro debemos poner un punto y
coma después del grupo, como sigue:
@View(members=
"general [" +
" codigo;" +
" tipo;" +
" nombre;" +
"];" +
"contacto [" +
" telefono;" +
" correoElectronico;" +
" sitioWeb;" +
"]"
)
OpenXava 3.1.4 64
Capítulo 4: Vista
OpenXava 3.1.4 65
Capítulo 4: Vista
Obtendremos lo siguiente:
OpenXava 3.1.4 66
Capítulo 4: Vista
Esto es feo. Sería mejor tener la información alineada por columnas. Podemos
definir el grupo de esta forma:
@View(name="Amounts", members=
"año, numero;" +
"importes [#" +
"descuentoCliente, descuentoTipoCliente, descuentoAño;" +
"sumaImportes, porcentajeIVA, iva;" +
"]"
)
Secciones
Además de en grupo los miembros se pueden organizar en secciones. Para
definir una sección solo necesitamos poner el nombre de la sección y
después sus miembros entre llaves. Veamos un ejemplo en la entidad
Factura:
@View(members=
"año, numero, fecha, pagada;" +
"comentario;" +
"cliente { cliente }" +
"lineas { lineas }" +
"importes { sumaImportes; porcentajeIVA; iva }" +
"albaranes { albaranes }"
)
OpenXava 3.1.4 67
Capítulo 4: Vista
OpenXava 3.1.4 68
Capítulo 4: Vista
Al igual que en los grupos, las secciones permiten usar # para conseguir
alineado por columnas, así:
@View(name="ImportesAlineadosEnSeccion", members=
"año, numero;" +
"cliente { cliente }" +
"lineas { lineas }" +
"importes {#" +
"descuentoCliente, descuentoTipoCliente, descuentoAño;" +
"sumaImportes, porcentajeIVA, iva;" +
"}"
)
OpenXava 3.1.4 69
Capítulo 4: Vista
Si ahora queremos crear una nueva vista que extienda de esta simplemente
hemos de escribir:
@View(name="Simple", extendsView="MuySimple", members="lenguajePrincipal")
y obtendremos lo siguiente:
OpenXava 3.1.4 70
Capítulo 4: Vista
Si queremos extender la vista por defecto (la vista por defecto es la vista sin
nombre) hemos de usar la palabra DEFAULT como nombre en extendsView.
Como en este ejemplo:
@Views({
@View(members="nombre, sexo; lenguajePrincipal, marcoTrabajoFavorito; experiencias"),
@View(name="Completa", extendsView="DEFAULT",
members = "marcosTrabajo"
)
})
OpenXava 3.1.4 71
Capítulo 4: Vista
podría escoger usar un árbol u otro control gráfico para representar las
secciones, por ejemplo.
De esta forma nombre es de solo lectura en todas las vistas. Ahora bien,
puede que queramos que nombre sea de solo lectura solo en las vistas B y
C, entonces podemos definir el miembro como sigue:
@ReadOnly(forViews="B, C")
private String nombre;
OpenXava 3.1.4 72
Capítulo 4: Vista
@ReferenceView("Simple")
private Comercial comercial;
Personalización de propiedad
Podemos refinar la forma de visualización y comportamiento de una
propiedad en la vista usando las siguientes anotaciones:
@ReadOnly // 1
@LabelFormat // 2
@DisplaySize // 3
@OnChange // 4
@Action // 5
@Editor // 6
private tipo nombrePropiedad;
Todas estas anotaciones siguen las normas para anotaciones de vista y todas
ellas son opcionales. OpenXava siempre asume valores por defecto correcto si
se omiten.
1. @ReadOnly (OX): Si marcas una propiedad con esta anotaciones no
será nunca editable por el usuario en esta vista. Una alternativa a
esto es hacer la propiedad editable/no editable programáticamente
usando org.openxava.view.View.
OpenXava 3.1.4 73
Capítulo 4: Vista
Formato de etiqueta
Un ejemplo sencillo para cambiar el formato de la etiqueta (@LabelFormat):
@LabelFormat(LabelFormatType.SMALL)
private int codigoPostal;
OpenXava 3.1.4 74
Capítulo 4: Vista
import org.openxava.actions.*;
import org.openxava.test.model.*;
/**
* @author Javier Paniza
*/
public class AlCambiarNombreCliente extends OnChangePropertyBaseAction { // 1
Acciones de la propiedad
También podemos especificar acciones (@Action) que el usuario puede pulsar
directamente:
@Action("Albaran.generarNumero")
private int numero;
OpenXava 3.1.4 75
Capítulo 4: Vista
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
public class GenerarNumeroAlbaran extends ViewBaseAction {
import org.openxava.actions.*;
import org.openxava.view.*;
/**
* @author Javier Paniza
*/
OpenXava 3.1.4 76
Capítulo 4: Vista
Esta acción implementa IPropertyAction (1), esto requiere que la clase tenga
los métodos setProperty() (3) y setView() (4), estos valores serán inyectados
en la acción antes de llamar al método execute(), donde pueden ser usados
(2). En este caso no necesitas inyectar el objeto xava_view al definir la acción
en controladores.xml. La vista inyectada por setView() (4) es la vista más
interna que contiene la propiedad, por ejemplo, si la propiedad está dentro
de un agregado es la vista de ese agregado, no la vista principal del módulo.
De esta manera podemos escribir acciones más reutilizables.
OpenXava 3.1.4 77
Capítulo 4: Vista
Este editor está incluido con OpenXava, pero nosotros podemos crear nuestro
propios editores con nuestro propios JSPs y declararlos en el archivo xava/
editores.xml de nuestro proyecto.
Esta característica es para cambiar el editor solo en una vista. Si lo que se
pretende es cambiar el editor para un estereotipo, tipo o una propiedad de
un modelo a nivel de aplicación entonces lo mejor es configurarlo usando el
archivo xava/editors.xml.
Personalización de referencia
Podemos refinar la forma de visualización y comportamiento de una
referencia en la vista usando las siguientes anotaciones:
@ReferenceView // 1
@ReadOnly // 2
@NoFrame // 3
@NoCreate // 4
@NoModify // 5
@NoSearch // 6
@AsEmbedded // 7
@SearchAction // 8
@DescriptionsList // 9
@LabelFormat // 10
@Action // 11
@OnChange // 12
@OnChangeSearch // 13
@Editor // 14 Nuevo en v3.1.3
@ManyToOne
private tipo nombreReferencia;
Todas estas anotaciones siguen las normas para anotaciones de vista y todas
ellas son opcionales. OpenXava siempre asume valores por defecto correcto si
se omiten.
1. @ReferenceView (OX): Si omitimos esta anotación usa la vista por
defecto del objeto referenciado para visualizarlo, con este anotación
podemos indicar que use otra vista.
2. @ReadOnly (OX): Si usamos esta anotación esta referencia no será
nunca editable por el usuario en esta vista. Una alternativa a esto es
hacer la propiedad editable/no editable programáticamente usando
org.openxava.view.View.
3. @NoFrame (OX): El dibujador de la interfaz gráfica usa un marco
para envolver todos los datos de la referencia. Con esta anotación se
puede indicar que no se use ese marco.
OpenXava 3.1.4 78
Capítulo 4: Vista
OpenXava 3.1.4 79
Capítulo 4: Vista
14. @Editor (OX): (Nuevo en v3.1.3) Nombre del editor a usar para
visualizar la referencia en esta vista. El editor tiene que estar
declarado en OpenXava/xava/default-editors.xml o xava/editores.xml de
nuestro proyecto.
Si no usamos ninguna de estas anotaciones OpenXava dibuja la referencia
usando su vista por defecto. Por ejemplo si tenemos una referencia así:
@ManyToOne
private Familia familia;
Escoger vista
La modificación más sencilla sería especificar que vista del objeto
referenciado queremos usar. Esto se hace mediante @ReferenceView:
@ManyToOne(fetch=FetchType.LAZY)
@ReferenceView("Simple")
private Factura factura;
Para esto en el componente Factura tenemos que tener una vista llamada
simple:
@Entity
@Views({
...
@View(name="Simple", members="año, numero, fecha, descuentoAño;"),
...
})
public class Factura {
OpenXava 3.1.4 80
Capítulo 4: Vista
Personalizar el enmarcado
Si combinamos @NoFrame con un grupo podemos agrupar visualmente una
propiedad que no forma parte de la referencia, por ejemplo:
@View( members=
...
"comercial [" +
" comercial; " +
" relacionConComercial;" +
"]" +
...
)
public class Cliente {
...
@ManyToOne(fetch=FetchType.LAZY)
@NoFrame
private Comercial comercial;
...
}
Así obtendríamos:
OpenXava 3.1.4 81
Capítulo 4: Vista
Ahora al pulsar la linternita ejecuta nuestra acción, la cual tenemos que tener
definida en controladores.xml:
<controlador nombre="MiReferencia">
<accion nombre="buscar" oculta="true"
clase="org.openxava.test.acciones.MiAccionBuscar"
imagen="images/search.gif">
<usa-objeto nombre="xava_view"/>
<usa-objeto nombre="xava_referenceSubview"/>
<usa-objeto nombre="xava_tab"/>
<usa-objeto nombre="xava_currentReferenceLabel"/>
</accion>
...
</controlador>
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
OpenXava 3.1.4 82
Capítulo 4: Vista
OpenXava 3.1.4 83
Capítulo 4: Vista
<!--
Dado que su nombre es AlmacenModification (nombre modelo + Modification) es usado
por defecto para modificar desde referencias, en lugar de Modification.
La acción 'search' se ejecuta automáticamente.
-->
<controlador nombre="AlmacenModification">
<hereda-de controlador="Modification"/>
<accion nombre="search" oculta="true"
clase="org.openxava.test.actions.ModificarAlmacenDesdeReferencia">
<usa-objeto nombre="xava_view"/>
</accion>
</controlador>
OpenXava 3.1.4 84
Capítulo 4: Vista
OpenXava 3.1.4 85
Capítulo 4: Vista
@ManyToOne(fetch=FetchType.LAZY) @NoCreate // 2
@DescriptionsList(
descriptionProperties="descripcion", // 3
depends="familia", // 4
condition="${familia.codigo} = ?" // 5
order="${descripcion} desc" // 6
)
private Subfamilia subfamilia;
OpenXava 3.1.4 86
Capítulo 4: Vista
@DescriptionsList(descriptionProperties="nivel.descripcion, nombre")
private Comercial comercialAlternativo;
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
public class AlCambiarTransportistaEnAlbaran
extends OnChangePropertyBaseAction { // 1
OpenXava 3.1.4 87
Capítulo 4: Vista
import org.openxava.actions.*;
/**
*
* @author Javier Paniza
*/
public class BuscarAlCambiarSubfamilia
extends OnChangeSearchAction { // 1
OpenXava 3.1.4 88
Capítulo 4: Vista
Personalización de colección
Podemos refinar la forma de visualización y comportamiento de una colección
en la vista usando las siguientes anotaciones:
@CollectionView // 1
@ReadOnly // 2
@EditOnly // 3
@NoCreate // 4
@NoModify // 5
@AsEmbedded // 6
@ListProperties // 7
@RowStyle // 8
OpenXava 3.1.4 89
Capítulo 4: Vista
@EditAction // 9
@ViewAction // 10
@NewAction // 11
@SaveAction // 12
@HideDetailAction // 13
@RemoveAction // 14
@RemoveSelectedAction // 15
@ListAction // 16
@DetailAction // 17
@OnSelectElementAction // 18 Nuevo en v3.1.2
@Editor // 19 Nuevo en v3.1.3
@OneToMany/@ManyToMany
private Collection nombreColeccion;
Todas estas anotaciones siguen las normas para anotaciones de vista y todas
ellas son opcionales. OpenXava siempre asume valores por defecto correcto si
se omiten.
1. @CollectionView (OX): La vista del objeto referenciado que se ha de
usar para representar el detalle. Por defecto usa la vista por defecto.
2. @ReadOnly (OX): Si la ponemos solo podremos visualizar los
elementos de la colección, no podremos ni añadir, ni borrar, ni
modificar los elementos.
3. @EditOnly (OX): Si la ponemos podemos modificar los elementos
existentes, pero no podemos añadir nuevos ni eliminar.
4. @NoCreate (OX): Si la ponemos el usuario final no tendrá el vínculo
que le permite crear objetos del tipo del objeto referenciado. No
aplica a colecciones incrustadas.
5. @NoModify (OX): Si la ponemos el usuario final no tendrá el vínculo
que le permite modificar objetos del tipo del objeto referenciado. No
aplica a colecciones incrustadas.
6. @AsEmbedded (OX): Por defecto las colecciones incrustadas permiten
al usuario crear y añadir elementos, mientras que las colecciones
convencionales permiten solo escoger entidades existentes para
añadir (o quitar) de la colección. Si ponemos @AsEmbedded entonces
la colección de entidades se comportan como una colección de
agregados, permitiendo al usuario añadir objetos y editarlos
directamente. No tiene efecto en el caso de una colección incrustada.
7. @ListProperties (OX): Indica las propiedades que han de salir en la
lista al visualizar la colección. Podemos calificar las propiedades. Por
defecto saca todas las propiedades persistentes del objeto
referenciado (sin incluir referencias ni calculadas). Solo una
@ListProperties por vista está permitida.
OpenXava 3.1.4 90
Capítulo 4: Vista
OpenXava 3.1.4 91
Capítulo 4: Vista
OpenXava 3.1.4 92
Capítulo 4: Vista
OpenXava 3.1.4 93
Capítulo 4: Vista
</accion>
...
</controlador>
import java.text.*;
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
public class EditarLineaFactura extends EditElementInCollectionAction { // 1
En este caso queremos solamente refinar y por eso nuestra acción desciende
de (1) EditElementInCollectionAction. Nos limitamos a poner un valor por
defecto en la propiedad remarks. Es de notar que para acceder a la vista
que visualiza el detalle podemos usar el método getCollectionElementView()
(2).
También es posible eliminar la acción para editar de la interfaz de usuario,
de esta manera:
@EditAction("")
@OneToMany (mappedBy="factura", cascade=CascadeType.REMOVE)
private Collection<LineaFactura> lineas;
Sólo necesitamos poner una cadena vacía como valor para la acción. Aunque
en la mayoría de los casos es suficiente declarar la colección como de solo
lectura (@ReadOnly).
La técnica para refinar una acción 'ver' (la acción para cada fila cuando la
colección es de solo lectura) es la misma pero usando @ViewAction en vez
de @EditAction.
OpenXava 3.1.4 94
Capítulo 4: Vista
import java.util.*;
import org.openxava.actions.*;
import org.openxava.test.modelo.*;
/**
* @author Javier Paniza
*/
public class TraducirNombreTransportista extends CollectionBaseAction { // 1
OpenXava 3.1.4 95
Capítulo 4: Vista
De esta forma todas las colecciones tendrán las acciones del controlador Print
(para exportar a Excel y generar informes PDF) y nuestra propia acción
ExportarComoXML. Esto tiene el mismo efecto que el elemento @ListAction
(ver la sección acciones de lista propias) pero aplica a todas las colecciones a
la vez.
Esta característica no aplica a las colecciones calculadas.
OpenXava 3.1.4 96
Capítulo 4: Vista
import java.util.*;
import javax.ejb.*;
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
public class VerProductoDesdeLineaFactura
extends CollectionElementViewBaseAction // 1
implements INavigationAction {
OpenXava 3.1.4 97
Capítulo 4: Vista
getCollectionElementView().getValue("producto.codigo"); // 2
Map clave = new HashMap();
clave.put("codigo", codigo);
getView().setModelName("Producto"); // 3
getView().setValues(clave);
getView().findObject();
getView().setKeyEditable(false);
getView().setEditable(false);
}
catch (ObjectNotFoundException ex) {
getView().clear();
addError("object_not_found");
}
catch (Exception ex) {
ex.printStackTrace();
addError("system_error");
}
}
OpenXava 3.1.4 98
Capítulo 4: Vista
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
OpenXava 3.1.4 99
Capítulo 4: Vista
@RemoveSelectedAction("")
@OneToMany (mappedBy="albaran", cascade=CascadeType.REMOVE)
private Collection<LineaAlbaran> lineas;
@OneToMany(mappedBy="comercial")
@Editor("NombresClientes")
private Collection<Cliente> clientes;
Acciones de la vista
Además de poder asociar acciones a una propiedad, referencia o colección,
podemos tambien definir acciones arbitrarias en cualquier parte de nuestra
vista. Para poder hacer esto se ponemos el nombre calificado de la acción
seguido de paréntesis (), de esta manera:
@View( members=
"codigo;" +
"tipo;" +
"nombre, Cliente.cambiarEtiquetaDeNombre();" +
...
hacer es crear una clase, no marcarla como entidad y a partir de ésta definir
una vista.
Una clase transitoria no está asociada a ninguna tabla de la base de datos,
normalmente se usa solo para visualizar interfaces de usuario no relacionadas
con ninguna tabla de la base de datos.
Un ejemplo puede ser:
package org.openxava.test.model;
import javax.persistence.*;
import org.openxava.annotations.*;
/**
* Ejemplo de una clase OpenXava transitoria (no persistente) del modelo. <p>
*
* Esto se puede usar, por ejemplo, para visualizar un diálogo,
* o cualquier otro interfaz gráfica.<p>
*
* Notemos como no está marcada con @Entity <br>
*
* @author Javier Paniza
*/
@Views({
@View(name="Familia1", members="subfamilia"),
@View(name="Familia2", members="subfamilia"),
@View(name="ConFormularioSubfamilia", members="subfamilia"),
@View(name="Rango", members="subfamilia; subfamiliaHasta")
})
public class FiltroPorSubfamilia {
@ManyToOne(fetch=FetchType.LAZY) @Required
@NoCreate(forViews="Familia1, Familia2")
@NoModify(forViews="Familia2, ConFormularioSubfamilia")
@NoSearch(forViews="ConFormularioSubfamilia")
@DescriptionsLists({
@DescriptionsList(forViews="Familia1",
condition="${familia.codigo} = 1", order="${codigo} desc"
),
@DescriptionsList(forViews="Familia2",
condition="${familia.codigo} = 2"
)
})
private Subfamilia subfamilia;
@ManyToOne(fetch=FetchType.LAZY)
private Subfamilia subfamiliaHasta;
Para definir una clase del modelo como transitorio solo necesitamos definir
una clase convencional sin @Entity. No hemos de poner el mapeo ni declarar
propiedades como clave.
De esta forma podemos hacer un diálogo que puede servir, por ejemplo, para
lanzar un listado de familias o productos filtrado por subfamilias.
Las clases transitorias se usan con módulos con solo detalle.
El paquete para las clases transitorias del modelo tiene que ser el mismo
que el de las clases persistentes.
Podemos así tener un generador de cualquier tipo de interfaz gráficas sencillo
y bastante flexible, aunque no queramos que la información visualizada sea
persistente.
° ° ° ° °
import java.util.*;
import org.openxava.filters.*;
/**
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} = ?"
)
import java.util.*;
import org.openxava.filters.*;
/**
* @author Javier Paniza
*/
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"));
Select íntegro
Tenemos la opción de poner el select completo para obtener los datos del
tab:
@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"
)
Este orden es solo el inicial, el usuario puede escoger otro con solo pulsar la
cabecera de una columna.
° ° ° ° °
Mapeo de entidad
La anotación @Table especifica la tabla principal para la entidad. Se pueden
especificar tablas adicionales usando @SecondaryTable o @SecondaryTables.
Si no se especifica @Table para una entidad se aplicaran los valores por
defecto.
Ejemplo:
@Entity
@Table(name="CLI", schema="XAVATEST")
public class Cliente {
Mapeo propiedad
La anotación @Column se usa para especificar como mapear una propiedad
persistente. Si no se especifica @Column se aplican los valores por defecto.
Un ejemplo sencillo:
@Column(name="DESC", length=512)
private String descripcion;
Otros ejemplos:
@Column(name="DESC",
columnDefinition="CLOB NOT NULL",
table="EMP_DETAIL")
@Lob
private String descripcion;
Mapeo de referencia
La anotación @JoinColumn se usa para especificar el mapeo de una columna
para una referencia.
Ejemplo:
@ManyToOne
@JoinColumn(name="CLI_ID")
private Cliente cliente;
Mapeo de colección
Cuando usamos @OneToMany para una colección el mapeo depende de la
referencia usada en la otra parte de la asociación, es decir, normalmente no
es necesario hacer nada. Pero si estamos usando @ManyToMany, quizás nos
sea útil declarar la tabla de unión (@JoinTable), como sigue:
@ManyToMany
@JoinTable(name="CLIENTE_PROVINCIA",
joinColumns=@JoinColumn(name="CLIENTE"),
inverseJoinColumns=@JoinColumn(name="PROVINCIA")
)
private Collection<Provincia> provincias;
Conversión de tipo
La conversión de tipos entre Java y la base de datos relacional es un trabajo
de la implementación de JPA (OpenXava usa Hibernate por defecto).
Normalmente, la conversión de tipos por defecto es buena para la mayoría
de los casos, pero si trabajamos con bases de datos legadas quizás
necesitemos algunos de los trucos que aquí se muestran.
Conversión de propiedad
Cuando el tipo de una propiedad Java y el tipo de su columna
correspondiente en la base de datos no coincide necesitamos escribir un
Hibernate Type para poder hacer nuestra conversión de tipo personalizada.
Por ejemplo, si tenemos una propiedad de tipo String [], y queremos
almacenar su valor concatenándolo en una sola columna de base de datos de
tipo VARCHAR. Entonces tenemos que declarar la conversión para nuestra
propiedad de esta manera:
@Type(type="org.openxava.test.types.RegionesType")
private String [] regiones;
import java.io.*;
import java.sql.*;
import org.apache.commons.logging.*;
import org.hibernate.*;
import org.hibernate.usertype.*;
import org.openxava.util.*;
/**
*
* @author Javier Paniza
*/
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
Al poner 'LNI' como valor para letters, hace corresponder la 'L' con 1, la 'N'
con 2 y la 'I' con 3. Vemos como el que se puedan configurar propiedades
del conversor de tipos nos permite hacer conversores reutilizables.
@Column(name="DIAENTREGA")
})
private java.util.Date fechaEntrega;
import java.io.*;
import java.sql.*;
import org.hibernate.*;
import org.hibernate.engine.*;
import org.hibernate.type.*;
import org.hibernate.usertype.*;
import org.openxava.util.*;
/**
* In java a <tt>java.util.Date</tt> and in database 3 columns of
* integer type. <p>
*
* @author Javier Paniza
*/
case 0:
Dates.setYear(date, intValue);
case 1:
Dates.setMonth(date, intValue);
case 2:
Dates.setYear(date, intValue);
}
throw new HibernateException(XavaResources.getString("date3_type_only_3_properties"));
}
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner)
throws HibernateException, SQLException // 4
{
Number year = (Number) Hibernate.INTEGER.nullSafeGet( rs, names[0] );
Number month = (Number) Hibernate.INTEGER.nullSafeGet( rs, names[1] );
Number day = (Number) Hibernate.INTEGER.nullSafeGet( rs, names[2] );
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session)
throws HibernateException, SQLException // 5
{
java.util.Date d = (java.util.Date) value;
Hibernate.INTEGER.nullSafeSet(st, Dates.getYear(d), index);
Hibernate.INTEGER.nullSafeSet(st, Dates.getMonth(d), index + 1);
Hibernate.INTEGER.nullSafeSet(st, Dates.getDay(d), index + 2);
}
public Object replace(Object original, Object target, SessionImplementor session, Object owner)
throws HibernateException
{
return deepCopy(original);
}
Conversión de referencia
La conversión de referencias no se soporta directamente por Hibernate. Pero
en alguna circunstancias extremas puede ser que necesitemos hacer
conversión de referencias. En esta sección se explica como hacerlo.
Por ejemplo, puede que tengamos una referencia a permiso de conducir
usando dos columnas, PERMISOCONDUCIR_NIVEL y PERMISOCONDUCIR_TIPO, y
la columna PERMISOCONDUCIR_TIPO no admita nulos, pero es posible que el
objeto puede no tener permiso de conducir, en cuyo caso la columna
PERMISOCONDUCIR_TIPO almacena una cadena vacía. Esto no es algo normal
si nosotros diseñamos la base de datos usando claves foráneas, pero si la
base de datos fue diseñada por un programador RPG, por ejemplo, esto se
habrá hecho de esta forma, porque los programadores RPG no están
acostumbrados a lidiar con nulos.
Es decir, necesitamos una conversión para PERMISOCONDUCIR_TIPO, para
transformar el nulo en una cadena vacía. Esto se puede conseguir con un
código como este:
@PrePersist @PreUpdate
private void conversionPermisoConducir() { // 5
if (this.permisoConducir_tipo == null) this.permisoConducir_tipo = "";
}
° ° ° ° °
Capítulo 7: Controladores
Variable de entorno
Las variables de entorno contienen información de configuración. Estas
variables pueden ser accedidas desde las acciones y los filtros, y su valor
puede ser sobrescrito en cada módulo. Su sintaxis es:
<var-entorno
nombre="nombre" <!-- 1 -->
valor="valor" <!-- 2 -->
/>
Objetos de sesión
Los objetos Java declarados en controladores.xml tienen ámbito de sesión; es
decir, son objetos que son creado para un usuario y existen durante toda su
sesión. Su sintaxis es:
<objeto
nombre="nombreObjeto" <!-- 1 -->
clase="tipoObjeto" <!-- 2 -->
valor="valorInicial" <!-- 3 -->
ambito="modulo|global" <!-- 4 Muevo en v2.1 -->
/>
clase="org.openxava.test.acciones.PonerValorPropiedad">
<poner propiedad="propiedad" valor="observaciones" />
<poner propiedad="valor" valor="Demonios tus ojos" />
<usa-objeto nombre="xava_view"/>
</accion>
</controladores>
Vemos como las acciones con imagen se colocan arriba, y las acciones sin
imagen abajo.
Podemos observar el código ocultarObservaciones por ejemplo:
package org.openxava.test.acciones;
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
...permite desde esta acción manipular mediante view la vista, o dicho de otra
forma la interfaz de usuario que éste está viendo.
El <usa-objeto /> coge el objeto de sesión xava_view y lo asigna a la
propiedad view (quita el prefijo xava_, y en general quita el prefijo
miaplicacion_ antes de asignar el objeto) de nuestra acción justo antes de
llamar a execute().
Ahora dentro del método execute() podemos usar getView() a placer (3), en
este caso para ocultar una propiedad. Todas las posibilidades de View las
podemos ver consultando la documentación JavaDoc de
org.openxava.view.View.
Con...
Herencia de controladores
Podemos crear un controlador que herede todas sus acciones de uno o más
controladores. Un ejemplo de esto lo encontramos en el controlador genérico
más típico Typical, este controlador se encuentra en OpenXava/xava/
default-controllers.xml:
<controller name="Typical">
<extends controller="Print"/>
<extends controller="CRUD"/>
</controller>
Ponemos mode="list" para que solo aparezca en modo lista (1). Ya que esta
acción borra registros hacemos que el usuario tenga que confirmar antes de
ejecutarse (2). No es necesario incluir un <usa-objeto/> para xava_tab (nuevo
en v2.1.4).
Programar la acción sería así:
package org.openxava.actions;
import java.util.*;
import org.openxava.model.*;
import org.openxava.validators.*;
/**
* @author Javier Paniza
*/
Esta acción es una acción estándar de OpenXava, pero nos sirve para ver
que cosas podemos hacer dentro de nuestras acciones de modo lista.
Observamos (1) como desciende de TabBaseAction e implementa
IModelAction, al descender de TabBaseAction (new in v2.1.4) tiene un conjunto
de utilidades disponible y no estamos obligados a implementar todos los
métodos de IAction; y al implementar IModelAction nuestra acción tendrá un
metoto setModel() (7) con el que recibirá el nombre del modelo (del
componente OpenXava) antes de ejecutarse.
Puedes acceder al Tab usando el método getTab() (2); este método está
implementado en TabBaseAction y permite acceder al objeto de sesión
xava_tab. Mediante getTab() podemos manipular la lista de objetos
visualizados. Por ejemplo, con getTab().getSelected() (2) obtenemos los índices
de las filas seleccionadas, con getTab().getTableModel() un table model para
acceder a los datos, y con getTab().deselectAll() deseleccionar las filas.
Podemos echar un vistazo a la documentación JavaDoc de
org.openxava.tab.Tab para más detalles sobre sus posibilidades.
Algo muy interesante que se ve en este ejemplo es el uso de la clase
MapFacade (3). MapFacade permite acceder a la información del modelo
mediante mapas de Java (java.util.Map), esto es conveniente cuando
obtenemos datos de Tab o View en formato Map y queremos con ellos
actualizar el modelo (y por ende la base de datos) o viceversa. Todas las
clases genéricas de OpenXava interactúan con el modelo mediante
MapFacade y nosotros también lo podemos usar, pero como consejo general
de diseño decir que trabajar con mapas es práctico para proceso automáticos
pero cuando queremos hacer cosas específicas es mejor usar directamente
los objetos del modelo. Para más detalles podemos ver la documentación
JavaDoc de org.openxava.model.MapFacade.
Observamos como añadir mensajes que serán visualizados al usuario con
addError(). El método addError() recibe el id de una entrada en nuestros
archivos i18n y los argumentos que el mensaje pueda usar. Los mensajes
añadidos se visualizaran al usuario como errores. Si queremos añadir
mensajes de advertencia podemos usar addMessage() que tiene exactamente
el mismo funcionamiento que addError(). Los archivos i18n para errores y
mensajes han de llamarse MiProyecto-messages.properties o
MensajeMiProyecto.properties y el sufijo del idioma (_en, _ca, _es, _it, etc).
Podemos ver como ejemplos los archivos que hay en OpenXavaTest/xava/
i18n. Todas las excepciones no atrapadas producen un mensaje de error
genérico, excepto si la excepción es una ValidationException en cuyo caso
visualiza el mensaje de error de la excepción.
Y su código es:
package org.openxava.test.acciones;
import java.util.*;
import org.openxava.actions.*;
import org.openxava.util.*;
/**
* @author Javier Paniza
*/
Y en nuestra acción:
package org.openxava.test.acciones;
import org.openxava.actions.*;
import org.openxava.tab.*;
/**
* @author Javier Paniza
*/
Establecemos el año por defecto a 2002 (1), hacemos que el título de la lista
sea visible (2) y asignamos un valor como argumento para ese título (3). El
título de la lista está definido en los archivos i18n, normalmente se usa para
los informes, pero podemos visualizarlos también en modo lista.
En este modulo solo aparece la lista (sin la parte de detalle) para eso
decimos que el controlador de modo ha de ser Void (3) y así no aparece lo
de detalle y lista, y añadimos un controlador llamado ListOnly (2) para que
sea el modo lista el que aparezca (si ponemos controlador de modo Void y
Y el código de la acción:
package org.openxava.test.acciones;
import java.util.*;
import org.openxava.actions.*;
import org.openxava.controller.*;
import org.openxava.tab.*;
/**
* @author Javier Paniza
*/
public class ListarFacturasDeCliente extends BaseAction
implements IChangeModuleAction, // 1
IModuleContextAction { // 2
package org.openxava.test.acciones;
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
Para ir a una vista personalizada (a una página JSP en este caso) hacemos
que nuestra acción implemente INavigationActionICustomViewAction (con
hubiera bastado) y de esta forma podemos indicar con getNextControllers()
(2) los siguientes controladores a usar y con getCustomView() (3) la página
JSP que ha de visualizarse (3).
package org.openxava.test.acciones;
import java.util.*;
import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.data.*;
import org.openxava.actions.*;
import org.openxava.model.*;
import org.openxava.test.model.*;
import org.openxava.util.*;
import org.openxava.validators.*;
/**
* Informe de productos de la subfamilia seleccionada. <p>
*
* Usa JasperReports. <br>
*
* @author Javier Paniza
*/
public class InformeProductosDeFamiliaAction extends JasperReportBaseAction { // 1
}
return subfamilia;
}
...
}
<table>
<th align='left' class=<%=style.getLabel()%>>
<fmt:message key="introducir_nueva_imagen"/>
</th>
<td>
<input name = "nuevaImagen" class=<%=style.getEditor()%> type="file" size='60'/>
</td>
</table>
Para saber más como funcionan las acciones lo ideal es mirar la API JavaDoc
del paquete org.openxava.actions y ver los ejemplos disponibles en el
proyecto OpenXavaTest.
° ° ° ° °
Capítulo 8: Aplicación
Una aplicación es el software que el usuario final puede usar. Hasta ahora
hemos visto como definir las piezas que forman una aplicación (los
componentes y las acciones principalmente), ahora vamos a ver como
ensamblarlas para crear aplicaciones.
La definición de una aplicación OpenXava se hace en el archivo
aplicacion.xml que encontramos en el directorio xava de nuestro proyecto.
La sintaxis de este archivo es:
<aplicacion
nombre="nombre" <!-- 1 -->
etiqueta="etiqueta" <!-- 2 -->
>
<modulo-defecto ... /> ... <!-- 3 Nuevo en v2.2.2 -->
<modulo ... /> ... <!-- 4 -->
</aplicacion>
Un módulo típico
Definir un módulo sencillo puede ser como sigue:
<aplicacion nombre="Gestion">
<modulo nombre="Almacen" carpeta="almacen">
<modelo nombre="Almacen"/>
<controlador nombre="Typical"/>
<controlador nombre="Almacen"/>
</modulo>
...
</aplicacion>
En este caso tenemos un módulo que nos permite hacer altas, bajas
modificaciones, consultas, listados en PDF y exportación a Excel de los datos
de los almacenes (gracias a Typical) y acciones propias que aplican solo a
almacenes (gracias al controlador Almacen). En el caso en que el sistema
genere una estructura de módulos (como en JetSpeed2) este módulo estará
en la carpeta "almacen".
Para ejecutar este módulo podemos desde nuestro navegador escribir:
http://localhost:8080/Gestion/xava/
modulo.jsp?application=Gestion&module=Almacen
También se genera un portlet para poder desplegar el módulo como un
portlet JSR-168 en un portal Java.
<modulo-defecto>
<controlador nombre="MantenimientoGestion"/>
</modulo-defecto>
</aplicacion>
En este caso todos los módulos por defecto de la aplicación Gestion tendrán
el controlador MantenimientoGestion asignado a ellos.
<controlador nombre="Typical"/>
<controlador nombre="ListOnly"/> <!-- 2 -->
<controlador-modo nombre="Void"/> <!-- 3 -->
</modulo>
Módulo de documentación
Un módulo de documentación solo visualiza un documento HTML. Es fácil de
definir:
<modulo nombre="Descripcion">
<doc url="doc/descripcion" idiomas="es,en"/>
</modulo>
° ° ° ° °
Capítulo 9: Personalización
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"?>
<editores>
<editor .../> ...
</editores>
<%
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%>
<%
}
%>
<% if (!editable) { %>
<input type="hidden" name="<%=propertyKey%>" value="<%=fvalue%>">
<% } %>
<editor url="textEditor.jsp">
<formatedor clase="org.openxava.formatters.UpperCaseFormatter"/>
<para-tipo tipo="java.lang.String"/>
</editor>
import javax.servlet.http.*;
/**
* @author Javier Paniza
*/
usuario seleccionar más de una región para una propiedad. Ese estereotipo
se puede usar de esta manera:
@Stereotype("REGIONES")
private String [] regiones;
<%
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++) {
<%
if (!editable) {
for (int i=0; i<fvalues.length; i++) {
%>
<input type="hidden" name="<%=propertyKey%>" value="<%=fvalues[i]%>">
<%
}
}
%>
Con este código estamos diciendo que cualquier referencia a la entidad Color
se tiene que visualizar usando colorEditor.jsp (para usar un editor solo para
una referencia concreta en una entidad concreta véase la sección Escoger un
<%@page import="org.openxava.view.View"%>
<%@page import="org.openxava.model.MapFacade"%>
<%@page import="org.openxava.test.model.Blog"%>
<%@page import="org.openxava.test.model.BlogComment"%>
<%@page import="java.util.Iterator"%>
<%@page import="java.util.Map"%>
<%@page import="java.text.DateFormat"%>
<%@page import="org.openxava.util.Locales"%>
<%@page import="org.openxava.util.Is"%>
<%
String viewObject = request.getParameter("viewObject"); // Id para acceder al objeto view de la colección
View collectionView = (View) context.get(request, viewObject); // Obtenemos el objeto view de la colección mediant
View rootView = collectionView.getRoot(); // En este caso usamos la vista raiz, la vista de Blog
Map key = rootView.getKeyValues();
if (Is.empty(key)) {
%>
No hay comentarios
<%
} else { // Si la clave tiene valor dibujamos la colección de comentarios
}
%>
Este editor dibuja los comentarios del blog como un texto simple con una
cabecera con la fecha.
La lista de parámetro a usar en un editor de lista para colecciones es:
1. collectionName: El nombre de la colección tal y como lo tenemos en
nuestra entidad.
2. viewObject: El nombre del objeto de sesión de la subvista que
representa esta colección.
3. rowAction: El nombre calificado de acción (Controlador.accion como
está en controladores.xml) a ejecutar en cada elemento para
visualizarlo o editarlo.
Por supuesto, podemos crear nuestro editor para colección desde cero, sin
usar collectionEditor.jsp. En este caso has de escribir la interfaz de usuario
completa para la colección. Veamos un ejemplo en
nombresTransportistaEditor.jsp:
<%@page import="org.openxava.view.View"%>
<%@page import="org.openxava.model.MapFacade"%>
<%@page import="org.openxava.test.model.Carrier"%>
<%@page import="java.util.Iterator"%>
<%
String viewObject = request.getParameter("viewObject"); // viewObject es el id del objeto view del padre
View view = (View) context.get(request, viewObject); // view es el objeto view de Transportista, el padre de la col
Transportista transportista = (Transportista) MapFacade.findEntity("Transportista", view.getKeyValues());
%>
Los compañeros de <%=transportista.getNombre()%> son:<br>
<ul>
<%
for (Iterator it = transportista.getCompaneros().iterator(); it.hasNext(); ) {
Transportista companero = (Transportista) it.next();
%>
<li><%=companero.getNombre()%></li>
<%
}
%>
</ul>
Dado que hemos marcado el editor con <para-colecciones/> ahora todas las
colecciones en nuestra aplicación se visualizan usando nuestro
miColeccionEditor.jsp. Esta es una forma sencilla de personalizar el
comportamiento del generador de interfaz gráfica de OpenXava.
@Stereotype("SUBFAMILY")
private int subfamilyNumber;
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.
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ó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 -->
/>
Esta marca solo genera el texto del mensaje, sin ningun tipo de formateo
HTML.
Un ejemplo:
<xava:message key="cantidad_lista" intParam="<%=cantidadTotal%>"/>