Reference guide:
Model |
View |
Tabular data |
Object/relational mapping |
Controllers |
Application |
Customizing
Controllers are used to define actions (buttons, links, images) that the final user can click. The controllers are defined in the
controllers.xml file that is in the
src/main/resources/xava directory of your project (in
xava directory in v6 or older).
The actions are not defined in entities because there are a lot of generic actions that can be applied to any entity.
In
openxava/src/main/resources/xava (in
OpenXava/xava for v6 or older) you have a
default-controllers.xml that contains a group of generic controllers that can be used in your applications.
The
controllers.xml file contains an element of type
<controllers/> with the syntax:
<controllers>
<env-var ... /> ... <!-- 1 -->
<object ... /> ... <!-- 2 -->
<controller ... /> ... <!-- 3 -->
</controllers>
- env-var (several, optional): Variable that contains configuration information. This variable can be accessed from the actions and filters, and its value can be overwritten in each module.
- object (several, optional): Defines Java object with session scope; that is objects that are created for a user and exist during his session.
- controller (several, optional): A controller is a group of actions (controller is optional since v6.4.2, before it was required).
Environment variables
The environment variables contain configuration information. These variables can be accessed from the actions and filters, and their value can be overwritten in each module. Their syntax is:
<env-var
name="name" <!-- 1 -->
value="value" <!-- 2 -->
/>
- name (required): Name of the environment variable in uppercase and using underscore to separate words.
- value (required): Value for the environment variable.
These are some example:
<env-var name="MYAPPLICATION_DEFAULT_YEAR" value="2007"/>
<env-var name="MYAPPLICATION_COLOR" value="RED"/>
To access to an environment variable value from an action use
getEnvironment():
String color = getEnvironment().getValue("MYAPPLICATION_COLOR");
Session objects
The Java objects declared in
controllers.xml have session scope; that is, they are objects that are created for a user and exist during his session. It's syntax is:
<object
name="objectName" <!-- 1 -->
class="objectType" <!-- 2 -->
value="initialValue" <!-- 3 -->
scope="module|global" <!-- 4 New in v2.1 -->
/>
- name (required): Name of the object, usually you use the application name as prefix to avoid name collision in large projects.
- class (required): Full qualified Java class for this object.
- value (optional): Initial value for the object.
- scope (optional): (New in v2.1) The default value is module. If you use module scope each module will have its own copy of this object. If you use global scope the same object will be shared by all modules of all OpenXava applications (running in the same .war).
Defining session objects is very easy, you can see the defined ones in
openxava/src/main/resources/xava/default-controllers.xml (
OpenXava/xava/default-controllers.xml in v6 or older):
<object name="xava_view" class="org.openxava.view.View"/>
<object name="xava_referenceSubview" class="org.openxava.view.View"/>
<object name="xava_tab" class="org.openxava.tab.Tab"/>
<object name="xava_mainTab" class="org.openxava.tab.Tab"/>
<object name="xava_row" class="java.lang.Integer" value="0"/>
<object name="xava_language" class="org.openxava.session.Language"/>
<object name="xava_newImageProperty" class="java.lang.String"/>
<object name="xava_currentReferenceLabel" class="java.lang.String"/>
<object name="xava_activeSection" class="java.lang.Integer" value="0"/>
<object name="xava_previousControllers" class="java.util.Stack"/>
<object name="xava_previousViews" class="java.util.Stack"/>
These objects are used by OpenXava in order to work, although it is quite normal that you use some of these from your actions. If you want to create your own objects you can do it in your
controllers.xml in the
src/main/resources/xava directory (just
xava before v7) of your project.
The controller and its actions
The syntax of controller is:
<controller
name="name" <!-- 1 -->
>
<extends ... /> ... <!-- 2 -->
<action ... /> ... <!-- 3 -->
<subcontroller .../> ... <!-- 4 New in v4.8 -->
</controller>
- name (required): Name of the controller.
- extends (several, optional): Allows to use multiple inheritance, to do this the controller inherits all actions from other controller(s). Add the excluded-actions (new in v5.8) attribute to prevent certain actions to be inherited.
- action (several, required): Implements the logic to execute when the final user clicks a button or link.
- subcontroller (several, optional): (New in v4.8) Allows grouping several actions that are shown in a drop-down button.
Since version 5.6 actions and subcontrollers can be intercalated.
The controllers consist of actions, and actions are the main things. Here is the syntax:
<action
name="name" <!-- 1 -->
label="label" <!-- 2 -->
description="description" <!-- 3 -->
mode="detail|list|ALL" <!-- 4 -->
icon="icon" <!-- 5 New in v5.4 -->
image="image" <!-- 6 -->
class="class" <!-- 7 -->
hidden="true|false" <!-- 8 -->
on-init="true|false" <!-- 9 -->
on-each-request="true|false" <!-- 10 New in v2.1.2 -->
before-each-request="true|false" <!-- 11 New in v2.2.5 -->
after-each-request="true|false" <!-- 12 New in v4.0.1 -->
by-default="never|almost-never (new in v4m6)|
if-possible|almost-always|always" <!-- 13 -->
takes-long="true|false" <!-- 14 -->
confirm="true|false" <!-- 15 -->
keystroke="keystroke" <!-- 16 New in v2.0.1 -->
show-dialog="true|false" <!-- 17 Only in v4m1 -->
hide-dialog="true|false|default" <!-- 18 Only in v4m1 -->
in-each-row="true|false" <!-- 19 New in v4m4 -->
process-selected-items="true|false" <!-- 20 New in v5.7 -->
available-on-new="true|false" <!-- 21 New in v5.8 -->
loses-changed-data="true|false" <!-- 22 New in v6.3 -->
>
<set ... /> ... <!-- 23 -->
<use-object ... /> ... <!-- 24 -->
</action>
- name (required): Action name that must be unique within its controller, but it can be repeated in other controllers. When you reference an action always use the format ControllerName.actionName.
- label (optional): Button label or link text. It's much better to use i18n files.
- description (optional): Description text of the action. It's much better to use i18n files.
- mode (optional): Indicates in which mode the action has to be visible. The default value is ALL, that means that this action is always visible.
- icon (optional): (New in v5.4) Icon id from Material Design Icons. For example, if you write icon="bell" a bell will be used as icon for you action and you don't need to include any image. Since v5.4 this is the preferred way to define an icon instead of image.
- image (optional): URL of the image associated with this action. In the current implementation if you specify an image, it is shown to user in link format. Until v4.1.x the root for the URL was xava, it was needed to use images/ as prefix. Since v4.2 the root depend on the style and in the default style it is xava/images, so it is not longer needed to use the images/ as prefix, though it is still supported for not breaking backward compatibility. If you define both image and icon, icon has preference over image, though you can change this behavior with useIconsInsteadOfImages=false in xava.properties. Inside portals image has always preference.
- class (optional): Implements the logic to execute. Must implement IAction interface.
- hidden (optional): A hidden action is not shown in the button bar, although it can be used in all other places, for example to associate it to an event, as action of a property, in collections, etc. The default is false.
- on-init (optional): If you set this property to true, then the action will be executed automatically on initiating the module. The default is false.
- on-each-request (optional): (New in v2.1.2) If you set this property to true, then the action will be executed automatically on each request of the user, that is, on first module execution and before each user action execution. In the moment of execution all OpenXava session objects are setup and ready to use. That is, from this action you can use xava_view and xava_tab. The default is false. Using in conjunction with mode you can discriminate the execution of this action for a concrete mode (list or detail) (new in v3.0.2).
- before-each-request (optional): (New in v2.2.5) If you set this property to true, then the action will be executed automatically before each request of the user, that is, on first module execution and before each user action execution, but before the OpenXava session objects are setup and ready to use. That is, from this action you cannot use xava_view or xava_tab. The default is false. Using in conjunction with mode you can discriminate the execution of this action for a concrete mode (list or detail) (new in v3.0.2).
- after-each-request (optional): (New in v4.0.1) If you set this property to true, then the action will be executed automatically after each request of the user, that is, on first module execution and before each user action execution. The default is false. Using in conjunction with mode you can discriminate the execution of this action for a concrete mode (list or detail).
- by-default (optional): Indicates the weight of this action on choosing the action to execute as the default one. The default action is executed when the user presses ENTER. The default is almost-never (until v4m5 was never).
- takes-long (optional): If you set it to true, then you are indicating that this action takes long time in executing (minutes or hours). In the current implementation OpenXava shows a progress bar. The default is false.
- confirm (optional): If you set it to true, then before executing the action a dialog is shown to the user to ask if he is sure to execute it. The default is false.
- keystroke (optional): (New in v2.0.1) Defines a keystroke that the user can press for executing this action. The possible values are the same as for javax.swing.KeyStroke. Examples: "control A", "alt x", "F7".
- show-dialog (optional): (Only in v4m1, not available since v4m2) If true after the action execution the current user interface will be show inside a modal dialog. The default is false.
- hide-dialog (optional): (Only in v4m1, not available since v4m2) If true then if currently there is a modal dialog shown then it will be hidden. The default value is default, it means that the dialog will be hidden only when the action name is "cancel" or it is the default action.
- in-each-row (optional): (New in v4m4) If true and this action is shown in list mode or in a collection the action will be shown in each row. The action must have an int row property (TabBaseAction and CollectionBaseAction already have the row property). The default is false.
- process-selected-items (optional): (New in v5.7) If true this action processes the selected rows in the list. In this way OpenXava can hide this action if selecting items is not available. The default is false.
- available-on-new (optional): (New in v5.8) If false this action is not available when the user is creating a new entity (when he clicks on New). The default is true.
- loses-changed-data (optional): (New in v6.3) If true it means that when executed all the data changed by the user in the view since the last saving will be lost. This can be used by OpenXava in order to ask for confirmation before executing the action The default is false.
- set (several, optional): Sets a value of action properties. Thus the same action class can be configured in different ways and it can be used in several controllers.
- use-object (several, optional): Assigns a session object to an action property just before executing the action. After the execution the property value is put back into the context again (update the session object, thus you can update even immutable objects).
Actions are short life objects, when a user clicks a button, then the action object is created, configured (with set,
use-object or
@Inject) and executed. After that the session objects are updated, and finally the action object is discarded.
A plain controller might look like this:
<controller name="Remarks">
<action name="hideRemarks"
class="org.openxava.test.actions.HideShowPropertyAction">
<set property="property" value="remarks" />
<set property="hide" value="true" />
<use-object name="xava_view"/> <!-- Not needed since v4m2 -->
</action>
<action name="showRemarks" mode="detail"
class="org.openxava.test.actions.HideShowPropertyAction">
<set property="property" value="remarks" />
<set property="hide" value="false" />
<use-object name="xava_view"/> <!-- Not needed since v4m2 -->
</action>
<action name="setRemarks" mode="detail"
class="org.openxava.test.actions.SetPropertyValueAction">
<set property="property" value="remarks" />
<set property="value" value="Hell in your eyes" />
<use-object name="xava_view"/> <!-- Not needed since v4m2 -->
</action>
</controller>
Now you can include this controller in the module that you want; this is done by editing in
application.xml, in
src/main/resources/xava folder of your project (in
xava folder with v6 or older) , the module in which you can use these actions:
<module name="Deliveries">
<model name="Delivery"/>
<controller name="Typical"/>
<controller name="Remarks"/>
</module>
Thus you have in your module the actions of
Typical (CRUD and printing) plus those defined by you in the controller named
Remarks. The top button bar of the module will have this aspect:

And the bottom button bar:

Note that actions
with image/icon are located at the top of the page, and actions
without image/icon are located at the bottom of the page.
You can write the code for
hideRemarks like this:
package org.openxava.test.actions;
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
public class HideShowPropertyAction extends ViewBaseAction { // 1
private boolean hide;
private String property;
public void execute() throws Exception { // 2
getView().setHidden(property, hide); // 3
}
public boolean isHide() {
return hide;
}
public void setHide(boolean b) {
hide = b;
}
public String getProperty() {
return property;
}
public void setProperty(String string) {
property = string;
}
}
An action must implement
IAction, but usually it extends from a base class that implements this interface. The base action more basic is
BaseAction that implements most of the method of
IAction except
execute(). In this case you use
ViewBaseAction as the base class.
ViewBaseAction has the property
view of type
View. This, joined to the next declaration in action...
<use-object name="xava_view"/> <!-- Not needed since v4m2 -->
...allows you to manage the view (the user interface) from an action using view.
The
<use-object /> gets the session object
xava_view and assigns it to the property
view (removing the prefix
xava_, in general removes the prefix
myapplication_ before assigning object to property) of your action just before calling
execute(). Though since v4m2 is not needed to use
<use-object /> to inject
xava_view because
ViewBaseAction already inject it using
@Inject.
Now inside the
execute() method you can use
getView() as you want (3), in this case for hiding a property. You can see all View possibilities in the JavaDoc of
org.openxava.view.View.
With...
<set property="property" value="remarks" />
<set property="hide" value="true" />
you can set constant values to the properties of your action.
Subcontroller (new in v4.8)
With the subcontroller we can grouping several actions and show them in a drop-down button.
Here is its syntax:
<subcontroller
controller="controllerName" <!-- 1 -->
image="image" <!-- 2 -->
icon="icon" <!-- 3 New in v5.4 -->
mode="detail|list|ALL" /> <!-- 4 -->
- controller: (required) Controller's name that we reference
- image: (optional) URL of the image associated with this subcontroller
- icon: (optional): (New in v5.4) Icon id from Material Design Icons. For example, if you write icon="bell" a bell will be used as icon for you subcontroller and you don't need to include any image. Since v5.4 this is the preferred way to define an icon instead of image.
- mode: (optional) The default value is ALL. Indicates in which mode the action has to be visible.
In your controllers.xml you can put:
<controllers>
<controller name="Color">
<action name="seeMessage"
class="org.openxava.test.actions.SeeMessageAction"/>
<subcontroller
controller="ColorSub" <!-- 1 -->
icon="menu"/>
</controller>
</controllers>
You obtain this:
You can put the controller name in i18n files.
Dependency injection
Using
Dependency injection the value of a field or property is set by the framework, not by the developer.
@Inject (JSR-330) (new in v4m2)
Since v4m2 OpenXava supports
JSR-330, the Java standard for dependency injection. In order to inject a session object in an action you only have to annotate a field with
@javax.inject.Inject . Thas is, if you have a session object and an action in your
controllers.xml as following:
<controllers>
<object name="xavatest_activeYear" class="java.lang.Integer" value="2010" scope="global"/>
<controller name="ActiveYear">
<action name="change"
class="org.openxava.test.actions.ChangeActiveYearAction"/>
</controller>
</controllers>
In order to inject the
xavatest_activeYear object into
ChangeActionYearAction you have to use
@Inject, in this way:
public class ChangeActiveYearAction extends ViewBaseAction {
@Inject
private int activeYear; // Getter and setter are not needed
public void execute() throws Exception {
assert activeYear == 2010; // The value from session object
activeYear = 2012; // It changes the value of session object
}
}
Thus the
xavatest_activeYear object is injected in the
activeYear field before call to
execute(); and after
execute() execution the value of the
activeYear field is assigned back to
xavatest_actionYear. So, you can change session objects state from inside an action, even with immutable and primitive types.
As you see, the default name of the session object to inject is the name of the attribute ignoring the prefix (ignoring
xavatest_ in this case). Although you can use
@Named (also JSR-330 standard) to specify a different name for session object and field:
public class ChangeActiveYearAction extends ViewBaseAction {
@Inject @Named("xavatest_activeYear")
private int year; // getter and setter are not needed
public void execute() throws Exception {
assert year == 2010; // The value from xavatest_actionYear session object
year = 2012; // It changes the value of xavatest_actionYear
}
}
In this way the
xavatest_activeYear session object is injected into the
year field of the action.
Using <use-object /> for dependency injection
Dependency injection has been in OpenXava since its very beginning. The traditional way to inject session objects in an action is by means of
<use-object /> in
<action/> declaration. If you are using a version previous to v4m2, you must write the above example in using
<use-object/> in
controllers.xml:
<controllers>
<object name="xavatest_activeYear" class="java.lang.Integer" value="2010" scope="global"/>
<controller name="ActiveYear">
<action name="change"
class="org.openxava.test.actions.ChangeActiveYearAction">
<use-object name="xavatest_activeYear"/>
</action>
</controller>
</controllers>
And writing your class without
@Inject, and using getter and setter:
public class ChangeActiveYearAction extends ViewBaseAction {
private int activeYear; // No @Inject, we are using <use-object/>
public void execute() throws Exception {
assert activeYear == 2010; // The value from session object
activeYear = 2012; // It changes the value of session object
}
public void setActiveYear(int activeYear) { // Setter and...
this.activeYear = activeYear;
}
public int getActiveYear() { // getter are needed
return activeYear;
}
}
You can use
action-property attribute to specify a different name for session object and field:
<action name="change"
class="org.openxava.test.actions.ChangeActiveYearAction">
<use-object name="xavatest_activeYear" action-property="year"/>
</action>
And then you have to write the action in this way:
public class ChangeActiveYearAction extends ViewBaseAction {
private int year; // No @Inject, we are using <use-object/>
public void execute() throws Exception {
assert year == 2010; // The value from xavatest_actionYear session object
year = 2012; // It changes the value of xavatest_actionYear
}
public void setYear(int year) { // Setter and...
this.year = year;
}
public int getYear() { // getter are needed
return year;
}
}
You see how
action-property is the counterpart of
@Name of JSR-330.
You can use both
<use-object/> and
@Inject for dependency injection, but given that JSR-330 is the Java standard it's the favorite one.
(New in v4.7) If the session object to be injected has a property named
request, this property is populated with the current
HttpServletRequest before injecting the object in the action. It works in this way for objects injected using
<use-object/> or
@Inject.
Controllers inheritance
You can create a controller that inherits all actions from one or more controllers. An example of this is the generic controller called
Typical, this controller is in
openxava/src/main/resources/xava/default-controllers.xml (in
OpenXava/xava/default-controllers.xml for v6 or older):
<controller name="Typical">
<extends controller="Print"/>
<extends controller="CRUD"/>
</controller>
When you assign the controller
Typical to a module this module will have available all actions of
Print controller (to generate PDF reports and export to Excel) and
CRUD controller (to Create, Read, Update and Delete)
You can use inheritance to refine the way a standard controller works, e. g. like this:
<controller name="Family">
<extends controller="Typical" excluded-actions="refresh, delete"/>
<action name="new" icon="library-plus"
class="org.openxava.test.actions.CreateNewFamilyAction">
<use-object name="xava_view"/> <!-- Not needed since v4m2 -->
</action>
</controller>
As you see the name of your action
new matches with an action in
Typical controller (in reality in
CRUD controller from which extends
Typical). In this case the original action is ignored and your action is used. Thus you can put your own logic to execute when a final user clicks the 'new' link. Also you can prevent that certain actions would be inherited using the
excluded-action attribute
(new in v5.8), in this case
Family will not include
refresh and
delete, normally available from
CRUD via
Typical.
List mode actions
You can write actions that apply to several objects. These actions are usually shown in list mode only and normally have effects on the objects chosen by user only.
An example can be:
<action name="deleteSelected" mode="list" <!-- 1 -->
confirm="true" <!-- 2 -->
class="org.openxava.actions.DeleteSelectedAction">
</action>
You set
mode=”list” in order to show it only in list mode (1). Since this action deletes records you require that the user must confirm explicitly before the action is executed (2). It's not needed to include a
<use-object/> for
xava_tab (new in v2.1.4).
The action source code:
package org.openxava.actions;
import java.util.*;
import org.openxava.model.*;
import org.openxava.validators.*;
/**
* @author Javier Paniza
*/
public class DeleteSelectedAction extends TabBaseAction implements IModelAction { // 1
private String model;
public void execute() throws Exception {
// int [] selectedOnes = getTab().getSelected(); // 2
// int [] selectedOnes = getSelected(); // New in v4m4. Deprecated in v4.7 // 2
Map [] selectedOnes = getSelectedKeys(); // New in 4.7 // 2
if (selectedOnes != null) {
for (int i = 0; i < selectedOnes.length; i++) {
Map key = selectedOnes[i];
try {
MapFacade.remove(model, key); // 3
}
catch (ValidationException ex) {
addError("no_delete_row", new Integer(i), key); // 4
addErrors(ex.getErrors());
}
catch (Exception ex) {
addError("no_delete_row", new Integer(i), key);
}
}
getTab().deselectAll(); // 5
resetDescriptionsCache(); // 6
}
}
public void setModel(String modelName) { // 7
this.model = modelName;
}
}
This action is a standard action of OpenXava, but it allows you to see the things that you can do within an action in list mode. You can observe (1) how the action extends from
TabBaseAction and implements
IModelAction. Since it extends from
TabBaseAction (new in v2.1.4) it has a group of utilities and you don't need to implement all methods of
IAction; and as it implements
IModelAction this action has a method called
setModel() (7) that receives the model name (the name of OpenXava component) before executing it.
You can access to the
Tab using the
getTab() method (2); this method is implemented in
TabBaseAction and it allows you to access to the
xava_tab session object. By means of
getTab() you are allowed to manage the list of displayed objects. For example, with
getTab().getSelected() (2) you obtain the indexes of selected rows, though since v4m4 is better to use
getSelected() instead which is a method from
TabBaseAction. Since v4.7
getSelected() is
deprecated instead we use
getSelectedKeys() that returns the keys map. With
getTab().getTableModel() a table model to access to data, and with
getTab().deselectAll() you deselect the rows. You can take a look of
org.openxava.tab.Tab JavaDoc for more details on its possibilities.
Something very interesting you can see in this example is the use of
MapFacade (3).
MapFacade allows you to access the data model using Java maps (
java.util.Map). This is useful, if you get data from
Tab or
View in
Map format and you want to update the model (and therefore the database) with it, or vice versa. All generic classes of OpenXava use
MapFacade to manage the model and you also can use
MapFacade. As general design tip: working with maps is useful in the case of generic logic, but if you need to program specific things it is better to use directly the object of model layer. For more details have a look at the JavaDoc of
org.openxava.model.MapFacade.
You see here how to display messages to the user with
addError(). The
addError() method receives the id of an entry in your i18n files and the argument to send to the message. The added messages are displayed to the user as errors. If you want to add warning or informative messages you can use
addMessage(), addInfo() (new in v4.3) or addWarning() (new in v4.3) whose behavior is like
addError(). The i18n files that hold errors and messages must be called
MyProject-messages.properties and the language sufix (_en, _ca, _es, _it, etc). You can see the examples in
openxavatest/src/main/resources/i18n (in
OpenXavaTest/i18n for v6 or older). All not caught exceptions produces a generic error messages, except if the not caught exception is of the type
ValidationException. In this case the message exception is displayed.
The
resetDescriptionsCache() (6) method deletes all cache entries used by OpenXava to display descriptions list (combos). It's a good idea to call it whenever data is updated.
You can see more possibilities in
org.openxava.actions.BaseAction and
org.openxava.actions.TabBaseAction JavaDoc.
Since v2.1.4 this type of actions
can also be used as @ListAction (<list-action/> of a
<collection-view/>).
Overwriting default search
When a module is shown in list mode and the user clicks to display a detail, then OpenXava searches the corresponding object and displays it in detail. Now, if in detail mode the user fills the key fields and clicks on search (the binoculars), it also does the same. And when the user navigates by the records clicking the next or previous buttons then it does the same search. How can you customize this search? Let's see that:
You only need to define the module in
src/main/resources/xava/application.xml (just in
xava folder for v6 or older) this way:
<module name="Deliveries">
<env-var name="XAVA_SEARCH_ACTION" value="Deliveries.search"/>
<model name="Delivery"/>
<controller name="Typical"/>
<controller name="Remarks"/>
<controller name="Deliveries"/>
</module>
You see how it is necessary to define an environment variable named XAVA_SEARCH_ACTION that contains the action that you want to use for searching. This action is defined in
controllers.xml:
<controller name="Deliveries">
<action name="search" mode="detail"
by-default="if-possible" hidden="true"
class="org.openxava.test.actions.SearchDeliveryAction"
keystroke="F8">
<use-object name="xava_view"/> <!-- Not needed since v4m2 -->
</action>
...
</controller>
And its code:
package org.openxava.test.actions;
import java.util.*;
import org.openxava.actions.*;
import org.openxava.util.*;
/**
* @author Javier Paniza
*/
public class SearchDeliveryAction extends SearchByViewKeyAction { // 1
public void execute() throws Exception {
super.execute(); // 2
if (!Is.emptyString(getView().getValueString("employee"))) {
getView().setValue("deliveredBy", new Integer(1));
getView().setHidden("carrier", true);
getView().setHidden("employee", false);
}
else {
Map carrier = (Map) getView().getValue("carrier");
if (!(carrier == null || carrier.isEmpty())) {
getView().setValue("deliveredBy", new Integer(2));
getView().setHidden("carrier", false);
getView().setHidden("employee", true);
}
else {
getView().setHidden("carrier", true);
getView().setHidden("employee", true);
}
}
}
}
In this action you have to search the database (or through EJB2, EJB3 JPA or Hibernate) and fill the view. Most times it is better that it extends
SearchByViewKeyAction (1) and within
execute() write a
super.execute() (2).
OpenXava comes with 3 predefined search actions:
- CRUD.searchByViewKey: This is the default one. It does a search using the key values in the view, it executes no event.
- CRUD.searchExecutingOnChange: Works as searchByViewKey but it executes the @OnChange/on-change actions after search data.
- CRUD.searchReadOnly: Works as searchByViewKey but it set the detail view to not editable state on searching. Useful for creating read only modules.
If you want that the
@OnChange/on-change actions will be executed on search then you must define your module this way:
<module name="Products3ChangeActionsOnSearch">
<env-var name="XAVA_SEARCH_ACTION" value="CRUD.searchExecutingOnChange"/>
<model name="Product3"/>
<view name="WithDescriptionsList"/>
<controller name="Typical"/>
<controller name="Products3"/>
</module>
As you see, simply by setting the value of the XAVA_SEARCH_ACTION environment variable.
Initialize a module with an action
By setting
on-init=”true” when you define an action, you configure that this action will be executed automatically when the module is executed for the first time. This is a chance to initialize the module. Let's see an example. In your
controllers.xml you write:
<controller name="Invoices2002">
<action name="init" on-init="true" hidden="true"
class="org.openxava.test.actions.InitDefaultYearTo2002Action">
<!-- <use-object name="xavatest_defaultYear"/>
Since v4m2 you can use @Inject instead -->
<!-- <use-object name="xava_tab"/>
Since v4m2 you can use @Inject instead -->
</action>
...
</controller>
And in your action:
package org.openxava.test.actions;
import javax.inject.*;
import org.openxava.actions.*;
import org.openxava.tab.*;
/**
* @author Javier Paniza
*/
public class InitDefaultYearTo2002Action extends BaseAction {
@Inject // Since 4m2, if you do not use <use-object />
private int defaultYear;
@Inject // Since 4m2, if you do not use <use-object />
private Tab tab;
public void execute() throws Exception {
setDefaultYear(2002); // 1
tab.setTitleVisible(true); // 2
tab.setTitleArgument(new Integer(2002)); // 3
}
public int getDefaultYear() {
return defaultYear;
}
public void setDefaultYear(int i) {
defaultYear = i;
}
public Tab getTab() {
return tab;
}
public void setTab(Tab tab) {
this.tab = tab;
}
}
In this action you set the default year to 2002 (1), you make the title list visible (2) and you assign a value as an argument to that title (3). The list title is defined in the i18n files, usually it's used for reports, but you can show it in list mode too.
Calling another module
Sometimes it's convenient to call programmatically one module from another one. For example, imagine that you want to show a list of customers and when the user clicks on one customer, then a list of its invoices is displayed and the user can choose an invoice to edit. One way to obtain this effect is to have a module with only list mode and when the user clicks on a detail, the user is directed to an invoices module that shows only the invoices of the chosen customer. Let's see it. First you need to define the module in
application.xml this way:
<module name="InvoicesFromCustomers">
<env-var name="XAVA_LIST_ACTION" value="Invoices.listOfCustomer"/> <!-- 1 -->
<model name="Customer"/>
<controller name="Print"/>
<controller name="ListOnly"/> <!-- 2 Not needed since v6.0 -->
<mode-controller name="Void"/> <!-- 3 Not needed since v6.0 -->
</module>
In this module only the list is shown (without detail part), for this you set the mode controller to
Void (3) thus 'detail' and 'list' links are not displayed; and also you add a controller called
ListOnly (2) in order to show the list mode, and only the list mode (if you only set the mode controller to Void the detail, and only the detail is displayed). Since v6.0 is not needed to use
ListOnly and
Void because the mode buttons no longer exist, in any module.
Moreover you declare the variable XAVA_LIST_ACTION to define your custom action. When the user clicks the link in each row, then your own action will be executed. You must declare this action in
controllers.xml:
<controller name="Invoices">
<action name="listOfCustomer" hidden="true"
class="org.openxava.test.actions.ListCustomerInvoicesAction">
<!-- <use-object name="xava_tab"/>
Since v4m2 you can use @Inject instead -->
</action>
...
</controller>
And the action code:
package org.openxava.test.actions;
import java.util.*;
import org.openxava.actions.*;
import org.openxava.controller.*;
import org.openxava.tab.*;
/**
* @author Javier Paniza
*/
public class ListCustomerInvoicesAction extends BaseAction
implements IChangeModuleAction, // 1
IModuleContextAction { // 2
private int row; // 3
@Inject // Since v4m2, if you do not use <use-object />
private Tab tab;
private ModuleContext context;
public void execute() throws Exception {
Map customerKey = (Map) tab.getTableModel().getObjectAt(row); // 4
int customerNumber = ((Integer) customerKey.get("number")).intValue();
Tab invoiceTab = (Tab)
context.get("OpenXavaTest", getNextModule(), "xava_tab"); // 5
invoiceTab.setBaseCondition("${customer.number} = "+customerNumber); // 6
}
public int getRow() { // 3
return row;
}
public void setRow(int row) { // 3
this.row = row;
}
public Tab getTab() {
return tab;
}
public void setTab(Tab tab) {
this.tab = tab;
}
public String getNextModule() { // 7
return "CustomerInvoices";
}
public void setContext(ModuleContext context) { // 8
this.context = context;
}
public boolean hasReinitNextModule() { // 9
return true;
}
}
In order to change to another module the action implements
IChangeModuleAction (1) thus forces the action to have a method called
getNextModule() (7). This will indicate to which module OpenXava will switch after executing this action. The method
hasReinitNextModule() (9) indicates, whether you want that the target module has re-initiated on changing to it.
On the other hand this action implements
IModuleContextAction (2) too and therefore it receives an object of type
ModuleContext with the method
setContext() (8).
ModuleContext allows you to access the session objects of others modules. This is useful to configure the target module before changing to it.
Since v4m1
BaseAction implements
IModuleContextAction, so you only need to use
getContext() from your
execute() method:
public class ListCustomerInvoicesAction extends BaseAction
implements IChangeModuleAction,
// IModuleContextAction { // Not needed since v4m1
...
// private ModuleContext context; // Not needed since v4m1
public void execute() throws Exception {
...
Tab invoiceTab = (Tab)
getContext(). // You can use getContext() from BaseAction
get("OpenXavaTest", getNextModule(), "xava_tab");
...
}
...
// public void setContext(ModuleContext context) { // Not needed since v4m1
// this.context = context;
// }
...
}
Another detail is that the action specified in XAVA_LIST_ACTION must have a property named
row (3); before executing the action this property is filled with the row number that user has clicked.
If you keep in mind the above details it is easy to understand the action:
- Gets the key of the object associated to the clicked row (4), to do this it uses the tab of the current module.
- Accesses to the tab of the target module using context (5).
- Sets the base condition of the tab of target module using the key obtained from current tab.
Showing a new view (new in v4m2)
As an alternative to change the module you can choose to show a new view. This is easy, you only need to use the APIs available from
View. There are methods such as
showNewView(),
showView() and
returnToPreviousView(). An example:
public class ViewCustomerFromInvoiceAction extends ViewBaseAction {
public void execute() throws Exception {
try {
Object number = getView().getValue("customer.number"); // 1
Map key = new HashMap();
key.put("number", number);
showNewView(); // 2
getView().setModelName("Customer"); // 3
getView().setValues(key); // 4
getView().findObject(); // 5
getView().setKeyEditable(false);
getView().setEditable(false);
setControllers("Return"); // 6
}
catch (ObjectNotFoundException ex) {
getView().clear();
addError("object_not_found");
}
catch (Exception ex) {
ex.printStackTrace();
addError("system_error");
}
}
}
This is the code of an action that allows to visualize an object of another type. First you have to memorize the key of of the object to read (1). After this, you show a new view (2) by means of
showNewView(). It creates a new view and set it as the default one, so it is displayed. After it any reference to
getView() will be for the new one (3). Finally you fill the key values (4) and use
findObject() (5) to load all data in the view. Also we use
setControllers() (6) to set a new group of actions to show.
When using
showNewView() the current view is stored in a stack, and it would be back to the user interface calling to
returnToPreviousView() method.
Changing the model of current view
If you work with an OX version older than v4m2 an alternative to show a new view is changing the model of the current view. This is easy, you only need to use the APIs available in
View. An example:
public void execute() throws Exception {
try {
setInvoiceValues(getView().getValues()); // 1
Object number = getView().getValue("product.number");
Map key = new HashMap();
key.put("number", number);
getView().setModelName("Product"); // 2
getView().setValues(key); // 3
getView().findObject(); // 4
getView().setKeyEditable(false);
getView().setEditable(false);
}
catch (ObjectNotFoundException ex) {
getView().clear();
addError("object_not_found");
}
catch (Exception ex) {
ex.printStackTrace();
addError("system_error");
}
}
This is an extract of an action that allows to visualize an object of another type. First you need to memorize the current displayed data (1), to restore it on returning. After this, you change the model of view (2), this is the important part. Finally you fill the key values (3) and use
findObject() (4) to load all data in the view.
When you use this technique you have to keep in mind that each module has only one
xava_view object active at a time, thus if you wish to go back you have the responsibility of restoring the original model in the view and restoring the original data.
Go to a JSP page
The automatic view generator of OpenXava is good for most cases, but it can be required to display a JSP page hand-written by you. You can do this with an action like this:
package org.openxava.test.actions;
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
public class MySearchAction extends BaseAction implements INavigationAction { // 1
public void execute() throws Exception {
}
public String[] getNextControllers() { // 2
return new String [] { "MyReference" } ;
}
public String getCustomView() { // 3
return "doYouWishSearch.jsp";
}
public void setKeyProperty(String s) {
}
}
In order to go to a custom view (in this case a JSP page) your action has to implement
INavigationAction (
ICustomViewAction is enough). This way you can indicate with
getNextControllers() (2) the next controllers to use and with
getCustomView() (3) the JSP page to display (3).
Generating a custom report with JasperReports
By default, OpenXava includes actions that allow end-users to generate PDF reports and export to Excel, as well as generate their own reports from the list model by filtering, ordering, adding/removing fields, changing the positions of the fields and then generating a PDF report from the list.
Note that these actions are executed in a browser pop-up. If your browser is configured to block pop-ups, you must add the hostname of your OpenXava instance to your browser's list of pop-up exceptions (usually 'localhost' or '127.0.0.1'), otherwise the actions will not execute.
These actions are sufficient for simple reporting requirements, but in all non-trivial business applications you need to create your own customised reports using JasperReports and then integrating the reports into your OpenXava application using
JasperReportBaseAction.
You can use iReport Designer, an excellent report development tool for JasperReports, to create the JRXML file that contains the definition of the report layout, then write your action to print the report like this:
package org.openxava.test.actions;
import java.util.*;
import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.data.*;
import org.openxava.actions.*;
import org.openxava.model.*;
import org.openxava.test.model.*;
import org.openxava.util.*;
import org.openxava.validators.*;
/**
* Report of products of the selected subfamily. <p>
*
* Uses JasperReports. <br>
*
* @author Javier Paniza
*/
public class FamilyProductsReportAction extends JasperReportBaseAction { // 1
private Subfamily subfamily;
public Map getParameters() throws Exception { // 2
Messages errors =
MapFacade.validate("FilterBySubfamily", getView().getValues());
if (errors.contains()) throw new ValidationException(errors); // 3
Map parameters = new HashMap();
parameters.put("family", getSubfamily().getFamily().getDescription());
parameters.put("subfamily", getSubfamily().getDescription());
return parameters;
}
protected JRDataSource getDataSource() throws Exception { // 4
return new JRBeanCollectionDataSource(getSubfamily().getProductsValues());
}
protected String getJRXML() { // 5
return "Products.jrxml"; // To read from classpath
// return "/home/javi/Products.jrxml"; // To read from file system
}
private Subfamily getSubfamily() throws Exception {
if (subfamily == null) {
int subfamilyNumber = getView().getValueInt("subfamily.number");
subfamily = XPersistence.getManager().find(
Subfamily.class, subfamilyNumber);
}
return subfamily;
}
}
Your action has to extend
JasperReportBaseAction (1) and it has to overwrite the next three method:
- getParameters() (2): A Map with the parameters to send to the report, in this case we validate the input data (using MapFacade.validate()) before (3).
- getDataSource() (4): A JRDataSource with data to print. In this case it is a collection of JavaBeans obtained calling a method of the model object. If you use EJB CMP2 Entities be careful and do not loop over an EJB2 Entity collection inside this method, as in this case is better only one EJB call to obtain all data.
- getJRXML() (5): The XML with the JasperReports design, this file can be in the classpath. You should put these file in src/main/resources/reports in your project (new in v7.0). Before v7.0 you may have a source code folder called reports in your project to hold these files. Other option is put this file in the file system (new in v2.0.3), this is achieved by specifying the full path of file, for example: /home/javi/Products.jrxml or c:\\reports\\Products.jrxml (starting with windows driver letter recognized as absolute path since v3.0.3).
By default the report is displayed in a popup window, but if you wish the report in the current window, then you can overwrite the method
inNewWindow().
You can merge several reports in one PDF
(new in v5.0). Your action has to extend
JasperConcatReportBaseAction. Useful when you need to concatenate several reports with different page formats (landscape, portrait).
You can find more examples of JasperReport actions in the
OpenXavaTest project, as
InvoiceReportAction for printing an Invoice and
MovieReportAction to concatenate reports.
Uploading and processing a file from client (AJAX) (new in v6.2)
This feature allows you to process in your OpenXava application a binary file provided by the client, without page reloading and using drag & drop.
The first step is to create a transient class that includes a property of type org.apache.commons.fileupload.FileItem:
package com.yourcompany.uploadtest.model;
import java.time.*;
import org.apache.commons.fileupload.*;
import org.openxava.annotations.*;
public class ShowFile {
@FileItemUpload(acceptFileTypes="text/plain", maxFileSizeInKb=200) // Since v6.6
private FileItem file;
private LocalDate date;
public FileItem getFile() {
return file;
}
public void setFile(FileItem file) {
this.file = file;
}
public LocalDate getDate() {
return date;
}
public void setDate(LocalDate date) {
this.date = date;
}
}
Note as you can have more properties apart from
file, such a date property in this case. Since v6.6 you can annotate your
FileItem property with
@FileItemUpload to limit the files to upload, in
acceptFileTypes you put a
list of mime types separated by commas and you can use wildcards, and with
maxFileSizeInKb you limit the size of the file. In this case only plain text files of 200 Kb at most can be uploaded.
Now you have to define a module in application.xml because transient classes are not automatically recognized as modules:
<module name="ShowFile">
<model name="ShowFile"/>
<controller name="ShowFile"/>
</module>
Then you have to define the controller with the action to process the file. Add it in controllers.xml:
<controller name="ShowFile">
<action name="showFile"
class="com.yourcompany.uploadtest.actions.ShowFileAction"/>
</controller>
Finally, the logic that process the uploaded file in your action:
package com.yourcompany.uploadtest.actions;
import java.time.*;
import org.apache.commons.fileupload.*;
import org.openxava.actions.*;
public class ShowFileAction extends ViewBaseAction {
public void execute() throws Exception {
FileItem file = (FileItem) getView().getValue("file");
LocalDate date = (LocalDate) getView().getValue("date");
addMessage(file.getName() + " shown on: " + date);
addMessage(file.getName() + " content: " + file.getString());
}
}
In this case we just display the content as string in a message, but you can do whatever you want with the file using the
FileItem API. Note as using the other properties of the view,
date in this case, is done in the usual way.
Uploading and processing a file from client (multipart form)
If you use v6.2 or better use FileItem instead of multipart forms, as explained in the above section
This feature allows you to process in your OpenXava application a binary file (or several) provided by the client. This is implemented in a HTTP/HTML context using HTML multipart forms, although the OpenXava code is technologically neutral, hence your action will be portable to another environment with no recoding.
In order to upload a file the first step is creating an action to direct to a form where the user can choose his file. This action must implements
ILoadFileAction in this way:
public class ChangeImageAction extends BaseAction implements ILoadFileAction { // 1
...
public void execute() throws Exception { // 2
showDialog(); // 3
}
public String[] getNextControllers() { // 4
return new String [] { "LoadImage" };
}
public String getCustomView() { // 5
return "xava/editors/changeImage";
}
public boolean isLoadFile() { // 6
return true;
}
...
}
An
ILoadFileAction (1) action is also an
INavigationAction action that allows you to navigate to another controller (4) and to a custom view (5). The new controller (4) usually will have an action of type
IProcessLoadedFileAction. The method
isLoadFile() (6) returns
true in case that you want to navigate to the form to upload the file, you can use the logic in
execute() (2) to determine this value. The custom view is (5) a JSP with your own form to upload the file. Optionally you can call
showDialog() (3) if you want the form to choose files would be in a dialog.
An example of a JSP for a custom view is:
<%@ include file="../imports.jsp"%>
<jsp:useBean id="style" class="org.openxava.web.style.Style" scope="request"/>
<table>
<th align='left' class=<%=style.getLabel()%>>
<fmt:message key="enter_new_image"/>
</th>
<td>
<input name = "newImage" class=<%=style.getEditor()%> type="file" size='60'/>
</td>
</table>
As you see, the HTML form is not specified, because the OpenXava module already has the form.
The last piece is the action for processing the uploaded files:
public class LoadImageAction extends BaseAction
implements INavigationAction, IProcessLoadedFileAction { // 1
private List fileItems;
private View view;
private String newImageProperty;
public void execute() throws Exception {
Iterator i = getFileItems().iterator(); // 2
while (i.hasNext()) {
FileItem fi = (FileItem)i.next(); // 3
String fileName = fi.getName();
if (!Is.emptyString(fileName)) {
getView().setValue(getNewImageProperty(), fi.get()); // 4
}
}
}
public String[] getNextControllers() {
return DEFAULT_CONTROLLERS;
}
public String getCustomView() {
return DEFAULT_VIEW;
}
public List getFileItems() {
return fileItems;
}
public void setFileItems(List fileItems) { // 5
this.fileItems = fileItems;
}
...
}
The action implements
IProcessLoadedFileAction (1), thus the action must have a method
setFileItem() (5) to receive the list of uploaded files. This list can be processed in
execute() (2). The elements of the collection are of type
org.apache.commons.fileupload.FileItem (4) (from fileupload project of apache commons). Only calling to get() (4) in the file item you will access to the content of the uploaded file.
Override the default controllers (new in v2.0.3)
The controllers in
openxava/src/main/resources/xava/default-controllers.xml (before v7.0 it was
OpenXava/xava/default-controllers.xml, before v2.0.3 it was
OpenXava/xava/controllers.xml) are used by OpenXava to give to application a default behavior. Many times the easier way to override the default behavior of OpenXava is creating our own controllers and use them in our applications, that is you can create a controller called
MyTypical, and using it in your modules instead of
Typical that comes with OpenXava.
Another option is override a default controller of OpenXava. In order to override a default controller you only need to create in your application a controller with the same name of default one. For example, if you want refine the collections behavior for your application then you have to create a
Collection controller in your
controllers.xml, as following:
<controller name="Collection">
<action name="new"
class="org.openxava.actions.CreateNewElementInCollectionAction"/>
<action name="hideDetail" <!-- 1 -->
class="org.openxava.test.actions.MyHideDetailElementInCollection"/>
<action name="save"
class="org.openxava.actions.SaveElementInCollectionAction">
<use-object name="xava_view"/> <!-- Not needed since 4m2 -->
</action>
<action name="remove"
class="org.openxava.actions.RemoveElementFromCollectionAction">
<use-object name="xava_view"/> <!-- Not needed since 4m2 -->
</action>
<action name="edit"
class="org.openxava.actions.EditElementInCollectionAction">
<use-object name="xava_view"/> <!-- Not needed since 4m2 -->
</action>
<action name="view"
class="org.openxava.actions.EditElementInCollectionAction">
<use-object name="xava_view"/> <!-- Not needed since 4m2 -->
</action>
</controller>
In this case we only override the behavior of
hideDetail (1) action. But we must declare all actions of the original controller, because OpenXava rely on all these actions for working; we cannot remove or rename actions.
Showing a modal dialog (new in v4m2)
You can show a dialog calling to
showDialog() method of
ViewBaseAction. We suppose the current view contains an address (embeddable), and we want an action to show a dialog to fill the full address in only one line.
The action declaration in
controllers.xml can be:
<controller name="Address">
<action name="addFullAddress"
class="org.openxava.test.actions.GoAddFullAddressAction"/>
</controller>
Here you have the action code:
public class GoAddFullAddressAction extends ViewBaseAction { // 1
public void execute() throws Exception {
showDialog(); // 2
getView().setTitleId("entry_full_address"); // 3
// getView().setTitle("Entry the full address"); // 4
getView().setModelName("OneLineAddress"); // 5
setControllers("AddFullAddress", "Dialog"); // 6
// addActions("AddFullAddress.add", "Dialog.cancel"); // 7
}
}
Basically, it shows a new view inside a dialog (2) , set its title (3), its content (5) and the dialog buttons (6).
It must extend
ViewBaseAction (1) to use
showDialog().
The dialog buttons are specified by means of
setControllers (6) or
addActions (7). The 'Dialog' controller contains a default 'cancel' action. Though you can specify your own cancel action, indeed if you have an action named 'cancel' it will be executed automatically when the user will close the dialog window.
The title can be set using
View.setTitleId() (3), in this case you indicate a id from i18n files (labels or messages), or you can use
View.setTitle() (4) to put the title literally. If you do not specify title OpenXava generates one from the action description.
To set the dialog content we use
View.setModelName() to assign an entity or
transient class to de current view. In our case it's a transient class,
OneLineAddress:
public class OneLineAddress {
private String fullAdress;
public String getFullAdress() {
return fullAdress;
}
public void setFullAdress(String fullAdress) {
this.fullAdress = fullAdress;
}
}
Just one property. So the dialog will have only a label with a text field to entry the full address.
Let's see the
AddFullAddress controller that defines the dialog buttons:
<controller name="AddFullAddress">
<action name="add"
class="org.openxava.test.actions.AddFullAddressAction"/>
</controller>
As you can see we declare the main action of the dialog, whose code is:
public class AddFullAddressAction extends ViewBaseAction {
public void execute() throws Exception {
String fullAddress = getView().getValueString("fullAdress"); // 1
String [] tokens = fullAddress.split(" ");
View addressView = getPreviousView().getSubview("address"); // 2
String [] properties = { "state.id", "city", "zipCode", "street" };
int iTokens = tokens.length;
for (int iProperties = 0; iProperties < 4 && iTokens > 0; iProperties++) {
addressView.setValue(properties[iProperties], tokens[--iTokens]);
}
StringBuffer street = new StringBuffer();
for (int i = 0; i <= iTokens; i++) {
street.append(tokens[i]);
street.append(' ');
}
addressView.setValue("street", street.toString().trim());
closeDialog(); // 3
}
}
Note how using
getView() (1) you can access to the dialog content, because now the current view is the dialog. Also you can access to the previous view (2) (the view back, the main view of the module in this case) to manage it, using
getPreviousView(). To discard the dialog view and set as current view the previous one we call to
closeDialog() (3).
Showing a modal dialog (only in v4m1)
Note: This way to use dialogs is not available since v4m2
Showing a dialog is declarative. You can take any of your existing actions, marking it as
show-dialog="true" in its
<action /> declaration, and when the action will be executed a dialog will be shown.
Let's make an example. We suppose the current view contains an address (embeddable), and we want an action to show a dialog to fill the full address in only one line.
The action declaration in
controllers.xml can be:
<controller name="Address">
<action name="addFullAddress"
show-dialog="true" <!-- 1 -->
class="org.openxava.test.actions.GoAddFullAddressAction">
<use-object name="xava_view"/> <!-- 2 -->
<use-object name="xava_previousViews"/> <!-- 3 -->
</action>
</controller>
Just
show-dialog="true" is enough to show the dialog. By default the dialog shows the current view, so we want to change the current view in order to define the dialog content, and when the dialog will be close we'll restore the original view. To do this we inject
xava_view (2) to manager the current view and
xava_previousViews to navigate to a new view and then return back.
Here you have the action code:
public class GoAddFullAddressAction
extends ViewBaseAction // 1
implements IChangeControllersAction { // 2
public void execute() throws Exception {
showNewView(); // 3
getView().setTitleId("entry_full_address"); // 4
// getView().setTitle("Entry the full address");
getView().setModelName("OneLineAddress"); // 5
}
public String[] getNextControllers() throws Exception {
return new String [] { "AddFullAddress" }; // 2
}
}
Basically, it shows a new view (3), set its title (4), its content (5) and the dialog buttons (1).
It must extend
ViewBaseAction (1) to use
showNewView() (that uses
xava_previousViews) and
getView() (that uses
xava_view), and it implements
IChangeControllersAction (2) to define the action buttons, in this case the actions from
AddFullAddress controller.
The title can be set using
View.setTitleId() (4) in this case you indicate a id from i18n files (labels or messages), or you can use
View.setTitle() to put the title literally. If you do not specify title OpenXava generates one from the action description.
To set the dialog content we use
View.setModelName() to assign an entity or
transient class to de current view. In our case it's a transient class,
OneLineAddress:
public class OneLineAddress {
private String fullAdress;
public String getFullAdress() {
return fullAdress;
}
public void setFullAdress(String fullAdress) {
this.fullAdress = fullAdress;
}
}
Just one property. So the dialog will have only a label with a text editor to entry the full address.
Let's see the
AddFullAddress controller that defines the dialog buttons:
<controller name="AddFullAddress">
<action name="add" hide-dialog="true" <!-- 1 -->
class="org.openxava.test.actions.AddFullAddressAction">
<use-object name="xava_previousViews"/> <!-- 2 -->
<use-object name="xava_view"/>
</action>
<action name="cancel" <!-- 3 -->
class="org.openxava.actions.CancelAction">
<use-object name="xava_previousViews"/> <!-- 2 -->
<use-object name="xava_view"/>
</action>
</controller>
As you can see the dialog will have two buttons: "add" and "cancel". Both actions hide the dialog on finish, "add" because declare
hide-dialog="true" and "cancel" because its name (3); actions named "cancel" hide by default the dialog. Both actions inject
xava_previousViews (2) to return back to previous view.
The
CancelAction is included in OpenXava. The
AddFullAddressAction code is as following:
public class AddFullAddressAction extends ViewBaseAction {
public void execute() throws Exception {
String fullAddress = getView().getValueString("fullAdress"); // 1
String [] tokens = fullAddress.split(" ");
View addressView = getPreviousView().getSubview("address"); // 2
String [] properties = { "state.id", "city", "zipCode", "street" };
int iTokens = tokens.length;
for (int iProperties = 0; iProperties < 4 && iTokens > 0; iProperties++) {
addressView.setValue(properties[iProperties], tokens[--iTokens]);
}
StringBuffer street = new StringBuffer();
for (int i = 0; i <= iTokens; i++) {
street.append(tokens[i]);
street.append(' ');
}
addressView.setValue("street", street.toString().trim());
returnToPreviousView(); // 3
}
Note how using
getView() (1) you can access to the dialog content, because now the current view is the dialog. Also you can access to the previous view (2) (the view back, the main view of the module in this case) to manage it, using
getPreviousView(). To discard the dialog view and set as current view the previous one we call to
returnToPreviousView() (3).
This view navigation logic is not specific for dialogs, indeed you can remove the
show-dialog="true" and see how it works fine, but without dialog.
Overwriting go list action
The action to go list from detail mode:
It's by default Mode.list. You can overwrite it defining your own mode-controller for your module. For that, define your module in application.xml (in src/main/resources/xava for v7 or better and just in xava folder for older versions) of your project in this way:
<module name="Author">
<model name="Author"/>
<controller name="Typical"/>
<mode-controller name="MyGoListMode"/>
</module>
Then you have to define the MyGoListMode controller in your controllers.xml, thus:
<controller name="MyGoListMode">
<extends controller="Mode"/>
<action name="list"
icon="chevron-left"
class="org.openxava.test.actions.MyGoListAction"
keystroke="F9"/>
</controller>
Note that we extends the Mode controller and overwrite list action. Now just remains writing the action code, it could be:
package org.openxava.test.actions;
import org.openxava.actions.*;
public class MyGoListAction extends GoListAction {
public void execute() throws Exception {
super.execute();
addMessage("back_to_list");
}
}
The logic can be whatever you want, in this case we just refine the standard behavior extending
GoListAction.
All action types
You have seen until now that the behavior of your actions depends on which interfaces they implement. Next the available interfaces for actions are enumerated:
- IAction: Basic interface to be implemented by all actions.
- IAvailableAction: (New in v5.9) The action can be available for the user or hidden, depend on a programmatic condition.
- IChainAction: Allows you to chain actions, that is when the execution of the action finishes, then the next action is executed immediately.
- IChainActionWithArgv: (New in v2.2) It's a refinement of IChainAction. It allows to send as arguments values for filling properties of the chained action before execute it.
- IChangeControllersAction: To change the controller (the actions) available to user. New in v4m2: You can just use the setControllers(), returnToPreviousControllers(), setDefaultControllers(), addActions(), removeActions() and clearActions() methods of BaseAction instead of implementing directly this interface.
- IChangeModeAction: To change the mode, from list to detail or vice versa. New in v4m1: You can just use the setNextMode() method of BaseAction instead of implementing directly this interface.
- IChangeModuleAction: To change the module.
- ICustomViewAction: To use as view your custom JSP.
- IForwardAction: Redirects to an internal URI in the same application, like a JSP or Servlet, or to an absolute URL in internet (absolute URL new in v4m1). It is not like ICustomViewAction; ICustomViewAction puts your JSP inside the user interface generated by OpenXava (that can be inside a portal), while IForwardAction redirects completely to the specified URI.
- IHideActionAction, IHideActionsAction: Allows to hide an action or a group of actions in the User Interface (new in v2.0). New in v4m2: You can just use the removeActions() and clearActions() methods of BaseAction instead of implementing directly these interfaces. Another alternative to hide and show actions is that the action to be hidden implements IAvailableAction (new in v5.9).
- IJavaScriptPostAction: Executes some JavaScript code after the regular action execution.
- IJDBCAction: Allows to use JDBC in an action directly. It receives an IConnectionProvider. Works like an IJDBCCalculator (see chapter 3).
- ILoadFileAction: Navigates to a view that allows the final user to load a file.
- IModelAction: An action that receives the model name.
- IModuleContextAction: Gets a ModuleContext in order to access the session objects of other modules. New in v4m1: You can just use the getContext() method of BaseAction instead of implementing directly this interface.
- IMultipleForwardAction: (New in v4.3) Redirects to several internal URIs in the same application, like JSPs or Servlets, or to several absolute URLs in internet.
- INavigationAction: Extends from IChangeControllersAction and ICustomViewAction.
- IOnChangePropertyAction: This interface must be implemented by the actions that react to the value change event in the user interface.
- IProcessLoadedFileAction: Processes a list of files uploaded from client to server.
- IPropertyAction: This action is associated to an property (displayed in User Interface), before execute it the property name and the container view is injected (new in v2.0.2).
- IRequestAction: Receives a servlet request. This type of actions links your application to the Servlet/JSP technology, hence it is better avoiding it. But sometimes a little bit of flexibility is needed. New in v4m1: You can just use the getRequest() method of BaseAction instead of implementing directly this interface.
- IShowActionAction, IShowActionsAction: Allows to show an action or a group of actions previously hidden in an IHideAction(s)Action (new in v2.0). New in v4m2: You can just use the addActions() method of BaseAction instead of implementing directly these interfaces. Another alternative to hide and show actions is that the action to be shown implements IAvailableAction (new in v5.9).
Many times instead of implementing directly these interfaces your action can extend from a base action, such as
BaseAction that already implement them.
If you wish to learn more about actions the best thing you can do is to have a look at the JavaDoc API of the package
org.openxava.actions and to try out the examples of
OpenXavaTest project.
excluded-actions