Skip to content

03. Defining an Event Bus

Frank Hossfeld edited this page Apr 18, 2018 · 26 revisions

Event Bus

The main goal of Mvp4g2 is to allow you to easily set an event bus and events without creating classes and interfaces for each event.

An event is defined by:

  • its name
  • the objects that can be fired with it
  • the @Event-annotation

All you have to do to create an event bus and events is to create an interface and define one method for each event. An implementation of this interface will be automatically generated by the framework and will be injected to each presenter.

Thus Mvp4g2 provides an easy and strongly-type way to define an event bus.

Mvp4g2 supports only one MVP4G2 eventbus per application!

Creating an event bus

To create an event bus, you need to:

  • create an interface that extends IsEventBus
  • annotate the interface with @EventBus

The @EventBus-annotation has the following attributes:

  • shell (mandatory) see ShellPresenter.
  • historyOnStart (optional, by default false), use only in case of history management is implemented and handling history at start is desired (see MVP4G2 & Browser history).
@EventBus(shell = ShellPresenter.class,
          historyOnStart = true)
public interface MyEventBus
  extends IsEventBus {

Creating an event

To create an event, you need to:

  • create a method in your event bus interface. This method shouldn't return anything (must be returning void) and can have as many parameters as you want.
  • annotate it with @Event.
@EventBus(shell = ShellPresenter.class, historyOnStart = true)
public interface MyEventBus
  extends IsEventBus {
 
  @Event
  void fireMyEvent01(String value);

  @Event
  void fireMyEvent02(String value);

  @Event
  void fireMyEvent03(long id);

  ...

Adding an handler to an event

The easiest way to add an handler to an event is to create a event handling method inside the presenter/handler and annotate this method with @EventHandler. In this case the event defined in the event bus with:

  @Event
  void fireMyEvent(String value);

can be handled be a presenter / handler with this code:

  @EventHandler
  public void onFireMyEvent(String value) {
    ... 
  }

Or, use the handlers-attribute inside the @Event annotation. To add an handler to an event you just have to add its class to the attribute 'handlers' of the @Event annotation that annotates the method of your event in your event bus interface.

  @Event(handlers = {ListPresenter.class, MyApplicationHandler.class})
  void fireMyEvent(String value01,
                   String value02);

By adding the presenter class, MVP4G2 will be able to retrieve the instance of the class and add it to the list of handlers of the event.

In case the handler attribute is used, there is no need to annotate the handling presenter- or handler-methods with @EventHandler. All you have to do is to implement a method in your presenter / handler that looks like this:

  public void onFireMyEvent(String value01,
                            String value02) {
    ... 
  }

The @Event annotation has the following attributes:

Adding an handler to an event

To handle an event create a method inside the presenter / handler that looks like this:

  • should return void(mvp4g2 will ignore any returned value!)
  • start with 'on' followed by the method name of the event with the first letter capitalized
  • have the same signature

If the presenter should handle the

eventBus.fireMyEvent01(String value)

it needs to define the following method:

  public void onFireMyEvent01(String value) {
  }

The easiest way to add an handler to an event is to create a event handling method inside the presenter/handler and annotate this method with @EventHandler.

  @EventHandler
  public void onFireMyEvent01(String value) { 
  }

Or, use the handlers-attribute inside the @Event annotation. To add an handler to an event you just have to add its class to the attribute 'handlers' of the @Event annotation that annotates the method of your event in your event bus interface.

  public void onFireMyEvent01(String value) {
  }

By adding the presenter class, MVP4G2 will be able to retrieve the instance of this class and add it to the list of handlers of the event.

Attention: Only presenters & event handlers can handle an event.

Firing an event

To fire an event, all you have to do is calling the event's method:

  eventBus.gotoList("a String value", "another String value");

Firing event

  1. you can filter events and stop them if needed. This mechanism is automatically removed by MVP4G2 if no filter is set see Event filtering
  2. this method has 2 goals:
  • check if the presenter is activated. If not, the event won't be forwarded to it.
  • if activated and if it's the first event handled by the presenter, it calls the bind method.

You don't have to manage eventbus creation and implementation, the framework will do it for you. It will also inject the instance of the event bus and the view automaticaly to all your presenters/event handlers (only event bus).

Activating/Deactivating presenters

In your application, you may want a presenter to stop handling events for a certain time. MVP4G2 let you do that thanks to the activate/deactivate options of the @Event annotations. When a presenter is deactivated, no event will be forwarded to it even if it can handle them. To deactivate (or activate) an handler when firing an event, you just have to add its class to the attribute 'deactivate' (or 'activate') of the @Event annotation that annotates the method of your event in your event bus interface.

@Event(..., activate = CreatePresenter.class, deactivate = EditPresenter.class)
public void displayCreatePage();

@Event(..., activate = EditPresenter.class, deactivate = CreatePresenter.class)
public void displayEditPage();

@Event(handlers = { EditPresenter.class, CreatePresenter.class })
public void valueChosen(String value);

In this example, when you display the create page, the 'valueChosen' event will only be forwarded to the create presenter. At the opposite, when you display the edit page, the 'valueChosen' event will only be forwarded to the edit presenter

A presenter can also deactivate itself by calling its setActivated method.

Shell Presenter & Start Event

Shell Presenter

Each application needs to define a shell presenter. The shell presenter is the presenter which will be called to add the shell to the browser's viewport when the application starts.

To define a shell presenter, you need to specify its class thanks to the attribute 'shell' of the @Events annotation that annotates your event bus interface. MVP4G2 will automaticaly find the instance of the presenter if it is annotated with @Presenter. The shell presenter has to implement the IsShell interface (otherwise you can not use the presenter as shell presenter!). Using the IsShell interface forces the shell presenter to add a setShell- method which is automatically called by the framework.

@Presenter(viewClass = ShellView.class, viewInterface = IShellView.class)
public class ShellPresenter
  extends AbstractPresenter<MyEventBus,
                             IShellView>
  implements IShellView.Presenter,
             IsShell {

  public ShellPresenter() {
  }

  @Override
  public void setShell() {
    document.body.appendChild(view.asElement());
  }
}

Example using Elemental 2 to add a view to the viewport.

Start Event

When your application starts, you may want to automatically fire an event so that actions needed at first can occur. To define a start event, you need to annotate it with @Start. You can have only one start event by module and no object can be fired with the start event.

@Events(...)
public interface MyEventBus extends EventBus {

     @Start
     @Event
     void start();

}

Logs

Mvp4g2 integrates a log feature that let you trace the events fired on the event bus and see who handles each event. To enable mvp4g2 logging, please add -setProperty mvp4g2.logging=true to the devmode arguments.

To activate the log feature, you need to annotate your event bus class with @Debug:

@EventBus(...)
@Debug
public interface MyEventBus
  extends IsEventBus {

  @Event(handlers={EditPresenter.class, CreatePresenter.class)
  public void displayName(String name);
        
}

The @Debug annotation will only be handle if the interface is also annotated with @EventBus!

@Debug annotation has the following attributes:

  • logLevel (optional, default: SIMPLE): define the level of log: SIMPLE (only names of fired events will be logged) DETAILED (names of fired events and names of handlers will be logged)
  • logger (optional, default: DefaultMvp4g2Logger): define the class of the logger to use

The following traces will be logged when this code is executed:

eventBus.gotoList("Si", "");
  • SIMPLE:
DEBUG - EventBus -> handling event: >>gotoList<< with parameters: >>Si<<, >><<
  • DETAILED:
DEBUG - EventBus -> handling event: >>gotoList<< with parameters: >>Si<<, >><<
   DEBUG - EventBus -> event: >>gotoList<< binding handler: >>de.gishmo.gwt.example.mvp4g2.simpleapplication.client.ui.list.ListPresenter<<
   DEBUG - EventBus -> event: >>gotoList<< activating handler: >>de.gishmo.gwt.example.mvp4g2.simpleapplication.client.handler.SimpleApplicationHandler<<
   DEBUG - EventBus -> event: >>gotoList<< handled by: >>de.gishmo.gwt.example.mvp4g2.simpleapplication.client.ui.list.ListPresenter<<

Mvp4g logger

By default, Mvp4g will use the DefaultMvp4g2Logger. This logger uses DomGlobal.window.console.log to display the logs. To see any logs, you have to turn on Mvp4g2 logging using setProperty mvp4g2.logging=true. You can easily use the logger of your choice (like gwt-log for example). All you have to is to create a logger that implements Mvp4gLogger?.

public class CustomLogger 
  implements IsMvp4g2Logger {

    public void log( String message, int depth ) {
        String indent = "";
                for ( int i = 0; i < depth; ++i )
                        indent += "    ";
                //really annoying log :)        
        DomGlobal.window.alert( indent + "CustomLogger: " + message );
    }

}

and then tell Mvp4g to use this logger thanks to @Debug annotation:

@Events(...)
@Debug(logger=CustomLogger.class)
public interface MainEventBus extends IsEventBus {...}

Event Filtering

Mvp4g2 allows you to filter events in order to stop an event before it is forwarded to handlers.

Creating Filters

To create a filter, you need to:

  1. implement IsEventFilter< e > - e: type of your event bus interface.
  2. override filterEvent method: this method will allow to stop an event or not. If filterEvent method returns false, then the event is stopped, otherwise, it is forwarded to the handlers.
Adding Filters

Once you have created a filter, you need to tell Mvp4g to use it. This will be done thanks to the @Filters that annotates your event bus interface.

        @Events( ... )
        @Filters( filterClasses = OneEventFilter.class )
        public interface OneEventBus extends EventBus {...}

The @Filters annotation will only be handle if the interface is also annotated with @EventBus!

Annotation @Filters has the following attributes:

  • filterClasses: set one or several filters to use. An instance will be created for each class specified. afterHistory (optional, by default false): set if events should be filtered before or after browser history convertion. If this attribute is set to true (ie events are filtered after history convertion), even if the event is stopped, a token will still be stored in browser history for this event.

You can also dynamically add or delete filters thanks to the addEventFilter and removeEventFilter methods of the event bus.

        OneEventFilter filter = new OneEventFilter();
        eventBus.addEventFilter(filter);
        eventBus.removeEventFilter(filter);

Temporarily disable filtering

In some cases, you may need to temporarily stop events filtering. In order to do so, Mvp4g2 provides two methods using the event bus, setFilteringEnabled and setFilteringEnabledForNextOne. The first method allows you to enable/disable filtering for all events of the event bus. The second one allows you enable/disable filtering for the next fired event (other events fired while handling this event won't be affected by the call of this method).

In this example, none of the events fired will be filtered:

        eventBus.setFilteringEnabled(false);
        eventBus.selectCompanyMenu();

In this example, only the selectCompanyMenu event won't be filtered:

        eventBus.setFilteringEnabledForNextOne(false);
        eventBus.selectCompanyMenu();

In this example, selectCompany event and all the events that are fired by handlers of selectCompany event won't be filtered:

        eventBus.setFilteringEnabled(false);
        eventBus.selectCompanyMenu();
        eventBus.setFilteringEnabled(true);

Navigation Event

Navigation event represents events that, when fired, usually lead to the load of a new screen (but it's not mandatory), like if the user navigates to a new page. A special control can be added to stop this type of events before it is forwarded to the handlers in order to prevent the user to switch page. A common use case of this control is to prevent user to loose data when leaving a page.

Defining an event as a Navigation Event

To define an event as a navigation event, you just need to set the navigationEvent attribute of @Event to true (by default it is set to false):

@Event(..., navigationEvent = true)
void goToPage1();
Creating a control object for navigation events

To create a control object, you need to:

  • implement IsNavigationConfirmation
  • define the confirm method.

When an event needs to be control, the confirm method is called with one parameter, a NavigationEventCommand? that represents the event to control. To confirm an event, you just have to call the NavigationEventCommand's method fireEvent. To stop an event, just don't call this method.

public class Presenter extends ... implements NavigationConfirmationInterface {

     public void confirm(NavigationEventCommand event) {
          //pseudo method to verify if the view has changed
          if (isViewModified(){
               //Window shouldn't be used inside a presenter
               //this is just to give a simple example
               if (Window.confirm("Are you sure you want to leave?")){
                    event.fireEvent();
               }                                
          } else {
               event.fireEvent();
          }
     }
}

Navigation control is asynchron. The main advantage of this solution is that you can use your own widget to ask for user's confirmation and to validate your data against the server before a navigation will be done.

public class Presenter extends ... implements NavigationConfirmationInterface {

     public void confirm (NavigationEventCommand event) {
          view.getConfirmWidget().setYesClickHandler(new ClickHandler() {
               public void onClick(ClickEvent event) {
                    event.fireEvent();                  
               }
          });                           
     }    
}

By default, calling the fireEvent method will automatically remove the navigation control in order to prevent other navigation events to be controlled. If a user confirms that he wants to leave a screen, then no control should be done until a new screen is displayed. When displaying the new screen, you should set a new control.

However if for any reason you don't want to remove the control, you can just call the fireEvent method with a false parameter:

event.fireEvent(false);
Setting a control for navigation events

Whenever you display a screen that needs user's confirmation before leaving it, you need to set a IsNavigationConfirmation instance. In order to do this, you need to call the eventBus's method setNavigationConfirmation with the IsNavigationConfirmation instance to set

eventBus.setNavigationConfirmation(navigationConfirmationInstance);

If you need to remove the IsNavigationConfirmation instance, just call the setNavigationConfirmation method with a null parameter:

eventBus.setNavigationConfirmation(null);

You can set only one NavigationConfirmationInterface? instance for the whole application. This instance will be used to control any navigation events even fired by other Mvp4g2 modules.

Passive Event

Unlike a regular event, a passive event won't automatically build presenters that handles it. Thus a passive event will be forwarded only to presenters/child modules that have already been build/loaded.

To set an event as passive, you just need to set the passive attribute of @Event to true (by default it is set to false):

@Event(..., passive = true)
void sessionExpired();
Bind Event

If you need a presenter to be binded when an event is fired but actually don't need this presenter to handle this event.

@Event( ..., bind = { FirstNestedViewPresenter.class, SecondNestedViewPresenter.class } )
public void start();

By adding a presenter to bind attribute, Mvp4g2 will bind this presenter/event handler the first time this event is fired.

You can't use the same presenter in handler and bind annotation for one event because a presenter set as an handler will be already automatically binded when the event is fired. Also, Mvp4g2 prohibits using bind attribute for passive events. Passive event, by definition, don't bind handlers (only handlers already binded will react to the passive event).

You don't need to provide any methods in Presenter when you're using bind annotation. It will call 'bind' method of your presenter automatically.