Обработчики UI
Начиная с Jmix 3.1
Обработчик UI добавляет поведение экрану без Java-кода и без повторного развёртывания. Он связывает событие UI, например нажатие кнопки, изменение значения поля или событие жизненного цикла экрана, с коротким выражением. Выражение может читать и изменять редактируемую сущность, менять состояние компонентов, показывать уведомления и диалоги, открывать другие экраны и вызывать методы приложения, которые разработчик сделал доступными.
Обработчики объявляются в списке handlers объявления экрана, в YAML или на вкладке Handlers диалога экрана. Их можно добавить в следующих экранах:
-
экран динамической сущности;
-
переопределение экрана статической сущности;
-
объявление экрана статической сущности, которое содержит только
handlersи не содержитdescriptor. Так можно добавить поведение существующему экрану приложения без изменения его XML.
Объявление обработчиков
У каждого обработчика есть event, componentId для событий компонентов и одно выражение expression. Пример ниже добавляет обработчики в сгенерированный экран деталей динамической сущности LoyaltyLevel. Первый обработчик задаёт имя по умолчанию для нового экземпляра. Второй делает поле description обязательным, когда введена скидка:
handlers:
- event: "initEntity"
expression: "entity.name = 'New level'"
- event: "valueChange"
componentId: "discountField"
expression: "components.setRequired('descriptionField', event.value != null)"
Компоненты экрана, сгенерированного из шаблона, имеют идентификаторы, составленные из имени атрибута и Field, например discountField.
Следующий пример добавляет обработчики в экран Customer.detail приложения. Объявление не содержит descriptor, поэтому XML экрана не изменяется:
- 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))"
Обработчик beforeShow делает поле имени доступным только для чтения для существующих клиентов. Нажатие на checkNameButton открывает диалог подтверждения. Когда пользователь нажимает Yes, диалог запускает обработчик doCheckName, который вызывает операцию приложения checkCustomerName и показывает результат. Как определяется операция, см. в разделе Вызов кода приложения.
Несколько обработчиков могут использовать одно и то же событие и компонент. Они выполняются в том порядке, в котором объявлены.
События
| Событие | Когда выполняется обработчик | componentId |
|---|---|---|
|
Нажатие на компонент. |
обязателен |
|
Пользователь изменяет значение компонента. |
обязателен |
|
Экран инициализируется. |
не допускается |
|
Экран деталей инициализирует новую сущность. Выполняется только при создании сущности. |
не допускается |
|
Перед отображением экрана. |
не допускается |
|
После отображения экрана. |
не допускается |
Обработчик без event должен иметь name. Он не связан ни с каким событием и выполняется, только когда его вызывает диалог подтверждения того же экрана. См. dialogs.confirm() ниже.
Выражения
Выражение — это одно выражение SpEL. Чтобы выполнить два действия, объявите два обработчика.
В выражении можно использовать следующие объекты:
| Имя | Члены |
|---|---|
|
Редактируемая сущность экрана деталей или единственная выбранная строка экрана списка. Атрибуты можно читать и записывать: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Для |
Больше в выражении ничего использовать нельзя. Spring-бины, статические методы, конструкторы и другие Java-методы недоступны.
Вызов кода приложения
Единственный способ для обработчика вызвать код приложения — это services.call(). Разработчик делает метод доступным для обработчиков, добавляя аннотацию @DynamicModelOperation с именем операции:
@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";
}
}
Правила для операций:
-
Метод должен быть публичным и принадлежать Spring-бину.
-
Имена операций должны быть уникальными. Пустое или повторяющееся имя останавливает приложение при запуске.
-
Аргументы преобразуются к объявленным типам параметров. Например,
services.call('checkCustomerName', entity)передаёт редактируемый экземплярCustomerв метод выше. -
Операция выполняется в сессии текущего пользователя. Всё, что она делает, подчиняется разрешениям пользователя, включая ограничения ролей уровня строк.
-
Возвращаемое значение можно использовать в выражении. Можно читать свойства возвращаемых чисел, строк, сущностей, DTO и коллекций, но нельзя вызывать их методы.
Редактирование обработчиков в административном UI
Обработчики редактируются на вкладке Handlers диалога экрана. Диалог обработчика содержит следующие поля:
-
Event – одно из событий.
initEntityдоступно только для экранов деталей. -
Component ID – выпадающий список компонентов экрана с идентификатором и XML-элементом каждого компонента. Список содержит только компоненты, которые может использовать выбранное событие: компоненты, на которые можно нажать, для
clickи компоненты со значением дляvalueChange. Можно также ввести идентификатор, которого нет в списке. Поле доступно только дляclickиvalueChange. -
Name – необязательное имя обработчика, уникальное в пределах экрана. Обязательно для обработчика без события.
-
Expression – редактор кода с автодополнением. Он предлагает объекты и члены, перечисленные в разделе Выражения, атрибуты сущности после
entity., идентификаторы компонентов в методахcomponents, имена операций вservices.call()и имена обработчиков вdialogs.confirm(). Автодополнение открывается после точки или при нажатии Ctrl+Space.
В таблице обработчиков обработчики одного события и компонента располагаются вместе. Используйте кнопки вверх и вниз, чтобы изменить их порядок в группе.
Проверки и ошибки
Обработчики проверяются при применении модели. Применение отклоняется с сообщением, которое объясняет ошибку, в следующих случаях:
-
выражение пустое или не может быть разобрано;
-
идентификатор компонента отсутствует или задан для события, которое его не допускает;
-
компонент с таким идентификатором не существует в экране. Это проверяется, только если объявление экрана содержит XML в
source; -
у обработчика нет ни события, ни имени;
-
два обработчика в экране имеют одинаковое имя;
-
dialogs.confirm()ссылается на имя обработчика, которого нет в экране; -
services.call()ссылается на неизвестную операцию.
Для экрана, сгенерированного из шаблона, и для объявления экрана статической сущности без descriptor неверный идентификатор компонента не обнаруживается при применении. Об этом сообщается в логе приложения при открытии экрана.
Если выражение завершается с ошибкой во время выполнения, пользователь видит уведомление о том, что операция не удалась, а экран продолжает работать. Лог приложения содержит идентификатор экрана, обработчик и причину ошибки.