📜 ⬆️ ⬇️

Vaadin: Useful Improvements and Observations

Vaadin is a component UI framework for building Java web applications. We have been using Vaadin as part of our CUBA platform for 4 years and during this time we have gained a lot of experience working with it.

Vaadin was chosen by us for several reasons:

Among the shortcomings worth noting:

In this article, I will share solutions to some of the problems and challenges that we encountered when using Vaadin. I will analyze several solutions in detail, for the rest - only important points.

Empty place in GridLayout

One of the features of the corporate application is the requirement to change the interface screens depending on user rights and data state. Often, components on a form are placed in a grid using GridLayout , and then when you hide rows or columns in a standard Vaadin, there are empty spaces of indents for invisible components. This behavior can be changed, which will require the creation of its successor GridLayout . SuperGridLayout call it SuperGridLayout .
')


We will need:
  1. SuperGridLayout - the heir of the server-side GridLayout
  2. SuperGridLayoutConnector - connector for connection of the server with the widget, the successor to the GridLayoutConnector
  3. SuperGridLayoutWidget - the widget itself, a successor to VGridLayout

Not all Vaadin components are extensible yet, so don’t be surprised at some hacks to override the package local methods. We are forced to create our components in the com.vaadin.ui package. Developers add-ons are generally quite common practice, although there is progress towards extensibility.

SuperGridLayout itself SuperGridLayout not contain any logic:
 public class SuperGridLayout extends GridLayout { } 

The SuperGridLayoutConnector indicates that we will use the SuperGridLayoutWidget widget. Vaadin defines this by the return type of the getWidget() method.

 @Connect(SuperGridLayout.class) public class SuperGridLayoutConnector extends GridLayoutConnector { @Override public SuperGridLayoutWidget getWidget() { return (SuperGridLayoutWidget) super.getWidget(); } } 

Well, the widget code itself with a fix to hide passes:
SuperGridLayoutWidget
 public class SuperGridLayoutWidget extends VGridLayout { // .. @Override void layoutCellsHorizontally() { // ... for (int i = 0; i < cells.length; i++) { for (int j = 0; j < cells[i].length; j++) { // ... // Fix for GridLayout leaves an empty space for invisible components #VAADIN-12655 // hide zero width columns if (columnWidths[i] > 0) { x += columnWidths[i] + horizontalSpacing; } } // ... } @Override void layoutCellsVertically() { // ... for (int column = 0; column < cells.length; column++) { // ... for (int row = 0; row < cells[column].length; row++) { // ... // Fix for GridLayout leaves an empty space for invisible components #VAADIN-12655 // hide zero height rows if (rowHeights[row] > 0) { y += rowHeights[row] + verticalSpacing; } } } // ... } } 


Now you need to add to your project an assembly of a set widget with a new component. This is described in detail in the Vaadin documentation.
The full code can be found here: https://github.com/Haulmont/vaadin-super-grid

Right-click selection in the tree and table

By default, Vaadin does not highlight the record for which we have opened the context menu. And this behavior can not be changed without any special tricks. Add a right-click selection for the tree, a similar process for the table.

Let's name our tree SuperTree and we will get SuperTree , SuperTreeWidget and SuperTreeConnector . SuperTree is a simple heir to Tree. And in SuperTreeWidget will completely copy the code from VTree , into SuperTreeConnector - the code from TreeConnector . Next, change the code in the SuperTreeConnector so that it uses the SuperTreeWiget widget and the @Connect(SuperTree.class) annotation @Connect(SuperTree.class) .

We have got our own client-side implementation for the server component Tree. In SuperTreeConnector we SuperTreeConnector turn on the contextMenuSelection flag and accessors for it. In the updateFromUIDL method with the flag set, we will reset the flag for the widget with the flag rendering = false and interrupt the execution. This is necessary so that our context menu is not minimized. Next, in the SuperTreeWidget.TreeNode add a showContextMenu selection to the showContextMenu method if it is not selected:
#showContextMenu
 public void showContextMenu(Event event) { if (!readonly && !disabled) { // Select node by right click if (!isSelected()) { toggleSelection(); getConnector().setContextMenuSelection(true); } if (actionKeys != null) { int left = event.getClientX(); int top = event.getClientY(); top += Window.getScrollTop(); left += Window.getScrollLeft(); client.getContextMenu().showAt(this, left, top); } event.stopPropagation(); event.preventDefault(); } } 




Now if the user clicks on the node with the right mouse button, our node will be selected.
The full code here: https://github.com/Haulmont/vaadin-super-tree

Hotkeys for input fields

It so happened in the Vaadin API that hotkeys are attached to Panel , Window or UI objects. This means that adding leafers for hotkeys, for example, for a field, you add them to the guardian container nearest the hierarchy. This behavior leads to the fact that for the same keys in the two fields you already need to write clever code, and the writing of your components with hot keys is complicated by an order of magnitude. If we simply wrap all duplicate components in the panel, then we will complicate our browser screen.



To solve this problem for tables and trees is quite difficult, consider a simple solution on the example of text fields. Let's try to make your SuperTextField with a search for Enter and the ability to use several such fields on the screen.

In SuperTextField we define our ActionManager responsible for the hotkeys of this field.
SuperTextField
 public class SuperTextField extends TextField implements Action.Container { //.. /** * Keeps track of the Actions added to this component, and manages the * painting and handling as well. */ protected ActionManager shortcutsManager; @Override public void paintContent(PaintTarget target) throws PaintException { super.paintContent(target); if (shortcutsManager != null) { shortcutsManager.paintActions(null, target); } } @Override protected ActionManager getActionManager() { if (shortcutsManager == null) { shortcutsManager = new ConnectorActionManager(this); } return shortcutsManager; } @Override public void changeVariables(Object source, Map<String, Object> variables) { super.changeVariables(source, variables); if (shortcutsManager != null) { shortcutsManager.handleActions(variables, this); } } @Override public void addShortcutListener(ShortcutListener listener) { getActionManager().addAction(listener); } @Override public void removeShortcutListener(ShortcutListener listener) { getActionManager().removeAction(listener); } @Override public void addActionHandler(Action.Handler actionHandler) { getActionManager().addActionHandler(actionHandler); } @Override public void removeActionHandler(Action.Handler actionHandler) { getActionManager().removeActionHandler(actionHandler); } } 


In the SuperTextFieldConnector add the loading of hot keys from JSON and transfer them to the widget.
SuperTextFieldConnector
 @Connect(SuperTextField.class) public class SuperTextFieldConnector extends TextFieldConnector { @Override public SuperTextFieldWidget getWidget() { return (SuperTextFieldWidget) super.getWidget(); } @Override public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { super.updateFromUIDL(uidl, client); // We may have actions attached to this text field if (uidl.getChildCount() > 0) { final int cnt = uidl.getChildCount(); for (int i = 0; i < cnt; i++) { UIDL childUidl = uidl.getChildUIDL(i); if (childUidl.getTag().equals("actions")) { if (getWidget().getShortcutActionHandler() == null) { getWidget().setShortcutActionHandler(new ShortcutActionHandler(uidl.getId(), client)); } getWidget().getShortcutActionHandler().updateActionMap(childUidl); } } } } } 


Well, in the widget we will listen to keystrokes and pass them to a special handler who knows about the shortcut keys.
SuperTextFieldWidget
 public class SuperTextFieldWidget extends VTextField implements ShortcutActionHandler.ShortcutActionHandlerOwner { protected ShortcutActionHandler shortcutHandler; public SuperTextFieldWidget() { // handle shortcuts DOM.sinkEvents(getElement(), Event.ONKEYDOWN); } @Override public void onBrowserEvent(Event event) { super.onBrowserEvent(event); final int type = DOM.eventGetType(event); if (type == Event.ONKEYDOWN && shortcutHandler != null) { shortcutHandler.handleKeyboardEvent(event); } } public void setShortcutActionHandler(ShortcutActionHandler handler) { this.shortcutHandler = handler; } @Override public ShortcutActionHandler getShortcutActionHandler() { return shortcutHandler; } //.. } 


Now we can SuperTextField as many SuperTextField fields with the same key combinations.
Full code here: https://github.com/Haulmont/vaadin-super-textfield

Styles "-focus" for TabSheet, Table, CheckBox, Tree, MenuBar

In Vaadin for some components there are not enough styles of various states. Let's try adding a "-focus" selector for trees with focus.

The scheme of actions is simple: we start the FocusTree , FocusTreeConnector and FocusTreeWidget .

Add the style "-focus" in the widget:
Focustreewidget
 public class FocusTreeWidget extends VTree { @Override public void onFocus(FocusEvent event) { super.onFocus(event); addStyleDependentName("focus"); } @Override public void onBlur(BlurEvent event) { super.onBlur(event); removeStyleDependentName("focus"); } } 




Now it remains only to get the necessary CSS styles for the component with the “v-tree-focus” selector.
Example here: https://github.com/Haulmont/vaadin-focus-selector

Ability to display a value in ComboBox that is not in the list of options

In the CUBA platform, soft deletion of objects from the database is standard. Deleted objects are not available for use, but must be displayed as part of other objects using them. That is, if you delete some Buyer object, then by opening the Order made by this customer, we should see the name of the remote Buyer in the Buyer selection field, but it should be absent in the selection list. However, Vaadin does not allow setting a value in the drop-down field that is missing from the options.

This feature can simply be implemented in an options container. It is enough that for any key it reports (containsId) that such an element exists. The limitation of such a hack is that the key and its container element must be the same object.



If you select data for drop-down lists along with setting a value, then you just need to use an IndexedContainer or BeanContainer , which contains both options and a value. When you do not control the loading of data for the container, such a hack may be useful. (eg SQLContainer or self-written data sources).
SuperBeanContainer
 public class SuperBeanContainer<IDTYPE, BEANTYPE> extends BeanContainer<IDTYPE, BEANTYPE> { protected Object missingBoxValue; public SuperBeanContainer(Class<? super BEANTYPE> type) { super(type); } @Override public boolean containsId(Object itemId) { boolean containsFlag = super.containsId(itemId); if (!containsFlag) { missingBoxValue = itemId; } return true; } @Override public List getItemIds() { List<IDTYPE> itemIds = super.getItemIds(); if (missingBoxValue != null && !itemIds.contains(missingBoxValue)) { List<IDTYPE> newItemIds = new ArrayList<>(itemIds); newItemIds.add((IDTYPE) missingBoxValue); for (IDTYPE itemId : itemIds) { newItemIds.add(itemId); } itemIds = newItemIds; } return itemIds; } @Override public BeanItem<BEANTYPE> getItem(Object itemId) { if (missingBoxValue == itemId) { return new BeanItem(itemId); } return super.getItem(itemId); } @Override public int size() { int size = super.size(); if (missingBoxValue != null) { size++; } return size; } } 


Example here: https://github.com/Haulmont/vaadin-super-combobox

About switching to Vaadin 7

Vaadin 7 has changed a lot, including browser support. IE7 is no longer supported, IE8 + support is announced. But at the same time, there were big performance problems in IE 8. The process of rendering components has changed dramatically, it is now phased and uses intensive JavaScript calculations. This behavior can not be changed. Some “complex” screens (a table with 10 columns in 5 embedded vertical boxes) in IE8 are drawn 10-20 times slower than in Chrome. When moving or choosing Vaadin 7, keep this in mind.
We solved this problem straightforwardly - we support both 6 and 7 versions in the Vaadin platform, and in the project of the application you can choose which version to use.

dev.vaadin.com/ticket/12797 - Bug checked, but there is no activity yet.

Also, before moving on, make sure that your add-ons will work in the new version. Not all add-on developers have released versions that are compatible with Vaadin 7.

Addons for Vaadin, which we translated into version 7 (maybe it will be useful to someone):

For prototyping on Vaadin, we use a convenient procurement with Maven, Groovy and Jetty: https://github.com/Haulmont/vaadin-sandbox - mvn clean package jetty:run

Reservations

I tried to show the simplest solutions, there are many other improvements, but their consideration can result in a separate article.

We do not use the hacks described in this article in this form, because we support our own version of Vaadin and can add the necessary hooks and protected API to it. https://github.com/Haulmont/vaadin . Perhaps for you this will also be a better option than copying entire classes of the framework. The benefit of git allows you to conveniently merge changes from Upstream.

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


All Articles