События экранов

Этот раздел описывает события жизненного цикла экранов, которые можно обрабатывать в контроллерах. Для получения общего представления о последовательности событий, см. диаграммы в конце раздела.

Для генерации обработчика события экрана в Jmix Studio, выберите корневой элемент view в панели структуры Jmix UI и используйте вкладку Handlers панели инспектора.

В качестве альтернативы, выполните действие Generate Handler, доступное в верхней панели класса экрана и через меню CodeGenerate (Alt+Insert / Cmd+N).

InitEvent

InitEvent — это первое событие в процессе открытия экрана. Экран и все его компоненты, определенные декларативно, созданы, инжекция зависимостей выполнена. Некоторые визуальные компоненты не полностью инициализированы, например, кнопки не связаны с действиями. В этом событии можно создавать и/или инициализировать визуальные компоненты и компоненты данных.

@ViewComponent
private JmixComboBox<String> timeZoneField;

@Subscribe
public void onInit(final InitEvent event) {
    timeZoneField.setItems(List.of(TimeZone.getAvailableIDs()));
}

Событие отсылается только один раз после создания экрана. Это означает, что если навигация на экран происходит впервые, то событие отсылается. Однако, если навигация выполняется на экран, который уже открыт, например, при повторном клике на один и тот же элемент меню, событие не будет отослано снова, потому что экран уже создан.

BeforeShowEvent

BeforeShowEvent — это второе событие (после View.InitEvent) в процессе открытия экрана. Все компоненты завершили внутреннюю процедуру инициализации. Загрузчики данных вызваны автоматически настроенным фасетом DataLoadCoordinator. Политики безопасности применены к UI-компонентам. В этом событии можно загружать данные, проверять разрешения и модифицировать UI-компоненты. Например:

@ViewComponent
private CollectionLoader<User> usersDl;

@Subscribe
public void onBeforeShow(final BeforeShowEvent event) {
    usersDl.load();
}

Повторная навигация на экран, который уже открыт, приводит к отсылке BeforeShowEvent снова для одного и того же экземпляра экрана.

Например, пользователь переходит на экран впервые, создается экземпляр экрана и отсылается BeforeShowEvent. Затем пользователь нажимает на тот же элемент меню, переходя на этот же открытый экран снова, и BeforeShowEvent отсылается снова для этого же экземпляра экрана. Если обработчик BeforeShowEvent добавляет компоненты или загружает данные, это произойдет дважды, что может привести к дублированию компонентов или повторной загрузке данных.

BeforeShowEvent работает так потому, что в случае навигации это событие отсылается в обработчике события жизненного цикла Vaadin BeforeEnterEvent.

ReadyEvent

ReadyEvent — это последнее событие (после View.BeforeShowEvent) в процессе открытия экрана. В обработчике этого события можно выполнить окончательную конфигурацию экрана в соответствии с загруженными данными и показать уведомления или диалоги.

@Autowired
private Notifications notifications;

@Subscribe
public void onReady(final ReadyEvent event) {
    notifications.show("Just opened");
}

Повторная навигация на экран, который уже открыт, приводит к отсылке View.ReadyEvent снова для того же экземпляра экрана.

Например, пользователь переходит на экран впервые, создается экземпляр экрана и отсылается ReadyEvent. Затем пользователь нажимает на тот же элемент меню, переходя на этот же открытый экран снова, и ReadyEvent отсылается снова для этого же экземпляра экрана. Если обработчик ReadyEvent добавляет компоненты или загружает данные, это произойдет дважды, что может привести к дублированию компонентов или повторной загрузке данных.

ReadyEvent работает так потому, что в случае навигации это событие отсылается в обработчике события жизненного цикла Vaadin AfterNavigationEvent.

AttachEvent

AttachEvent отсылается после того, как экран прикреплен к UI.

@Subscribe
public void onAttachEvent(final AttachEvent event) {
    log.debug("View is attached");
}

Это событие отсылается Vaadin, поскольку экран также является UI-компонентом.

BeforeCloseEvent

BeforeCloseEvent - это первое событие в процессе закрытия экрана. Экран все еще отображается и полностью функционален. В обработчике этого события вы можете проверить любые условия и предотвратить закрытие с помощью метода preventClose() события.

@Subscribe
public void onBeforeClose(BeforeCloseEvent event) {
    if (!isLicenseAgreementAccepted()) {
        CloseAction action = event.getCloseAction();
        if (action instanceof NavigateCloseAction navigateCloseAction) {
            BeforeLeaveEvent beforeLeaveEvent = navigateCloseAction.getBeforeLeaveEvent();
            beforeLeaveEvent.postpone();
        }

        event.preventClose();
    }
}
Если вы предотвращаете закрытие экрана, в случае навигации вам нужно ее отложить. Проверьте тип действия, и если это NavigateCloseAction, получите BeforeLeaveEvent Vaadin и вызовите его метод postpone().

AfterCloseEvent

AfterCloseEvent - это второе событие (после View.BeforeCloseEvent) в процессе закрытия экрана. В обработчике этого события вы можете показывать уведомления или диалоги после закрытия экрана, например:

@Autowired
private Notifications notifications;

@Subscribe
public void onAfterClose(final AfterCloseEvent event) {
    notifications.show("View is closed");
}

DetachEvent

DetachEvent срабатывает перед отсоединением экрана от пользовательского интерфейса. Этот обработчик событий можно использовать для освобождения ресурсов, захваченных экраном.

@Subscribe
public void onDetachEvent(final DetachEvent event) {
    log.debug("View is detached");
}
Это событие генерируется Vaadin, поскольку экран также является UI-компонентом.

QueryParametersChangeEvent

QueryParametersChangeEvent информирует, с какими параметрами запроса был открыт экран. Он отсылается после InitEvent, если экран был открыт навигацией.

@ViewComponent
private Span messageLabel;

public void setMessage(String message) {
    messageLabel.setText(message);
}

@Subscribe
public void onQueryParametersChange(final QueryParametersChangeEvent event) {
    event.getQueryParameters()
            .getSingleParameter("message")
            .ifPresent(this::setMessage);

}

InitEntityEvent

InitEntityEvent отсылается в экранах, унаследованных от StandardDetailView, перед установкой нового экземпляра сущности в контейнер редактируемой сущности.

Используйте обработчик этого события для инициализации значений в новом экземпляре сущности, например:

@Subscribe
public void onInitEntity(final InitEntityEvent<User> event) {
    User user = event.getEntity();
    user.setOnboardingStatus(OnboardingStatus.NOT_STARTED);
}

ValidationEvent

ValidationEvent отсылается в экранах, унаследованных от StandardDetailView, когда экран проходит валидацию при сохранении DataContext экрана. Используйте обработчик этого события для выполнения дополнительной проверки экрана.

@Subscribe
public void onValidation(final ValidationEvent event) {
    if (entityStates.isNew(getEditedEntity())
            && !Objects.equals(passwordField.getValue(), confirmPasswordField.getValue())) {
        event.getErrors().add("Passwords do not match");
    }
}

BeforeSaveEvent

BeforeSaveEvent отсылается в экранах, унаследованных от StandardDetailView, перед сохранением DataContext экрана. Используйте обработчик этого события для предотвращения сохранения, изменения значений атрибутов сущности перед сохранением и/или взаимодействия с пользователем перед сохранением.

@ViewComponent
private JmixPasswordField passwordField;
@ViewComponent
private JmixPasswordField confirmPasswordField;

@Autowired
private EntityStates entityStates;
@Autowired
private PasswordEncoder passwordEncoder;

@Subscribe
public void onBeforeSave(final BeforeSaveEvent event) {
    if (entityStates.isNew(getEditedEntity())) {
        if (!Objects.equals(passwordField.getValue(), confirmPasswordField.getValue())) {
            notifications.create("Passwords do not match")
                    .withType(Notifications.Type.WARNING)
                    .show();
            event.preventSave(); (1)
        }

        getEditedEntity().setPassword(passwordEncoder.encode(passwordField.getValue())); (2)
    }
}
1 Прервать процесс сохранения, если пароли не совпадают.
2 Установить закодированный пароль в сущность.

AfterSaveEvent

AfterSaveEvent отсылается в экранах, унаследованных от StandardDetailView, после сохранения DataContext экрана. Используйте обработчик этого события для уведомления пользователей после сохранения, например:

@Autowired
private Notifications notifications;

@Subscribe
public void onAfterSave(final AfterSaveEvent event) {
    notifications.create("Entity saved successfully")
            .withType(Notifications.Type.SUCCESS)
            .withPosition(Notification.Position.TOP_END)
            .show();
}

Диаграммы

Открытие стандартного экрана

На следующей диаграмме показан процесс открытия стандартного экрана:

open standard view

Закрытие стандартного экрана

На следующей диаграмме показан процесс закрытия стандартного экрана:

close standard view

Открытие экрана деталей сущности

На следующей диаграмме показан процесс открытия экрана деталей:

open detail view

Закрытие экрана деталей сущности

На следующей диаграмме показан процесс сохранения изменений и закрытия экрана деталей действием detail_saveClose:

detail view close with save