GWT EventBus


December 5, 2013 Maithilish

4.3. GWT EventBus and Events

ContentPanel has to listen for MenuItem clicks and add tab with appropriate content.
Traditional Event Handling

Implement ClickHandler in ContentPanel with the following modifications.

in.fins.client.widget/ContentPanel.java

....

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.datepicker.client.DateBox;

.....

public class ContentPanel extends ResizeComposite implements ClickHandler

....

  @Override
  public void onClick(ClickEvent event) {
   String text = ((Anchor) event.getSource()).getText();
   addTab(text, new DateBox());
  }

....

DateBox is a placeholder, and actual content replaces it later.
In MenuBar class add a method addClickHandler() which sets click handler for each and every anchor embedded in StackLayoutPanel. It iterates through the LayoutPanels and adds the handler to all MenuItems.
in.fins.client.widget/MenuBar.java

....

import java.util.Iterator;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.event.dom.client.ClickHandler;

....

public void addClickHandler(ClickHandler clickHandler) {
    Widget widget = getWidget();
    Iterator<Widget> it = ((HasWidgets) widget).iterator();
      while (it.hasNext()) {
         Widget w = it.next();
         if (w instanceof LayoutPanel) {
            Iterator<Widget> it1 = ((HasWidgets) w).iterator();
            while (it1.hasNext()) {
                Widget menuItem = it1.next();
                if (menuItem instanceof MenuItem) {
               ((MenuItem) menuItem).anchor
               .addClickHandler(clickHandler);
                }
            }
         }
     }
}

....

Link ContentPanel and MenuBar by calling addClickHandler() method in FinsShell.java .
in.fins.client.content/FinsShell.java

....

import in.fins.client.widget.MenuBar;

....

@UiField
MenuBar menuBar;

public FinsShell() {

....

  contentPanel.addTab("Home", new DateBox());

  menuBar.addClickHandler(contentPanel);
}

....

 
With this, menu adds tab to ContentPanel.
This is the traditional way to handle events which directly couples MenuBar and ContentPanel. EventBus mechanism, introduced in GWT 2.1, is an elegant way to decouple events source and handler. Revert back the modifications done in this section i.e. remove ClickHandler implementation in ContentPanel.java, addClickHandler() method in MenuBar.java and method call in FinsShell.java, and we ready to look at GWT EventBus.
 
GWT EventBus

GWT EventBus allows objects to interact without having direct dependencies upon one another, and with this event sources no longer require to maintain handler lists. Typically an application will have one EventBus, which carries the events broadcast by event sources.

Create a new package in.fins.client.event to hold events and their handlers classes. First task is to create an application wide EventBus. Add EventBus.java to in.fins.client.event package.
in.fins.client.event/EventBus.java

package in.fins.client.event;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.shared.SimpleEventBus;

public class EventBus {
	private EventBus() {
	}

	private static final SimpleEventBus INSTANCE = GWT
			.create(SimpleEventBus.class);

	public static SimpleEventBus get() {
		return INSTANCE;
	}
}

It is a singleton which return an instance of SimpleEventBus.
Next we require MenuEvent, that has to fire when user clicks MenuItem. Add MenuEvent to in.fins.client.event package. Eclipse shows some error about MenuHandler which may be ignored as we are going to add it in the next step.
in.fins.client.event/MenuEvent.java

package in.fins.client.event;

import com.google.web.bindery.event.shared.Event;

public class MenuEvent extends Event<MenuHandler> {                       1
        public static final Type<MenuHandler> TYPE = new Type<MenuHandler>();    2
        private String menu;

	public MenuEvent(String menu) {
		this.menu = menu;
	}

	public String getMenu() { 
		return menu;
	}

	@Override
	public Type<MenuHandler> getAssociatedType() {                           3
                return TYPE;
	}

	@Override
	protected void dispatch(MenuHandler handler) {                           4
		handler.onMenuSelection(this);
	}

}

1

extends Event<MenuHandler> and implements its two methods

2

Type associated with this event is MenuHandler

3

Type<MenuHandler> getAssociatedType() – Returns the Type used to register this event, allowing an EventBus to find handlers of MenuHandler class.

4

void dispatch(MenuHandler handler) – calls the handler and pass on the source MenuEvent.
Next add MenuHandler interface, which extends EventHandler. Method defined here is called in MenuEvent.dispatch(). There is no restriction in naming the method.
in.fins.client.event/MenuHandler.java

package in.fins.client.event;

import com.google.gwt.event.shared.EventHandler;

public interface MenuHandler extends EventHandler {

	public void onMenuSelection(MenuEvent menuEvent);

}

Make ContentPanel as MenuHandler with following modifications to ContentPanel.java.
in.fins.client.widget/ContentPanel.java

....

import in.fins.client.event.EventBus;
import in.fins.client.event.MenuEvent;
import in.fins.client.event.MenuHandler;
import com.google.gwt.user.datepicker.client.DateBox;

....

public class ContentPanel extends ResizeComposite implements MenuHandler {

        public ContentPanel() {
                initWidget(binder.createAndBindUi(this));
                EventBus.get().addHandler(MenuEvent.TYPE, this);
        }
....
        @Override
        public void onMenuSelection(MenuEvent menuEvent) {
                String contentName = menuEvent.getMenu();
                addTab(contentName, new DateBox());
        }
 }

ContentPanel implements MenuHandler and its handler method retrieves the name of the selected menu from MenuEvent and adds a tab with addTab() method. In constructor, addHandler() registers the ContentPanel with EventBus to receive any MenuEvent.
Now all that is left is to fire MenuEvents.
in.fins.client.widget/MenuItem.java

....

import in.fins.client.event.EventBus;
import in.fins.client.event.MenuEvent;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;

....

        @UiConstructor
        public MenuItem(String text, ImageResource imageResource,
                        final String contentName) {
                initWidget(binder.createAndBindUi(this));
                anchor.setHTML(getHtml(imageResource, text));
                anchor.setName(contentName);
                anchor.addClickHandler(new ClickHandler() {
                        @Override
                        public void onClick(ClickEvent event) {
                                EventBus.get().fireEvent(new MenuEvent(contentName));
                        }
                });
        }

....

This adds ClickHandler to the Anchor which fires a new MenuEvent with menu name to EventBus. On click, MenuItem sends a MenuEvent to EventBus, and it in turn, dispatches it to any interested handler i.e. to ContentPanel and all these without any direct coupling between Menu and ContentPanel.
GWT EventBus coding is slightly complicated as compared to event-listener and you may want to switch back to event-listener model. But hold that thought, once we get to the next widget benefits of EventBus become apparent. Throughout the application, we are going to use EventBus to handle events and to some its logic may still bit fuzzy, so to reinforce the concept let’s add Status widget to application.