События 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. Создайте слушатель события в экране, который должен быть уведомлен. В приведенном ниже примере это главный экран:

    @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:

    @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