Thursday, August 30, 2012

Spring and GWT tutorial. Part 5 - UiBinder

After pretty long time we are continuing developing our application based on Spring and GWT.
Earlier user interface and application logic were described in one class HelloGWT. Now we will create new widget for that and describe all user interface via XML with usage of UiBinder.

This post is available in Russian.

Create class HelloGWTWidget in package com.hellogwt.client. To make UiBinder work we have to satisfy several conditions:
  • class HelloGWTWidget has to extend class Composite
  • declare in class HelloGWTWidget interface HelloGWTWidgetUiBinder which extends interface UiBinder:
    interface HelloGWTWidgetUiBinder extends UiBinder<Widget, HelloGWTWidget> {
    }
    
  • declare variable uiBinder which refers to HelloGWTWidgetUiBinder instance:
    private static HelloGWTWidgetUiBinder uiBinder = GWT.create(HelloGWTWidgetUiBinder.class);
    
  • declare a variable annotated with @UiField for every UI element:
    @UiField
    TextBox authorTextBox;
    
  • invoke method initWidget() in HelloGWTWidget constructor:
    public HelloGWTWidget() {
        initWidget(uiBinder.createAndBindUi(this));
        ...
    }
    
  • describe UI elements in corresponding file HelloGWTWidget.ui.xml:
    <ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
                 xmlns:g='urn:import:com.google.gwt.user.client.ui'>
    
        <g:VerticalPanel>
            <g:HorizontalPanel>
                <g:Label text="Author:" width="50"/>
                <g:TextBox ui:field="authorTextBox" width="100"/>
            </g:HorizontalPanel>
            ...        
        </g:VerticalPanel>
    
    </ui:UiBinder>
    

Moreover we can use annotation @UiHandler for event handlers. Earlier class HelloGWT contained method initHandlers() where event handlers were added for UI elements:
public class HelloGWT implements EntryPoint {
    ...
    @Override
    public void onModuleLoad() {
        ...
        initHandlers();
        ...
    }
    
    private void initHandlers() {
        addButton.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent clickEvent) {
                ...
            }
        });
        ...
    }
    ...
}

We can put every handler's logic in corresponding method annotated with @UiHandler setting appropriate UI element. The method's argument is an event object that should be handled. This approach helps to escape from anonymous classes declarations and improves code insight. For example, that is how a click handler for "Add" button looks like:
@UiHandler("addButton")
void handleAddButtonClick(ClickEvent clickEvent) {
    if (!authorTextBox.getText().isEmpty() && !textTextBox.getText().isEmpty()) {
        greetingService.getGreeting(textTextBox.getText(), new AsyncCallback<Greeting>() {
            @Override
            public void onFailure(Throwable caught) {
                Window.alert("ERROR: Cannot find greeting!");
            }

            @Override
            public void onSuccess(Greeting result) {
                if (result == null) {
                    greetingService.addGreeting(authorTextBox.getText(), textTextBox.getText(), 
                            callback);
                } else {
                    Window.alert("Greeting already exists!");
                }
            }
        });
    } else {
        Window.alert("\"Author\" and \"Text\" fields cannot be empty!");
    }
}

After all changes class HelloGWTWidget will look like that:
package com.hellogwt.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.*;
import com.hellogwt.client.service.GreetingService;
import com.hellogwt.client.service.GreetingServiceAsync;
import com.hellogwt.shared.domain.Greeting;

import java.util.List;

public class HelloGWTWidget extends Composite {

    interface HelloGWTWidgetUiBinder extends UiBinder<Widget, HelloGWTWidget> {
    }

    private static HelloGWTWidgetUiBinder uiBinder = GWT.create(HelloGWTWidgetUiBinder.class);

    private GreetingServiceAsync greetingService = GWT.create(GreetingService.class);

    private AsyncCallback<Void> callback = new AsyncCallback<Void>() {
        @Override
        public void onFailure(Throwable caught) {
            Window.alert("ERROR: Cannot edit greetings!");
        }

        @Override
        public void onSuccess(Void result) {
            refreshGreetingsTable();
        }
    };

    @UiField
    TextBox authorTextBox;
    @UiField
    TextBox textTextBox;
    @UiField
    Button addButton;
    @UiField
    Button updateButton;
    @UiField
    Button deleteButton;
    @UiField
    FlexTable greetingsFlexTable;

    public HelloGWTWidget() {
        initWidget(uiBinder.createAndBindUi(this));

        refreshGreetingsTable();
    }

    @UiHandler("addButton")
    void handleAddButtonClick(ClickEvent clickEvent) {
        if (!authorTextBox.getText().isEmpty() && !textTextBox.getText().isEmpty()) {
            greetingService.getGreeting(textTextBox.getText(), new AsyncCallback<Greeting>() {
                @Override
                public void onFailure(Throwable caught) {
                    Window.alert("ERROR: Cannot find greeting!");
                }

                @Override
                public void onSuccess(Greeting result) {
                    if (result == null) {
                        greetingService.addGreeting(authorTextBox.getText(), textTextBox.getText(), 
                                callback);
                    } else {
                        Window.alert("Greeting already exists!");
                    }
                }
            });
        } else {
            Window.alert("\"Author\" and \"Text\" fields cannot be empty!");
        }
    }

    @UiHandler("updateButton")
    void handleUpdateButtonClick(ClickEvent clickEvent) {
        if (!authorTextBox.getText().isEmpty() && !textTextBox.getText().isEmpty()) {
            greetingService.updateGreeting(authorTextBox.getText(), textTextBox.getText(), callback);
        } else {
            Window.alert("\"Author\" and \"Text\" fields cannot be empty!");
        }
    }

    @UiHandler("deleteButton")
    void handleDeleteButtonClick(ClickEvent clickEvent) {
        greetingService.deleteGreeting(textTextBox.getText(), callback);
    }

    private void refreshGreetingsTable() {
        greetingService.getGreetings(new AsyncCallback<List<Greeting>>() {
            @Override
            public void onFailure(Throwable throwable) {
                Window.alert("ERROR: Cannot load greetings!");
            }

            @Override
            public void onSuccess(List<Greeting> greetings) {
                fillGreetingsTable(greetings);
            }
        });
    }

    private void fillGreetingsTable(List<Greeting> greetings) {
        greetingsFlexTable.removeAllRows();

        greetingsFlexTable.setText(0, 0, "Author");
        greetingsFlexTable.setText(0, 1, "Text");

        for (Greeting greeting : greetings) {
            int row = greetingsFlexTable.getRowCount();

            greetingsFlexTable.setText(row, 0, greeting.getAuthor());
            greetingsFlexTable.setText(row, 1, greeting.getText());
        }
    }
}

File HelloGWTWidget.ui.xml which corresponds to class HelloGWTWidget will look like that:
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
             xmlns:g='urn:import:com.google.gwt.user.client.ui'>

    <g:VerticalPanel>
        <g:HorizontalPanel>
            <g:Label text="Author:" width="50"/>
            <g:TextBox ui:field="authorTextBox" width="100"/>
        </g:HorizontalPanel>

        <g:HorizontalPanel>
            <g:Label text="Text:" width="50"/>
            <g:TextBox ui:field="textTextBox" width="100"/>
        </g:HorizontalPanel>

        <g:HorizontalPanel>
            <g:Button ui:field="addButton" text="Add" width="50"/>
            <g:Button ui:field="updateButton" text="Update" width="50"/>
            <g:Button ui:field="deleteButton" text="Delete" width="50"/>
        </g:HorizontalPanel>

        <g:FlexTable ui:field="greetingsFlexTable"/>
    </g:VerticalPanel>

</ui:UiBinder>

After all these changes content of class HelloGWT has decreased a lot and now it does not contain UI creation and application logic code - all this stuff is helloGWTWidget object's responsibility:
package com.hellogwt.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.RootPanel;

public class HelloGWT implements EntryPoint {

    @Override
    public void onModuleLoad() {
        HelloGWTWidget helloGWTWidget = GWT.create(HelloGWTWidget.class);

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

Build and deploy application by executing command "mvn clean tomcat:deploy":
[INFO] Scanning for projects...
[WARNING] 
[WARNING] Some problems were encountered while building the effective model for hellogwt:hellogwt:war:1.0
[WARNING] 'build.plugins.plugin.version' for org.codehaus.mojo:tomcat-maven-plugin is missing. @ line 76, column 21
[WARNING] 
[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.
[WARNING] 
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
[WARNING] 
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building hellogwt 1.0
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ hellogwt ---
[INFO] Deleting d:\Work\hellogwt\target
[INFO] 
[INFO] >>> tomcat-maven-plugin:1.1:deploy (default-cli) @ hellogwt >>>
[WARNING] The POM for com.google.code:spring4gwt:jar:0.0.1 is missing, no dependency information available
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:resources (default-resources) @ hellogwt ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ hellogwt ---
[WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent!
[INFO] Compiling 7 source files to d:\Work\hellogwt\target\classes
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:testResources (default-testResources) @ hellogwt ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory d:\Work\hellogwt\src\test\resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ hellogwt ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-surefire-plugin:2.7.2:test (default-test) @ hellogwt ---
[INFO] No tests to run.
[INFO] Surefire report directory: d:\Work\hellogwt\target\surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
There are no tests to run.

Results :

Tests run: 0, Failures: 0, Errors: 0, Skipped: 0

[INFO] 
[INFO] --- gwt-maven-plugin:2.4.0:compile (default) @ hellogwt ---
[INFO] auto discovered modules [com.hellogwt.HelloGWT]
[INFO] Compiling module com.hellogwt.HelloGWT
[INFO]    Compiling 6 permutations
[INFO]       Compiling permutation 0...
[INFO]       Process output
[INFO]          Compiling
[INFO]             Compiling permutation 1...
[INFO]       Compiling permutation 2...
[INFO]       Compiling permutation 3...
[INFO]       Compiling permutation 4...
[INFO]       Compiling permutation 5...
[INFO]    Compile of permutations succeeded
[INFO] Linking into d:\Work\hellogwt\target\hellogwt-1.0\hellogwt
[INFO]    Link succeeded
[INFO]    Compilation succeeded -- 41.403s
[INFO] 
[INFO] --- maven-war-plugin:2.1.1:war (default-war) @ hellogwt ---
[INFO] Packaging webapp
[INFO] Assembling webapp [hellogwt] in [d:\Work\hellogwt\target\hellogwt-1.0]
[INFO] Processing war project
[INFO] Copying webapp resources [d:\Work\hellogwt\src\main\webapp]
[INFO] Webapp assembled in [278 msecs]
[INFO] Building war: d:\Work\hellogwt\target\hellogwt-1.0.war
[WARNING] Warning: selected war files include a WEB-INF/web.xml which will be ignored 
(webxml attribute is missing from war task, or ignoreWebxml attribute is specified as 'true')
[INFO] 
[INFO] <<< tomcat-maven-plugin:1.1:deploy (default-cli) @ hellogwt <<<
[INFO] 
[INFO] --- tomcat-maven-plugin:1.1:deploy (default-cli) @ hellogwt ---
[INFO] Deploying war to http://localhost:8080/hellogwt  
[INFO] OK - Deployed application at context path /hellogwt
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 52.101s
[INFO] Finished at: Tue Aug 28 23:18:30 EEST 2012
[INFO] Final Memory: 17M/176M
[INFO] ------------------------------------------------------------------------

Go to http://localhost:8080/hellogwt/. Page with edit form and greetings list should be displayed. It must be identical to form that was created earlier without UiBinder:















Folder structure:











































Application source code is available at http://code.google.com/p/hellogwt-5/.

8 comments:

  1. hi is it possible to train in gwt?

    ReplyDelete
    Replies
    1. Hello! Unfortunately no, I have no time for this

      Delete
  2. nice post. I don't think it is necesary to create the uibinder widget using GWT.create() in the following line:

    HelloGWTWidget helloGWTWidget = GWT.create(HelloGWTWidget.class);

    Just use new HelloGWTWidget() will work.

    ReplyDelete
    Replies
    1. Hi, Sebastian!
      Of course, it is possible to create objects using new keyword or GWT.create() method.
      The last one is used for deferred binding.

      Delete