Wednesday, August 29, 2012

Создаем приложение с помощью Spring и GWT. Часть 5 - UiBinder

Спустя довольно длительное время продолжаем писать наше приложение, использующее Spring и GWT.

Ранее интерфейс пользователя и логика работы нашего приложения была описана в одном классе HelloGWT. Сейчас же мы создадим для этого отдельный виджет и при этом вынесем описание интерфейса пользователя из java-кода в XML с помощью UiBinder.

Создадим в пакете com.hellogwt.client класс HelloGWTWidget. Для работы UiBinder'а необходимо следующее:
  • класс HelloGWTWidget должен наследоваться от класса Composite
  • объявить в классе HelloGWTWidget интерфейс HelloGWTWidgetUiBinder, расширяющий интерфейс UiBinder:
    interface HelloGWTWidgetUiBinder extends UiBinder<Widget, HelloGWTWidget> {
    }
    
  • объявить переменную uiBinder, которая будет ссылаться на экземпляр HelloGWTWidgetUiBinder:
    private static HelloGWTWidgetUiBinder uiBinder = GWT.create(HelloGWTWidgetUiBinder.class);
    
  • для каждого элемента UI объявить переменную с аннотацией @UiField:
    @UiField
    TextBox authorTextBox;
    
  • в конструкторе класса HelloGWTWidget вызвать метод initWidget():
    public HelloGWTWidget() {
        initWidget(uiBinder.createAndBindUi(this));
        ...
    }
    
  • описать элементы UI в соответствующем файле 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>
    

Кроме того, для обработчиков событий можно использовать аннотацию @UiHandler. Ранее класс HelloGWT содержал метод initHandlers(), где элементам UI добавлялись обработчики событий:
public class HelloGWT implements EntryPoint {
    ...
    @Override
    public void onModuleLoad() {
        ...
        initHandlers();
        ...
    }
    
    private void initHandlers() {
        addButton.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent clickEvent) {
                ...
            }
        });
        ...
    }
    ...
}

Обработку событий можно вынести в отдельные методы, помеченные аннотацией @UiHandler c указанием элемента UI, к которому этот обработчик относится. В качестве аргументов этих методов выступают объекты событий, которые они обрабатывают. Это позволяет избавиться от определения анонимных классов и упрощает понимание кода. Например, так будет выглядеть обработка нажатия на кнопку "Add":
@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!");
    }
}

После внесения всех необходимых изменений класс HelloGWTWidget будет выглядеть так:
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());
        }
    }
}

Соответствующий классу HelloGWTWidget файл 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: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>

В результате изменений содержимое класса HelloGWT сильно сократилось и теперь в его обязанности не входит создание UI и логики работы приложения - за это отвечает объект helloGWTWidget:
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);
    }
}

Соберем проект, выполнив команду "mvn clean install":
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building hellogwt 1.0
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ hellogwt ---
[INFO] Deleting d:\Dev\blog\hellogwt-5\target
[INFO] 
[INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ hellogwt ---
[debug] execute contextualize
[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:\Dev\blog\hellogwt-5\target\classes
[INFO] 
[INFO] --- maven-resources-plugin:2.5:testResources (default-testResources) @ hellogwt ---
[debug] execute contextualize
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory d:\Dev\blog\hellogwt-5\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.10:test (default-test) @ hellogwt ---
[INFO] No tests to run.
[INFO] Surefire report directory: d:\Dev\blog\hellogwt-5\target\surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------

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 1...
[INFO]       Process output
[INFO]          Compiling
[INFO]             Compiling permutation 2...
[INFO]       Process output
[INFO]          Compiling
[INFO]             Compiling permutation 0...
[INFO]       Process output
[INFO]          Compiling
[INFO]             Compiling permutation 3...
[INFO]       Compiling permutation 4...
[INFO]       Compiling permutation 5...
[INFO]    Compile of permutations succeeded
[INFO] Linking into d:\Dev\blog\hellogwt-5\target\hellogwt\hellogwt
[INFO]    Link succeeded
[INFO]    Compilation succeeded -- 38.889s
[INFO] 
[INFO] --- maven-war-plugin:2.1.1:war (default-war) @ hellogwt ---
[INFO] Packaging webapp
[INFO] Assembling webapp [hellogwt] in [d:\Dev\blog\hellogwt-5\target\hellogwt]
[INFO] Processing war project
[INFO] Copying webapp resources [d:\Dev\blog\hellogwt-5\src\main\webapp]
[INFO] Webapp assembled in [206 msecs]
[INFO] Building war: d:\Dev\blog\hellogwt-5\target\hellogwt.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] --- gwt-maven-plugin:2.4.0:test (default) @ hellogwt ---
[INFO] 
[INFO] --- maven-install-plugin:2.3.1:install (default-install) @ hellogwt ---
[INFO] Installing d:\Dev\blog\hellogwt-5\target\hellogwt.war to d:\Dev\.m2\repository\hellogwt\hellogwt\1.0\hellogwt-1.0.war
[INFO] Installing d:\Dev\blog\hellogwt-5\pom.xml to d:\Dev\.m2\repository\hellogwt\hellogwt\1.0\hellogwt-1.0.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 45.394s
[INFO] Finished at: Mon Apr 21 20:31:09 EEST 2014
[INFO] Final Memory: 15M/153M
[INFO] ------------------------------------------------------------------------

Скопируем полученный war-файл в директорию Tomcat'а webapps, запустим сервер и перейдем по ссылке http://localhost:8080/hellogwt/. Должна отобразиться страница с формой редактирования и списком всех приветствий, идентичная форме без использования UiBinder:















Структура проекта:











































Исходный код созданного приложения доступен по ссылке http://code.google.com/p/hellogwt-5/.


Рекомендуемые посты:

2 comments:

  1. Один из лучших пошаговых мануалов на русском! Огромное спасибо за проделанную работу.

    ReplyDelete
    Replies
    1. Спасибо, мне очень приятно! Именно из-за недостатка подобной информации на русском я и начал вести этот блог. Рад, что это приносит пользу :)

      Delete