Friday, February 7, 2014

GWT - UiBinder Quick Intro

Before we start the UiBinder business, let's rewind the clock to GWT 1.x era. This is how to create a simple "click me" button.
public class Entry implements EntryPoint {

    @Override
    public void onModuleLoad() {
        Button button = new Button();
        button.setText("click me");
        button.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                Window.alert("hello world");
            }
        });

        RootPanel.get().add(button);
    }
}

Thanks to java's verbosity, the signal to noise ratio is pretty low. It goes wild quite fast when we need to express nested DOM structures, like
public class Entry implements EntryPoint {

    @Override
    public void onModuleLoad() {
        Button button = new Button();
        // ... some init code
        Panel outterContainer = new FlowPanel();
        // ... some init codes
        Panel innerContainer = new FlowPanel();
        // ... some init codes
        
        outterContainer.add(innerContainer);
        innerContainer.add(button);

        RootPanel.get().add(outterContainer);
    }
}
Another problem here is, since java code is kind of linear, little aid is provided by the editor to present that nesting structure.

So the xml came to save the world, and we moved to the GWT 2.x era. The basic idea of UiBinder is to express the dom in xml instead of java.
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder 
    xmlns:ui='urn:ui:com.google.gwt.uibinder' 
    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
    <g:FlowPanel>
        <g:Button>hello world</g:Button>
    </g:FlowPanel>
</ui:UiBinder>
The above code will be translated by GWT compiler as
FlowPanel panel = new FlowPanel();

Button button = new Button();
button.setText("hello world");

panel.add(button);
To programmatically access the generated component, we need to give an identifier in the xml
<g:Button ui:field='myButton'>hello world</g:Button>
Now let's move to the java code, the key here is we are going to declare a seemingly orphan interface, then obtaining an instance of it via the might GWT.create() method.
public class Entry implements EntryPoint {

    interface UI extends UiBinder<FlowPanel, Entry> {}

    @Override
    public void onModuleLoad() {
        UI uiBinder = GWT.create(UI.class);        
    }
}
Then using the @UiField annotation to link to the button.
public class Entry implements EntryPoint {

    interface UI extends UiBinder<FlowPanel, Entry> {}

    @UiField Button myButton;
    
    @Override
    public void onModuleLoad() {
        UI uiBinder = GWT.create(UI.class);
        uiBinder.createAndBindUi(this);
        
        Window.alert(myButton.getText());
        RootPanel.get().add(myButton);
    }
}
Now run the project, you will see a boring alert once the app is loaded.

But wait! UiBinder can do more than that, it can be used to hook events. Things to note here,
  1. @UiHandler annotation, it's value needs to be the same as the java field name (myButton)
  2. the annotated method has no return value, void. 
  3. the annotated method takes exactly one argument, which needs to be of some GwtEvent. here ClickEvent is a sub class of GwtEvent
  4. the field must have corresponding addXXXHandler()
  5. the annotated method name as well as the argument name can be anything 
Here is a sample declaration
@UiHandler("myButton")
void onButtonClick(ClickEvent e) {
    myButton.setText("hello event");
}
Now refresh the page and click the button to see its text changed.

Codes are available from https://github.com/verydapeng/gwt-tutorials under the folder UiBinder-Quick-Intro

To dig more out of uibinder, follow the link here http://www.gwtproject.org/doc/latest/DevGuideUiBinder.html