UI Events

You can use the Spring’s ApplcationEvent to respond to events occurred in different parts of the application.

To send application events to UI components including opened views you need to use the UiEventPublisher bean provided by the framework.

The UiEventPublisher bean has the following methods:

  • publishEventForCurrentUI() - publishes event only to the currently active browser tab from which the event was sent.

  • publishEvent() - publishes event to all browser tabs for the current session.

  • publishEventForUsers() - publishes the event to all browser tabs for all sessions of the users specified in the usernames collection, which is passed as a second parameter. If the usernames collection is null the event will be published for all users.

To correctly update the UI, the class that implements AppShellConfigurator must contain the @Push annotation. Usually, this is the main Spring Boot application class:

@Push
@SpringBootApplication
public class OnboardingApplication implements AppShellConfigurator {

Usage Example

Consider the following development task: users need to complete the onboarding process by following some steps. Each step is represented by an entity instance and there is a view that shows information about completed steps. Also, users need a reminder about the number of uncompleted steps, which updates its value every time the steps are updated. For example, a badge in a menu item, so as not to go to a statistics view every time.

Entity instances representing steps can be updated in entity detail views or in the business logic of the application. The menu item badge should be updated whenever an entity related to the current user changes.

The solution to the task above is as follows:

  1. Create an event class:

    import org.springframework.context.ApplicationEvent;
    
    public class OnboardingStatusChangedEvent extends ApplicationEvent {
    
        public OnboardingStatusChangedEvent(Object source) {
            super(source);
        }
    }
  2. Create an event listener in the view that should be notified. In the example below it’s the main view:

    @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 Update onboarding status the first time a user opens the main view.
    2 Annotate a method with the @EventListener annotation to enable handling of application events in the view.
    3 Get uncompleted steps number from a service.
    4 Set uncompleted steps number value as a badge to the menu item.
  3. Send the event using the UiEventPublisher bean from the EntityChangedEvent listener:

    @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 Publish the event to all UIs for all sessions of the user specified in the UserStep entity that has been changed.
on boarding status
Figure 1. Menu item with a badge that displays the number of uncompleted steps