Using Event Handlers in GWT-1.6

I’ve already blogged about Event Handlers in GWT-1.6. Everyone coding Java knows how an event Listener works. How to attach one to an event source, how to handle the events, and how to create one yourself. Handlers are a bit different though, as an example I thought I’d show you how to write a very simple (un-styled) VerticalTabBarclass using GWT-1.6. The VerticalTabBar class has the following design points:

  • It’s root Element is a simple <div> with a primary style of “VerticalTabBar”
  • Each tab is a simple <div> element (wrapped in it’s own Widget class)
  • Each tab has the CSS class-name “Tab”
  • The selected tab has the CSS class-name “Tab Tab-Selected”
  • The VerticalTabBar fires “ValueChangeEvents” with the index of the tab that has been selected

So here goes:

import java.util.List;
import java.util.ArrayList;

import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.DivElement;

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

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

import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.logical.shared.HasValueChangeHandlers;

import com.google.gwt.user.client.Element;

import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.ComplexPanel;

// Unlike previous GWT implementation, implementing the Has***Handlers interface of the Event type
// your class dispatches is now mandatory, and you'll see why a little further down.
public class VerticalTabBar extends ComplexPanel
        implements HasValueChangeHandlers<Integer> {

    private final DivElement root;

    private final List<Tab> tabs = new ArrayList<Tab>();

    private int selected = -1;

    public VerticalTabBar() {
        root = Document.get().createDivElement();
        setElement(root);
        setStylePrimaryName("VerticalTabBar");
    }

    private void setTabSelected(final int tabIndex, final boolean selected) {
        if(tabIndex != -1) {
            tabs.get(tabIndex).setSelected(selected);
        }
    }

    private void setSelectedTabImpl(final int tabIndex) {
        setTabSelected(selected, false);
        setTabSelected(selected = tabIndex, true);
    }

    public void add(final String tabText) {
        add(tabText, false);
    }

    public void add(final String tabText, final boolean asHTML) {
        final Tab tab = new Tab(tabText, asHTML);
        tabs.add(tab);
        add(tab, root.<Element>cast());
    }

    public int getSelectedTab() {
        return selected;
    }

    public void setSelectedTab(final int tabIndex) {
        if(tabIndex != selected) {
            if(tabIndex < 0 || tabIndex >= tabs.size()) {
                setSelectedTabImpl(-1);
            } else {
                setSelectedTabImpl(tabIndex);
            }

            // This is how you fire an Event with a Handler.
            // The signature of the "fire" method in ValueChangeEvent is:
            // public static <I> void fire(HasValueChangeHandlers<I> source, I value)
            // If we don't implement the HasValueChangeHandlers, we can't fire ValueChangeEvents
            ValueChangeEvent.fire(this, selected);
        }
    }

    public int getTabCount() {
        return tabs.size();
    }

    public HandlerRegistration addValueChangeHandler(
            final ValueChangeHandler<Integer> handler) {

        // This is really all we need to do to add a new Handler
        // addHandler is defined in Widget, and registers the Handler
        // with our HandlerManager
        return addHandler(handler, ValueChangeEvent.getType());
    }

    private class Tab extends Widget {
        private HandlerRegistration registration;

        private boolean selected = false;

        private Tab(final String tabText, final boolean asHTML) {
            final DivElement element = Document.get().createDivElement();

            if(asHTML) {
                element.setInnerHTML(tabText);
            } else {
                element.setInnerText(tabText);
            }

            setElement(element);
            setStylePrimaryName("Tab");
        }

        @Override
        protected void onLoad() {
            super.onLoad();

            // As you can see, you don't have to use onBrowserEvent anymore,
            // instead just register a Handler without exposing the Has***Handlers.
            // This technique only works for low-level browser (DOM) events.
            // Also not the fact that we invoke "addDomHandler" here, and not "addHandler"
            registration = addDomHandler(new ClickHandler() {
                public void onClick(final ClickEvent event) {
                    setSelectedTabImpl(tabs.indexOf(Tab.this));
                }
            }, ClickEvent.getType());
        }

        @Override
        protected void onUnload() {
            super.onUnload();

            registration.removeHandler();
            registration = null;
        }

        public void setSelected(final boolean selected) {
            if(this.selected = selected) {
                addStyleDependentName("Selected");
            } else {
                removeStyleDependentName("Selected");
            }
        }

    }

}

As you can see from this example, event dispatching and handling has become much easier with Handlers. You can invent your own Event types, and use them in the same way as all the other Handlers.


GWT’s new Event Model – Handlers in GWT 1.6

Theres a lot of concern and worry around the “new event model” in GWT 1.6: throwing out Listeners (a well know and used Java construct) and replacing them with Handlers (a GWT team invention). Let me put your mind at rest, Handlers will make your GWT life much easier.

So what are the differences between Listeners and Handlers?

Listeners Handlers
Have one Listener per event source (ie: “mouse”, “keyboard”, etc) Have one Handler per event type (ie: “mouse press”, “mouse down”, “mouse up”)
Are uncategorized, and the Widget is responsible for sinking / un-sinking the DOM events Are categorized into 2 types: DOM and Logical, adding a DOM Handler to any Widget deferrers to Widget.addDomHandler which sinks events as required
Each Widget uses a ListenerCollection for each class of Listener added All Handlers are stored with a HandlerManager which also manages dispatching any type of event (including new ones you create yourself)
Listeners are removed with the pattern Button .removeClickListener(listener); Adding a Handler returns a HandlerRegistration object, removing a Handler is done by calling HandlerRegistration .removeHandler();
GWT Listeners accept a parameter for each bit of relative event information (ie: sender, mouse x, mouse y) Handlers (more like normal Java Event Listeners) are passed an Event object with all the Event details as their only parameter

So what do Handlers actually buy you in code?

Possibly the most important thing to notice is they take up a lot less space when compiled to GWT. This in mainly due to the fact that their management is centralized in HandlerManager instead of requiring a separate ListenerCollection for each Listener type, and that an implementation only needs a single method overridden (instead of having to listen for events you’re not interested in).

Another point to take note of is that all DOM related Event objects extend from DomEvent. Why is this important? Because DomEvent includes the methods:

  • preventDefault()
  • stopPropagation()
  • getNativeEvent()

I find the last (getNativeEvent()) to be the most useful, it works much the same as Event.getCurrentEvent() and returns a NativeEvent (the new parent class to Event). The advantage of this mechanism over Event.getCurrentEvent() is that you have all the access to the underlying event information (mouse button, modifiers, etc) but attached directly to the dispatched event.

Summary

Handlers are a good step forward for GWT, and make abundant sense in a Web environment where code size is very important. They also make it much easier to add you own events since you no longer need to manage your own Listener registration / deregistration cycle or firing of the events to all of the registered Listeners (HandlerManager does it all for you). I think there will be some interesting new patterns emerging with regards to managing events on generated structures (built based on data fetched from the server for example).

I’m looking forward to seeing what comes of these changes.