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.


13 Responses to “Using Event Handlers in GWT-1.6”

  1. GWT’s new Event Model - Handlers in GWT 1.6 « Lemming Technology Blog « try {} catch () Says:

    […] Using Event Handlers in GWT-1.6 « Lemming Technology Blog. […]

  2. d Says:

    Hi,

    I am a Java newbie and this is opaque to me. I see where the ValueChange event is registered but I do not see where / how the value in question is communicated.

    In my case I have a small app that creats a Grid. The Grid will have a small number of columns. One of the columns will hold a TextBox in each cell of that column.

    When the value of any TextBox changes I want to do something with the value but i need to know the row number of the changed TextBox.

    I decide to wrap the TextBox and in int into a Composite widget. I can implement the onChanre method and, using your example, the “ValueChangeEvent.fire(this, row);” trigger. I do not see how the HandlerRegistration interface in your example says “Here is the row of the TextBox that was just changed.”

    I know I am without clue here but that is exactly what I am searching for, the clue.

    • Jason Says:

      The HandlerRegistration is simply a reference object that can be used to remove the registered Handler object at a later date (see the Tab.onUnload() method).

      If you ValueChangeEvent.fire(this, row); each registered ValueChangeHandler will receive a ValueChangeEvent. The GwtEvent.getSource() method will return the first parameter (this), while the ValueChangeEvent.getValue() method will return the second (row).

      You maybe want the Grid to rather extend Composite and fire the onValueChange() events, that way you only need one ChangeHandler listening on all the TextBox’s (rather than one Handler per TextBox). If you like I can post you a code example, but it may take me a bit of time (I’ll be traveling over the next few days).

      • d Says:

        A code example would be very nice. Thank you.

        I went into the gwt-user.jar and grep’d around for other examples of ValueChange and found a few. Seeing them did not help at all.

  3. Dave Coble Says:

    Here’s what I tried:

    VerticalTabBar verticalTabBar = new VerticalTabBar();
    verticalTabBar.add(“category 1”);
    verticalTabBar.add(“category 2”);
    verticalTabBar.addValueChangeHandler(new ValueChangeHandler(){

    public void onValueChange(ValueChangeEvent event) {

    });
    RootPanel.get(“leftNav”).add(verticalTabBar);

    “category 1” and “category 2” appear where they should in the the browser, but it seems that onValueChange is not called when I click on either of my category entries.

    Also, my css file looks like this:

    .VerticalTabBar {
    background: #F00;
    }

    .VerticalTabBar-Selected {
    background: #0F0;
    }

    Tab {
    background: #FF0;
    }

    Tab Tab-Selected {
    background: #0FF;
    }

    I see the red background, but never the green, yellow or bluegreen.

    What did I forget?

  4. Dave Coble Says:

    Oops, the code really looks like this:


    verticalTabBar.addValueChangeHandler(new ValueChangeHandler(){

    @Override
    public void onValueChange(ValueChangeEvent event) {
    RootPanel.get(“eventDescription”).add(new Label(event.toDebugString()));
    }

    });

    • Jason Says:

      You didn’t forget anything, the code in the post has a little bug in it. The VerticalTabPanel.add(String, boolean) method should look like this:

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

      I’ll update the post to patch it 😉

  5. d Says:

    Thanks Jason. This is simpler than I thought.

    cheers,

  6. Frank Zhao Says:

    I think in your onClick() method of Class Tab (line 120), it should be “setSelectedTab(tabs.indexOf(Tab.this));” rather than “setSelectedTabImpl(tabs.indexOf(Tab.this));”. Otherwise, no event will be fired when the tab is clicked.

  7. Zoran Rilak Says:

    Just a small typo to fix,

    if(this.selected = selected) {

    should really have an equality operator ‘==’ instead of the assignment operator ‘=’. How did you get your code to compile, anyway? 😉

    But silly criticisms aside, this is an excellent post. Thank you!

    • Jason Says:

      Actually, that’s not a typo at all it’s supposed to be an assignment.

      The idea there is to assign the parameter to the Tab.select field and then test the fields new value. It’s much the same as the following code:

      this.selected = selected;
      if(this.selected) {
      }

  8. joe Says:

    thanks a lot, great little demo

  9. Piotr Sobczyk Says:

    Thank you for very useful article!


Leave a comment