×News:
OpenXava with AI - Refine the UI (Part 2) -
December 1 ·
Read more
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. Moreover, you have a lot of examples in the test
application, in
openxavatest/src/main/resources/xava/controllers.xml
(in
OpenXavaTest/xava for v6 or older).
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 or if you are
developing a library or framework in
default-controllers-ext.xml (since
v7.5).
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 for
@OneToMany and
@Condition
collections, but not for calculated collections,
@ManyToMany or
@OrderColumn
lists.
Optional actions (new in v5.9)
You can create actions that are available or hidden based on a programmatic condition.
These actions implement the
IAvailableAction interface, which defines a method
isAvailable() that returns a boolean indicating if the action should be displayed
or not.
The
IAvailableAction interface can be implemented by any type of action (detail, list, collection, etc.).
When OpenXava is about to display an action, it checks if the action implements
IAvailableAction
and if so, it calls the
isAvailable() method. If the method returns
false, the action
is not displayed.
Let's see an example of a list action that is only available for specific rows depending on their content. In this case, the action will only be displayed for rows where the carrier's name is not already in uppercase:
package org.openxava.test.actions;
import java.util.*;
import org.apache.commons.logging.*;
import org.openxava.actions.*;
import org.openxava.model.*;
import org.openxava.test.model.*;
/**
* Action to convert the selected items in a list to uppercase.
* @author Javier Paniza
*/
public class CarrierToUpperCaseAction extends TabBaseAction implements IAvailableAction { // 1
private static Log log = LogFactory.getLog(CarrierToUpperCaseAction.class);
@Override
@SuppressWarnings("unchecked")
public void execute() throws Exception {
Map[] selectedOnes = getSelectedKeys();
for (int i = 0; i < selectedOnes.length; i++) {
Carrier carrier = (Carrier) MapFacade.findEntity("Carrier", selectedOnes[i]);
carrier.setName(carrier.getName().toUpperCase());
}
}
@Override
@SuppressWarnings("unchecked")
public boolean isAvailable() { // 2
try {
if (getRow() < 0) return false; // 3
Map key = getSelectedKeys()[0];
Carrier carrier = (Carrier) MapFacade.findEntity("Carrier", key);
return !carrier.getName().equals(carrier.getName().toUpperCase()); // 4
}
catch (Exception ex) {
log.error("Error checking if CarrierToUpperCaseAction is available", ex);
return false;
}
}
}
This action converts to uppercase the names of the selected carriers. The action extends from
TabBaseAction
and implements
IAvailableAction (1), which forces it to implement the
isAvailable() method (2).
The
isAvailable() method first checks if there is a selected row (3), and then verifies if the carrier's
name is not already in uppercase (4). The action will only be displayed for rows that meet these conditions.
Note that from the
isAvailable() method you have access to the same resources as from the
execute() method, such as
getRow() or
getSelectedKeys(), which allows you to implement complex logic to determine when an action should be available.
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.
You can also follow our reporting
course
here.
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>
<action name="next" <!-- New since v7.3 -->
image="next.gif"
icon="skip-next"
class="org.openxava.actions.EditElementInCollectionAction">
<set property="openDialog" value="false"/>
<set property="nextValue" value="1"/>
</action>
<action name="previous" <!-- New since v7.3 -->
image="previous.gif"
icon="skip-previous"
class="org.openxava.actions.EditElementInCollectionAction">
<set property="openDialog" value="false"/>
<set property="nextValue" value="-1"/>
</action>
<action name="deleteSelected" <!-- New since v7.4 -->
confirm="true"
icon="delete"
image="delete.gif"
in-each-row="true"
class="org.openxava.actions.DeleteSelectedInCollectionAction"/>
</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.
Starting from v7.5, if you are
developing a library or framework instead of a final application, you can
define your library's controllers in
default-controllers-ext.xml
(instead of
controllers.xml). This way, in your library or
framework, you can override an OpenXava controller, but the user of your
library will also be able to override it if they wish, always respecting
the following priority: first
controllers.xml (application), then
default-controllers-ext.xml (library/framework), and finally
default-controllers.xml
(OpenXava).
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