События UI

ApplicationEvent из Spring можно использовать для реагирования на события в различных частях приложения.

Чтобы отослать события компонентам UI, включая открытые экраны, необходимо использовать бин UiEventPublisher фреймворка.

Бин UiEventPublisher предоставляет следующие методы:

  • publishEventForCurrentUI() - публикует событие только для текущей активной вкладки браузера, из которой было отправлено событие.

  • publishEvent() - публикует событие для всех вкладок браузера в текущей сессии.

  • publishEventForUsers() - публикует событие для всех вкладок браузера для всех сессий пользователей, указанных в коллекции usernames, которая передается в качестве второго параметра. Если коллекция usernames равна null, событие будет опубликовано для всех пользователей.

Для правильного обновления UI класс, реализующий AppShellConfigurator, должен содержать аннотацию @Push. Обычно это основной класс приложения Spring Boot:

@Push
@SpringBootApplication
public class OnboardingApplication implements AppShellConfigurator {

Пример использования

Рассмотрим следующую задачу: пользователи должны завершить процесс онбординга, следуя определенным шагам. Каждый шаг представлен экземпляром сущности, и есть экран, который показывает информацию о завершенных шагах. Кроме того, пользователям нужно напоминание о количестве незавершенных шагов, которое обновляет свое значение каждый раз, когда состояние шагов изменяется. Например, таким напоминанием может быть значок в элементе меню.

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

Задачу можно решить следующим образом:

  1. Создайте класс события:

    import org.springframework.context.ApplicationEvent;
    
    public class OnboardingStatusChangedEvent extends ApplicationEvent {
    
        public OnboardingStatusChangedEvent(Object source) {
            super(source);
        }
    }
  2. Создайте слушатель события в экране, который должен быть уведомлен. В приведенном ниже примере это главный экран:

    import com.company.onboarding.event.OnboardingStatusChangedEvent;
    import com.vaadin.flow.component.html.Span;
    import io.jmix.flowui.kit.component.main.ListMenu;
    import io.jmix.flowui.view.Subscribe;
    import org.springframework.context.event.EventListener;
    
    // class declaration and annotations omitted
    
        @Subscribe
        public void onInit(final InitEvent event) {
            updateOnboardingStatus(); (1)
        }
    
        @EventListener
        private void onBoardingStatusChanged(OnboardingStatusChangedEvent event) { (2)
            updateOnboardingStatus();
        }
    
        private void updateOnboardingStatus() {
            long number = getUncompletedStepsNumber(); (3)
    
            Span badge = null; (4)
            if (number > 0) {
                badge = new Span("" + number);
                badge.getElement().getThemeList().add("badge warning");
            }
    
            ListMenu.MenuItem menuItem = menu.getMenuItem("MyOnboardingView");
            // Can be 'null' if menu item isn't permitted by security
            if (menuItem != null) {
                menuItem.setSuffixComponent(badge);
            }
        }
    1 Обновление статуса онбординга в первый раз, когда пользователь открывает главный экран.
    2 Аннотация @EventListener на методе включает обработку событий приложения.
    3 Получение количество незавершенных шагов из сервиса.
    4 Установка количества незавершенных шагов в виде значка для элемента меню.
  3. Отправьте событие, используя бин UiEventPublisher из слушателя EntityChangedEvent:

    import com.company.onboarding.entity.User;
    import com.company.onboarding.entity.UserStep;
    import io.jmix.core.DataManager;
    import io.jmix.core.Id;
    import io.jmix.core.event.EntityChangedEvent;
    import io.jmix.flowui.UiEventPublisher;
    import org.springframework.context.event.EventListener;
    import org.springframework.stereotype.Component;
    
    @Component
    public class UserStepEventListener {
    
        private final DataManager dataManager;
        private final UiEventPublisher uiEventPublisher;
    
        public UserStepEventListener(DataManager dataManager,
                                     UiEventPublisher uiEventPublisher) {
            this.dataManager = dataManager;
            this.uiEventPublisher = uiEventPublisher;
        }
    
        @EventListener
        public void onUserStepChangedBeforeCommit(EntityChangedEvent<UserStep> event) {
            User user;
            if (event.getType() != EntityChangedEvent.Type.DELETED) {
                Id<UserStep> userStepId = event.getEntityId();
                UserStep userStep = dataManager.load(userStepId).one();
                user = userStep.getUser();
            } else {
                Id<User> userId = event.getChanges().getOldReferenceId("user");
                if (userId == null) {
                    throw new IllegalStateException("Cannot get User from deleted UserStep");
                }
                user = dataManager.load(userId).one();
            }
    
            uiEventPublisher.publishEventForUsers( (1)
                    new OnboardingStatusChangedEvent(this),
                    Collections.singleton(user.getUsername())
            );
        }
    }
    1 Публикация события для всех UI всех сессий пользователя, указанного в измененной сущности UserStep.
  4. Элемент меню с значком, отображающим количество незавершенных шагов

on boarding status