@View(
name="name", // 1
members="members", // 2
extendsView="view" // 3 New in v3.1.2
)
public class MyEntity {
@Entity
@IdClass(ClerkKey.class)
public class Clerk {
@Id @Required
@Column(length=3, name="ZONE")
private int zoneNumber;
@Id @Required
@Column(length=3, name="OFFICE")
private int officeNumber;
@Id @Required
@Column(length=3, name="NUMBER")
private int number;
@Required @Column(length=40)
private String name;
// Getters and setters
...
}
Generates a view that looks like this:
@Entity
@IdClass(ClerkKey.class)
@View(members="zoneNumber; officeNumber; number")
public class Clerk {
In this case name is not shown.@View(members=
"zoneNumber, officeNumber, number;" +
"name"
)
You can observe that the member names are separated by commas or by
semicolon, this is used to indicate layout. With comma the member is
placed just the following (at right), and with semicolon the next member
is put below (in the next line). Hence the previous view is displayed in
this way:
@View( members=
"number, type, name;" +
"telephone, email, website")
...will be displayed as following:
@View( members=
"#number, type, name;" +
"telephone, email, website")
Note that now you use # at the beginning of members. Now you obtain
this result:
@View(members=
"id [ zoneNumber, officeNumber, number ];" +
"name"
)
In this case the result is:
@View(members=
"general [" +
" number;" +
" type;" +
" name;" +
"]" +
"contact [" +
" telephone;" +
" email;" +
" website;" +
"]"
)
In this case the groups are shown one next to the other:
@View(members=
"general [" +
" number;" +
" type;" +
" name;" +
"];" +
"contact [" +
" telephone;" +
" email;" +
" website;" +
"]"
)
In this case the view is shown this way:
@View(members=
"invoice;" +
"deliveryData [" +
" type, number;" +
" date;" +
" description;" +
" shipment;" +
" transportData [" +
" distance; vehicle; transportMode; driverType;" +
" ]" +
" deliveryByData [" +
" deliveredBy;" +
" carrier;" +
" employee;" +
" ]" +
"]"
)
And the result will be:
@View(name="Amounts", members=
"year, number;" +
"amounts [" +
"customerDiscount, customerTypeDiscount, yearDiscount;" +
"amountsSum, vatPercentage, vat;" +
"]"
)
...will be displayed as following:
@View(name="Amounts", members=
"year, number;" +
"amounts [#" +
"customerDiscount, customerTypeDiscount, yearDiscount;" +
"amountsSum, vatPercentage, vat;" +
"]"
)
Note that now you use [# instead of [. Now you obtain
this result:
@View(members=
"year, number, date, paid;" +
"comment;" +
"customer { customer }" +
"details { details }" +
"amounts { amountsSum; vatPercentage; vat }" +
"deliveries { deliveries }"
)
The visual result is:
@View(name="NestedSections", members=
"year, number, date;" +
"customer { customer }" +
"data {" +
" details { details }" +
" amounts {" +
" vat { vatPercentage; vat }" +
" amountsSum { amountsSum }" +
" }" +
"}" +
"deliveries { deliveries }"
)
In this case you will obtain a user interface like this:
@View(name="AlignedAmountsInSection", members=
"year, number;" +
"customer { customer }" +
"details { details }" +
"amounts {#" +
"customerDiscount, customerTypeDiscount, yearDiscount;" +
"amountsSum, vatPercentage, vat;" +
"}"
)
With the same effect as in the group
case.Members that are displayed within a frame, such as references, groups or collections, can be displayed side by side instead of one below the other, if you wish. That is, it is possible to arrange two frames in the same row. To do this you only have to separate them with a comma instead of a semicolon, as you can see in this example:
@View( members=
"name;" +
"seller, alternateSeller"
)
In this case name is a plain property, but seller and alternateSeller are references. The visual result would be:
Where you can see that seller and alternateSeller are displayed on the same line. This also works for collections and groups. Additionally, you can combine them, you can put a reference and a group (for example) on the same line.
What is not allowed is to place members that are displayed with a simple field, such as properties or references with @DescriptionsList, and members that use frames like reference, groups and collections on the same line. If you declare it this way in @View, OpenXava will show the frames on their own line.
For example, if you have this code:
@View( members="name, seller" )
Where you indicate that name, which is a plain property, and seller, which is a reference, should be displayed side by side, since you separate them by comma. When displayed, OpenXava ends up doing it like this:
That is, one below the other. OpenXava has put the seller reference on its own line. We see how we cannot display simple fields and frames side by side.
@View(name="VerySimple", members="name, sex"),
That produces the next UI:
@View(name="Simple", extendsView="VerySimple", members="mainLanguage")
and you will have the next view:
@Entity
@View(name="WithSections",
members =
"name, sex;" +
"mainLanguage;" +
"experiences { experiences }"
)
public class Programmer {
You can reuse the WithSections view in a child class of Programmer:@Entity
@View(name="WithSections", extendsView="super.WithSections",
members =
"favouriteFramework;" +
"frameworks { frameworks }"
)
public class JavaProgrammer extends Programmer {
As you can see, the way to extends a view of the superclass is using the super
prefix for extendsView. In this case the WithSections
view of the JavaProgrammer entity will have all the members of
the WithSections view of Programmer entity plus its
own ones.
@View(members="name, sex; mainLanguage, favouriteFramework; experiences")
@View(name="Complete", extendsView="DEFAULT", members = "frameworks")
The Complete view will have all the members of default view (name,
sex, mainLanguage, favouriteFramework, experiences) plus frameworks.flowLayout=true
After it, OpenXava adjusts the fields layout to the page size. For
example, from the next @View:@View( members=
"#number, description;" +
"color, photos;" +
"family, subfamily;" +
"warehouse, zoneOne;" +
"unitPrice, unitPriceInPesetas;" +
"unitPriceWithTax"
)
With a small screen you get:


@Entity
@View( members= "number; type; name; address" )
@View( name="A", members= "number; type; name; address; seller" )
@View( name="B", members= "number; type; name; seller; alternateSeller" )
@View( name="C", members="number; type; name; address; deliveryPlaces" )
public class Customer {
If you're using an OpenXava version older than 6.1 you have to use @Views
to wrap the views:@Entity
@Views({ // Only needed until v6.0.2
@View( members= "number; type; name; address" ),
@View( name="A", members= "number; type; name; address; seller" ),
@View( name="B", members= "number; type; name; seller; alternateSeller" ),
@View( name="C", members="number; type; name; address; deliveryPlaces" )
})
public class Customer {
If now you want the name property will be read only. You can
annotate it in this way:@ReadOnly
private String name;
In this way name is read only in all views. However, you may
want that name will be read only only on views B and C,
then you can define the member as following:@ReadOnly(forViews="B, C")
private String name;
Another way for defining this same case is:@ReadOnly(notForViews="DEFAULT, A")
private String name;
Using notForViews you indicate the views where name
property is not read only. DEFAULT is used for referencing to the default
view, the view with no name.@ReferenceView("Simple")
private Seller seller;
In this case when the seller is displayed the view Simple,
defined in Seller class, is used.@ReferenceView(forViews="B", value="Simple")
private Seller seller;
What if you want to use Simple view of Seller only in
B view of Customer and the VerySimple view of
Seller for A view of Customer? In this case
you have to use several @ReferenceView:@ReferenceView(forViews="B", value="Simple"),
@ReferenceView(forViews="A", value="VerySimple")
If you're using a version previous to v6.1 in order to have several @ReferenceView
you must use @ReferenceViews, just in this way:@ReferenceViews({ // Only needed until v6.0.2
@ReferenceView(forViews="B", value="Simple"),
@ReferenceView(forViews="A", value="VerySimple")
})
These rules apply to all the annotations in this chapter, except @View
and @Views.@ReadOnly // 1
@LabelFormat // 2
@DisplaySize // 3
@OnChange // 4
@Action // 5
@Editor // 6
@LabelStyle // 7 New in v4m4
@LargeDisplay // 8 New in v7.4
private type propertyName;
All these annotations follow the rules
for view annotations and all they are optionals. OpenXava always
assumes a correct default values if they are omitted.Apart of the above view related annotations you can annotate properties with stereotype like annotations.
@LabelFormat(LabelFormatType.SMALL)
private int zipCode;
In this case the zip code is displayed as:
@OnChange(OnChangeCustomerNameAction.class)
private String name;
The code to execute is:package org.openxava.test.actions;
import org.openxava.actions.*;
import org.openxava.test.model.*;
/**
* @author Javier Paniza
*/
public class OnChangeCustomerNameAction extends OnChangePropertyBaseAction { // 1
public void execute() throws Exception {
String value = (String) getNewValue(); // 2
if (value == null) return;
if (value.startsWith("Javi")) {
getView().setValue("type", Customer.Type.STEADY); // 3
}
}
}
The action has to implement IOnChangePropertyAction although it
is more convenient to extend it from OnChangePropertyBaseAction
(1). Within the action you can use getNewValue() (2) that
provides the new value entered by user, and getView() (3) that
allows you to access programmatically the View (change values, hide members, make them
editable and so on).@Action("Delivery.generateNumber")
private int number;
In this case instead of an action class you have to write the action
identifier that is the controller name and the action name. This action
must be registered in controllers.xml in this way:<controller name="Delivery">
...
<action name="generateNumber" hidden="true"
class="org.openxava.test.actions.GenerateDeliveryNumberAction">
<use-object name="xava_view"/> <!-- Not needed since v4m2 -->
</action>
...
</controller>
The actions are displayed as a link or an image beside the property. Like
this:
@Action(value="Delivery.generateNumber", alwaysEnabled=true)
The attribute alwaysEnabled is optional and its default value is
false.package org.openxava.test.actions;
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
public class GenerateDeliveryNumberAction extends ViewBaseAction {
public void execute() throws Exception {
getView().setValue("number", new Integer(77));
}
}
A simple but illustrative implementation. You can use any action defined
in controllers.xml and its behavior is the normal for an
OpenXava action. In the chapter
7 you will learn more details about actions.package org.openxava.test.actions;
import org.openxava.actions.*;
import org.openxava.view.*;
/**
* @author Javier Paniza
*/
public class GenerateDeliveryNumberAction
extends BaseAction
implements IPropertyAction { // 1
private View view;
private String property;
public void execute() throws Exception {
view.setValue(property, new Integer(77)); // 2
}
public void setProperty(String property) { // 3
this.property = property;
}
public void setView(View view) { // 4
this.view = view;
}
}
This action implements IPropertyAction (1), this required that
the class implements setProperty() (3) and setView()
(4), these values are injected in the action object before call to execute()
method, where they can be used (2). In this case you does not need to
inject xava_view object when defining the action in controllers.xml.
The view injected by setView() (4) is the inner view that
contains the property, for example, if the property is inside an aggregate
the view is the view of that aggregate not the main view of the module.
Thus, you can write more reusable actions.@Editor(forViews="TypeWithRadioButton", value="ValidValuesRadioButton")
private Type type;
public enum Type { NORMAL, STEADY, SPECIAL };
In this case for displaying/editing the editor ValidValuesRadioButton
will be used, instead of default one. ValidValuesRadioButton is
defined in openxava/src/main/resources/xava/default-editors.xml
(in OpenXava/xava/default-editors.xml for v6 or older) as
following:<editor name="ValidValuesRadioButton" url="radioButtonEditor.jsp"/>
This editor is included with OpenXava, but you can create your own editors
with your custom JSP code and declare them in the file editors.xml
in src/main/resources/xava (just xava in v6 or older) of
your project.private String color;
You can add a combo programmatically in this way:getView().addValidValue("color", "wht", "White");
getView().addValidValue("color", "blk", "Black");
This creates a combo for color property with two values wht
with label White and blk with label Black.
In addition to addValidValue(),
you have removeValidValue() , getValidValues()
and hasValidValues()
available. Since v6.3 you also have clearValidValues(),
disableValidValues(),
removeBlankValidValue()
and hasBlankValidValue().@Editor("EditableValidValues")
@Column(length = 15)
private String color;
In this example selecting White, you can edit it by White
beige or enter a new value as Yellow. These new values will
not be added to the original list of options for use in other records.
@LargeDisplay
int year;
@Money @LargeDisplay
BigDecimal discount;
@LargeDisplay(prefix="€")
BigDecimal amountsSum;
@LargeDisplay(suffix="%", icon="label-percent-outline")
BigDecimal vatPercentage;
.red input[type="text"], .red .xava_numeric, .red textarea {
color: red !important;
} getView().setStyle("date", "red"); getView().setStyle("date", null); getView().clearStyles(); @ReferenceView // 1
@ReadOnly // 2
@NoFrame // 3
@NoCreate // 4
@NoModify // 5
@NoSearch // 6
@AsEmbedded // 7
@SearchAction // 8
@SearchListCondition // 9 New in v4m4
@DescriptionsList // 10
@LabelFormat // 11
@Action // 12
@OnChange // 13
@OnChangeSearch // 14
@Editor // 15 New in v3.1.3
@LabelStyle // 16 New in v4m4
@Collapsed // 17 New in v5.0
@SearchListTab // 18 New in v7.4
@NewView // 19 New in v7.7
@EditView // 20 New in v7.7
@NewAction // 21 New in v7.7
@EditAction // 22 New in v7.7
@ManyToOne
private type referenceName;
All these annotations follow the rules
for view annotations and all they are optionals. OpenXava always
assumes a correct default values if they are omitted.@ManyToOne
private Family family;
The user interface will look like this:
@ManyToOne(fetch=FetchType.LAZY)
@ReferenceView("Simple")
private Invoice invoice;
In the Invoice entity you must have a view named Simple:@Entity
@Views({
...
@View(name="Simple", members="year, number, date, yearDiscount;"),
...
})
public class Invoice {
Thus, instead of using the default view of Invoice (that shows
all invoice data) OpenXava will use the next one:
@View( members=
...
"seller [" +
" seller; " +
" relationWithSeller;" +
"]" +
...
)
public class Customer {
...
@ManyToOne(fetch=FetchType.LAZY)
@NoFrame
private Seller seller;
...
}
And the result:
@ManyToOne(fetch=FetchType.LAZY) @SearchAction("MyReference.search")
private Seller seller;
When the user clicks in the lantern your action is executed, which must be
defined in controllers.xml.<controller name="MyReference">
<action name="search" hidden="true"
class="org.openxava.test.actions.MySearchAction"
image="images/search.gif">
<use-object name="xava_view"/> <!-- Not needed since v4m2 -->
<use-object name="xava_referenceSubview"/> <!-- Not needed since v4m2 -->
<use-object name="xava_tab"/> <!-- Not needed since v4m2 -->
<use-object name="xava_currentReferenceLabel"/> <!-- Not needed since v4m2 -->
</action>
...
</controller>
The logic of your MySearchAction is up to you. You can, for
example, refining the standard search action to filter the list for
searching, as follows:package org.openxava.test.actions;
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
public class MySearchAction extends ReferenceSearchAction {
public void execute() throws Exception {
super.execute(); // The standard search behaviour
getTab().setBaseCondition("${number} < 3"); // Adding a filter to the list
}
}
You will learn more about actions in chapter
7.private int code;
@ManyToOne(fetch=FetchType.LAZY)
@SearchListCondition("${number} < 3")
@SearchListCondition("${number} < ${this.code}", forViews="SearchListCondition") // ${this.} New in v7.4
private Seller seller;
Note as you do not need to create any action.

<!--
Because its name is WarehouseCreation (model name + Creation) it is used
by default for creating from reference, instead of NewCreation.
The action 'new' is executed automatically.
-->
<controller name="WarehouseCreation">
<extends controller="NewCreation"/>
<action name="new" hidden="true"
class="org.openxava.test.actions.CreateNewWarehouseFromReferenceAction"/>
<action name="saveNew" by-default="almost-always"
class="org.openxava.test.actions.SaveNewWarehouseFromReferenceAction"/>
</controller>
In this case when the user clicks on the create icon, the user is
directed to the default view of Warehouse and the actions in WarehouseCreation
will be allowed. If we have a new action, it is executed
automatically right after opening the dialog, we can use it to initialize
the view if needed. We define it as hidden, so it's not shown as a button
to the user. Here's an example implementation:package org.openxava.test.actions;
import org.openxava.actions.*;
public class CreateNewWarehouseFromReferenceAction extends NewAction {
@Override
public void execute() throws Exception {
super.execute();
getView().setValue("name", "NEW WAREHOUSE");
}
}
package org.openxava.test.actions;
import org.openxava.actions.*;
public class SaveNewWarehouseFromReferenceAction extends SaveNewAction {
@Override
public void execute() throws Exception {
String name = getView().getValueString("name"); // Get name before closing the dialog
super.execute(); // Saves the warehouse and closes the dialog (if not validation errors)
if (!getErrors().contains() && name.equals("NEW WAREHOUSE")) {
addWarning("warehouse_created_using_default_name"); // Message in i18n messages file
}
}
}
@NewAction("Artist.createNewLevel")
@ManyToOne
ActingLevel level;
<controller name="Artist">
<extends controller="Typical"/>
<action name="createNewLevel" hidden="true"
icon="plus-box-multiple"
class="org.openxava.test.actions.CreateNewLevelFromArtistAction"/>
</controller>
package org.openxava.test.actions;
import org.openxava.actions.*;
public class CreateNewLevelFromArtistAction extends CreateNewFromReferenceAction {
@Override
public void execute() throws Exception {
super.execute();
getView().setValue("description", "NEW ACTING LEVEL");
}
}


<!--
Because its name is WarehouseModification (model name + Modification) it is used
by default for modifying from reference, instead of Modification.
The action 'search' is executed automatically.
-->
<controller name="WarehouseModification">
<extends controller="Modification"/>
<action name="search" hidden="true"
class="org.openxava.test.actions.ModifyWarehouseFromReferenceAction"/>
<action name="update" by-default="almost-always"
class="org.openxava.test.actions.UpdateWarehouseFromReferenceAction"/>
</controller>
In this case when the user clicks on the modify icon, the user is
directed to the default view of Warehouse and the actions in WarehouseModification
will be allowed. If we have a search action, it is executed
automatically right after opening the dialog, we can use it to
initialize the view if needed. We define it as hidden, so it's not shown
as a button to the user. Here's an example implementation:package org.openxava.test.actions;
import org.openxava.actions.*;
public class ModifyWarehouseFromReferenceAction extends SearchByViewKeyAction {
public void execute() throws Exception {
super.execute();
getView().setValue("name", "MODIFIED WAREHOUSE");
}
}
package org.openxava.test.actions;
import org.openxava.actions.*;
public class UpdateWarehouseFromReferenceAction extends UpdateAction {
@Override
public void execute() throws Exception {
String name = getView().getValueString("name"); // Get name before closing the dialog
super.execute(); // Updates the warehouse and closes the dialog (if not validation errors)
if (!getErrors().contains() && name.equals("MODIFIED WAREHOUSE")) {
addWarning("warehouse_modified_using_default_name"); // Message in i18n messages file
}
}
}
@EditAction("Artist.modifyLevel")
@ManyToOne
ActingLevel level;
<controller name="Artist">
<extends controller="Typical"/>
<action name="modifyLevel" hidden="true"
icon="border-color"
class="org.openxava.test.actions.ModifyLevelFromArtistAction"/>
</controller>
package org.openxava.test.actions;
import org.openxava.actions.*;
public class ModifyLevelFromArtistAction extends ModifyFromReferenceAction {
@Override
public void execute() throws Exception {
super.execute();
String description = getView().getValueString("description").trim();
getView().setValue("description", description + " (MODIFIED)");
}
}
@DescriptionsList(
descriptionProperties="properties", // 1
depends="depends", // 2
condition="condition", // 3
orderByKey="true|false", // 4
order="order", // 5
filter="filter class", // 6 New in v6.4
showReferenceView="true|false", // 7 New in v5.5
forTabs="tab1,tab2,...", // 8 New in v4m4
notForTabs="tab1,tab2,..." // 9 New in v4m4
)
@ManyToOne(fetch=FetchType.LAZY)
@DescriptionsList
private Warehouse warehouse;
That displays a reference to warehouse in this way:
@ManyToOne(fetch=FetchType.LAZY)
@DescriptionsList(orderByKey=true) // 1
private Family family;
@ManyToOne(fetch=FetchType.LAZY) @NoCreate // 2
@DescriptionsList(
descriptionProperties="description", // 3
depends="family", // 4
condition="${family.number} = ?" // 5
order="${description} desc" // 6
)
private Subfamily subfamily;
Two combos are displayed one with all families loaded and the other one
empty. When the user chooses a family, then the second combo is filled
with all its subfamilies.@Entity @Getter @Setter
@IdClass(WarehouseKey.class)
public class Warehouse {
@Id
int zoneNumber;
@Id
int number;
@Column(length=40) @Required
String name;
}
@ManyToOne
@DescriptionsList
Warehouse mainWarehouse;
@ManyToOne
@DescriptionsList(
depends="mainWarehouse",
condition="${warehouse.zoneNumber} = ? and ${warehouse.number} = ?")
// condition="${warehouse} = ?" // Since v6.6.3
Carrier defaultCarrier;
@DescriptionsList(
depends="family",
// condition="${family.number} = ?" // Classic way
condition="${family} = ?" // Since v6.6.3
)
@DescriptionsList( descriptionProperties="name",
condition="${family.level.description} = 'TOP'"
)
private Subfamily subfamily;
You can define complex conditions using JPQL (new in v4.5, before v4.5 SQL was
used):@DescriptionsList(
condition="e.thing.number = (SELECT t.number FROM Thing t WHERE t.name = 'CAR')"
)
private Subfamily subfamily;
As you can see in the example above, with JPQL (new in v4.5) you
can use e.propertyName as alternative to ${propertyName}.@ManyToOne(fetch=FetchType.LAZY)
@ReadOnly
@DescriptionsList(descriptionProperties="level.description, name")
private Seller alternateSeller;
In this case the concatenation of the description of level
and the name is shown in the combo. Also you can see how it is
possible to use qualified properties (level.description).@ManyToOne(fetch=FetchType.LAZY)
@DescriptionsList(showReferenceView=true) // Combo and view at the same time
@ReferenceView("Simple") // This is the used view
private Seller seller;
You get:
public class OnChangeStateConditionInCity extends OnChangePropertyBaseAction {
public void execute() throws Exception{
String value = (String)getNewValue();
String condition = "";
if ( Is.empty(value)) condition = "1=1";
else condition = "upper(name) like '%" + value + "%'";
getView().setDescriptionsListCondition("state", condition); // we modify the condition of the combo 'state'
}
}
Depending on the value that the client types, with the method setDescriptionsListCondition(),
we modify the condition of the combo 'state' and that change the values
displayed in the combo.@ManyToOne(fetch=FetchType.LAZY)
@OnChange(OnChangeCarrierInDeliveryAction.class)
private Carrier carrier;
In this case your action listens to the change of carrier number.package org.openxava.test.actions;
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
public class OnChangeCarrierInDeliveryAction
extends OnChangePropertyBaseAction { // 1
public void execute() throws Exception {
if (getNewValue() == null) return;
getView().setValue("remarks", "The carrier is " + getNewValue());
addMessage("carrier_changed");
}
}
The action implements IOnChangePropertyAction, by means of OnChangePropertyBaseAction
(1), although it's a reference. We receive the change of the key property
of the reference; in this case carrier.number. The rest is as in
the
property case.@ManyToOne(fetch=FetchType.LAZY)
@OnChangeSearch(OnChangeSubfamilySearchAction.class)
private Subfamily subfamily;
This action is executed for doing the search, instead of the standard
action, when the user changes the subfamily number.package org.openxava.test.actions;
import org.openxava.actions.*;
/**
*
* @author Javier Paniza
*/
public class OnChangeSubfamilySearchAction
extends OnChangeSearchAction { // 1
public void execute() throws Exception {
if (getView().getValueInt("number") == 0) {
getView().setValue("number", new Integer("1"));
}
super.execute();
}
}
The action implements IOnChangePropertyAction, by means of OnChangeSearchAction
(1), although it's a reference. It receives the change of the key property
of the reference; in this case subfamily.number.@ManyToOne(fetch=FetchType.LAZY)
@Editor("ColorRadioButtons")
private Color color;
In this case the ColorRadioButtons editor will be used for
displaying/editing, instead of the default one. You must define your ColorRadioButton
editor in the editors.xml file in src/main/resources/xava
(just xava for v6 or older) of your project:<editor name="ColorRadioButtons" url="colorRadioButtonsEditor.jsp"/>
Also you have to write the JSP code for your editor in colorRadioButonsEditor.jsp.With @SearchListTab, you can specify which tab to display in the dialog when use a search action.
@ManyToOne(fetch=FetchType.LAZY)
@SearchListTab("ZoneA")
private Warehouse warehouse;
For this in the Warehouse we must have a tab called ZoneA:@Entity
@Tab(name="ZoneA", properties="number, name, zone", defaultOrder="${number} desc", baseCondition="${zone} = 'A'")
public class Warehouse {
In addition, the forViews and notForViews attributes are
available in @SearchListTab. @ManyToOne
@NewView("SimpleCreation")
@EditView("SimpleEdition")
private Customer customer;
When the user clicks on the button to create a new customer, the SimpleCreation
view of Customer will be used. When the user clicks on the button to
modify the current customer, the SimpleEdition view will be used instead.@CollectionView // 1
@ReadOnly // 2
@EditOnly // 3
@NoCreate // 4
@NoModify // 5
@AsEmbedded // 6
@ListProperties // 7
@RowStyle // 8
@EditAction // 9
@ViewAction // 10
@NewAction // 11
@AddAction // 12 New in v5.7
@SaveAction // 13
@HideDetailAction // 14
@RemoveAction // 15
@RemoveSelectedAction // 16
@DeleteSelectedAction // 17 New in v7.4
@ListAction // 18
@RowAction // 19 New in v4.6
@DetailAction // 20
@OnSelectElementAction // 21 New in v3.1.2
@Editor // 22 New in v3.1.3
@SearchListCondition // 23 New in v4m4
@Tree // 24 New in v4m4
@Collapsed // 25 New in v5.0
@ListSubcontroller // 26 New in v5.7
@Chart // 27 New in v7.4
@SimpleList // 28 New in v7.4
@SearchListTab // 29 New in v7.4
@NoDefaultActions // 30 New in v7.4
@NewView // 31 New in v7.7
@EditView // 32 New in v7.7
@OneToMany/@ManyToMany // Not for calculated collections
private Collection collectionName; // Or a getter for calculated collections
And the next annotations for a @ElementCollection (new in v5.0):@ReadOnly // 2 New in v5.1
@EditOnly // 3 New in v5.1
@ListProperties // 7
@RemoveSelectedAction // 15 New in v5.3
@Editor // 20
@Collapsed // 23
@Chart // 27 New in v7.4
@SimpleList // 28 New in v7.4
@ElementCollection
private Collection collectionName;
All these annotations follow the rules
for view annotations and all they are optionals. OpenXava always
assumes a correct default values if they are omitted.@ListProperties("number, name, remarks, relationWithSeller, seller.level.description, type")
@OneToMany(mappedBy="seller")
private Collection<Customer> customers;
And the collection is displayed:
@CollectionView("Simple"),
@OneToMany(mappedBy="seller")
private Collection<Customer> customers;
When the user clicks on
('Edit'), then the view Simple of Customer will be
rendered; for this you must have defined a view called Simple in
the Customer entity (the model of the collection elements).
('Add') in an embedded
collection, otherwise OpenXava does not show this view, instead it
shown a list of entities to add.@View(name="Simple", members="number; type; name; address")
On clicking in a detail the following dialog will be shown:
@CollectionView("Simple")
@NewView("SimpleCreation") // Overrides @CollectionView for creation
@EditView("SimpleEdition") // Overrides @CollectionView for editing
@OneToMany(mappedBy="invoice")
private Collection<InvoiceDetail> details;
Using @CollectionView("Simple") alone is equivalent to using both
@NewView("Simple") and @EditView("Simple"). You can use
@NewView and @EditView to override @CollectionView
for creation or editing, or use them without @CollectionView to
specify views only for creation or editing while keeping the default view
for the other operation.@ElementCollection
@ListProperties("product.number, product.description, unitPrice, quantity, amount")
private Collection<QuoteDetail> details;
You get the next user interface:
@ElementCollection
@ListProperties("invoice.year, invoice.number, invoice.amount, status, receptionist") // receptionist is a reference
private Collection<ServiceExpense> expenses;
Where receptionist is not a property but a reference, a
reference annotated with @DescriptionsList, in this way:@Embeddable
public class ServiceExpense {
@ManyToOne(fetch=FetchType.LAZY)
@DescriptionsList
private Receptionist receptionist;
...
}
From the above code you get:
('Edit') link is clicked using @EditAction:@EditAction("Invoice.editDetail")
@OneToMany (mappedBy="invoice", cascade=CascadeType.REMOVE)
private Collection<InvoiceDetail> details;
You have to define Invoices.editDetail in controllers.xml:<controller name="Invoice">
...
<action name="editDetail" hidden="true"
image="images/edit.gif"
class="org.openxava.test.actions.EditInvoiceDetailAction">
<use-object name="xava_view"/> <!-- Not needed since v4m2 -->
</action>
...
</controller>
And finally write your action:package org.openxava.test.actions;
import java.text.*;
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
public class EditInvoiceDetailAction extends EditElementInCollectionAction { // 1
public void execute() throws Exception {
super.execute();
DateFormat df = new SimpleDateFormat("dd/MM/yyyy");
getCollectionElementView().setValue( // 2
"remarks", "Edit at " + df.format(new java.util.Date()));
}
}
In this case you only refine hence your action extends (1) EditElementInCollectionAction.
In this case you only specify a default value for the remarks
property. Note that to access the view that displays the detail you can
use the method getCollectionElementView() (2).@EditAction("")
@OneToMany (mappedBy="invoice", cascade=CascadeType.REMOVE)
private Collection<InvoiceDetail> details;
You only need to put an empty string as value for the action. Although in
most case it's enough to define the collection as @ReadOnly.@ListAction("Carrier.translateName")
private Collection<Carrier> fellowCarriers;
Now a new link is shown to the user:
<controller name="Carrier">
...
<action name="translateName" hidden="true"
class="org.openxava.test.actions.TranslateCarrierNameAction">
</action>
...
</controller>
And the action code:package org.openxava.test.actions;
import java.util.*;
import org.openxava.actions.*;
import org.openxava.test.model.*;
/**
* @author Javier Paniza
*/
public class TranslateCarrierNameAction extends CollectionBaseAction { // 1
public void execute() throws Exception {
Iterator it = getSelectedObjects().iterator(); // 2
while (it.hasNext()) {
Carrier carrier = (Carrier) it.next();
carrier.translate();
}
}
}
The action extends CollectionBaseAction (1), this way you can
use methods as getSelectedObjects() (2) that returns a
collection with the objects selected by the user. There are others useful
methods, as getObjects() (all elements collection), getMapValues()
(the collection values in map format) and getMapsSelectedValues()
(the selected elements in map format). In the case of @RowAction,
getSelectedObjects() and getMapsSelectedValues() return
an unique element, the one of the row of the action, even if the row is
not checked.<controller name="DefaultListActionsForCollections">
<extends controller="CollectionCopyPaste"/> <!-- New in v5.9 -->
<extends controller="Print"/>
<action name="exportAsXML"
class="org.openxava.test.actions.ExportAsXMLAction">
</action>
</controller>
In this way all the collections will have the actions of CollectionCopyPaste
(new in v5.9) and of Print controller (for export to
Excel and generate PDF report) and your own ExportAsXMLAction.
This has the same effect of @ListAction (look at custom list actions section) but it
applies to all collections at once.<controller name="DefaultRowActionsForCollections">
<extends controller="CollectionOpenInNewTab"/> <!-- New in v7.4 -->
<action name="openAsPDF"
class="org.openxava.test.actions.OpenAsPDFAction">
</action>
</controller>
In this way, the actions from the controller CollectionOpenInNewTab
(new in v7.4) and your own OpenAsPDFAction will be
present in each row of the collections. This has the same effect of @RowAction (look at custom list and row actions
section) but it applies to all collections at once.@DetailAction("Invoice.viewProduct")
@OneToMany (mappedBy="invoice", cascade=CascadeType.REMOVE)
private Collection<InvoiceDetail> details;
In this way the user has another link to click in the detail of the
collection element:
<controller name="Invoice">
...
<action name="viewProduct" hidden="true"
class="org.openxava.test.actions.ViewProductFromInvoiceDetailAction">
<use-object name="xava_view"/> <!-- Not needed since v4m2 -->
<use-object name="xavatest_invoiceValues"/> <!-- Not needed since v4m2 -->
</action>
...
</controller>
And the code of your action:package org.openxava.test.actions;
import java.util.*;
import javax.ejb.*;
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
public class ViewProductFromInvoiceDetailAction
extends CollectionElementViewBaseAction // 1
implements INavigationAction {
@Inject // Since v4m2
private Map invoiceValues;
public void execute() throws Exception {
try {
setInvoiceValues(getView().getValues());
Object number =
getCollectionElementView().getValue("product.number"); // 2
Map key = new HashMap();
key.put("number", number);
getView().setModelName("Product"); // 3
getView().setValues(key); // Since v4m5 you can
getView().findObject(); // use getParentView() instead
getView().setKeyEditable(false);
getView().setEditable(false);
}
catch (ObjectNotFoundException ex) {
getView().clear();
addError("object_not_found");
}
catch (Exception ex) {
ex.printStackTrace();
addError("system_error");
}
}
public String[] getNextControllers() {
return new String [] { "ProductFromInvoice" };
}
public String getCustomView() {
return SAME_VIEW;
}
public Map getInvoiceValues() {
return invoiceValues;
}
public void setInvoiceValues(Map map) {
invoiceValues = map;
}
}
You can see that it extends CollectionElementViewBaseAction (1)
thus it has available the view that displays the current element using getCollectionElementView()
(2). Also you can get access to the main view using getView()
(3) or to the parent view using getParentView() (since
v4m5), usually getView() and getParentView()
matches. In chapter 7
you will see more details about writing actions.@SaveAction("DeliveryDetail.save")
@OneToMany (mappedBy="delivery", cascade=CascadeType.REMOVE)
private Collection<DeliveryDetail> details;
You must have an action DeliveryDetails.save in your controllers.xml:<controller name="DeliveryDetail">
...
<action name="save"
class="org.openxava.test.actions.SaveDeliveryDetailAction">
<use-object name="xava_view"/> <!-- Not needed since v4m2 -->
</action>
...
</controller>
And define your action class for saving:package org.openxava.test.actions;
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
public class SaveDeliveryDetailAction extends SaveElementInCollectionAction { // 1
public void execute() throws Exception {
super.execute();
// Here your own code // 2
}
}
The more common case is extending the default behavior, for that you have
to extend the original class for saving a collection detail (1), that is SaveElementInCollection
action, then call to super from execute() method (2),
and after it, writing your own code.@RemoveSelectedAction("")
@OneToMany (mappedBy="delivery", cascade=CascadeType.REMOVE)
private Collection<DeliveryDetail> details;
In this case the action for removing the selected elements in the
collection will be missing in the User Interface. As you see, only it's
needed to declare an empty string as the name of the action.
@OnSelectElementAction("Formula.onSelectIngredient") // 1
@OneToMany(mappedBy="formula", cascade=CascadeType.REMOVE)
private Collection<FormulaIngredient> ingredients;
In this simple way (1), and thanks to the @OnSelectElementAction
annotation, you say that when the user clicks on the checkbox of the
collection row the Formula.onSelectIngredient action will be
executed. This action is declared in controllers.xml, in this
way:<controller name="Formula">
...
<action name="onSelectIngredient" hidden="true"
class="org.openxava.test.actions.OnSelectIngredientAction">
<use-object name="xava_view"/> <!-- Not needed since v4m2 -->
</action>
...
</controller>
Now, only remains the code of the OnSelectIngredientAction
class:public class OnSelectIngredientAction extends OnSelectElementBaseAction { // 1
public void execute() throws Exception {
int size = getView().getValueInt("selectedIngredientSize");
size = isSelected() ? size + 1 : size - 1; // 2
getView().setValue("selectedIngredientSize", new Integer(size));
}
}
The easiest way to implement the action is extending from OnSelectElementBaseAction, this allows you to
access to the property selected (by means of isSelected(),
2) that indicates wheter the user has selected or unselected the row; and
row (using getRow()) that indicates the row number of
the affected collection element.@OneToMany(mappedBy="seller")
@Editor("CustomersNames")
private Collection<Customer> customers;
In this case the CustomersNames editor will be used for
displaying/editing, instead of the default one. You must define your CustomersNames
editor in the editors.xml file in src/main/resources/xava
(just xava for v6 or older) of your project:<editor name="CustomersNames" url="customersNamesEditor.jsp"/>
Also you have to write the JSP code for your editor in customersNamesEditor.jsp.private int code;
@OneToMany(mappedBy="seller")
@SearchListCondition(value="${number} < 5", forViews="SearchListCondition, SearchListConditionBlank") // 1
@SearchListCondition(value="${number} < ${this.code}", forViews="SearchListConditionCode") // 2
private Collection<Customer> customers;
@OneToMany(mappedBy="parentContainer", cascade = CascadeType.REMOVE)
@Tree // 1
@ListProperties("description") // 2
@OrderBy("folder, itemOrder") // 3
// @Editor(value="TreeView") // 4 Not needed since v7.5
private Collection<TreeItem> treeItems;
Annotating your collection with @Tree (1) indicates that you want
to visualize the collection as a tree. With the @ListProperties
(2) annotation, you can define the properties to display at each branch.
The items are displayed in the natural order of the collection defined by
@OrderBy (3). You must have a String property named path
to be used as the branch's path; you can also name it differently by using
the @Tree annotation (1) and indicating pathProperty. In
version previous to v7.5 you have to annotate your collection with @Editor("TreeView")
(4) to show the tree editor.

@Tree(
forViews="", // 1
notForViews="", // 2
pathProperty="path", // 3
idProperties="", // 4
initialExpandedState=true, // 5
orderIncrement=2, // 6 Deprecated since version 7.2
pathSeparator="/" // 7
allowMoveNodes=true // 8 New in v7.5
)
@ListProperties("deliveryDate[invoice.deliveryDate], quantity, amount[invoice.amountsSum, invoice.vat, invoice.total]")
public class Invoice {
...
@OneToMany (mappedBy="invoice", cascade=CascadeType.REMOVE)
@ListProperties("deliveryDate[invoice.deliveryDate], quantity, amount[invoice.amountsSum, invoice.vat, invoice.total]")
private Collection<InvoiceDetail> details;
public Date getDeliveryDate() {
Date result = null;
for (InvoiceDetail detail: getDetails()) {
result = (Date) ObjectUtils.min(result, detail.getDeliveryDate());
}
return result;
}
@Money
public BigDecimal getAmountsSum() {
BigDecimal result = BigDecimal.ZERO;
for (InvoiceDetail detail: getDetails()) {
result = result.add(detail.getAmount());
}
return result;
}
@Money @Depends("vatPercentage, amountsSum")
public BigDecimal getVat() {
return getAmountsSum().multiply(getVatPercentage()).divide(HUNDRED, 2, BigDecimal.ROUND_HALF_UP);
}
@Money @Depends("vat")
public BigDecimal getTotal() {
return getVat().add(getAmountsSum());
}
}

@ListProperties("vatPercentage, total+[workCost.profitPercentage, workCost.profit, workCost.total]")
Note the + after total and then the total properties inside [].
In this case profitPercentage is a persistent property, so it is
editable. The profit and total are also persistent but
with @Calculation and @ReadOnly (so not editable). This is
the complete code:public class WorkCost {
...
@OneToMany (mappedBy="workCost")
@ListProperties("number, vatPercentage, total+[workCost.profitPercentage, workCost.profit, workCost.total]")
private Collection<WorkInvoice> invoices;
@DefaultValueCalculator(value=IntegerCalculator.class,
properties=@PropertyValue(name="value", value="13")
)
private int profitPercentage; // Persistent with getter and setters, editable
@Calculation("sum(invoices.total) * profitPercentage / 100")
@ReadOnly
private BigDecimal profit; // Persistent with getter and setters, not editable because of @ReadOnly
@Calculation("sum(invoices.total) + profit")
@ReadOnly
private BigDecimal total; // Persistent with getter and setters, not editable because of @ReadOnly
}

@Calculation("sum(invoices.total) * profitPercentage / 100")
@ReadOnly
private BigDecimal profit;
In this case the sum(invoices.total) inside @Calculation
is the summation of the total property of all the elements of invoices
collection, that is the same of total+ in @ListProperties,
that is the 131.08 you see in the image.@OneToMany(mappedBy="team", cascade=CascadeType.ALL)
@ListSubcontroller("Stuff")
private Collection<TeamMember> members;
You obtain this:
<controller name="Stuff" icon="exclamation">
<action name="viewNames" class="org.openxava.test.actions.ViewNamesOrRolesNamesFromTeamMember" icon="message-outline"/>
<action name="viewRoles" class="org.openxava.test.actions.ViewNamesOrRolesNamesFromTeamMember" icon="message">
<set value="true" property="roles"/>
</action>
</controller>
@OneToMany(mappedBy="company")
@Chart
Collection<Employee> employees;
@OneToMany(mappedBy="company")
@Chart(labelProperties="firstName, lastName", dataProperties="salary")
Collection<Employee> employees;

@OneToMany(mappedBy="corporation")
@Chart(type=ChartType.LINE)
private Collection employees;
@Chart(type = ChartType.PIE)
public Collection<Ratio> getExternalEmployeesRatio() {
EntityManager em = XPersistence.getManager();
// Query to count internal employees (email contains corporation name)
Query internalQuery = em.createQuery(
"SELECT COUNT(e) FROM CorporationEmployee e " +
"WHERE e.corporation.id = :corporationId AND LOWER(e.email) LIKE :pattern");
internalQuery.setParameter("corporationId", getId());
internalQuery.setParameter("pattern", "%" + name.toLowerCase() + "%");
Long internalCount = (Long) internalQuery.getSingleResult();
// Query to count external employees (email does not contain corporation name)
Query externalQuery = em.createQuery(
"SELECT COUNT(e) FROM CorporationEmployee e " +
"WHERE e.corporation.id = :corporationId AND LOWER(e.email) NOT LIKE :pattern");
externalQuery.setParameter("corporationId", getId());
externalQuery.setParameter("pattern", "%" + name.toLowerCase() + "%");
Long externalCount = (Long) externalQuery.getSingleResult();
// Create and return the collection of ratios
Collection<Ratio> ratios = new ArrayList<>();
ratios.add(new Ratio("Internal", internalCount.intValue()));
ratios.add(new Ratio("External", externalCount.intValue()));
return ratios;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Ratio {
String description;
int value;
}
@OneToMany(mappedBy="parent")
@SimpleList
Collection<StaffTurnover> turnoverByYear;
Applies to @OneToMany/@ManyToMany and calculated collections
With @SearchListTab, you can specify which tab to display in the dialog when use an add action.
@OneToMany(mappedBy="seller")
@SearchListTab("Demo")
private Collection<Customer> customers;
For this in the Customer we must have a tab called Demo:@Entity
@Tab(name="Demo", properties="name, type, seller.name", defaultOrder="${name} desc", baseCondition="${type} = 'R'")
public class Customer {
In addition, the forViews and notForViews attributes
are available in @SearchListTab.Applies to @OneToMany/@ManyToMany and calculated collections
With @NoDefaultActions you can hide actions from the DefaultListActionsForCollections and DefaultRowActionsForCollections controllers.
@OneToMany(mappedBy="vendedor")
@NoDefaultActions
private Collection<Carrier> carriers;

@Transient
@DefaultValueCalculator(value=EnumCalculator.class,
properties={
@PropertyValue(name="enumType", value="org.openxava.test.model.Delivery$DeliveredBy")
@PropertyValue(name="value", value="CARRIER")
}
)
@OnChange(OnChangeDeliveryByAction.class)
private DeliveredBy deliveredBy;
public enum DeliveredBy { EMPLOYEE, CARRIER }
You can see that the syntax is exactly the same as in the case of a
regular property of an entity; you can even use enum and @DefaultValueCalculator. After defining the
property you can use it in the view as usual, for example with @OnChange or putting it as member of a view.@View( members=
"number;" +
"type;" +
"name, Customer.changeNameLabel();" +
...
The visual effect will be:
@View( name="Simple", members=
"number;" +
"type;" +
"name, Customer.changeNameLabel(ALWAYS);" +
...
The standard way to expose actions to the user is using the controllers
(actions in a bar), the controllers are reusable between views, but
sometimes you will need an action specific to a view, and you want display
it inside the view (not in the button bar), for these cases the view
actions may be useful.package org.openxava.test.model;
import javax.persistence.*;
import org.openxava.annotations.*;
/**
* Example of an transient OpenXava model class (not persistent). <p>
*
* This can be used, for example, to display a dialog,
* or any other graphical interface.<p>
*
* Note that is not marked as @Entity <br>
*
* @author Javier Paniza
*/
@Views({
@View(name="Family1", members="subfamily"),
@View(name="Family2", members="subfamily"),
@View(name="WithSubfamilyForm", members="subfamily"),
@View(name="Range", members="subfamily; subfamilyTo")
})
public class FilterBySubfamily {
@ManyToOne(fetch=FetchType.LAZY) @Required
@NoCreate(forViews="Family1, Family2")
@NoModify(forViews="Family2, WithSubfamilyForm")
@NoSearch(forViews="WithSubfamilyForm")
@DescriptionsLists({
@DescriptionsList(forViews="Family1",
condition="${family.number} = 1", order="${number} desc"
),
@DescriptionsList(forViews="Family2",
condition="${family.number} = 2"
)
})
private Subfamily subfamily;
@ManyToOne(fetch=FetchType.LAZY)
private Subfamily subfamilyTo;
public Subfamily getSubfamily() {
return subfamily;
}
public void setSubfamily(Subfamily subfamily) {
this.subfamily = subfamily;
}
public Subfamily getSubfamilyTo() {
return subfamilyTo;
}
public void setSubfamilyTo(Subfamily subfamilyTo) {
this.subfamilyTo = subfamilyTo;
}
}
For defining a model class as transient you only need to define a regular
Java class without @Entity annotation. You mustn't put the
mapping annotations nor declare properties as key.