3.5.17.4.4. 创建 GWT 组件
在本节中,我们介绍如何创建一个简单的 GWT 组件(由 5 颗星组成的评级字段)及其在应用程序界面中的用法。
在 CUBA Studio 中创建一个新项目,并将其命名为 ratingsample
。
创建 web-toolkit 模块。一个简便的方法就是使用 CUBA Studio:在主菜单,点击 CUBA > Advanced > Manage modules > Create 'web-toolkit' Module。
为了创建 GWT 组件,需要创建下列文件:
-
RatingFieldWidget.java
- web-toolkit 模块中的 GWT 部件。 -
RatingFieldServerComponent.java
- Vaadin 组件类。 -
RatingFieldState.java
- 组件状态类。 -
RatingFieldConnector.java
- 连接器,用于连接客户端代码和服务器组件。 -
RatingFieldServerRpc.java
- 为客户端定义服务器 API 的类。
现在创建需要的文件并对其进行必要的更改。
-
在 web-toolkit 模块中创建
RatingFieldWidget
类。使用下面代码作为其内容: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<>(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"); } } }
部件(Widget)是一个客户端类,负责在 Web 浏览器中显示组件并处理浏览器事件。它定义了与服务端配合起来工作的接口。在这个的例子中,这些接口是
setValue()
方法和StarClickListener
接口。 -
RatingFieldServerComponent
是一个 Vaadin 组件类。它定义了服务端代码 API、访问器方法、事件监听器和数据源连接。开发人员在应用程序代码中使用的是这个类的方法。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)); } @Override protected void doSetValue(Integer value) { if (value == null) { value = 0; } getState().value = value; } @Override public Integer getValue() { return getState().value; } // define own state class @Override protected RatingFieldState getState() { return (RatingFieldState) super.getState(); } @Override protected RatingFieldState getState(boolean markAsDirty) { return (RatingFieldState) super.getState(markAsDirty); } }
-
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 内置的 RPC 机制调用。我们将在此组件中实现此接口。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); }
-
在 web-toolkit 模块中创建
RatingFieldConnector
类,连接器将客户端代码与服务端连接起来。package com.company.ratingsample.web.toolkit.ui.client.ratingfield; import com.company.ratingsample.web.toolkit.ui.RatingFieldServerComponent; 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 = 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
类中不定义组件的外观样式,只为关键元素指定样式名称。要定义组件的外观,需要创建样式表文件。简便方法就是使用 CUBA Studio:在主菜单,点击 CUBA > Advanced > Manage themes > Create theme extension。在弹窗中选择 hover
主题。另一个方法是使用 CUBA CLI 的 extend-theme
命令。hover
主题使用了 FontAwesome 的象形符号字体替代了 icons。
建议以 SCSS 混入(Mixin)的形式将组件样式放到 components/componentname
目录中的单独文件 componentname.scss
中。在 web 模块的 themes/hover/com.company.ratingsample
目录中创建 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;
}
}
将此文件包含在 hover-ext.scss
主题文件中:
@import "components/ratingfield/ratingfield";
@mixin com_company_ratingsample-hover-ext {
@include ratingfield;
}
为了演示组件的工作原理,我们在 web 模块中创建一个新的界面。
将界面命名为 rating-screen
。
在 IDE 中打开 rating-screen.xml
文件。Rating 组件需要一个容器,我们在界面 XML 中声明它:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
caption="msg://caption"
messagesPack="com.company.ratingsample.web.screens.rating">
<layout expand="container">
<vbox id="container">
<!-- we'll add vaadin component here-->
</vbox>
</layout>
</window>
打开 RatingScreen.java
界面控制器并添加将组件放置到界面上的代码。
package com.company.ratingsample.web.screens.rating;
import com.company.ratingsample.web.toolkit.ui.RatingFieldServerComponent;
import com.haulmont.cuba.gui.components.VBoxLayout;
import com.haulmont.cuba.gui.screen.Screen;
import com.haulmont.cuba.gui.screen.Subscribe;
import com.haulmont.cuba.gui.screen.UiController;
import com.haulmont.cuba.gui.screen.UiDescriptor;
import com.vaadin.ui.Layout;
import javax.inject.Inject;
@UiController("ratingsample_RatingScreen")
@UiDescriptor("rating-screen.xml")
public class RatingScreen extends Screen {
@Inject
private VBoxLayout container;
@Subscribe
protected void onInit(InitEvent event) {
RatingFieldServerComponent field = new RatingFieldServerComponent();
field.setCaption("Rate this!");
container.unwrap(Layout.class).addComponent(field);
}
}
下图显示完成的项目结构
启动应用程序服务并查看结果。