UI Handlers
Since Jmix 3.1
A UI handler adds behavior to a view without Java code and without a redeploy. It connects a UI event, such as a button click, a field value change or a view lifecycle event, to a short expression. The expression can read and change the edited entity, change the state of components, show notifications and dialogs, open other views, and call application methods that a developer has made available.
Handlers are declared in the handlers list of a view declaration, in YAML or on the Handlers tab of the view dialog. They can be added to:
-
a view of a dynamic entity;
-
an override of a static entity view;
-
a static entity view declaration that has only
handlersand nodescriptor. This adds behavior to an existing application view without changing its XML.
Declaring Handlers
Each handler has an event, a componentId for component events, and one expression. The example below adds handlers to the generated detail view of the LoyaltyLevel dynamic entity. The first handler sets a default name for a new instance. The second one makes the description field required when a discount is entered:
handlers:
- event: "initEntity"
expression: "entity.name = 'New level'"
- event: "valueChange"
componentId: "discountField"
expression: "components.setRequired('descriptionField', event.value != null)"
Components of a view generated from a template have IDs made from the attribute name plus Field, for example discountField.
The next example adds handlers to the Customer.detail view of the application. The declaration has no descriptor, so the view XML is not changed:
- viewId: "Customer.detail"
handlers:
- event: "beforeShow"
expression: "components.setReadOnly('nameField', !isNew())"
- event: "click"
componentId: "checkNameButton"
expression: "dialogs.confirm('Check name', 'Check that no other customer has this name?', 'doCheckName')"
- name: "doCheckName"
expression: "notifications.show(services.call('checkCustomerName', entity))"
The beforeShow handler makes the name field read-only for existing customers. A click on checkNameButton opens a confirmation dialog. When the user clicks Yes, the dialog runs the doCheckName handler, which calls the checkCustomerName application operation and shows the result. See Calling Application Code for how the operation is defined.
Several handlers can use the same event and component. They run in the order in which they are declared.
Events
| Event | When the handler runs | componentId |
|---|---|---|
|
The component is clicked. |
required |
|
The user changes the value of the component. |
required |
|
The view is initialized. |
not allowed |
|
A detail view initializes a new entity. Runs only when an entity is created. |
not allowed |
|
Before the view is shown. |
not allowed |
|
After the view is shown. |
not allowed |
A handler without an event must have a name. It is not connected to any event and runs only when a confirmation dialog of the same view calls it. See dialogs.confirm() below.
Expressions
The expression is a single SpEL expression. To do two things, declare two handlers.
An expression can use the following objects:
| Name | Members |
|---|---|
|
The edited entity of a detail view, or the single selected row of a list view. Attributes can be read and written: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
For |
Nothing else can be used in an expression. Spring beans, static methods, constructors and other Java methods are not available.
Calling Application Code
The only way for a handler to call application code is services.call(). A developer makes a method available for handlers by adding the @DynamicModelOperation annotation with the operation name:
@Component
public class CustomerOperations {
private final DataManager dataManager;
public CustomerOperations(DataManager dataManager) {
this.dataManager = dataManager;
}
@DynamicModelOperation("checkCustomerName")
public String checkCustomerName(Customer customer) {
long count = dataManager.loadValue(
"select count(e) from Customer e where e.name = :name and e.id <> :id", Long.class)
.parameter("name", customer.getName())
.parameter("id", customer.getId())
.one();
return count == 0 ? "The name is not used" : "Another customer has this name";
}
}
Rules for operations:
-
The method must be public and belong to a Spring bean.
-
Operation names must be unique. A blank or duplicate name stops the application at startup.
-
Arguments are converted to the declared parameter types. For example,
services.call('checkCustomerName', entity)passes the editedCustomerinstance to the method above. -
An operation runs in the session of the current user. Everything it does follows the user’s permissions, including row-level security.
-
The returned value can be used in the expression. You can read properties of returned numbers, strings, entities, DTOs and collections, but you cannot call methods on them.
Editing Handlers in the Admin UI
Handlers are edited on the Handlers tab of the view dialog. The handler dialog has the following fields:
-
Event – one of the events.
initEntityis available only for detail views. -
Component ID – a drop-down list of the view components, with the ID and the XML element of each component. The list contains only components that the selected event can use: clickable components for
click, and components with a value forvalueChange. You can also type an ID that is not in the list. The field is enabled only forclickandvalueChange. -
Name – an optional handler name, unique in the view. It is required for a handler without an event.
-
Expression – a code editor with code completion. It suggests the objects and members listed in Expressions, entity attributes after
entity., component IDs incomponentsmethods, operation names inservices.call(), and handler names indialogs.confirm(). Completion opens after a dot or when you press Ctrl+Space.
In the handlers grid, handlers of the same event and component are kept together. Use the up and down buttons to change their order in the group.
Checks and Errors
Handlers are checked when the model is applied. The apply is refused, and the message explains the error, in the following cases:
-
the expression is empty or cannot be parsed;
-
the component ID is missing, or is set for an event that does not allow it;
-
a component with this ID does not exist in the view. This is checked only if the view declaration has an XML
source; -
a handler has neither an event nor a name;
-
two handlers in the view have the same name;
-
dialogs.confirm()refers to a handler name that does not exist in the view; -
services.call()refers to an unknown operation.
For a view generated from a template, and for a static view declaration without a descriptor, a wrong component ID is not found at apply time. It is reported in the application log when the view is opened.
If an expression fails when it runs, the user sees a notification that the operation failed, and the view keeps working. The application log contains the view ID, the handler, and the cause of the error.