5.8.7.4. Создание GWT компонента
В данном примере мы рассмотрим создание простого GWT-компонента - поля рейтинга в виде 5 звезд, а также использование его в экранах приложения.
Создадим новый проект в CUBA Studio. Имя проекта - ratingsample
.
Создайте модуль web-toolkit, нажав на кнопку Create web-toolkit module секции Project properties навигатора Studio.
Далее нажмите на ссылку Create new UI component. Откроется окно создания визуального компонента New UI component. В секции Component type выберите значение New GWT component
.
В поле Vaadin component class name диалога генерации компонента введите значение RatingFieldServerComponent
.
Снимите флажок Integrate into Generic UI. Процесс интеграции компонента в универсальный интерфейс аналогичен описанному в разделе Подключение аддона Vaadin с интеграцией в Generic UI, поэтому рассматривать его здесь мы не будем.
После нажатия кнопки OK Studio сгенерирует файлы:
-
RatingFieldWidget.java
- виджет GWT в модуле web-toolkit. -
RatingFieldServerComponent.java
- класс компонента Vaadin. -
RatingFieldState.java
- класс состояния компонента. -
RatingFieldConnector.java
- коннектор, связывающий клиентский код с серверным компонентом. -
RatingFieldServerRpc.java
- класс, определяющий API сервера для клиентской части.
Последовательно рассмотрим сгенерированные студией заготовки файлов и внесем в них необходимые изменения.
-
GWT виджет
RatingFieldWidget.java
. Замените содержимое файла на следующий код:package com.company.ratingsample.web.toolkit.ui.client.ratingfield; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.SpanElement; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.FocusWidget; import java.util.ArrayList; import java.util.List; public class RatingFieldWidget extends FocusWidget { private static final String CLASSNAME = "ratingfield"; // API for handle clicks public interface StarClickListener { void starClicked(int value); } protected List<SpanElement> stars = new ArrayList<SpanElement>(5); protected StarClickListener listener; protected int value = 0; public RatingFieldWidget() { DivElement container = DOM.createDiv().cast(); container.getStyle().setDisplay(Display.INLINE_BLOCK); for (int i = 0; i < 5; i++) { SpanElement star = DOM.createSpan().cast(); // add star element to the container DOM.insertChild(container, star, i); // subscribe on ONCLICK event DOM.sinkEvents(star, Event.ONCLICK); stars.add(star); } setElement(container); setStylePrimaryName(CLASSNAME); } // main method for handling events in GWT widgets @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); switch (event.getTypeInt()) { // react on ONCLICK event case Event.ONCLICK: SpanElement element = event.getEventTarget().cast(); // if click was on the star int index = stars.indexOf(element); if (index >= 0) { int value = index + 1; // set internal value setValue(value); // notify listeners if (listener != null) { listener.starClicked(value); } } break; } } @Override public void setStylePrimaryName(String style) { super.setStylePrimaryName(style); for (SpanElement star : stars) { star.setClassName(style + "-star"); } updateStarsStyle(this.value); } // let application code change the state public void setValue(int value) { this.value = value; updateStarsStyle(value); } // refresh visual representation private void updateStarsStyle(int value) { for (SpanElement star : stars) { star.removeClassName(getStylePrimaryName() + "-star-selected"); } for (int i = 0; i < value; i++) { stars.get(i).addClassName(getStylePrimaryName() + "-star-selected"); } } }
Виджет представляет собой клиентский класс, отвечающий за отображение компонента в веб-браузере и реакцию на события. Он определяет интерфейсы для работы с серверной частью. В нашем случае это метод
setValue()
и интерфейсStarClickListener
. -
Класс компонента Vaadin
RatingFieldServerComponent
. Он определяет API для серверного кода, различные get/set методы, слушатели событий и подключение источников данных. Прикладные разработчики используют в своём коде методы этого класса.package com.company.ratingsample.web.toolkit.ui; import com.company.ratingsample.web.toolkit.ui.client.ratingfield.RatingFieldServerRpc; import com.company.ratingsample.web.toolkit.ui.client.ratingfield.RatingFieldState; import com.vaadin.ui.AbstractField; // the field will have a value with integer type public class RatingFieldServerComponent extends AbstractField<Integer> { public RatingFieldServerComponent() { // register an interface implementation that will be invoked on a request from the client registerRpc((RatingFieldServerRpc) value -> setValue(value, true)); } // field value type @Override public Class<? extends Integer> getType() { return Integer.class; } // define own state class @Override protected RatingFieldState getState() { return (RatingFieldState) super.getState(); } @Override protected RatingFieldState getState(boolean markAsDirty) { return (RatingFieldState) super.getState(markAsDirty); } // we need to refresh the state when setValue is invoked from the application code @Override protected void setInternalValue(Integer newValue) { super.setInternalValue(newValue); if (newValue == null) { newValue = 0; } getState().value = newValue; } }
-
Класс состояния
RatingFieldState
отвечает за то, какие данные будут пересылаться между клиентом и сервером. В нём определяются публичные поля, которые будут автоматически сериализованы на сервере и десериализованы на клиенте.package com.company.ratingsample.web.toolkit.ui.client.ratingfield; import com.vaadin.shared.AbstractFieldState; public class RatingFieldState extends AbstractFieldState { { // change the main style name of the component primaryStyleName = "ratingfield"; } // define a field for the value public int value = 0; }
-
Интерфейс
RatingFieldServerRpc
— определяет API сервера для клиентской части, его методы могут вызываться с клиента при помощи механизма удалённого вызова процедур, встроенного в Vaadin. Этот интерфейс мы реализуем в самом компоненте, в данном случае просто вызываем методsetValue()
нашего поля.package com.company.ratingsample.web.toolkit.ui.client.ratingfield; import com.vaadin.shared.communication.ServerRpc; public interface RatingFieldServerRpc extends ServerRpc { //method will be invoked in the client code void starClicked(int value); }
-
Коннектор
RatingFieldConnector
связывает клиентский код с серверной частью.package com.company.ratingsample.web.toolkit.ui.client.ratingfield; import com.company.ratingsample.web.toolkit.ui.RatingFieldServerComponent; import com.company.ratingsample.web.toolkit.ui.client.ratingfield.RatingFieldServerRpc; import com.company.ratingsample.web.toolkit.ui.client.ratingfield.RatingFieldState; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.ui.AbstractFieldConnector; import com.vaadin.shared.ui.Connect; // link the connector with the server implementation of RatingField // extend AbstractField connector @Connect(RatingFieldServerComponent.class) public class RatingFieldConnector extends AbstractFieldConnector { // we will use a RatingFieldWidget widget @Override public RatingFieldWidget getWidget() { RatingFieldWidget widget = (RatingFieldWidget) super.getWidget(); if (widget.listener == null) { widget.listener = new RatingFieldWidget.StarClickListener() { @Override public void starClicked(int value) { getRpcProxy(RatingFieldServerRpc.class).starClicked(value); } }; } return widget; } // our state class is RatingFieldState @Override public RatingFieldState getState() { return (RatingFieldState) super.getState(); } // react on server state change @Override public void onStateChanged(StateChangeEvent stateChangeEvent) { super.onStateChanged(stateChangeEvent); // refresh the widget if the value on server has changed if (stateChangeEvent.hasPropertyChanged("value")) { getWidget().setValue(getState().value); } } }
Код виджета RatingFieldWidget
не определяет внешний вид компонента, кроме назначения имён стилей ключевым элементам. Для того, чтобы определить внешний вид нашего компонента, создадим файлы стилей. Для этого можно воспользоваться ссылкой Create theme extension секции Project properties в навигаторе Studio. В появившемся диалоге выбираем тему halo
. Эта тема использует вместо иконок глифы шрифта FontAwesome, чем мы и воспользуемся. Studio создаст пустые файлы SCSS для расширения темы в каталоге themes
модуля web.
Стили каждого компонента принято выделять в отдельный файл componentname.scss
в каталоге components/componentname
в формате примеси SCSS. В каталоге themes/halo
модуля web
создадим структуру вложенных каталогов: components/ratingfield
. Затем внутри ratingfield
создадим файл ratingfield.scss
:
@mixin ratingfield($primary-stylename: ratingfield) {
.#{$primary-stylename}-star {
font-family: FontAwesome;
font-size: $v-font-size--h2;
padding-right: round($v-unit-size/4);
cursor: pointer;
&:after {
content: '\f006'; // 'fa-star-o'
}
}
.#{$primary-stylename}-star-selected {
&:after {
content: '\f005'; // 'fa-star'
}
}
.#{$primary-stylename} .#{$primary-stylename}-star:last-child {
padding-right: 0;
}
.#{$primary-stylename}.v-disabled .#{$primary-stylename}-star {
cursor: default;
}
}
Подключим этот файл в главном файле темы halo-ext.scss
:
@import "../halo/halo";
@import "components/ratingfield/ratingfield";
/* Define your theme modifications inside next mixin */
@mixin halo-ext {
@include halo;
@include ratingfield;
}
Для демонстрации работы компонента создадим новый экран в модуле web.
Назовите файл с экраном rating-screen.xml
.
Добавим экран в меню приложения. Перейдите в секцию Main menu навигатора Studio и нажмите кнопку Edit. Откроется редактор меню. Добавьте созданный экран в меню application
.
Перейдем к редактированию экрана rating-screen.xml
в IDE. Нам понадобится контейнер для нашего компонента, объявим его в XML экрана:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
caption="msg://caption"
class="com.company.ratingsample.web.screens.RatingScreen"
messagesPack="com.company.ratingsample.web.screens">
<layout expand="container">
<vbox id="container">
<!-- we'll add vaadin component here-->
</vbox>
</layout>
</window>
Откроем класс контроллера экрана RatingScreen.java
и добавим код размещения нашего компонента на экране:
package com.company.ratingsample.web.screens;
import com.company.ratingsample.web.toolkit.ui.RatingFieldServerComponent;
import com.haulmont.cuba.gui.components.AbstractWindow;
import com.haulmont.cuba.gui.components.BoxLayout;
import com.haulmont.cuba.web.gui.components.WebComponentsHelper;
import com.vaadin.ui.Layout;
import javax.inject.Inject;
import java.util.Map;
public class RatingScreen extends AbstractWindow {
@Inject
private BoxLayout container;
@Override
public void init(Map<String, Object> params) {
super.init(params);
com.vaadin.ui.Layout containerLayout = (Layout) WebComponentsHelper.unwrap(container);
RatingFieldServerComponent field = new RatingFieldServerComponent();
field.setCaption("Rate this!");
containerLayout.addComponent(field);
}
}
Запускаем сервер приложения и смотрим на результат.