📜 ⬆️ ⬇️

We write and debug the component for GWT and Vaadin

image


The Google Web Toolkit and Vaadin frameworks do quite well when you just use them. If suddenly you need to expand their functionality, then you have to seriously sweat. In this article I want to tell you how to write a simple component on GWT, add the Vaadin server part to it and use it in my application. I will not create a completely empty GWT / Vaadin project and from the very beginning set up a GWT compilation, instead I will take an application based on the CUBA.Platform , in which I implement the component itself, and then I will try it. So it will be seen how well everything will fit into this application. I would like to pay special attention to debugging the component, since it is non-trivial and always causes difficulties for developers.

I want to warn you that not everything described in the article is related to GWT and Vaadin, some of the steps and techniques are applicable only in the context of using CUBA.Platform, due to this, setting up the environment and some routine actions are greatly simplified.

Environment preparation


We will use for the experiments an empty project created in CUBA Studio . CUBA is our platform for developing Java business applications, which allows you to quickly create a data model and application interface, define the logic of working with data and manage user rights. At the core of the UI platform, the Vaadin web framework is actively used, which allows us to implement many interesting ideas.
')
To begin with, we will create a GWT module that will be compiled into JavaScript. Perform the 'Create web toolkit module' action in Studio. This is a simple auxiliary operation that does not make sense to perform manually. Studio will generate the AppWidgetSet.gwt.xml module GWT descriptor, the directory for the module and an empty package, as well as add the necessary tasks to the build description.

The next step is to run the 'Create or update IDE project files' action to generate IntelliJ IDEA project files, and go to write the component code to the IDE.

To program the component itself, we don’t need any special IDE features besides highlighting Java code, so it’s not necessary to use IntelliJ Idea, with the same success we can use Eclipse or Netbeans. Thanks to Google Web Toolkit, we can use familiar Java tools, and this is a great advantage when developing large-scale projects.

Writing a component


image

The component itself will be quite simple - the rating field in the form of 5 stars. This is an input field in which the user selects a rating using the mouse. It has a status on the server and the display should change when it changes.

image

This is what our new web-toolkit module looks like in the Idea project window. The root package contains the GWT module descriptor.
AppWidgetSet.gwt.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.7.0//EN" "http://google-web-toolkit.googlecode.com/svn/tags/1.7.0/distro-source/core/src/gwt-module.dtd"> <module> <inherits name="com.haulmont.cuba.web.toolkit.ui.WidgetSet" /> </module> 

It inherits the base module CUBA.Platform and is the starting point of the entire client part of our application (the one that runs in the browser). By default, GWT component implementations must be in the 'client' subpackage. Create a client package and in it a subfield ratingfield.

The first part of our component is the GWT widget. The RatingFieldWidget class, which we will place in the web-toolkit module:
RatingFieldWidget.java
 package com.haulmont.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; //  GWT  public class RatingFieldWidget extends FocusWidget { private static final String CLASSNAME = "ratingfield"; // API     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(); //      DOM.insertChild(container, star, i); //    ONCLICK DOM.sinkEvents(star, Event.ONCLICK); stars.add(star); } setElement(container); setStylePrimaryName(CLASSNAME); } //       GWT @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); switch (event.getTypeInt()) { //    ONCLICK case Event.ONCLICK: SpanElement element = event.getEventTarget().cast(); //      int index = stars.indexOf(element); if (index >= 0) { int value = index + 1; //    setValue(value); //   if (listener != null) { listener.starClicked(value); } } break; } } //       primaryStyleName //        @Override public void setStylePrimaryName(String style) { super.setStylePrimaryName(style); for (SpanElement star : stars) { star.setClassName(style + "-star"); } updateStarsStyle(this.value); } //      public void setValue(int value) { this.value = value; updateStarsStyle(value); } //    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"); } } } 

A widget is an isolated class that is responsible for displaying and responding to events. It does not need to know about the server part, it only defines the interfaces for working with it. In our case, this is the setValue method and the StarClickListener interface.

It is worth noting that throughout the widget code there is not a single JavaScript line, which is pretty good for large and complex components. But do not forget that this Java code will be compiled into JavaScript and you may not have access to many parts of the standard Java library, such as reflection and I / O (for complete compatibility information, see here: www.gwtproject.org/doc/latest/RefJreEmulation.html ).

Determine the appearance


As you can see, in the widget code there are no references to the appearance, except for the assignment of style names to key elements. This technique is constantly used in the development of components and allows you to determine the appearance in CSS based on combinations of styles.

In order to determine the appearance of our component, first, create style files. To do this, we can use the 'Create theme extension' action for the 'halo' theme. This theme uses FontAwesome font glyphs instead of icons, which we will use. Studio will create empty SCSS files for our experiments in the themes directory of the web module.

The styles of each component can be divided into a separate file componentname.scss in the components / componentname directory in the SCSS impurity format:
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; } } 

Then this file is connected in the main theme file.
halo-ext.scss
 @import "../halo/halo"; @import "components/ratingfield/ratingfield"; /* Define your theme modifications inside next mixin */ @mixin halo-ext { @include halo; @include ratingfield; } 

A few words about SCSS. This is a format for describing CSS styles, allowing you to use variables, impurities, and calculated values. It is actively used in many web frameworks, in Vaadin 7 it is the basic format of the themes of the application. In the CUBA.Platform application, we can simply use this format, since Studio takes on the dirty work of organizing the SCSS assembly.

The format of the description as an impurity will help us if the component has heirs with another primary stylename. We simply include ancestor styles with SCSS include .

For our stars, we use two FontAwesome glyphs - 'fa-star' and 'fa-star-o'. CSS itself is pretty simple and includes only the symbols of the stars in two states and the mouse cursor for them.

To switch the theme in the application, select halo on the Project Properties page in CUBA.Studio.

Add the server side


Up to this point we could use the widget we wrote in some GWT application, since it was not dependent on the server at all. Now let's talk about the Vaadin framework and its server-oriented model. It has a couple of features:

- all state of the components and input fields are stored on the server and can be restored even after a full page refresh or the connection with the server is broken
- all useful application code, with the exception of the client part, is executed on the server

That is, the Vaadin components hide from the developer how they work in the browser and a careless Java developer never sees HTML / CSS (or almost never sees, and then suddenly write components).

Create the com.haulmont.ratingsample.web.toolkit.ui package in the web module. In it, we will place the code for our RatingField component. We will inherit the class from the Vaadin AbstractField class, which defines the basic logic of the input fields.

Key server components of the Vaadin component:

1) The RatingField component class defines an API for server-side code, various get / set methods for working, event listeners and connecting data sources. Application developers always use methods of this class in their code.
RatingField.java
 package com.haulmont.ratingsample.web.toolkit.ui; import com.haulmont.ratingsample.web.toolkit.ui.client.RatingFieldServerRpc; import com.haulmont.ratingsample.web.toolkit.ui.client.RatingFieldState; import com.vaadin.ui.AbstractField; //       Integer public class RatingField extends AbstractField<Integer> { public RatingField() { //    ,        registerRpc(new RatingFieldServerRpc() { @Override public void starClicked(int value) { setValue(value, true); } }); } //    @Override public Class<? extends Integer> getType() { return Integer.class; } //      @Override protected RatingFieldState getState() { return (RatingFieldState) super.getState(); } @Override protected RatingFieldState getState(boolean markAsDirty) { return (RatingFieldState) super.getState(markAsDirty); } //   setValue       @Override protected void setInternalValue(Integer newValue) { super.setInternalValue(newValue); if (newValue == null) { newValue = 0; } getState().value = newValue; } } 

2) The Status class RatingFieldState is responsible for what data will be sent between the client and the server. It defines public fields that will be automatically serialized on the server and deserialized on the client.
RatingFieldState.java
 package com.haulmont.ratingsample.web.toolkit.ui.client; import com.vaadin.shared.AbstractFieldState; public class RatingFieldState extends AbstractFieldState { { //      primaryStyleName = "ratingfield"; } //      public int value = 0; } 

3) RatingFieldServerRpc interface - defines the server API for the client part, its methods can be called from the client using the remote procedure call procedure built into Vaadin. We implement this interface in the component itself, in this case simply call the setValue method of our field.
RatingFieldServerRpc.java
 package com.haulmont.ratingsample.web.toolkit.ui.client; import com.vaadin.shared.communication.ServerRpc; public interface RatingFieldServerRpc extends ServerRpc { //       void starClicked(int value); } 

The important point is that the status classes and rpc must be located in the 'client' subpackage, so the GWT compiler’s clinging hands will easily get to them to create their JavaScript representation for the client code. In addition, classes should not use code that cannot be compiled by GWT.

Now it is time to connect our client code with the server part. This role in Vaadin is performed by connector classes. They are placed next to the widget classes. The connector class is annotated by Connect (ComponentName.class), and the client part is assigned to the server part:
RatingFieldConnector.java
 package com.haulmont.ratingsample.web.toolkit.ui.client.ratingfield; import com.haulmont.ratingsample.web.toolkit.ui.RatingField; import com.haulmont.ratingsample.web.toolkit.ui.client.RatingFieldServerRpc; import com.haulmont.ratingsample.web.toolkit.ui.client.RatingFieldState; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.ui.AbstractFieldConnector; import com.vaadin.shared.ui.Connect; //       RatingField //    AbstractField @Connect(RatingField.class) public class RatingFieldConnector extends AbstractFieldConnector { //     RatingFieldWidget @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; } //    - RatingFieldState @Override public RatingFieldState getState() { return (RatingFieldState) super.getState(); } //       @Override public void onStateChanged(StateChangeEvent stateChangeEvent) { super.onStateChanged(stateChangeEvent); //     ,   if (stateChangeEvent.hasPropertyChanged("value")) { getWidget().setValue(getState().value); } } } 

Trial run


To try all this in, let's do a few preparatory steps:

1) Create a database for the application from the Studio menu: Run - Create database
2) Create a screen to place the component in the web module:

image

3) Add a screen to the application menu: Main menu - Edit

image

4) Now let's move on to editing our screen in the IDE.
We need a container for our component, let's declare it in an XML screen:
rating-screen.xml
 <?xml version="1.0" encoding="UTF-8" standalone="no"?> <window xmlns="http://schemas.haulmont.com/cuba/5.3/window.xsd" caption="msg://caption" class="com.haulmont.ratingsample.web.RatingScreen" messagesPack="com.haulmont.ratingsample.web"> <layout expand="container"> <vbox id="container"> <!--       Vaadin --> </vbox> </layout> </window> 

Open the RatingScreen.java screen controller class and add the code for placing our component on the screen:
RatingScreen.java
 package com.haulmont.ratingsample.web; import com.haulmont.ratingsample.web.toolkit.ui.RatingField; import com.haulmont.cuba.gui.components.AbstractWindow; import com.haulmont.cuba.gui.components.BoxLayout; import com.haulmont.cuba.web.gui.components.WebComponentsHelper; 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); //  API CUBA    Vaadin  : com.vaadin.ui.Layout containerLayout = WebComponentsHelper.unwrap(container); //       Vaadin : RatingField field = new RatingField(); field.setCaption("Rate this!"); containerLayout.addComponent(field); } } 

The Web module perfectly supports the integration of Vaadin components, both third-party and self-written. You can directly use them as if writing an application on pure Vaadin.

Run the application from the Studio: Start application server, go to http: // localhost: 8080 / app to see the result:

image

We rejoice at the full-featured component, which we can now use from our Java code on the server. All code is suitable for use in any Vaadin application.

The full application code can be found here: github.com/Haulmont/ratingsample.git

Browser debugging


We will only consider debugging widget code, since debugging Java code on the server is quite simple.

Debugging any GWT code is completely non-trivial and requires accuracy. For debugging we will use the SuperDevMode mode. It is imperative that your project be built with GWT 2.5.1 or later. This mode assumes the use of matching Java code with JavaScript code in the browser (source-maps, see developer.chrome.com/devtools/docs/javascript-debugging#source-maps ). That is, you will see and debug Java code in the browser, but with some restrictions.

The scheme of work is as follows:
  1. You start the com.google.gwt.dev.codeserver.CodeServer server that gives the browser side a matching JS code and Java code, and also collects your widgetset when the page is updated
  2. Open the application with the parameters? Debug & superdevmode
  3. Customize Developer Tools, F12, in the lower right corner there is a button for opening settings. Mark the option Enable source maps
  4. Refresh the page, open the Sources tab in Developer Tools. There all the Java classes of the GWT widgets should be shown. In the Chrome debugger, you can set breakpoints, watch variables and execute expressions.
  5. When changing the widget code in the project, it is enough to refresh the page, the widgetset will be reassembled and picked up by the browser. This allows you to see changes to the widget code on the fly, which significantly speeds up development.

We try to run everything in our project:
1) To start this mode, we need to add the runtime servletApi dependency for the web-toolkit module in the build.gradle file:
build.gradle
 ... configure(webToolkitModule) { dependencies { ... runtime(servletApi) } ... 

2) In Studio, execute the 'Create or update IDE project files' action so that Idea sees the new dependency.
3) Create a new launch configuration in Idea with the type Application and the following parameters:

Main class: com.google.gwt.dev.codeserver.CodeServer
VM options: -Xmx512M
Use classpath of module: app-web-toolkit
Program arguments: -workDir C: \ Users \ yuriy \ work \ ratingsample \ build \ tomcat \ webapps \ app \ VAADIN \ widgetsets -src C: \ Users \ yuriy \ work \ ratingsample \ modules \ web \ src -src C: \ Users \ yuriy \ work \ ratingsample \ modules \ web-toolkit \ src com.haulmont.ratingsample.web.toolkit.ui.AppWidgetSet

The paths to the build \ tomcat \ webapps \ app \ VAADIN \ widgetsets, modules \ web \ src and modules \ web-toolkit \ src directories must be replaced with their own.

image

4) Run in Studio: Run-Start application server
5) Run the previously created GWT configuration in Idea
6) Go to http: // localhost: 8080 / app? Debug & superdevmode
7) Open DevTools in Chrome and see your Java code:

image

The advantage of this method is that it does not require special support from the IDE, it works quickly and allows you to debug the code directly in the browser. The disadvantages include the fact that you can’t execute Java code during debugging, as well as breakpoints with Java conditions, and it’s somehow unusual. There is still a fat minus - old browsers are not able to source-maps at all, which makes normal debugging difficult.

Instead of conclusion


GWT is a very strong and developed web framework, in recent years it has been actively used by a large number of developers around the world. Google does not forget about its offspring and actively applies it, most recently they released Gmail Inbox ( http://gmailblog.blogspot.ru/2014/11/going-under-hood-of-inbox.html ), which GWT intensively uses for web interface.

Vaadin is also not lagging behind and is now one of the best server-side options for GWT. Server-oriented model allows you to develop faster, easier to maintain applications and less worried about data security. The complexity of finalizing the GWT and Vaadin functional is rather isolated and you should not be afraid of it, the advantages of these technologies for development cover all their disadvantages.

We have been actively using Vaadin for 5+ years and are confident in it. I advise everyone to consider it as the main framework for building web applications, especially business.

Thanks for attention!

Source: https://habr.com/ru/post/248545/


All Articles