I have a vaadin grid with active, buffered editor. By default, the editor is opened when double clicking a row. Everything works fine, except when I double click the button I get an exception: (The exception doesn't point anywhere at my code)
java.lang.NullPointerException: Editor can't edit null
at java.base/java.util.Objects.requireNonNull(Objects.java:246)
at com.vaadin.ui.components.grid.EditorImpl.doEdit(EditorImpl.java:216)
at com.vaadin.ui.components.grid.EditorImpl$1.bind(EditorImpl.java:151)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:155)
at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:116)
at com.vaadin.server.communication.ServerRpcHandler.handleInvocation(ServerRpcHandler.java:445)
at com.vaadin.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:410)
at com.vaadin.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:274)
at com.vaadin.server.communication.PushHandler.lambda$new$1(PushHandler.java:145)
at com.vaadin.server.communication.PushHandler.callWithUi(PushHandler.java:235)
at com.vaadin.server.communication.PushHandler.onMessage(PushHandler.java:520)
at com.vaadin.server.communication.PushAtmosphereHandler.onMessage(PushAtmosphereHandler.java:87)
at com.vaadin.server.communication.PushAtmosphereHandler.onRequest(PushAtmosphereHandler.java:77)
at org.atmosphere.cpr.AsynchronousProcessor.action(AsynchronousProcessor.java:223)
at org.atmosphere.cpr.AsynchronousProcessor.suspended(AsynchronousProcessor.java:115)
at org.atmosphere.container.Servlet30CometSupport.service(Servlet30CometSupport.java:67)
at org.atmosphere.cpr.AtmosphereFramework.doCometSupport(AtmosphereFramework.java:2284)
at org.atmosphere.websocket.DefaultWebSocketProcessor.dispatch(DefaultWebSocketProcessor.java:593)
at org.atmosphere.websocket.DefaultWebSocketProcessor$3.run(DefaultWebSocketProcessor.java:345)
at org.atmosphere.util.VoidExecutorService.execute(VoidExecutorService.java:101)
at org.atmosphere.websocket.DefaultWebSocketProcessor.dispatch(DefaultWebSocketProcessor.java:340)
at org.atmosphere.websocket.DefaultWebSocketProcessor.invokeWebSocketProtocol(DefaultWebSocketProcessor.java:447)
at org.atmosphere.container.JSR356Endpoint$3.onMessage(JSR356Endpoint.java:272)
at org.atmosphere.container.JSR356Endpoint$3.onMessage(JSR356Endpoint.java:269)
at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:395)
at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:119)
at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:495)
at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:294)
at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133)
at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:82)
at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:171)
at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:151)
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:148)
at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:54)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:844)
This is how I add the Button to the grid:
// Adding the column
grid.addComponentColumn(this::buildAddButton);
This is the method that returns the buttons for each row:
// Building the button
private Button buildAddButton(ProductTemplate p) {
Button button = new Button("add");
// Configurate the button
...
return button;
}
Unfortunatly I have found very little about this problem... Since the vaadin button doesn't stop click event propagation, I tried this:
Adding the button to a layout to "block" the click event by removing the click listeners from the layout.
// Adding the column
grid.addComponentColumn(this::buildAddLayout);
Build a layout instead of just a button:
// Building the layout with the button
private VerticalLayout buildAddLayout(ProductTemplate p) {
Button button = new Button("add");
// Configurate the button
...
VerticalLayout layout = new VerticalLayout();
layout.addComponent(button);
layout.getListeners(Event.class).clear();
return layout;
}
Disabling the button when clicked until it's task is finished (Prohibiting double clicking the button).
// Building the button
private Button buildAddButton(ProductTemplate p) {
Button button = new Button("add");
// Configurate the button
...
button.addClickListener(e -> {
button.setEnabled(false);
buttonClicked(p);
button.setEnabled(true);
});
return button;
}
Both didn't make me able to get rid of the exception. Any suggestions, how I can prevent this type of exception, when double clicking a button on a grid with enabled editor?
Edit:
The editor is trying to edit the value of the button, what obviously is not possible. I want to stop the editor from doing this. ("can't edit null "means that vaadin wasn't able to create a valid bean out of the button)
Edit 2: My previous assumption, made in the first edit, seemed to be wrong. My button click is refreshing the grid and I am not able to stop the editor from trying to edit a row, even though all items are getting removed and reloaded.
Here are a class and a pom, you can use to reproduce the exception (The exception occurs when clicking the button very fast):
MyUI.java
package com.example.sample;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.annotation.WebServlet;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.Button;
import com.vaadin.ui.Grid;
import com.vaadin.ui.Notification;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
@Theme("mytheme")
public class MyUI extends UI {
List<Product> items = new ArrayList<Product>();
Grid<Product> grid;
@Override
protected void init(VaadinRequest vaadinRequest) {
items.add(new Product("test", "test test"));
final VerticalLayout layout = new VerticalLayout();
grid = new Grid<Product>();
layout.addComponent(grid);
grid.getEditor().setBuffered(true);
grid.getEditor().setEnabled(true);
grid.removeAllColumns();
grid.addComponentColumn(this::buildAddButton);
TextField nameField = new TextField();
TextField descriptionField = new TextField();
grid.addColumn(Product::getName).setCaption("Name").setEditorComponent(nameField, Product::setName)
.setExpandRatio(1);
grid.addColumn(Product::getDescription).setCaption("Description")
.setEditorComponent(descriptionField, Product::setDescription).setExpandRatio(1);
grid.getEditor().addSaveListener(event -> {
Notification.show((event.getBean() + "saved"));
});
grid.setItems(items);
setContent(layout);
}
private Button buildAddButton(Product p) {
Button button = new Button();
button.addClickListener(event -> addButtonClicked(p));
return button;
}
private void addButtonClicked(Product p) {
refreshGrid();
}
private void refreshGrid() {
grid.setItems(items);
}
@WebServlet(urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true)
@VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
public static class MyUIServlet extends VaadinServlet {
}
public class Product {
String name;
String description;
public Product(String name, String description) {
super();
this.name = name;
this.description = description;
}
public Product() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public String toString() {
return name + " " + description;
}
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>sample</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>sample</name>
<prerequisites>
<maven>3</maven>
</prerequisites>
<properties>
<vaadin.version>8.3.1</vaadin.version>
<vaadin.plugin.version>8.3.1</vaadin.plugin.version>
<jetty.plugin.version>9.3.9.v20160517</jetty.plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!-- If there are no local customizations, this can also be "fetch" or "cdn" -->
<vaadin.widgetset.mode>local</vaadin.widgetset.mode>
</properties>
<repositories>
<repository>
<id>vaadin-addons</id>
<url>http://maven.vaadin.com/vaadin-addons</url>
</repository>
</repositories>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>${vaadin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-server</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-push</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-client-compiled</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-themes</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<!-- Exclude an unnecessary file generated by the GWT compiler. -->
<packagingExcludes>WEB-INF/classes/VAADIN/widgetsets/WEB-INF/**</packagingExcludes>
</configuration>
</plugin>
<plugin>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId>
<version>${vaadin.plugin.version}</version>
<executions>
<execution>
<goals>
<goal>update-theme</goal>
<goal>update-widgetset</goal>
<goal>compile</goal>
<!-- Comment out compile-theme goal to use on-the-fly theme compilation -->
<goal>compile-theme</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
<!-- Clean up also any pre-compiled themes -->
<configuration>
<filesets>
<fileset>
<directory>src/main/webapp/VAADIN/themes</directory>
<includes>
<include>**/styles.css</include>
<include>**/styles.scss.cache</include>
</includes>
</fileset>
</filesets>
</configuration>
</plugin>
<!-- The Jetty plugin allows us to easily test the development build by
running jetty:run on the command line. -->
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty.plugin.version}</version>
<configuration>
<scanIntervalSeconds>2</scanIntervalSeconds>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<!-- Vaadin pre-release repositories -->
<id>vaadin-prerelease</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<repositories>
<repository>
<id>vaadin-prereleases</id>
<url>http://maven.vaadin.com/vaadin-prereleases</url>
</repository>
<repository>
<id>vaadin-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/vaadin-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>vaadin-prereleases</id>
<url>http://maven.vaadin.com/vaadin-prereleases</url>
</pluginRepository>
<pluginRepository>
<id>vaadin-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/vaadin-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</project>
Disclaimer: this solution is not applicable for the versions prior 8.3 due to a missing setHandleWidgetEvents
method. Otherwise, while complicated, this solution might work for someone:
Remove a click event handling from a button (addClickListener
). There is no way to get an information about double click there, BUT
There is a way using addItemClickListener
and its clickedItem.getMouseEventDetails().isDoubleClick()
, BUT again
Once you enable grid using grid.getEditor().setEnabled(true);
a double-click check always returns false
,so
You would need to disable editor by default grid.getEditor().setEnabled(false);
(A good related answer here Couldn't capture double click event using vaadin 7)
And , instead, re-enable it once a double click event has occurred(and open a row)
grid.getEditor().setEnabled(true);
grid.getEditor().editRow(item.getRowIndex());
If, otherwise, a button column is clicked once, execute your addButtonClicked
action then
grid.addComponentColumn(this::buildAddButton).setHandleWidgetEvents(true).setId("buttonClick");
A complete modified code from the init
method. All others remain the same (also, remember to remove a addClickListener
from button):
items.add(new Product("test", "test test"));
grid = new Grid<Product>();
grid.getEditor().setBuffered(true);
grid.getEditor().setEnabled(false);
grid.removeAllColumns();
// Important! Propagate events from components to Grid
grid.addComponentColumn(this::buildAddButton).setHandleWidgetEvents(true).setId("buttonClick");
TextField nameField = new TextField();
TextField descriptionField = new TextField();
grid.addColumn(Product::getName).setCaption("Name").setEditorComponent(nameField, Product::setName)
.setExpandRatio(1);
grid.addColumn(Product::getDescription).setCaption("Description")
.setEditorComponent(descriptionField, Product::setDescription).setExpandRatio(1);
//Once close editor--> Disable it
grid.getEditor().addSaveListener(event -> {
grid.getEditor().setEnabled(false);
});
grid.getEditor().addCancelListener(e->{
grid.getEditor().setEnabled(false);
});
//THIS IS WHERE ALL THE LOGIC IS HAPPENING
grid.addItemClickListener(item->{
//If the button column is clicked
if("buttonClick".equals(item.getColumn().getId())){
//Regual click--> update content; also fired twice before editor is opened
if(!item.getMouseEventDetails().isDoubleClick()){
addButtonClicked(item.getItem());
}
//If Double click is detected, just opened editor. The data is already updated
else{
grid.getEditor().setEnabled(true);
grid.getEditor().editRow(item.getRowIndex());
}
}
//In all the other cases, when double click is detected--> open editor
else if(item.getMouseEventDetails().isDoubleClick()){
grid.getEditor().setEnabled(true);
grid.getEditor().editRow(item.getRowIndex());
}
});
grid.setItems(items);
addComponent(grid);