Finalmente he tenido que hacer tres publicaciones para este apartado, así que en esta segunda veremos el desarrollo de CbAddresses, CbAddress, CbEnterprise y CbCustomer, continuando el proceso empezado en Tutorial Openxava (3.1): Actualizando la presentación con View .
Vamos a continuar con la actualización de la presentación de estas entidades, para ello vamos a ver como podemos definir varias vistas de edición para una entidad (@Views y @View), explicar los datos tabulares para la presentación de las tablas (@Tabs y @Tab), definir nuevos estereotipos y ver nuevas formas de presentar los elementos en el detalle presentándolos en diferentes grupos o secciones dentro de cada vista, como veremos a continuación.
OpenXava Tab
Datos tabulares son aquellos que se visualizan en formato de tabla, OpenXava tiene una presentación por defecto de estas listas, mediante la anotación @Tab dentro de la definición de la entidad podemos modificar esta presentación.Código Xules
Nos quedará para la tercera entrega la finalización de la presentación de la clase CbAddresses, ya que como veremos en la entidad CbCustomer, quedará algún paso por resolver como explicaré en ese apartado.
Vamos a empezar actualizando la presentación de las direcciones con las clases CbAddresses y CbAddress, y viendo como se reflejan estos cambios en los clientes CbCustomer.
 
Actualizamos la presentación CbAddress
Vamos a definir varias vistas para la presentación de CbAddress, para ello siguiendo las indicaciones de OpenXava creamos las siguientes vistas utilizando la anotación @Views como se indica a continuación:
@Entity @Views({ @View( members = // Vista por defecto al no tener nombre "address;" + "addresstype;" + "locality, postalnumber;" + "state, cbCountry;" + "carrier; " + "Phones [ mainphone; movilephone; phone2; phone3; ];" + "Notes [ notes1]; "), @View( name = "ViewConTab", // Solo se usa cuando se referencia específicamente members ="address;" + "addresstype;" + "locality, postalnumber;" + "state, cbCountry;" + "carrier; " + "Phones { mainphone; movilephone; phone2; phone3; }" + "Notes { notes1}; " ), @View( name = "ViewListas", // Solo se usa cuando se referencia específicamente members = "address;" + "postalnumber;" + "locality;" + "state, cbCountry;mainphone; movilephone;" // Shows only number and name in the same line ) }) @Table(name = "cb_address") public class CbAddress {
Vamos a explicar el uso de múltiples vistas usando usando la anotación @Views, ya explicamos en la publicación anterior los parámetros que podemos usar en una vista (@View), recordamos el significado de los que usamos aquí:
@View(
name=»nombre», // Nombre de la vista, no es obligatorio
members=»miembros», // Definimos que miembros se visualizan y como se presentan
)
La vista que vemos en el código que no tiene nombre es la que se usa por defecto, y en este caso, definimos dos vistas más con los nombres: «ViewConTab» y «ViewListas», antes de explicar como especificar como incluirlas, ya que lo necesitamos hacer en la clase que queremos modificar la presentación, vamos a analizar el resultado final y explicar los nuevos elementos que se utilizan en estas.
Grupos
Lo primero que encontramos diferente es el uso de grupos, con los grupos podemos agrupar un conjunto de propiedades relacionadas modificando la presentación, la definición de un grupo se hace indicando el nombre del grupo y después sus miembros entre corchetes, tal y como hacemos en la primera vista que es la que hemos definido como vista por defecto:
+ "Phones [ mainphone; movilephone; phone2; phone3; ];" + "Notes [ notes1];
Esta es la presentación resultante:
Como puedes ver en la figura no aparecen los valore indicados: Phones y Notes , esto es debido a que en el fichero de configuración de las etiquetas: EtiquetasCustomerdb_es.properties en la carpeta i18n, hemos definido sus traducciones, así de simple (míralo en el fichero al final de este apartado).
En este caso, también optamos por poner un elemento debajo de otro, si quisiésemos presentarlos en línea uno a continuación del otro simplemente tenemos que eliminar el punto y coma de separación (compruébalo tu mismo):
+ "Phones [ mainphone; movilephone; phone2; phone3; ]" + "Notes [ notes1];
Se pueden anidar grupos, esta interesante característica permite disponer los elementos de la interfaz gráfica de una forma simple y flexible.
Secciones
Además de en grupos, los miembros también se pueden organizar en secciones (información en OpenXava: Secciones). Para definir una sección solo necesitamos poner el nombre de la sección y después sus miembros entre llaves, como hacemos en la vista: «ViewConTab».
Este es el resultado final:
CbAddress es la tabla de direcciones que vamos a definir para los clientes u otras entidades que lo necesiten, a través de la relación que establecemos mediante CbAddresses para cada entidad, esta relación ya estaba establecida en la publicación de la creación de las tablas:
- Definición de la relación en CbAddress
@ManyToOne @JoinColumn( name = "idaddresses", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_address_idaddresses")) private CbAddresses cbAddressesLocal; // Esta es la relación entre CbAddresses y CbAddress que es bidireccional.
- Definición de la relación en CbAddresses
@OneToMany( mappedBy = "cbAddressesLocal", // El objeto de CbAddress que almacena la relación cascade = CascadeType.ALL) private Collection<CbAddress> cbAddress = new ArrayList<CbAddress>();
En la clase CbAddresses explicamos como definimos la vista a utilizar en las listas, y la presentación a utlizar en la edición de CbAddress.
Veamos ahora algún estereotipo nuevo y su funcionalidad, en este caso usamos TELEPHONE, nos aporta validación de número de teléfono aunque el campo establecido es un String. También, usamos MEMO para las observaciones a la dirección:
... @Column(name = "mainphone", length = 30, nullable = true) @Stereotype("TELEPHONE") private String mainphone; @Column(name = "movilephone", length = 30, nullable = true) @Stereotype("TELEPHONE") private String movilephone; @Stereotype("TELEPHONE") @Column(name = "phone2", length = 30, nullable = true) private String phone2; @Stereotype("TELEPHONE") @Column(name = "phone3", length = 30, nullable = true) private String phone3; ... @Stereotype("MEMO") @Column(name = "notes1", length = 500, nullable = true) private String notes1;
Por último, en la relación que ya habíamos establecido para mostrar los países, vamos a añadir la condición de que esos países estén activos, el código que utilizamos lo marcamos en negrita:
@ManyToOne @JoinColumn( name = "idcountry", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_address_idcountry")) @DescriptionsList( descriptionProperties = "country", condition = "${isactive} = true", order = "${country} " ) private CbCountry cbCountry;
Utilizamos la anotación @DescriptionList , estableciendo el filtro: condition = «${isactive} = true» k, y también indicando el campo que mostramos en la presentación con: descriptionProperties = «country» , finalmente se específica la ordenación de los datos presentados: order = «${country} «.
Seguimos definiendo los nombres de los campos para la presentación actualizando el fichero EtiquetasCustomerdb_es.properties en la carpeta i18n con las nuevas etiquetas para esta clase:
# Componentes de CbAddress CbAddresses=Direcciones CbAddress=Dirección address=Dirección postalnumber=Código postal carrier=Transporte addresstype=Tipo de dirección locality=Localidad state=Estado mainphone=Teléfono principal movilephone=Tlf. móvil phone2=Tlf. 2 phone3=Tlf. 3 notes1=Notas Phones=Teléfonos Notes=Notas
Actualizamos la presentación CbAddresses
Los elementos de esta clase son de creación automática y los vamos a utilizar pero ocultándoselos al usuario porque es una información que no necesita. Esto lo veremos en la definición de CbCustomer donde utilizaremos la anotación: @AsEmbedded, el uso de esta anotación hará que la clase sea transparente para el usuario, que verá directamente las direcciones del usuario, incrustándose dentro de la definición del cliente.
Propiedades de la vista
En CbAddress ya indicamos como se relacionaban las tablas, vamos a ver ahora como definimos los elementos que queremos visualizar a nivel listado, y la vista que que queremos utilizar para la edición, veamos la definición:
@OneToMany( mappedBy = "cbAddressesLocal", // El objeto de CbAddress que almacena la relación cascade = CascadeType.ALL) @ListProperties("addresstype, address, locality, postalnumber, state, cbCountry.country, carrier, mainphone") @CollectionView("ViewConTab") private Collection&lt;CbAddress&gt; cbAddress = new ArrayList&lt;CbAddress&gt;();
Ahora expliquémoslo, en primer lugar usamos la anotación @ListProperties para indicar los campos y el orden que se muestran en el listado al visualizar la colección, solo está permitida una @ListProperties por vista, por defecto saca todos los campos persistentes del objeto referenciado.
En OpenXava: Propiedades de la lista tienes más información, puedes ver en la definición que además de los campos básicos del objeto referenciado, también podemos indicar que campo queremos mostrar de una colección referenciada en el objecto, como hacemos en este caso con el nombre del país: cbCountry.country. Este es el resultado:
Si quitamos @ListProperties lo veríamos así:
Como puedes ver en este caso los campos salen ordenados tal y como los hemos definido.
Vista para edición
La otra anotación utilizada es @CollectionView, que nos sirve para indicar la vista de CbAddress que queremos que se muestre al utilizarla desde CbAddresses como se explica en OpenXava: Vista para edición se aplica Aplica a colecciones @OneToMany/@ManyToMany.
Si no usamos ninguna anotación se usa la vista por defecto para editar la entidad en cada línea; aunque como en este caso es habitual indicar la vista para representar el detalle. Veamos el resultado:
En la imagen superior para mostrar el resultado final os adelanto la presentación que veremos desde el formulario de CbCustomer que explicaremos más adelante.
Actualizamos la presentación CbEnterprise
Vamos a actualizar la presentación ahora de las empresas de la aplicación, cada empresa tendrá sus clientes, pero eso lo veremos en CbCustomer, ahora vamos a actualizar la vista de edición de CbEnterprise, y ver cosas nuevas:
/** * Empresas de la aplicación, cada empresa tendrá sus clientes. * @author xulescode */ @Entity @View(members = // Esta vista no tiene nombre, será usada por defecto. "identerprise, name; " + "enterprisealias; " + "contact;" + "balance, enterprisepayer; " + "state; " + "ei;" + "cbLanguage, cbCurrency; " + "cbCountry; " + "description; ") @Tab( rowStyles={ @RowStyle(style="row-highlight", property="state", value="ACTIVE"), @RowStyle(style="row-highlight-cancel", property="state", value="CANCELED")}, properties="identerprise, name, enterprisealias, state, cbLanguage.idlanguage, cbCurrency.description, cbCountry.country" ) @Table(name = "cb_enterprise") public class CbEnterprise {
Como ya explicamos en otras ocasiones utilizamos la anotación @View para definir la vista de edición de CbEnterprise, en este caso solo implementamos una, que será la que se usará como siempre por defecto.
Nos quedaba ver de la presentación como modificar los campos que se presentan en los listados, es decir, en la pestaña Lista, esto lo vamos a indicar con @Tab, es lo que en OpenXava denominan datos tabulares, y son aquellos que se visualizan en formato de tabla. La vista utilizada por defecto está bien, pero como en cualquier proyecto se presentarán múltiples ocasiones en que habrá que especificar los valores presentados en una vista por múltiples razones y necesidades.
Veamos entonces la sintaxis de la anotación @Tab:
@Tab( name="nombre", // 1 filter=clase del filtro, // 2 rowStyles=array de @RowStyle, // 3 properties="propiedades", // 4 baseCondition="condición base", // 5 defaultOrder="orden por defecto" // 6 ) public class MyEntity {
- name (opcional):
- Nombre que le damos a cada Tab para una entidad, con la anotación @Tabs (tal y como hicimos con la anotación @Views) se pueden definir diferentes vistas y ese nombre se utilizará para indicar en cada módulo la que se utiliza (normalmente se específica en aplicación.xml al definir un módulo).
- filter (opcional):
- Definición de filtros propios mediante programación.
- rowStyles (varios, opcional):
- Una forma sencilla de especificar una estilo de visualización diferente para ciertas filas. Normalmente para resaltar filas que cumplen cierta condición, con un array de @RowStyle podemos usar varios estilo por tab.
- properties (opcional):
- La lista de propiedades a visualizar inicialmente. Pueden ser calificadas, con el sufijo + se pueden añadir sumas a la vista por columna.
- baseCondition (opcional):
- Condición que se aplicará siempre a los datos visualizados añadiéndose a las que pueda poner el usuario.
- defaultOrder (opcional):
- Permite especificar el orden de los datos presentados inicialmente.
Analicemos nuestro @Tab:
@Tab( rowStyles={ @RowStyle(style="row-highlight", property="state", value="ACTIVE"), @RowStyle(style="row-highlight-cancel", property="state", value="CANCELED")}, properties="identerprise, name, enterprisealias, state, cbLanguage.idlanguage, cbCurrency.description, cbCountry.country" )
- En nuestro caso inicial no le damos nombre (propiedad name) porque solo definimos una por defecto.
- Definimos los estilos para las columnas con rowStyles como indicamos anteriormente, en nuestro caso vamos a diferenciar en color de la fila si el estado es ACTIVE (color verde #4e9a4e) y si es CANCELED (color #9e9b9b), de los tres estados, utilizados solo queda STOPPED sin asignar color, puedes comprobar el efecto en la figura de abajo. Veamos el funcionamiento:
- Para el estado igual a ACTIVE:
- Usamos @RowStyle para indicar el estilo a aplicar : style=»row-highlight» a la propiedad state: property=»state» cuando tiene el valor indicado en este caso ACTIVE: value=»ACTIVE»
- OpenXava ya tiene unos valores asignados a row-highlight para asignar escribir su valor vamos a definir su valor en la hoja de estilos /web/xava/style/custom.css, en este caso simplemente:
- Para el estado igual a ACTIVE:
.row-highlight A.row-highlight { font-weight: bold; color: #4e9a4e; }
- Para el estado igual a CANCELED:
- Usamos @RowStyle para indicar el estilo a aplicar : style=»row-highlight-cancel« a la propiedad state: property=»state» cuando tiene el valor indicado en este caso ACTIVE: value=»CANCELED»
- Creamos un nuevo valor row-highlight-cancel definimos su valor en la hoja de estilos /web/xava/style/custom.css, en este caso:
.row-highlight-cancel A.row-highlight-cancel { font-weight: bold; color: #9e9b9b; }
Siguiendo con la explicación de estereotipos nuevos, vamos a usar HTML_TEXT quen nos proporciona un editor de texto enriquecido en HTML, y también MONEY para especificar que un campo es de tipo moneda y mostrarlo como tal, vemos en la imagen como queda:
Un ejemplo de definición de varios tabs utilizando la anotación @Tabs sería la siguiente:
@Tabs({ @Tab( rowStyles={ @RowStyle(style="row-highlight", property="state", value="ACTIVE"), @RowStyle(style="row-highlight-cancel", property="state", value="CANCELED")}, properties="identerprise, name, enterprisealias, state, cbLanguage.idlanguage, cbCurrency.description, cbCountry.country" ), @Tab( name="EmpresasActivas", rowStyles=@RowStyle(style="row-highlight", property="idlanguage", value="es_ES"), properties="identerprise, name, cbLanguage.idlanguage, cbCurrency.description, cbCountry.country", baseCondition="${state} =ACTIVE" ), @Tab( name="Lista", properties="identerprise, name, cbCountry.country", defaultOrder="identerprise desc, name desc" ) })
Como puedes ver aquí se utilizan algunos valores nuevos de la sintaxis de @Tab, y que creo que se entiende bien su significado, haz tus pruebas y saca tus propias conclusiones.
Lo definimos como el resto de estereotipos:
... @Stereotype("HTML_TEXT") @Column(name = "description", length = 250, nullable = true) private String description; ... @Stereotype("MONEY") @Column(name = "balance", length = 10, precision = 3, nullable = true) private java.math.BigDecimal balance; ...
Como ya explicamos antes, definimos las condiciones en View: @DescriptionList, para especificar los filtros para cada tabla para reducir los resultados que presentamos a los que solo están activos para CbCurrency, CbLanguage y CbCountry, el resto de las relaciones ya se explicaron en la publicación de Learning Project Tutorial OpenXava (2): Creación del resto de las clases para las tablas, estas son las definiciones:
@ManyToOne @JoinColumn( name = "idcurrency", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_enterprise_idcurrency")) @DescriptionsList( descriptionProperties = "currency", condition = "${isactive} = 'Y'", order = "${currency} " ) private CbCurrency cbCurrency; @ManyToOne @JoinColumn( name = "idlanguage", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_enterprise_idlanguage")) @DescriptionsList( descriptionProperties = "name", condition = "${isactive} = 'Y'", order = "${name} " ) private CbLanguage cbLanguage; @ManyToOne @JoinColumn( name = "idcountry", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_enterprise_idcountry")) @DescriptionsList( descriptionProperties = "country", condition = "${isactive} = true", order = "${country} " ) private CbCountry cbCountry;
Seguimos definiendo los nombres de los campos para la presentación actualizando el fichero EtiquetasCustomerdb_es.properties en la carpeta i18n con las nuevas etiquetas para esta clase:
# Componentes de CbEnterprises CbEnterprises=Empresas CbEnterprise=Empresa identerprise=Número de empresa name=Nombre state=Estado description=Descripción enterprisealias=Alias contact=Contacto balance=Saldo ei=Cif enterprisepayer=Pagador
Actualizamos la presentación CbCustomer
En la entidad CbCustomer vamos a definir varias vista de edición para ver en detalle más posibilidades de presentación, también vamos a definir un @Tab para seleccionar los datos y como los presentamos, este es el código:
/** * Tabla general de cliente. Tabla donde se almacenarán los clientes de las diferentes empresas, se entiende cliente como aquel que compra a una empresa. * @author xulescode */ @Entity @Views({ @View( members = // Vista por defecto sin nombre "customer;" + "customername;" + "customeralias;" + "contact;" + "sale, customerpayer; " + "customerstate;" + "identitynumber;" + "cbLanguage; cbCountry; cbCurrency;" + "cbAddresses;"), @View( name="CustomerConTab", members = "customer;" + "customername;" + "Datos { " + "customeralias;" + "contact;" + "sale, customerstate; " + "identitynumber, customerpayer;}" + "Más datos {cbLanguage; cbCountry; cbCurrency;}" + "Direcciones { " + "cbAddresses;}"), @View( name = "CustomerSimple", // Vista que solo se utiliza cuando se indica "CustomerSimple" members = "customer;" + "customername;" + "customeralias;" + "contact;" + "sale, customerstate;" + "identitynumber, customerpayer;" + "cbLanguage, cbCountry, cbCurrency;" + "cbAddresses;" // Shows only number and name in the same line ) }) @Tab( rowStyles=@RowStyle(style="row-highlight", property="customerstate", value="ACTIVE"), properties="customer, customername, contact, customerstate, cbEnterprise.name, cbLanguage.idlanguage, cbCurrency.description, cbCountry.country, cbPaymentmethod.paymentmethod" ) @Table(name = "cb_customer") public class CbCustomer {
Veamos primero el significado de la separación de las secciones y grupos en las imágenes, como ya se explicó anteriormente, no entraré en más detalles:
- Vista por defecto
- Vista CustomerConTab: mostrando el tab Datos
CbCustomer detalle con la vista Vista CustomerConTab[/caption]
- Vista CustomerConTab: mostrando el tab Datos:
- Vista CustomerConTab: mostrando el tab Direcciones:
No me voy a extender en la definición de los estereotipos ya que ya fueron ampliamente explicados, y con los campos definidos aquí, no vamos a mostrar nada nuevo.
Definimos las condiciones en View: @DescriptionList, para especificar los filtros para cada tabla para reducir los resultados que presentamos a los que solo están activos para CbCurrency, CbLanguage y CbCountry, el resto de las relaciones anteriomente también establecemos la relación con cbEnterprise sin entrar en más detalle y cbPaymentmethod especifícando el valor que se mostrará en el combo, la definición de cbAddresses lo explicaremos con más detalle , estas son las definiciones:
@ManyToOne @JoinColumn( name = "identerprise", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_customer_identerprise")) @DescriptionsList private CbEnterprise cbEnterprise; @ManyToOne @JoinColumn( name = "idcurrency", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_customer_idcurrency")) @DescriptionsList( descriptionProperties = "currency", condition = "${isactive} = 'Y'", order = "${currency} " ) private CbCurrency cbCurrency; @ManyToOne @JoinColumn( name = "idlanguage", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_customer_idlanguage")) @DescriptionsList( descriptionProperties = "name", condition = "${isactive} = 'Y'", order = "${name} " // 6 ) private CbLanguage cbLanguage; @ManyToOne @JoinColumn( name = "idcountry", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_customer_idcountry")) @DescriptionsList( descriptionProperties = "country", condition = "${isactive} = true", order = "${country} " ) private CbCountry cbCountry; @ManyToOne @JoinColumn( name = "idpaymentmethod", nullable = true, foreignKey = @ForeignKey(name = "cb_customers_idpaymentmethod")) @DescriptionsList(descriptionProperties = "paymentmethod") private CbPaymentmethod cbPaymentmethod;
Veamos ahora como modificamos la visualización de las direcciones del cliente, ya se explicó como se establecían las relaciones con CbAddress a través de CbAddresses:, ahora para mejorar la presentación vamos a hacer la entidad CbAddresses transparente al usuario con la anotación @AsEmbedded quedaría así:
@OneToOne @JoinColumn( name = "idaddresses", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_customer_idaddresses")) @AsEmbedded private CbAddresses cbAddresses;
Explicación del uso de @AsEmbedded en SourceForge: http://sourceforge.net/p/openxava/discussion/437015/thread/bd45c358/, donde está alojado el proyecto OpenXava (Sourceforge OpenXava) y donde hay varios foros de ayuda, en los cuales el propio Javier Paniza líder del proyecto contesta personalmente, aprovecho este caso para comentar el interesante foro, ya que fue aquí donde yo encontré la respuesta y explicación a lo que necesitaba. Veamos ahora su definición:
- @AsEmbedded
- Según la definición de OpenXava: «Hace que comportamiento en la vista de una referencia (o colección) a entidad sea como en el caso de un objeto incrustado (o colección de entidades con CascadeType.REMOVE)».
- Esto quiere decir que el uso de esta anotación hará que la clase sea transparente para el usuario que verá directamente las direcciones del usuario, incrustándose dentro de la definición del cliente.
- A continuación la explicación extraída de la wiki de OpenXava:
- «Por defecto en el caso de una referencia a una clase incrustable el usuario puede crear y editar sus datos, mientras que en el caso de una referencia a una entidad el usuario escoge una entidad existente».
- «Si ponemos @AsEmbedded entonces la interfaz de usuario para referencias a entidad se comporta como en el caso de los incrustados, permitiendo al usuario crear un nuevo objeto y editar sus datos directamente. No tiene efecto en el caso de una referencia a un objeto incrustado».
- «¡Ojo! Si borramos una entidad sus entidades referenciadas no se borran, incluso si estamos usando @AsEmbedded«.
Puedes encontrar la información detalle de otras etiquetas de personalización de referencia en OpenXava en este enlace.
Seguimos definiendo los nombres de los campos para la presentación actualizando el fichero EtiquetasCustomerdb_es.properties en la carpeta i18n con las nuevas etiquetas para esta clase:
# Componentes de CbCustomer idcustomer=Id. Cliente customer= Cliente customername=Nombre customeralias=Alias sale=Saldo customerstate=Estado identitynumber=CIF customerpayer=Pagador cbAddresses=Direcciones
Qué nos falta para finalizar la presentación de las direcciones
Lo planteo como apartado diferenciado ya que fue con lo que yo tuve más problemas para finalizar la correcta presentación, con lo que hemos hecho hasta ahora tenemos un par de problemas por resolver:
- Cuando creamos un nuevo cliente (CbCustomer), si antes de crearlo intentamos añadirle la dirección, con la definición que hemos hecho hasta ahora, no va a funcionar, este el error:
oct 03, 2015 4:46:38 PM org.openxava.controller.ModuleManager manageRegularException
GRAVE: No existe la colección cbAddresses en CbAddresses
org.openxava.util.ElementNotFoundException: No existe la colección cbAddresses en CbAddresses
- Esto sucede porque cuando intentamos crear la dirección todavía no tenemos creado en el cliente que no existe todavía un CbAddresses, como es lógico.
- De todas formas si guardamos el cliente sin añadirle la dirección, no se genera automáticamente un CbAddressses asociado.
Para solucionar esto, tengo que hacer dos cosas:
- Crear siempre una entidad CbAddresses al crear cada nuevo cliente.
- Para esto vamos a utilizar un método que se llama antes de persistir el objeto: @PreCreate este es propio de la retrollamadas de OpenXava. De esta manera vamos a poder utilizar el manejador de persistencia o crear búsquedas que no son permitidas dentro de las retrollamadas de JPA.
- Esto lo veremos a continuación.
- Ocultar a nivel de creación las direcciones hasta que exista un CbAddresses asociado el cliente, y así, tener un funcionamiento correcto.
- Para conseguir esto vamos a tener que definir dos vistas diferentes una para Crear y otra para Actualizar, esto no lo podemos hacer directamente en las anotaciones y para ello tendremos que modificar el controlador usado por defecto y definir las clases donde les indiquemos las vistas a utilizar en cada caso.
- Llegar a esto solución fue lo que más me costó, ya que creí que no estaba utilizando correctamente @AsEmbedded.
- Como la creación de controladores propios, se escapa a esta publicación, expondré mi solución en un siguiente post.
Veamos la creación de retrollamadas de OpenXava, esto nos permitirá obtener un funcionamiento más o menos correcto, dejando la creación de las direcciones del cliente para después de su creación ya que no se podrán añadir hasta que el cliente haya sido creado, pero si después, ya que en la creación le vamos a asociar al cliente un idaddresses de la entidad CbAddresses automáticamente, veamos como.
Con @PreCreate se pueden marcar métodos que serán ejecutados antes de persistir algún objeto, en nuestro caso antes de crear CbCustomer nos interesa crear un CbAddresses que se le asignará así automáticamente al cliente CbCustomer. Ahora definimos el método dentro de la clase CbCustomer que vamos a utilizar:
/** * Cuando creamos el cliente creamos la entidad de CbAddresses que lleva asociada, para que después se pueda añadir la dirección al actualizar el cliente. */ @PreCreate public void onPreCreate() { if (getCbAddresses() == null) { CbAddresses cbAddressesAux = new CbAddresses(); cbAddressesAux = XPersistence.getManager().merge(cbAddressesAux); setCbAddresses(cbAddressesAux); } }
En el siguiente capítulo, realizaremos el punto 2 para ocultar las direcciones hasta que se haya creado el cliente, con lo cual evitaremos el error que aparece aquí.
Espero que te haya sido útil.Código Xules
Continuación: Tutorial Openxava (3.3) : Vistas diferentes para crear y actualizar la entidad (Fin de la primera parte)
Recordemos que teníamos que resolver la presentación de las direcciones (CbAddresses) al crear una nuevo cliente (CbCustomer) ya que con el desarrollo inicial no era posible, esta fue la solución planteada:
1. Crear siempre una entidad CbAddresses al crear cada nuevo cliente.
2. Ocultar a nivel de creación las direcciones hasta que exista un CbAddresses asociado el cliente, y así, tener un funcionamiento correcto.
Finalmente he tenido que hacer tres publicaciones para este apartado, así que en esta segunda veremos el desarrollo de CbAddresses, CbAddress, CbEnterprise y CbCustomer, continuando el proceso empezado en Tutorial Openxava (3.1): Actualizando la presentación con View .
Vamos a continuar con la actualización de la presentación de estas entidades, para ello vamos a ver como podemos definir varias vistas de edición para una entidad (@Views y @View), explicar los datos tabulares para la presentación de las tablas (@Tabs y @Tab), definir nuevos estereotipos y ver nuevas formas de presentar los elementos en el detalle presentándolos en diferentes grupos o secciones dentro de cada vista, como veremos a continuación.
OpenXava Tab
Datos tabulares son aquellos que se visualizan en formato de tabla, OpenXava tiene una presentación por defecto de estas listas, mediante la anotación @Tab dentro de la definición de la entidad podemos modificar esta presentación.Código Xules
Nos quedará para la tercera entrega la finalización de la presentación de la clase CbAddresses, ya que como veremos en la entidad CbCustomer, quedará algún paso por resolver como explicaré en ese apartado.
Vamos a empezar actualizando la presentación de las direcciones con las clases CbAddresses y CbAddress, y viendo como se reflejan estos cambios en los clientes CbCustomer.
 
Actualizamos la presentación CbAddress
Vamos a definir varias vistas para la presentación de CbAddress, para ello siguiendo las indicaciones de OpenXava creamos las siguientes vistas utilizando la anotación @Views como se indica a continuación:
@Entity @Views({ @View( members = // Vista por defecto al no tener nombre "address;" + "addresstype;" + "locality, postalnumber;" + "state, cbCountry;" + "carrier; " + "Phones [ mainphone; movilephone; phone2; phone3; ];" + "Notes [ notes1]; "), @View( name = "ViewConTab", // Solo se usa cuando se referencia específicamente members ="address;" + "addresstype;" + "locality, postalnumber;" + "state, cbCountry;" + "carrier; " + "Phones { mainphone; movilephone; phone2; phone3; }" + "Notes { notes1}; " ), @View( name = "ViewListas", // Solo se usa cuando se referencia específicamente members = "address;" + "postalnumber;" + "locality;" + "state, cbCountry;mainphone; movilephone;" // Shows only number and name in the same line ) }) @Table(name = "cb_address") public class CbAddress {
Vamos a explicar el uso de múltiples vistas usando usando la anotación @Views, ya explicamos en la publicación anterior los parámetros que podemos usar en una vista (@View), recordamos el significado de los que usamos aquí:
@View(
name=»nombre», // Nombre de la vista, no es obligatorio
members=»miembros», // Definimos que miembros se visualizan y como se presentan
)
La vista que vemos en el código que no tiene nombre es la que se usa por defecto, y en este caso, definimos dos vistas más con los nombres: «ViewConTab» y «ViewListas», antes de explicar como especificar como incluirlas, ya que lo necesitamos hacer en la clase que queremos modificar la presentación, vamos a analizar el resultado final y explicar los nuevos elementos que se utilizan en estas.
Grupos
Lo primero que encontramos diferente es el uso de grupos, con los grupos podemos agrupar un conjunto de propiedades relacionadas modificando la presentación, la definición de un grupo se hace indicando el nombre del grupo y después sus miembros entre corchetes, tal y como hacemos en la primera vista que es la que hemos definido como vista por defecto:
+ "Phones [ mainphone; movilephone; phone2; phone3; ];" + "Notes [ notes1];
Esta es la presentación resultante:
Como puedes ver en la figura no aparecen los valore indicados: Phones y Notes , esto es debido a que en el fichero de configuración de las etiquetas: EtiquetasCustomerdb_es.properties en la carpeta i18n, hemos definido sus traducciones, así de simple (míralo en el fichero al final de este apartado).
En este caso, también optamos por poner un elemento debajo de otro, si quisiésemos presentarlos en línea uno a continuación del otro simplemente tenemos que eliminar el punto y coma de separación (compruébalo tu mismo):
+ "Phones [ mainphone; movilephone; phone2; phone3; ]" + "Notes [ notes1];
Se pueden anidar grupos, esta interesante característica permite disponer los elementos de la interfaz gráfica de una forma simple y flexible.
Secciones
Además de en grupos, los miembros también se pueden organizar en secciones (información en OpenXava: Secciones). Para definir una sección solo necesitamos poner el nombre de la sección y después sus miembros entre llaves, como hacemos en la vista: «ViewConTab».
Este es el resultado final:
CbAddress es la tabla de direcciones que vamos a definir para los clientes u otras entidades que lo necesiten, a través de la relación que establecemos mediante CbAddresses para cada entidad, esta relación ya estaba establecida en la publicación de la creación de las tablas:
- Definición de la relación en CbAddress
@ManyToOne @JoinColumn( name = "idaddresses", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_address_idaddresses")) private CbAddresses cbAddressesLocal; // Esta es la relación entre CbAddresses y CbAddress que es bidireccional.
- Definición de la relación en CbAddresses
@OneToMany( mappedBy = "cbAddressesLocal", // El objeto de CbAddress que almacena la relación cascade = CascadeType.ALL) private Collection<CbAddress> cbAddress = new ArrayList<CbAddress>();
En la clase CbAddresses explicamos como definimos la vista a utilizar en las listas, y la presentación a utlizar en la edición de CbAddress.
Veamos ahora algún estereotipo nuevo y su funcionalidad, en este caso usamos TELEPHONE, nos aporta validación de número de teléfono aunque el campo establecido es un String. También, usamos MEMO para las observaciones a la dirección:
... @Column(name = "mainphone", length = 30, nullable = true) @Stereotype("TELEPHONE") private String mainphone; @Column(name = "movilephone", length = 30, nullable = true) @Stereotype("TELEPHONE") private String movilephone; @Stereotype("TELEPHONE") @Column(name = "phone2", length = 30, nullable = true) private String phone2; @Stereotype("TELEPHONE") @Column(name = "phone3", length = 30, nullable = true) private String phone3; ... @Stereotype("MEMO") @Column(name = "notes1", length = 500, nullable = true) private String notes1;
Por último, en la relación que ya habíamos establecido para mostrar los países, vamos a añadir la condición de que esos países estén activos, el código que utilizamos lo marcamos en negrita:
@ManyToOne @JoinColumn( name = "idcountry", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_address_idcountry")) @DescriptionsList( descriptionProperties = "country", condition = "${isactive} = true", order = "${country} " ) private CbCountry cbCountry;
Utilizamos la anotación @DescriptionList , estableciendo el filtro: condition = «${isactive} = true» k, y también indicando el campo que mostramos en la presentación con: descriptionProperties = «country» , finalmente se específica la ordenación de los datos presentados: order = «${country} «.
Seguimos definiendo los nombres de los campos para la presentación actualizando el fichero EtiquetasCustomerdb_es.properties en la carpeta i18n con las nuevas etiquetas para esta clase:
# Componentes de CbAddress CbAddresses=Direcciones CbAddress=Dirección address=Dirección postalnumber=Código postal carrier=Transporte addresstype=Tipo de dirección locality=Localidad state=Estado mainphone=Teléfono principal movilephone=Tlf. móvil phone2=Tlf. 2 phone3=Tlf. 3 notes1=Notas Phones=Teléfonos Notes=Notas
Actualizamos la presentación CbAddresses
Los elementos de esta clase son de creación automática y los vamos a utilizar pero ocultándoselos al usuario porque es una información que no necesita. Esto lo veremos en la definición de CbCustomer donde utilizaremos la anotación: @AsEmbedded, el uso de esta anotación hará que la clase sea transparente para el usuario, que verá directamente las direcciones del usuario, incrustándose dentro de la definición del cliente.
Propiedades de la vista
En CbAddress ya indicamos como se relacionaban las tablas, vamos a ver ahora como definimos los elementos que queremos visualizar a nivel listado, y la vista que que queremos utilizar para la edición, veamos la definición:
@OneToMany( mappedBy = "cbAddressesLocal", // El objeto de CbAddress que almacena la relación cascade = CascadeType.ALL) @ListProperties("addresstype, address, locality, postalnumber, state, cbCountry.country, carrier, mainphone") @CollectionView("ViewConTab") private Collection&lt;CbAddress&gt; cbAddress = new ArrayList&lt;CbAddress&gt;();
Ahora expliquémoslo, en primer lugar usamos la anotación @ListProperties para indicar los campos y el orden que se muestran en el listado al visualizar la colección, solo está permitida una @ListProperties por vista, por defecto saca todos los campos persistentes del objeto referenciado.
En OpenXava: Propiedades de la lista tienes más información, puedes ver en la definición que además de los campos básicos del objeto referenciado, también podemos indicar que campo queremos mostrar de una colección referenciada en el objecto, como hacemos en este caso con el nombre del país: cbCountry.country. Este es el resultado:
Si quitamos @ListProperties lo veríamos así:
Como puedes ver en este caso los campos salen ordenados tal y como los hemos definido.
Vista para edición
La otra anotación utilizada es @CollectionView, que nos sirve para indicar la vista de CbAddress que queremos que se muestre al utilizarla desde CbAddresses como se explica en OpenXava: Vista para edición se aplica Aplica a colecciones @OneToMany/@ManyToMany.
Si no usamos ninguna anotación se usa la vista por defecto para editar la entidad en cada línea; aunque como en este caso es habitual indicar la vista para representar el detalle. Veamos el resultado:
En la imagen superior para mostrar el resultado final os adelanto la presentación que veremos desde el formulario de CbCustomer que explicaremos más adelante.
Actualizamos la presentación CbEnterprise
Vamos a actualizar la presentación ahora de las empresas de la aplicación, cada empresa tendrá sus clientes, pero eso lo veremos en CbCustomer, ahora vamos a actualizar la vista de edición de CbEnterprise, y ver cosas nuevas:
/** * Empresas de la aplicación, cada empresa tendrá sus clientes. * @author xulescode */ @Entity @View(members = // Esta vista no tiene nombre, será usada por defecto. "identerprise, name; " + "enterprisealias; " + "contact;" + "balance, enterprisepayer; " + "state; " + "ei;" + "cbLanguage, cbCurrency; " + "cbCountry; " + "description; ") @Tab( rowStyles={ @RowStyle(style="row-highlight", property="state", value="ACTIVE"), @RowStyle(style="row-highlight-cancel", property="state", value="CANCELED")}, properties="identerprise, name, enterprisealias, state, cbLanguage.idlanguage, cbCurrency.description, cbCountry.country" ) @Table(name = "cb_enterprise") public class CbEnterprise {
Como ya explicamos en otras ocasiones utilizamos la anotación @View para definir la vista de edición de CbEnterprise, en este caso solo implementamos una, que será la que se usará como siempre por defecto.
Nos quedaba ver de la presentación como modificar los campos que se presentan en los listados, es decir, en la pestaña Lista, esto lo vamos a indicar con @Tab, es lo que en OpenXava denominan datos tabulares, y son aquellos que se visualizan en formato de tabla. La vista utilizada por defecto está bien, pero como en cualquier proyecto se presentarán múltiples ocasiones en que habrá que especificar los valores presentados en una vista por múltiples razones y necesidades.
Veamos entonces la sintaxis de la anotación @Tab:
@Tab( name="nombre", // 1 filter=clase del filtro, // 2 rowStyles=array de @RowStyle, // 3 properties="propiedades", // 4 baseCondition="condición base", // 5 defaultOrder="orden por defecto" // 6 ) public class MyEntity {
- name (opcional):
- Nombre que le damos a cada Tab para una entidad, con la anotación @Tabs (tal y como hicimos con la anotación @Views) se pueden definir diferentes vistas y ese nombre se utilizará para indicar en cada módulo la que se utiliza (normalmente se específica en aplicación.xml al definir un módulo).
- filter (opcional):
- Definición de filtros propios mediante programación.
- rowStyles (varios, opcional):
- Una forma sencilla de especificar una estilo de visualización diferente para ciertas filas. Normalmente para resaltar filas que cumplen cierta condición, con un array de @RowStyle podemos usar varios estilo por tab.
- properties (opcional):
- La lista de propiedades a visualizar inicialmente. Pueden ser calificadas, con el sufijo + se pueden añadir sumas a la vista por columna.
- baseCondition (opcional):
- Condición que se aplicará siempre a los datos visualizados añadiéndose a las que pueda poner el usuario.
- defaultOrder (opcional):
- Permite especificar el orden de los datos presentados inicialmente.
Analicemos nuestro @Tab:
@Tab( rowStyles={ @RowStyle(style="row-highlight", property="state", value="ACTIVE"), @RowStyle(style="row-highlight-cancel", property="state", value="CANCELED")}, properties="identerprise, name, enterprisealias, state, cbLanguage.idlanguage, cbCurrency.description, cbCountry.country" )
- En nuestro caso inicial no le damos nombre (propiedad name) porque solo definimos una por defecto.
- Definimos los estilos para las columnas con rowStyles como indicamos anteriormente, en nuestro caso vamos a diferenciar en color de la fila si el estado es ACTIVE (color verde #4e9a4e) y si es CANCELED (color #9e9b9b), de los tres estados, utilizados solo queda STOPPED sin asignar color, puedes comprobar el efecto en la figura de abajo. Veamos el funcionamiento:
- Para el estado igual a ACTIVE:
- Usamos @RowStyle para indicar el estilo a aplicar : style=»row-highlight» a la propiedad state: property=»state» cuando tiene el valor indicado en este caso ACTIVE: value=»ACTIVE»
- OpenXava ya tiene unos valores asignados a row-highlight para asignar escribir su valor vamos a definir su valor en la hoja de estilos /web/xava/style/custom.css, en este caso simplemente:
- Para el estado igual a ACTIVE:
.row-highlight A.row-highlight { font-weight: bold; color: #4e9a4e; }
- Para el estado igual a CANCELED:
- Usamos @RowStyle para indicar el estilo a aplicar : style=»row-highlight-cancel« a la propiedad state: property=»state» cuando tiene el valor indicado en este caso ACTIVE: value=»CANCELED»
- Creamos un nuevo valor row-highlight-cancel definimos su valor en la hoja de estilos /web/xava/style/custom.css, en este caso:
.row-highlight-cancel A.row-highlight-cancel { font-weight: bold; color: #9e9b9b; }
Siguiendo con la explicación de estereotipos nuevos, vamos a usar HTML_TEXT quen nos proporciona un editor de texto enriquecido en HTML, y también MONEY para especificar que un campo es de tipo moneda y mostrarlo como tal, vemos en la imagen como queda:
Un ejemplo de definición de varios tabs utilizando la anotación @Tabs sería la siguiente:
@Tabs({ @Tab( rowStyles={ @RowStyle(style="row-highlight", property="state", value="ACTIVE"), @RowStyle(style="row-highlight-cancel", property="state", value="CANCELED")}, properties="identerprise, name, enterprisealias, state, cbLanguage.idlanguage, cbCurrency.description, cbCountry.country" ), @Tab( name="EmpresasActivas", rowStyles=@RowStyle(style="row-highlight", property="idlanguage", value="es_ES"), properties="identerprise, name, cbLanguage.idlanguage, cbCurrency.description, cbCountry.country", baseCondition="${state} =ACTIVE" ), @Tab( name="Lista", properties="identerprise, name, cbCountry.country", defaultOrder="identerprise desc, name desc" ) })
Como puedes ver aquí se utilizan algunos valores nuevos de la sintaxis de @Tab, y que creo que se entiende bien su significado, haz tus pruebas y saca tus propias conclusiones.
Lo definimos como el resto de estereotipos:
... @Stereotype("HTML_TEXT") @Column(name = "description", length = 250, nullable = true) private String description; ... @Stereotype("MONEY") @Column(name = "balance", length = 10, precision = 3, nullable = true) private java.math.BigDecimal balance; ...
Como ya explicamos antes, definimos las condiciones en View: @DescriptionList, para especificar los filtros para cada tabla para reducir los resultados que presentamos a los que solo están activos para CbCurrency, CbLanguage y CbCountry, el resto de las relaciones ya se explicaron en la publicación de Learning Project Tutorial OpenXava (2): Creación del resto de las clases para las tablas, estas son las definiciones:
@ManyToOne @JoinColumn( name = "idcurrency", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_enterprise_idcurrency")) @DescriptionsList( descriptionProperties = "currency", condition = "${isactive} = 'Y'", order = "${currency} " ) private CbCurrency cbCurrency; @ManyToOne @JoinColumn( name = "idlanguage", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_enterprise_idlanguage")) @DescriptionsList( descriptionProperties = "name", condition = "${isactive} = 'Y'", order = "${name} " ) private CbLanguage cbLanguage; @ManyToOne @JoinColumn( name = "idcountry", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_enterprise_idcountry")) @DescriptionsList( descriptionProperties = "country", condition = "${isactive} = true", order = "${country} " ) private CbCountry cbCountry;
Seguimos definiendo los nombres de los campos para la presentación actualizando el fichero EtiquetasCustomerdb_es.properties en la carpeta i18n con las nuevas etiquetas para esta clase:
# Componentes de CbEnterprises CbEnterprises=Empresas CbEnterprise=Empresa identerprise=Número de empresa name=Nombre state=Estado description=Descripción enterprisealias=Alias contact=Contacto balance=Saldo ei=Cif enterprisepayer=Pagador
Actualizamos la presentación CbCustomer
En la entidad CbCustomer vamos a definir varias vista de edición para ver en detalle más posibilidades de presentación, también vamos a definir un @Tab para seleccionar los datos y como los presentamos, este es el código:
/** * Tabla general de cliente. Tabla donde se almacenarán los clientes de las diferentes empresas, se entiende cliente como aquel que compra a una empresa. * @author xulescode */ @Entity @Views({ @View( members = // Vista por defecto sin nombre "customer;" + "customername;" + "customeralias;" + "contact;" + "sale, customerpayer; " + "customerstate;" + "identitynumber;" + "cbLanguage; cbCountry; cbCurrency;" + "cbAddresses;"), @View( name="CustomerConTab", members = "customer;" + "customername;" + "Datos { " + "customeralias;" + "contact;" + "sale, customerstate; " + "identitynumber, customerpayer;}" + "Más datos {cbLanguage; cbCountry; cbCurrency;}" + "Direcciones { " + "cbAddresses;}"), @View( name = "CustomerSimple", // Vista que solo se utiliza cuando se indica "CustomerSimple" members = "customer;" + "customername;" + "customeralias;" + "contact;" + "sale, customerstate;" + "identitynumber, customerpayer;" + "cbLanguage, cbCountry, cbCurrency;" + "cbAddresses;" // Shows only number and name in the same line ) }) @Tab( rowStyles=@RowStyle(style="row-highlight", property="customerstate", value="ACTIVE"), properties="customer, customername, contact, customerstate, cbEnterprise.name, cbLanguage.idlanguage, cbCurrency.description, cbCountry.country, cbPaymentmethod.paymentmethod" ) @Table(name = "cb_customer") public class CbCustomer {
Veamos primero el significado de la separación de las secciones y grupos en las imágenes, como ya se explicó anteriormente, no entraré en más detalles:
- Vista por defecto
- Vista CustomerConTab: mostrando el tab Datos
CbCustomer detalle con la vista Vista CustomerConTab[/caption]
- Vista CustomerConTab: mostrando el tab Datos:
- Vista CustomerConTab: mostrando el tab Direcciones:
No me voy a extender en la definición de los estereotipos ya que ya fueron ampliamente explicados, y con los campos definidos aquí, no vamos a mostrar nada nuevo.
Definimos las condiciones en View: @DescriptionList, para especificar los filtros para cada tabla para reducir los resultados que presentamos a los que solo están activos para CbCurrency, CbLanguage y CbCountry, el resto de las relaciones anteriomente también establecemos la relación con cbEnterprise sin entrar en más detalle y cbPaymentmethod especifícando el valor que se mostrará en el combo, la definición de cbAddresses lo explicaremos con más detalle , estas son las definiciones:
@ManyToOne @JoinColumn( name = "identerprise", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_customer_identerprise")) @DescriptionsList private CbEnterprise cbEnterprise; @ManyToOne @JoinColumn( name = "idcurrency", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_customer_idcurrency")) @DescriptionsList( descriptionProperties = "currency", condition = "${isactive} = 'Y'", order = "${currency} " ) private CbCurrency cbCurrency; @ManyToOne @JoinColumn( name = "idlanguage", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_customer_idlanguage")) @DescriptionsList( descriptionProperties = "name", condition = "${isactive} = 'Y'", order = "${name} " // 6 ) private CbLanguage cbLanguage; @ManyToOne @JoinColumn( name = "idcountry", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_customer_idcountry")) @DescriptionsList( descriptionProperties = "country", condition = "${isactive} = true", order = "${country} " ) private CbCountry cbCountry; @ManyToOne @JoinColumn( name = "idpaymentmethod", nullable = true, foreignKey = @ForeignKey(name = "cb_customers_idpaymentmethod")) @DescriptionsList(descriptionProperties = "paymentmethod") private CbPaymentmethod cbPaymentmethod;
Veamos ahora como modificamos la visualización de las direcciones del cliente, ya se explicó como se establecían las relaciones con CbAddress a través de CbAddresses:, ahora para mejorar la presentación vamos a hacer la entidad CbAddresses transparente al usuario con la anotación @AsEmbedded quedaría así:
@OneToOne @JoinColumn( name = "idaddresses", nullable = true, foreignKey = @ForeignKey(name = "fk_cb_customer_idaddresses")) @AsEmbedded private CbAddresses cbAddresses;
Explicación del uso de @AsEmbedded en SourceForge: http://sourceforge.net/p/openxava/discussion/437015/thread/bd45c358/, donde está alojado el proyecto OpenXava (Sourceforge OpenXava) y donde hay varios foros de ayuda, en los cuales el propio Javier Paniza líder del proyecto contesta personalmente, aprovecho este caso para comentar el interesante foro, ya que fue aquí donde yo encontré la respuesta y explicación a lo que necesitaba. Veamos ahora su definición:
- @AsEmbedded
- Según la definición de OpenXava: «Hace que comportamiento en la vista de una referencia (o colección) a entidad sea como en el caso de un objeto incrustado (o colección de entidades con CascadeType.REMOVE)».
- Esto quiere decir que el uso de esta anotación hará que la clase sea transparente para el usuario que verá directamente las direcciones del usuario, incrustándose dentro de la definición del cliente.
- A continuación la explicación extraída de la wiki de OpenXava:
- «Por defecto en el caso de una referencia a una clase incrustable el usuario puede crear y editar sus datos, mientras que en el caso de una referencia a una entidad el usuario escoge una entidad existente».
- «Si ponemos @AsEmbedded entonces la interfaz de usuario para referencias a entidad se comporta como en el caso de los incrustados, permitiendo al usuario crear un nuevo objeto y editar sus datos directamente. No tiene efecto en el caso de una referencia a un objeto incrustado».
- «¡Ojo! Si borramos una entidad sus entidades referenciadas no se borran, incluso si estamos usando @AsEmbedded«.
Puedes encontrar la información detalle de otras etiquetas de personalización de referencia en OpenXava en este enlace.
Seguimos definiendo los nombres de los campos para la presentación actualizando el fichero EtiquetasCustomerdb_es.properties en la carpeta i18n con las nuevas etiquetas para esta clase:
# Componentes de CbCustomer idcustomer=Id. Cliente customer= Cliente customername=Nombre customeralias=Alias sale=Saldo customerstate=Estado identitynumber=CIF customerpayer=Pagador cbAddresses=Direcciones
Qué nos falta para finalizar la presentación de las direcciones
Lo planteo como apartado diferenciado ya que fue con lo que yo tuve más problemas para finalizar la correcta presentación, con lo que hemos hecho hasta ahora tenemos un par de problemas por resolver:
- Cuando creamos un nuevo cliente (CbCustomer), si antes de crearlo intentamos añadirle la dirección, con la definición que hemos hecho hasta ahora, no va a funcionar, este el error:
oct 03, 2015 4:46:38 PM org.openxava.controller.ModuleManager manageRegularException
GRAVE: No existe la colección cbAddresses en CbAddresses
org.openxava.util.ElementNotFoundException: No existe la colección cbAddresses en CbAddresses
- Esto sucede porque cuando intentamos crear la dirección todavía no tenemos creado en el cliente que no existe todavía un CbAddresses, como es lógico.
- De todas formas si guardamos el cliente sin añadirle la dirección, no se genera automáticamente un CbAddressses asociado.
Para solucionar esto, tengo que hacer dos cosas:
- Crear siempre una entidad CbAddresses al crear cada nuevo cliente.
- Para esto vamos a utilizar un método que se llama antes de persistir el objeto: @PreCreate este es propio de la retrollamadas de OpenXava. De esta manera vamos a poder utilizar el manejador de persistencia o crear búsquedas que no son permitidas dentro de las retrollamadas de JPA.
- Esto lo veremos a continuación.
- Ocultar a nivel de creación las direcciones hasta que exista un CbAddresses asociado el cliente, y así, tener un funcionamiento correcto.
- Para conseguir esto vamos a tener que definir dos vistas diferentes una para Crear y otra para Actualizar, esto no lo podemos hacer directamente en las anotaciones y para ello tendremos que modificar el controlador usado por defecto y definir las clases donde les indiquemos las vistas a utilizar en cada caso.
- Llegar a esto solución fue lo que más me costó, ya que creí que no estaba utilizando correctamente @AsEmbedded.
- Como la creación de controladores propios, se escapa a esta publicación, expondré mi solución en un siguiente post.
Veamos la creación de retrollamadas de OpenXava, esto nos permitirá obtener un funcionamiento más o menos correcto, dejando la creación de las direcciones del cliente para después de su creación ya que no se podrán añadir hasta que el cliente haya sido creado, pero si después, ya que en la creación le vamos a asociar al cliente un idaddresses de la entidad CbAddresses automáticamente, veamos como.
Con @PreCreate se pueden marcar métodos que serán ejecutados antes de persistir algún objeto, en nuestro caso antes de crear CbCustomer nos interesa crear un CbAddresses que se le asignará así automáticamente al cliente CbCustomer. Ahora definimos el método dentro de la clase CbCustomer que vamos a utilizar:
/** * Cuando creamos el cliente creamos la entidad de CbAddresses que lleva asociada, para que después se pueda añadir la dirección al actualizar el cliente. */ @PreCreate public void onPreCreate() { if (getCbAddresses() == null) { CbAddresses cbAddressesAux = new CbAddresses(); cbAddressesAux = XPersistence.getManager().merge(cbAddressesAux); setCbAddresses(cbAddressesAux); } }
En el siguiente capítulo, realizaremos el punto 2 para ocultar las direcciones hasta que se haya creado el cliente, con lo cual evitaremos el error que aparece en este vídeo.
Espero que te haya sido útil.Código Xules
Hola! gracias por el tutorial, pero quiero consultarle si habra otro donde expliquen como debe de ser la forma correcta para ocultar el tab de direcciones y que solo sea visible cuando sea un update, me he quedado en esta parte y agradecere su apoyo.
gracias.
Encontre el siguiente tutorial gracias!
Hola Efrain, te iba a contestar pero veo que ya encontraste la continuación del tutorial Tutorial Openxava (3.3) : Vistas diferentes para crear y actualizar la entidad (Fin de la primera parte), aprovecho tu feedback para indicar la continuación en la publicación, a veces nos despistamos en estas cosas.
Saludos.