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.


Development patterns in EoD SQL 2.0

EoD SQL has always been focused on flexability, and EoD SQL 2.0 is taking that concept even further.

To introduce this topic, I have some important points (some of which may already be known):

  • EoD SQL understands Connection pooling
    • Creating a Query instance with a DataSource is completely different to creating one with a Connection
  • EoD SQL allows you to declare the type you want to return
  • EoD SQL does not mangle your SQL, ever!
    • You tell your database exactly what you want to

Here is a little example of some code that uses some of EoD SQL’s more advanced tricks:

public class User {
  private static final UserQuery QUERY = QueryTool.getQuery(UserQuery.class);

  @AutoGeneratedKeys
  private Long id = null;

  private String email;

  private String password;

  @ResultColumn( "display_name" )
  private String displayName;

  private User() {}

  public User(String email, String password, String displayName) {
    this.email = email;
    this.password = password;
    this.displayName = displayName;
  }

  public Long getId() {
    return id;
  }

  public String getEmailAddress() {
    return email;
  }

  public void setEmailAddress(String emailAddress) {
    this.email = emailAddress;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public String getDisplayName() {
    return displayName;
  }

  public void setDisplayName(String displayName) {
    this.displayName = displayName;
  }

  public synchronized void save() throws SQLException {
    if(id == null) {
      id = QUERY.insert(this);
    } else {
      QUERY.update(this);
    }
  }

  public static User getUser(long id) throws SQLException {
    return QUERY.selectById(id);
  }

  public static User getUser(String email) throws SQLException {
    return QUERY.selectByEmail(email);
  }
}

The things to note in the above code:

And then the Query declaration:

public interface UserQuery extends BaseQuery {
  @Select( "SELECT * FROM users WHERE id = ?1 LIMIT 1" )
  public User selectById(long id) throws SQLException;

  @Select( "SELECT * FROM users WHERE email = ?1 LIMIT 1" )
  public User selectByEmail(String email) throws SQLException;

  @Update( sql="INSERT INTO users (email, password, display_name) " +
    "VALUES (?{1.email}, ?{1.password}, ?{1.displayName})",
    keys=GeneratedKeys.RETURNED_KEYS_FIRST_COLUMN )
  public long insert(User user) throws SQLException;

  @Update( "UPDATE users SET email = ?{1.email}, password = ?{1.password}," +
    "display_name = ?{1.displayName} WHERE id = ?{1.id}" )
  public void update(User user) throws SQLException;
}

Things to note in the above code:

  1. The UserQuery instance QUERY in the User class is declared “static final”
    1. Because it is created with a DataSource, EoD SQL will automatically pull a Connection from the pool each time you run a query
    2. You need never close one of these Query objects, since the Connection is returned to the pool when you are no longer using it automatically
  2. Returning Database-Generated-Keys has become much easier in EoD SQL 2.0 as you can see from the “insert” method of the Query interface
  3. You are not forced to return a DataSet<User> in EoD SQL, you can return any type that EoD SQL understands (and it knows about quite a few)
    1. All the Java primitives and their wrapper classes
    2. Arrays
    3. UUID
    4. Any class you write that could be returned in a DataSet
    5. Many of the Collections types
      1. Collection
      2. List
      3. Set
      4. SortedSet