summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/src/main/java/com/vaadin/client/connectors/grid/ColumnConnector.java5
-rw-r--r--client/src/main/java/com/vaadin/client/connectors/grid/EditorConnector.java222
-rw-r--r--client/src/main/java/com/vaadin/client/widget/grid/EditorHandler.java19
-rw-r--r--client/src/main/java/com/vaadin/client/widgets/Grid.java5
-rw-r--r--server/src/main/java/com/vaadin/data/Binder.java8
-rw-r--r--server/src/main/java/com/vaadin/data/BinderValidationStatusHandler.java11
-rw-r--r--server/src/main/java/com/vaadin/ui/Grid.java275
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/EditorImpl.java310
-rw-r--r--server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java2
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/grid/ColumnState.java2
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/grid/editor/EditorClientRpc.java64
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/grid/editor/EditorServerRpc.java52
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/grid/editor/EditorState.java49
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java109
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicsTest.java10
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridEditorBufferedTest.java290
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridEditorTest.java238
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridEditorUnbufferedTest.java246
18 files changed, 1874 insertions, 43 deletions
diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/ColumnConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/ColumnConnector.java
index c3aa4fe5d2..700a3de575 100644
--- a/client/src/main/java/com/vaadin/client/connectors/grid/ColumnConnector.java
+++ b/client/src/main/java/com/vaadin/client/connectors/grid/ColumnConnector.java
@@ -132,6 +132,11 @@ public class ColumnConnector extends AbstractExtensionConnector {
column.setExpandRatio(getState().expandRatio);
}
+ @OnStateChange("editable")
+ void updateEditable() {
+ column.setEditable(getState().editable);
+ }
+
@Override
public void onUnregister() {
super.onUnregister();
diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/EditorConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/EditorConnector.java
new file mode 100644
index 0000000000..c4bd801022
--- /dev/null
+++ b/client/src/main/java/com/vaadin/client/connectors/grid/EditorConnector.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.connectors.grid;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.annotations.OnStateChange;
+import com.vaadin.client.extensions.AbstractExtensionConnector;
+import com.vaadin.client.widget.grid.EditorHandler;
+import com.vaadin.client.widgets.Grid;
+import com.vaadin.client.widgets.Grid.Column;
+import com.vaadin.shared.data.DataCommunicatorConstants;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.grid.editor.EditorClientRpc;
+import com.vaadin.shared.ui.grid.editor.EditorServerRpc;
+import com.vaadin.shared.ui.grid.editor.EditorState;
+import com.vaadin.ui.components.grid.EditorImpl;
+
+import elemental.json.JsonObject;
+
+/**
+ * Connector for Grid Editor.
+ *
+ * @author Vaadin Ltd
+ * @since
+ */
+@Connect(EditorImpl.class)
+public class EditorConnector extends AbstractExtensionConnector {
+
+ /**
+ * EditorHandler for communicating with the server-side implementation.
+ */
+ private class CustomEditorHandler implements EditorHandler<JsonObject> {
+ private EditorServerRpc rpc = getRpcProxy(EditorServerRpc.class);
+ private EditorRequest<JsonObject> currentRequest = null;
+ private boolean serverInitiated = false;
+
+ public CustomEditorHandler() {
+ registerRpc(EditorClientRpc.class, new EditorClientRpc() {
+ @Override
+ public void cancel() {
+ serverInitiated = true;
+ getParent().getWidget().cancelEditor();
+ }
+
+ @Override
+ public void confirmBind(final boolean bindSucceeded) {
+ endRequest(bindSucceeded);
+ }
+
+ @Override
+ public void confirmSave(boolean saveSucceeded) {
+ endRequest(saveSucceeded);
+ }
+
+ @Override
+ public void setErrorMessage(String errorMessage,
+ List<String> errorColumnsIds) {
+ Collection<Column<?, JsonObject>> errorColumns;
+ if (errorColumnsIds != null) {
+ errorColumns = new ArrayList<Grid.Column<?, JsonObject>>();
+ for (String colId : errorColumnsIds) {
+ errorColumns.add(getParent().getColumn(colId));
+ }
+ } else {
+ errorColumns = null;
+ }
+ getParent().getWidget().getEditor()
+ .setEditorError(errorMessage, errorColumns);
+ }
+ });
+ }
+
+ @Override
+ public void bind(EditorRequest<JsonObject> request) {
+ startRequest(request);
+ rpc.bind(getRowKey(request.getRow()));
+ }
+
+ @Override
+ public void save(EditorRequest<JsonObject> request) {
+ startRequest(request);
+ rpc.save();
+ }
+
+ @Override
+ public void cancel(EditorRequest<JsonObject> request) {
+ if (!handleServerInitiated(request)) {
+ // No startRequest as we don't get (or need)
+ // a confirmation from the server
+ rpc.cancel();
+ }
+ }
+
+ @Override
+ public Widget getWidget(Column<?, JsonObject> column) {
+ String connId = getState().columnFields
+ .get(getParent().getColumnId(column));
+ if (connId == null) {
+ return null;
+ }
+ return getConnector(connId).getWidget();
+ }
+
+ private ComponentConnector getConnector(String id) {
+ return (ComponentConnector) ConnectorMap.get(getConnection())
+ .getConnector(id);
+ }
+
+ /**
+ * Used to handle the case where the editor calls us because it was
+ * invoked by the server via RPC and not by the client. In that case,
+ * the request can be simply synchronously completed.
+ *
+ * @param request
+ * the request object
+ * @return true if the request was originally triggered by the server,
+ * false otherwise
+ */
+ private boolean handleServerInitiated(EditorRequest<?> request) {
+ assert request != null : "Cannot handle null request";
+ assert currentRequest == null : "Earlier request not yet finished";
+
+ if (serverInitiated) {
+ serverInitiated = false;
+ request.success();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void startRequest(EditorRequest<JsonObject> request) {
+ assert currentRequest == null : "Earlier request not yet finished";
+
+ currentRequest = request;
+ }
+
+ private void endRequest(boolean succeeded) {
+ assert currentRequest != null : "Current request was null";
+ /*
+ * Clear current request first to ensure the state is valid if
+ * another request is made in the callback.
+ */
+ EditorRequest<JsonObject> request = currentRequest;
+ currentRequest = null;
+ if (succeeded) {
+ request.success();
+ } else {
+ request.failure();
+ }
+ }
+ }
+
+ @OnStateChange("buffered")
+ void updateBuffered() {
+ getParent().getWidget().getEditor().setBuffered(getState().buffered);
+ }
+
+ @OnStateChange("enabled")
+ void updateEnabled() {
+ getParent().getWidget().getEditor().setEnabled(getState().enabled);
+ }
+
+ @OnStateChange("saveCaption")
+ void updateSaveCaption() {
+ getParent().getWidget().getEditor()
+ .setSaveCaption(getState().saveCaption);
+ }
+
+ @OnStateChange("cancelCaption")
+ void updateCancelCaption() {
+ getParent().getWidget().getEditor()
+ .setCancelCaption(getState().cancelCaption);
+ }
+
+ @Override
+ protected void extend(ServerConnector target) {
+ Grid<JsonObject> grid = getParent().getWidget();
+ grid.getEditor().setHandler(new CustomEditorHandler());
+ }
+
+ @Override
+ public GridConnector getParent() {
+ return (GridConnector) super.getParent();
+ }
+
+ @Override
+ public EditorState getState() {
+ return (EditorState) super.getState();
+ }
+
+ /**
+ * Returns the key of the given data row.
+ *
+ * @param row
+ * the row
+ * @return the row key
+ */
+ protected static String getRowKey(JsonObject row) {
+ return row.getString(DataCommunicatorConstants.KEY);
+ }
+}
diff --git a/client/src/main/java/com/vaadin/client/widget/grid/EditorHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/EditorHandler.java
index 764a6e5086..97e8cbf297 100644
--- a/client/src/main/java/com/vaadin/client/widget/grid/EditorHandler.java
+++ b/client/src/main/java/com/vaadin/client/widget/grid/EditorHandler.java
@@ -19,6 +19,7 @@ import java.util.Collection;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.widgets.Grid;
+import com.vaadin.client.widgets.Grid.Editor;
/**
* An interface for binding widgets and data to the grid row editor. Used by the
@@ -93,6 +94,15 @@ public interface EditorHandler<T> {
* Informs Grid that an error occurred while trying to process the
* request.
*
+ * @see Editor#setEditorError(String, Collection)
+ */
+ public void failure();
+
+ /**
+ * Informs Grid that an error occurred while trying to process the
+ * request. This method is a short-hand for calling {@link #failure()}
+ * and {@link Editor#setEditorError(String, Collection)}
+ *
* @param errorMessage
* and error message to show to the user, or
* <code>null</code> to not show any message.
@@ -100,9 +110,14 @@ public interface EditorHandler<T> {
* a collection of columns for which an error indicator
* should be shown, or <code>null</code> if no columns should
* be marked as erroneous.
+ *
+ * @see Editor#setEditorError(String, Collection)
*/
- public void failure(String errorMessage,
- Collection<Grid.Column<?, T>> errorColumns);
+ public default void failure(String errorMessage,
+ Collection<Grid.Column<?, T>> errorColumns) {
+ failure();
+ getGrid().getEditor().setEditorError(errorMessage, errorColumns);
+ }
/**
* Checks whether the request is completed or not.
diff --git a/client/src/main/java/com/vaadin/client/widgets/Grid.java b/client/src/main/java/com/vaadin/client/widgets/Grid.java
index e1633fdbd3..0cad901ec2 100644
--- a/client/src/main/java/com/vaadin/client/widgets/Grid.java
+++ b/client/src/main/java/com/vaadin/client/widgets/Grid.java
@@ -1152,9 +1152,8 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
}
@Override
- public void failure(String errorMessage,
- Collection<Grid.Column<?, T>> errorColumns) {
- complete(errorMessage, errorColumns);
+ public void failure() {
+ complete("", null);
if (callback != null) {
callback.onError(this);
}
diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java
index 771726ea3c..df6d72d79c 100644
--- a/server/src/main/java/com/vaadin/data/Binder.java
+++ b/server/src/main/java/com/vaadin/data/Binder.java
@@ -884,7 +884,7 @@ public class Binder<BEAN> implements Serializable {
private Label statusLabel;
- private BinderValidationStatusHandler statusHandler;
+ private BinderValidationStatusHandler<BEAN> statusHandler;
private boolean hasChanges = false;
@@ -1356,7 +1356,7 @@ public class Binder<BEAN> implements Serializable {
* @see Binding#withValidationStatusHandler(ValidationStatusHandler)
*/
public void setValidationStatusHandler(
- BinderValidationStatusHandler statusHandler) {
+ BinderValidationStatusHandler<BEAN> statusHandler) {
Objects.requireNonNull(statusHandler, "Cannot set a null "
+ BinderValidationStatusHandler.class.getSimpleName());
if (statusLabel != null) {
@@ -1377,7 +1377,7 @@ public class Binder<BEAN> implements Serializable {
* @return the status handler used, never <code>null</code>
* @see #setValidationStatusHandler(BinderStatusHandler)
*/
- public BinderValidationStatusHandler getValidationStatusHandler() {
+ public BinderValidationStatusHandler<BEAN> getValidationStatusHandler() {
return Optional.ofNullable(statusHandler)
.orElse(this::handleBinderValidationStatus);
}
@@ -1509,7 +1509,7 @@ public class Binder<BEAN> implements Serializable {
* validators
*/
protected void handleBinderValidationStatus(
- BinderValidationStatus<?> binderStatus) {
+ BinderValidationStatus<BEAN> binderStatus) {
// let field events go to binding status handlers
binderStatus.getFieldValidationStatuses()
.forEach(status -> ((BindingImpl<?, ?, ?>) status.getBinding())
diff --git a/server/src/main/java/com/vaadin/data/BinderValidationStatusHandler.java b/server/src/main/java/com/vaadin/data/BinderValidationStatusHandler.java
index 691b7bd6c0..1f3a95688b 100644
--- a/server/src/main/java/com/vaadin/data/BinderValidationStatusHandler.java
+++ b/server/src/main/java/com/vaadin/data/BinderValidationStatusHandler.java
@@ -23,8 +23,8 @@ import com.vaadin.ui.AbstractComponent;
/**
* Handler for {@link BinderValidationStatus} changes.
* <p>
- * {{@link Binder#setValidationStatusHandler(BinderStatusHandler) Register} an instance of
- * this class to be able to customize validation status handling.
+ * {{@link Binder#setValidationStatusHandler(BinderStatusHandler) Register} an
+ * instance of this class to be able to customize validation status handling.
* <p>
* The default handler will show
* {@link AbstractComponent#setComponentError(com.vaadin.server.ErrorMessage) an
@@ -39,9 +39,12 @@ import com.vaadin.ui.AbstractComponent;
* @see Binder#validate()
* @see ValidationStatus
*
+ * @param <BEAN>
+ * the bean type of binder
+ *
* @since 8.0
*/
-public interface BinderValidationStatusHandler
- extends Consumer<BinderValidationStatus<?>>, Serializable {
+public interface BinderValidationStatusHandler<BEAN>
+ extends Consumer<BinderValidationStatus<BEAN>>, Serializable {
}
diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java
index ee1547bfce..53e22a53d4 100644
--- a/server/src/main/java/com/vaadin/ui/Grid.java
+++ b/server/src/main/java/com/vaadin/ui/Grid.java
@@ -31,6 +31,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -39,6 +40,7 @@ import java.util.stream.Stream;
import org.jsoup.nodes.Element;
import com.vaadin.data.Binder;
+import com.vaadin.data.BinderValidationStatus;
import com.vaadin.data.SelectionModel;
import com.vaadin.event.ConnectorEvent;
import com.vaadin.event.ContextClickEvent;
@@ -48,6 +50,7 @@ import com.vaadin.server.Extension;
import com.vaadin.server.JsonCodec;
import com.vaadin.server.SerializableComparator;
import com.vaadin.server.SerializableFunction;
+import com.vaadin.server.data.DataCommunicator;
import com.vaadin.server.data.SortOrder;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.Registration;
@@ -63,6 +66,7 @@ import com.vaadin.shared.ui.grid.HeightMode;
import com.vaadin.shared.ui.grid.SectionState;
import com.vaadin.shared.util.SharedUtil;
import com.vaadin.ui.Grid.FooterRow;
+import com.vaadin.ui.components.grid.EditorImpl;
import com.vaadin.ui.components.grid.Footer;
import com.vaadin.ui.components.grid.Header;
import com.vaadin.ui.components.grid.Header.Row;
@@ -587,6 +591,11 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents {
.getSortOrder(order.getDirection()))
.forEach(s -> s.forEach(sortProperties::add));
getDataCommunicator().setBackEndSorting(sortProperties);
+
+ // Close grid editor if it's open.
+ if (getEditor().isOpen()) {
+ getEditor().cancel();
+ }
}
@Override
@@ -795,6 +804,8 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents {
private StyleGenerator<T> styleGenerator = item -> null;
private DescriptionGenerator<T> descriptionGenerator;
+ private SerializableFunction<T, Component> componentGenerator;
+
/**
* Constructs a new Column configuration with given header caption,
* renderer and value provider.
@@ -1498,8 +1509,85 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents {
}
/**
+ * Sets whether this Column has a component displayed in Editor or not.
+ *
+ * @param editable
+ * {@code true} if column is editable; {@code false} if not
+ * @return this column
+ *
+ * @see #setEditorComponent(Component)
+ * @see #setEditorComponentGenerator(SerializableFunction)
+ */
+ public Column<T, V> setEditable(boolean editable) {
+ Objects.requireNonNull(componentGenerator,
+ "Column has no editor component defined");
+ getState().editable = editable;
+ return this;
+ }
+
+ /**
+ * Gets whether this Column has a component displayed in Editor or not.
+ *
+ * @return {@code true} if the column displays an editor component;
+ * {@code false} if not
+ */
+ public boolean isEditable() {
+ return getState(false).editable;
+ }
+
+ /**
+ * Sets a static editor component for this column.
+ * <p>
+ * <strong>Note:</strong> The same component cannot be used for multiple
+ * columns.
+ *
+ * @param component
+ * the editor component
+ * @return this column
+ *
+ * @see Editor#getBinder()
+ * @see Editor#setBinder(Binder)
+ * @see #setEditorComponentGenerator(SerializableFunction)
+ */
+ public Column<T, V> setEditorComponent(Component component) {
+ Objects.requireNonNull(component,
+ "null is not a valid editor field");
+ return setEditorComponentGenerator(t -> component);
+ }
+
+ /**
+ * Sets a component generator to provide editor component for this
+ * Column. This method can be used to generate any dynamic component to
+ * be displayed in the editor row.
+ * <p>
+ * <strong>Note:</strong> The same component cannot be used for multiple
+ * columns.
+ *
+ * @param componentGenerator
+ * the editor component generator
+ * @return this column
+ *
+ * @see #setEditorComponent(Component)
+ */
+ public Column<T, V> setEditorComponentGenerator(
+ SerializableFunction<T, Component> componentGenerator) {
+ Objects.requireNonNull(componentGenerator);
+ this.componentGenerator = componentGenerator;
+ return setEditable(true);
+ }
+
+ /**
+ * Gets the editor component generator for this Column.
+ *
+ * @return editor component generator
+ */
+ public SerializableFunction<T, Component> getEditorComponentGenerator() {
+ return componentGenerator;
+ }
+
+ /**
* Checks if column is attached and throws an
- * {@link IllegalStateException} if it is not
+ * {@link IllegalStateException} if it is not.
*
* @throws IllegalStateException
* if the column is no longer attached to any grid
@@ -1557,7 +1645,7 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents {
* The cells to merge. Must be from the same row.
* @return The remaining visible cell after the merge
*/
- HeaderCell join(HeaderCell ... cellsToMerge);
+ HeaderCell join(HeaderCell... cellsToMerge);
}
@@ -1716,6 +1804,167 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents {
public GridStaticCellType getCellType();
}
+ /**
+ * Generator for creating editor validation and conversion error messages.
+ *
+ * @param <T>
+ * the bean type
+ */
+ public interface EditorErrorGenerator<T> extends Serializable,
+ BiFunction<Map<Component, Column<T, ?>>, BinderValidationStatus<T>, String> {
+
+ /**
+ * Generates an error message from given validation status object.
+ *
+ * @param fieldToColumn
+ * the map of failed fields and corresponding columns
+ * @param status
+ * the binder status object with all failures
+ *
+ * @return error message string
+ */
+ @Override
+ public String apply(Map<Component, Column<T, ?>> fieldToColumn,
+ BinderValidationStatus<T> status);
+ }
+
+ /**
+ * An editor in a Grid.
+ *
+ * @param <T>
+ */
+ public interface Editor<T> extends Serializable {
+
+ /**
+ * Sets the underlying Binder to this Editor.
+ *
+ * @param binder
+ * the binder for updating editor fields; not {@code null}
+ * @return this editor
+ */
+ public Editor<T> setBinder(Binder<T> binder);
+
+ /**
+ * Returns the underlying Binder from Editor.
+ *
+ * @return the binder; not {@code null}
+ */
+ public Binder<T> getBinder();
+
+ /**
+ * Sets the Editor buffered mode. When the editor is in buffered mode,
+ * edits are only committed when the user clicks the save button. In
+ * unbuffered mode valid changes are automatically committed.
+ *
+ * @param buffered
+ * {@code true} if editor should be buffered; {@code false}
+ * if not
+ * @return this editor
+ */
+ public Editor<T> setBuffered(boolean buffered);
+
+ /**
+ * Enables or disabled the Editor. A disabled editor cannot be opened.
+ *
+ * @param enabled
+ * {@code true} if editor should be enabled; {@code false} if
+ * not
+ * @return this editor
+ */
+ public Editor<T> setEnabled(boolean enabled);
+
+ /**
+ * Returns whether Editor is buffered or not.
+ *
+ * @see #setBuffered(boolean)
+ *
+ * @return {@code true} if editor is buffered; {@code false} if not
+ */
+ public boolean isBuffered();
+
+ /**
+ * Returns whether Editor is enabled or not.
+ *
+ * @return {@code true} if editor is enabled; {@code false} if not
+ */
+ public boolean isEnabled();
+
+ /**
+ * Returns whether Editor is open or not.
+ *
+ * @return {@code true} if editor is open; {@code false} if not
+ */
+ public boolean isOpen();
+
+ /**
+ * Saves any changes from the Editor fields to the edited bean.
+ *
+ * @return {@code true} if save succeeded; {@code false} if not
+ */
+ public boolean save();
+
+ /**
+ * Close the editor discarding any unsaved changes.
+ */
+ public void cancel();
+
+ /**
+ * Sets the caption of the save button in buffered mode.
+ *
+ * @param saveCaption
+ * the save button caption
+ * @return this editor
+ */
+ public Editor<T> setSaveCaption(String saveCaption);
+
+ /**
+ * Sets the caption of the cancel button in buffered mode.
+ *
+ * @param cancelCaption
+ * the cancel button caption
+ * @return this editor
+ */
+ public Editor<T> setCancelCaption(String cancelCaption);
+
+ /**
+ * Gets the caption of the save button in buffered mode.
+ *
+ * @return the save button caption
+ */
+ public String getSaveCaption();
+
+ /**
+ * Gets the caption of the cancel button in buffered mode.
+ *
+ * @return the cancel button caption
+ */
+ public String getCancelCaption();
+
+ /**
+ * Sets the error message generator for this editor.
+ * <p>
+ * The default message is a concatenation of column field validation
+ * failures and bean validation failures.
+ *
+ * @param errorGenerator
+ * the function to generate error messages; not {@code null}
+ * @return this editor
+ *
+ * @see EditorErrorGenerator
+ */
+ public Editor<T> setErrorGenerator(
+ EditorErrorGenerator<T> errorGenerator);
+
+ /**
+ * Gets the error message generator of this editor.
+ *
+ * @return the function that generates error messages; not {@code null}
+ *
+ * @see EditorErrorGenerator
+ */
+ public EditorErrorGenerator<T> getErrorGenerator();
+ }
+
private class HeaderImpl extends Header {
@Override
@@ -1768,6 +2017,8 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents {
private GridSelectionModel<T> selectionModel;
+ private Editor<T> editor;
+
/**
* Constructor for the {@link Grid} component.
*/
@@ -1782,6 +2033,11 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents {
addExtension(detailsManager);
addDataGenerator(detailsManager);
+ editor = createEditor();
+ if (editor instanceof Extension) {
+ addExtension((Extension) editor);
+ }
+
addDataGenerator((item, json) -> {
String styleName = styleGenerator.apply(item);
if (styleName != null && !styleName.isEmpty()) {
@@ -2651,6 +2907,10 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents {
return ((SingleSelectionModel<T>) model).asSingleSelect();
}
+ public Editor<T> getEditor() {
+ return editor;
+ }
+
/**
* Sets the selection model for this listing.
* <p>
@@ -2675,6 +2935,17 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents {
return (GridState) super.getState(markAsDirty);
}
+ /**
+ * Creates a new Editor instance. Can be overridden to create a custom
+ * Editor. If the Editor is a {@link AbstractGridExtension}, it will be
+ * automatically added to {@link DataCommunicator}.
+ *
+ * @return editor
+ */
+ protected Editor<T> createEditor() {
+ return new EditorImpl<>();
+ }
+
private void addExtensionComponent(Component c) {
if (extensionComponents.add(c)) {
c.setParent(this);
diff --git a/server/src/main/java/com/vaadin/ui/components/grid/EditorImpl.java b/server/src/main/java/com/vaadin/ui/components/grid/EditorImpl.java
new file mode 100644
index 0000000000..bc83547488
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/components/grid/EditorImpl.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.grid;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.vaadin.data.Binder;
+import com.vaadin.data.BinderValidationStatus;
+import com.vaadin.data.BinderValidationStatusHandler;
+import com.vaadin.shared.ui.grid.editor.EditorClientRpc;
+import com.vaadin.shared.ui.grid.editor.EditorServerRpc;
+import com.vaadin.shared.ui.grid.editor.EditorState;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.Grid.AbstractGridExtension;
+import com.vaadin.ui.Grid.Column;
+import com.vaadin.ui.Grid.Editor;
+import com.vaadin.ui.Grid.EditorErrorGenerator;
+
+import elemental.json.JsonObject;
+
+/**
+ * Implementation of {@code Editor} interface.
+ *
+ * @param <T>
+ * the grid bean type
+ */
+public class EditorImpl<T> extends AbstractGridExtension<T>
+ implements Editor<T> {
+
+ private class EditorStatusHandler
+ implements BinderValidationStatusHandler<T> {
+
+ @Override
+ public void accept(BinderValidationStatus<T> status) {
+ boolean ok = status.isOk();
+ if (saving) {
+ rpc.confirmSave(ok);
+ saving = false;
+ }
+
+ if (ok) {
+ binder.getBean().ifPresent(t -> refresh(t));
+ rpc.setErrorMessage(null, Collections.emptyList());
+ } else {
+ List<Component> fields = status.getFieldValidationErrors()
+ .stream().map(error -> error.getField())
+ .filter(columnFields.values()::contains)
+ .map(field -> (Component) field)
+ .collect(Collectors.toList());
+
+ Map<Component, Column<T, ?>> fieldToColumn = new HashMap<>();
+ columnFields.entrySet().stream()
+ .filter(entry -> fields.contains(entry.getValue()))
+ .forEach(entry -> fieldToColumn.put(entry.getValue(),
+ entry.getKey()));
+
+ String message = errorGenerator.apply(fieldToColumn, status);
+
+ List<String> columnIds = fieldToColumn.values().stream()
+ .map(Column::getId).collect(Collectors.toList());
+
+ rpc.setErrorMessage(message, columnIds);
+ }
+ }
+
+ }
+
+ private Binder<T> binder;
+ private Map<Column<T, ?>, Component> columnFields = new HashMap<>();
+ private T edited;
+ private boolean saving = false;
+ private EditorClientRpc rpc;
+ private EditorErrorGenerator<T> errorGenerator = (fieldToColumn,
+ status) -> {
+ String message = status.getFieldValidationErrors().stream()
+ .filter(e -> e.getMessage().isPresent()
+ && fieldToColumn.containsKey(e.getField()))
+ .map(e -> fieldToColumn.get(e.getField()).getCaption() + ": "
+ + e.getMessage().get())
+ .collect(Collectors.joining("; "));
+
+ String beanMessage = status.getBeanValidationErrors().stream()
+ .map(e -> e.getErrorMessage())
+ .collect(Collectors.joining("; "));
+
+ message = Stream.of(message, beanMessage).filter(s -> !s.isEmpty())
+ .collect(Collectors.joining("; "));
+
+ return message;
+ };
+
+ /**
+ * Constructor for internal implementation of the Editor.
+ */
+ public EditorImpl() {
+ rpc = getRpcProxy(EditorClientRpc.class);
+ registerRpc(new EditorServerRpc() {
+
+ @Override
+ public void save() {
+ saving = true;
+ EditorImpl.this.save();
+ }
+
+ @Override
+ public void cancel() {
+ doClose();
+ }
+
+ @Override
+ public void bind(String key) {
+ // When in buffered mode, the editor is not allowed to move.
+ // Binder with failed validation returns true for hasChanges.
+ if (isOpen() && (isBuffered() || getBinder().hasChanges())) {
+ rpc.confirmBind(false);
+ return;
+ }
+ doClose();
+ doEdit(getData(key));
+ rpc.confirmBind(true);
+ }
+ });
+
+ setBinder(new Binder<>());
+ }
+
+ @Override
+ public void generateData(T item, JsonObject jsonObject) {
+ }
+
+ @Override
+ public Editor<T> setBinder(Binder<T> binder) {
+ this.binder = binder;
+
+ binder.setValidationStatusHandler(new EditorStatusHandler());
+ return this;
+ }
+
+ @Override
+ public Binder<T> getBinder() {
+ return binder;
+ }
+
+ @Override
+ public Editor<T> setBuffered(boolean buffered) {
+ if (isOpen()) {
+ throw new IllegalStateException(
+ "Cannot modify Editor when it is open.");
+ }
+ getState().buffered = buffered;
+
+ return this;
+ }
+
+ @Override
+ public Editor<T> setEnabled(boolean enabled) {
+ if (isOpen()) {
+ throw new IllegalStateException(
+ "Cannot modify Editor when it is open.");
+ }
+ getState().enabled = enabled;
+
+ return this;
+ }
+
+ @Override
+ public boolean isBuffered() {
+ return getState(false).buffered;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return getState(false).enabled;
+ }
+
+ /**
+ * Handles editor component generation and adding them to the hierarchy of
+ * the Grid.
+ *
+ * @param bean
+ * the edited item; can't be {@code null}
+ */
+ protected void doEdit(T bean) {
+ Objects.requireNonNull(bean, "Editor can't edit null");
+ if (!isEnabled()) {
+ throw new IllegalStateException(
+ "Editing is not allowed when Editor is disabled.");
+ }
+
+ if (!isBuffered()) {
+ binder.setBean(bean);
+ } else {
+ binder.readBean(bean);
+ }
+ edited = bean;
+
+ getParent().getColumns().stream().filter(Column::isEditable)
+ .forEach(c -> {
+ Component component = c.getEditorComponentGenerator()
+ .apply(edited);
+ addComponentToGrid(component);
+ columnFields.put(c, component);
+ getState().columnFields.put(c.getId(),
+ component.getConnectorId());
+ });
+ }
+
+ @Override
+ public boolean save() {
+ if (isOpen() && isBuffered()) {
+ binder.validate();
+ if (binder.writeBeanIfValid(edited)) {
+ refresh(edited);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isOpen() {
+ return edited != null;
+ }
+
+ @Override
+ public void cancel() {
+ doClose();
+ rpc.cancel();
+ }
+
+ /**
+ * Handles clean up for closing the Editor.
+ */
+ protected void doClose() {
+ edited = null;
+
+ for (Component c : columnFields.values()) {
+ removeComponentFromGrid(c);
+ }
+ columnFields.clear();
+ getState().columnFields.clear();
+ }
+
+ @Override
+ public Editor<T> setSaveCaption(String saveCaption) {
+ Objects.requireNonNull(saveCaption);
+ getState().saveCaption = saveCaption;
+
+ return this;
+ }
+
+ @Override
+ public Editor<T> setCancelCaption(String cancelCaption) {
+ Objects.requireNonNull(cancelCaption);
+ getState().cancelCaption = cancelCaption;
+
+ return this;
+ }
+
+ @Override
+ public String getSaveCaption() {
+ return getState(false).saveCaption;
+ }
+
+ @Override
+ public String getCancelCaption() {
+ return getState(false).cancelCaption;
+ }
+
+ @Override
+ protected EditorState getState() {
+ return getState(true);
+ }
+
+ @Override
+ protected EditorState getState(boolean markAsDirty) {
+ return (EditorState) super.getState(markAsDirty);
+ }
+
+ @Override
+ public Editor<T> setErrorGenerator(EditorErrorGenerator<T> errorGenerator) {
+ Objects.requireNonNull(errorGenerator, "Error generator can't be null");
+ this.errorGenerator = errorGenerator;
+ return this;
+ }
+
+ @Override
+ public EditorErrorGenerator<T> getErrorGenerator() {
+ return errorGenerator;
+ }
+} \ No newline at end of file
diff --git a/server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java b/server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java
index a621549b83..14a6be0307 100644
--- a/server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java
+++ b/server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java
@@ -637,7 +637,7 @@ public class BinderBookOfVaadinTest {
public void withBinderStatusHandlerExample() {
Label formStatusLabel = new Label();
- BinderValidationStatusHandler defaultHandler = binder
+ BinderValidationStatusHandler<BookPerson> defaultHandler = binder
.getValidationStatusHandler();
binder.setValidationStatusHandler(status -> {
diff --git a/shared/src/main/java/com/vaadin/shared/ui/grid/ColumnState.java b/shared/src/main/java/com/vaadin/shared/ui/grid/ColumnState.java
index 8296dd8660..c2f4f6a07e 100644
--- a/shared/src/main/java/com/vaadin/shared/ui/grid/ColumnState.java
+++ b/shared/src/main/java/com/vaadin/shared/ui/grid/ColumnState.java
@@ -23,6 +23,7 @@ public class ColumnState extends SharedState {
public String caption;
public String id;
public boolean sortable;
+ public boolean editable = false;
/** The caption for the column hiding toggle. */
public String hidingToggleCaption;
@@ -58,4 +59,5 @@ public class ColumnState extends SharedState {
public boolean resizable = true;
public Connector renderer;
+
}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/grid/editor/EditorClientRpc.java b/shared/src/main/java/com/vaadin/shared/ui/grid/editor/EditorClientRpc.java
new file mode 100644
index 0000000000..04cdac431b
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/grid/editor/EditorClientRpc.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.shared.ui.grid.editor;
+
+import java.util.List;
+
+import com.vaadin.shared.communication.ClientRpc;
+
+/**
+ * An RPC interface for the grid editor server-to-client communications.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public interface EditorClientRpc extends ClientRpc {
+
+ /**
+ * Tells the client to cancel editing and hide the editor.
+ */
+ void cancel();
+
+ /**
+ * Confirms a pending {@link EditorServerRpc#bind(String) bind request} sent
+ * by the client.
+ *
+ * @param bindSucceeded
+ * {@code true} if and only if the bind action was successful
+ */
+ void confirmBind(boolean bindSucceeded);
+
+ /**
+ * Confirms a pending {@link EditorServerRpc#save() save request} sent by
+ * the client.
+ *
+ * @param saveSucceeded
+ * {@code true} if and only if the save action was successful
+ */
+ void confirmSave(boolean saveSucceeded);
+
+ /**
+ * Sets the displayed error messages for editor.
+ *
+ * @param errorMessage
+ * the error message to show the user; {@code} null to clear
+ * @param errorColumnsIds
+ * a list of column ids that should get error markers; empty list
+ * to clear
+ *
+ */
+ void setErrorMessage(String errorMessage, List<String> errorColumnsIds);
+}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/grid/editor/EditorServerRpc.java b/shared/src/main/java/com/vaadin/shared/ui/grid/editor/EditorServerRpc.java
new file mode 100644
index 0000000000..47f6d365cc
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/grid/editor/EditorServerRpc.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.shared.ui.grid.editor;
+
+import com.vaadin.shared.communication.ServerRpc;
+
+/**
+ * An RPC interface for the grid editor client-to-server communications.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public interface EditorServerRpc extends ServerRpc {
+
+ /**
+ * Asks the server to open the editor and bind data to it. When a bind
+ * request is sent, it must be acknowledged with a
+ * {@link EditorClientRpc#confirmBind() confirm call} before the client can
+ * open the editor.
+ *
+ * @param key
+ * the identifier key for edited item
+ */
+ void bind(String key);
+
+ /**
+ * Asks the server to save unsaved changes in the editor to the bean. When a
+ * save request is sent, it must be acknowledged with a
+ * {@link EditorClientRpc#confirmSave() confirm call}.
+ */
+ void save();
+
+ /**
+ * Tells the server to cancel editing. When sending a cancel request, the
+ * client does not need to wait for confirmation by the server before hiding
+ * the editor.
+ */
+ void cancel();
+}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/grid/editor/EditorState.java b/shared/src/main/java/com/vaadin/shared/ui/grid/editor/EditorState.java
new file mode 100644
index 0000000000..1cae17e135
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/grid/editor/EditorState.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.shared.ui.grid.editor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.vaadin.shared.communication.SharedState;
+import com.vaadin.shared.ui.grid.GridConstants;
+
+/**
+ * State object for Editor in Grid.
+ *
+ * @author Vaadin Ltd
+ * @since
+ */
+public class EditorState extends SharedState {
+
+ {
+ // Disable editor by default.
+ enabled = false;
+ }
+
+ /** Map from Column id to Component connector id. */
+ public Map<String, String> columnFields = new HashMap<>();
+
+ /** Buffer mode state. */
+ public boolean buffered = true;
+
+ /** The caption for the save button in the editor. */
+ public String saveCaption = GridConstants.DEFAULT_SAVE_CAPTION;
+
+ /** The caption for the cancel button in the editor. */
+ public String cancelCaption = GridConstants.DEFAULT_CANCEL_CAPTION;
+
+}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java b/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java
index f5f02bbf60..8917ce0241 100644
--- a/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java
+++ b/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java
@@ -1,6 +1,21 @@
package com.vaadin.tests.components.grid.basics;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import com.vaadin.annotations.Theme;
import com.vaadin.annotations.Widgetset;
+import com.vaadin.data.Binder;
+import com.vaadin.data.util.converter.StringToIntegerConverter;
import com.vaadin.server.VaadinRequest;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.grid.HeightMode;
@@ -19,6 +34,7 @@ import com.vaadin.ui.MenuBar.MenuItem;
import com.vaadin.ui.Notification;
import com.vaadin.ui.Panel;
import com.vaadin.ui.StyleGenerator;
+import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.components.grid.SingleSelectionModel;
import com.vaadin.ui.renderers.DateRenderer;
@@ -26,19 +42,8 @@ import com.vaadin.ui.renderers.HtmlRenderer;
import com.vaadin.ui.renderers.NumberRenderer;
import com.vaadin.ui.renderers.ProgressBarRenderer;
-import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-
@Widgetset("com.vaadin.DefaultWidgetSet")
+@Theme("tests-valo-disabled-animations")
public class GridBasics extends AbstractTestUIWithLog {
public static final String ROW_STYLE_GENERATOR_ROW_NUMBERS_FOR_3_OF_4 = "Row numbers for 3/4";
@@ -52,9 +57,9 @@ public class GridBasics extends AbstractTestUIWithLog {
public static final String CELL_STYLE_GENERATOR_EMPTY = "Empty string";
public static final String CELL_STYLE_GENERATOR_NULL = "Null";
- public static final String[] COLUMN_CAPTIONS = {"Column 0", "Column 1",
+ public static final String[] COLUMN_CAPTIONS = { "Column 0", "Column 1",
"Column 2", "Row Number", "Date", "HTML String", "Big Random",
- "Small Random"};
+ "Small Random" };
private final Command toggleReorderListenerCommand = new Command() {
private Registration registration = null;
@@ -144,7 +149,6 @@ public class GridBasics extends AbstractTestUIWithLog {
private List<DataObject> data;
private int watchingCount = 0;
private PersistingDetailsGenerator persistingDetails;
- private List<Column<DataObject, ?>> initialColumnOrder;
public GridBasics() {
generators.put("NULL", null);
@@ -169,24 +173,47 @@ public class GridBasics extends AbstractTestUIWithLog {
// Create grid
grid = new Grid<>();
grid.setItems(data);
-
- grid.addColumn(dataObj -> "(" + dataObj.getRowNumber() + ", 0)")
- .setCaption(COLUMN_CAPTIONS[0]);
+ grid.setSizeFull();
+
+ Binder<DataObject> binder = grid.getEditor().getBinder();
+
+ TextField html = new TextField();
+ TextField smallRandom = new TextField();
+ TextField coordinates = new TextField();
+ TextField rowNumber = new TextField();
+
+ binder.bind(html, DataObject::getHtmlString, DataObject::setHtmlString);
+ binder.forField(smallRandom)
+ .withConverter(new StringToIntegerConverter(
+ "Could not convert value to Integer"))
+ .withValidator(i -> i >= 0 && i < 5,
+ "Small random needs to be in range [0..5)")
+ .bind(DataObject::getSmallRandom, DataObject::setSmallRandom);
+ binder.bind(coordinates, DataObject::getCoordinates,
+ DataObject::setCoordinates);
+ binder.forField(rowNumber)
+ .withConverter(new StringToIntegerConverter(
+ "Could not convert value to Integer"))
+ .bind(DataObject::getRowNumber, DataObject::setRowNumber);
+
+ grid.addColumn(DataObject::getCoordinates)
+ .setCaption(COLUMN_CAPTIONS[0]).setEditorComponent(coordinates);
grid.addColumn(dataObj -> "(" + dataObj.getRowNumber() + ", 1)")
.setCaption(COLUMN_CAPTIONS[1]);
grid.addColumn(dataObj -> "(" + dataObj.getRowNumber() + ", 2)")
.setCaption(COLUMN_CAPTIONS[2]);
grid.addColumn(DataObject::getRowNumber, new NumberRenderer())
- .setCaption(COLUMN_CAPTIONS[3]);
+ .setCaption(COLUMN_CAPTIONS[3]).setEditorComponent(rowNumber);
grid.addColumn(DataObject::getDate, new DateRenderer())
.setCaption(COLUMN_CAPTIONS[4]);
grid.addColumn(DataObject::getHtmlString, new HtmlRenderer())
- .setCaption(COLUMN_CAPTIONS[5]);
+ .setCaption(COLUMN_CAPTIONS[5]).setEditorComponent(html);
grid.addColumn(DataObject::getBigRandom, new NumberRenderer())
.setCaption(COLUMN_CAPTIONS[6]);
grid.addColumn(data -> data.getSmallRandom() / 5d,
- new ProgressBarRenderer()).setCaption(COLUMN_CAPTIONS[7]);
+ new ProgressBarRenderer()).setCaption(COLUMN_CAPTIONS[7])
+ .setEditorComponent(smallRandom);
((SingleSelectionModel<DataObject>) grid.getSelectionModel())
.addSelectionChangeListener(
@@ -199,6 +226,9 @@ public class GridBasics extends AbstractTestUIWithLog {
private Component createMenu() {
MenuBar menu = new MenuBar();
+ menu.setErrorHandler(error -> log("Exception occured, "
+ + error.getThrowable().getClass().getName() + ": "
+ + error.getThrowable().getMessage()));
MenuItem componentMenu = menu.addItem("Component", null);
createStateMenu(componentMenu.addItem("State", null));
createSizeMenu(componentMenu.addItem("Size", null));
@@ -207,6 +237,7 @@ public class GridBasics extends AbstractTestUIWithLog {
createHeaderMenu(componentMenu.addItem("Header", null));
createFooterMenu(componentMenu.addItem("Footer", null));
createColumnsMenu(componentMenu.addItem("Columns", null));
+ createEditorMenu(componentMenu.addItem("Editor", null));
return menu;
}
@@ -274,9 +305,8 @@ public class GridBasics extends AbstractTestUIWithLog {
selectedItem -> col
.setHidden(selectedItem.isChecked()))
.setCheckable(true);
- columnMenu
- .addItem("Remove",
- selectedItem -> grid.removeColumn(col));
+ columnMenu.addItem("Remove",
+ selectedItem -> grid.removeColumn(col));
}
}
@@ -353,6 +383,11 @@ public class GridBasics extends AbstractTestUIWithLog {
.addItem("Column Reordering", selectedItem -> grid
.setColumnReorderingAllowed(selectedItem.isChecked()))
.setCheckable(true);
+
+ MenuItem enableItem = stateMenu.addItem("Enabled",
+ e -> grid.setEnabled(e.isChecked()));
+ enableItem.setCheckable(true);
+ enableItem.setChecked(true);
}
private void createRowStyleMenu(MenuItem rowStyleMenu) {
@@ -401,7 +436,7 @@ public class GridBasics extends AbstractTestUIWithLog {
}
private <T> void addGridMethodMenu(MenuItem parent, String name, T value,
- Consumer<T> method) {
+ Consumer<T> method) {
parent.addItem(name, menuItem -> method.accept(value));
}
@@ -453,7 +488,8 @@ public class GridBasics extends AbstractTestUIWithLog {
});
}
- private void mergeHeaderСells(int rowIndex, String jointCellText, int... columnIndexes) {
+ private void mergeHeaderСells(int rowIndex, String jointCellText,
+ int... columnIndexes) {
HeaderRow headerRow = grid.getHeaderRow(rowIndex);
List<Column<DataObject, ?>> columns = grid.getColumns();
Set<Grid.HeaderCell> toMerge = new HashSet<>();
@@ -522,8 +558,27 @@ public class GridBasics extends AbstractTestUIWithLog {
});
}
+ private void createEditorMenu(MenuItem editorMenu) {
+ editorMenu
+ .addItem("Enabled",
+ i -> grid.getEditor().setEnabled(i.isChecked()))
+ .setCheckable(true);
+ MenuItem bufferedMode = editorMenu.addItem("Buffered mode",
+ i -> grid.getEditor().setBuffered(i.isChecked()));
+ bufferedMode.setCheckable(true);
+ bufferedMode.setChecked(true);
+
+ editorMenu.addItem("Save", i -> grid.getEditor().save());
+ editorMenu.addItem("Cancel edit", i -> grid.getEditor().cancel());
+
+ editorMenu.addItem("Change save caption",
+ e -> grid.getEditor().setSaveCaption("ǝʌɐS"));
+ editorMenu.addItem("Change cancel caption",
+ e -> grid.getEditor().setCancelCaption("ʃǝɔuɐↃ"));
+
+ }
+
private void openOrCloseDetails(DataObject dataObj) {
grid.setDetailsVisible(dataObj, !grid.isDetailsVisible(dataObj));
}
-
}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicsTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicsTest.java
index c128ba2634..55098eb768 100644
--- a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicsTest.java
+++ b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicsTest.java
@@ -16,6 +16,7 @@ import org.openqa.selenium.remote.DesiredCapabilities;
import com.vaadin.testbench.TestBenchElement;
import com.vaadin.testbench.customelements.GridElement;
import com.vaadin.testbench.elements.GridElement.GridCellElement;
+import com.vaadin.testbench.elements.GridElement.GridEditorElement;
import com.vaadin.testbench.parallel.Browser;
import com.vaadin.tests.tb3.MultiBrowserTest;
import com.vaadin.v7.tests.components.grid.basicfeatures.GridBasicFeaturesTest.CellSide;
@@ -204,4 +205,13 @@ public abstract class GridBasicsTest extends MultiBrowserTest {
return null;
}
+ protected GridEditorElement getEditor() {
+ return getGridElement().getEditor();
+ }
+
+ protected int getGridVerticalScrollPos() {
+ return ((Number) executeScript("return arguments[0].scrollTop",
+ getGridVerticalScrollbar())).intValue();
+ }
+
}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridEditorBufferedTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridEditorBufferedTest.java
new file mode 100644
index 0000000000..17411c5a77
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridEditorBufferedTest.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.components.grid.basics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.NoSuchElementException;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+
+import com.vaadin.shared.ui.grid.GridConstants;
+import com.vaadin.testbench.TestBenchElement;
+import com.vaadin.testbench.elements.GridElement.GridCellElement;
+import com.vaadin.testbench.elements.GridElement.GridEditorElement;
+import com.vaadin.testbench.elements.NotificationElement;
+
+public class GridEditorBufferedTest extends GridEditorTest {
+
+ @Override
+ @Before
+ public void setUp() {
+ super.setUp();
+ }
+
+ @Test
+ public void testKeyboardSave() {
+ editRow(100);
+
+ WebElement textField = getEditor().getField(0);
+
+ textField.click();
+ // without this, the click in the middle of the field might not be after
+ // the old text on some browsers
+ new Actions(getDriver()).sendKeys(Keys.END).perform();
+
+ textField.sendKeys(" changed");
+
+ // Save from keyboard
+ new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
+
+ assertEditorClosed();
+ assertEquals("(100, 0) changed",
+ getGridElement().getCell(100, 0).getText());
+ }
+
+ @Test
+ public void testKeyboardSaveWithInvalidEdition() {
+ makeInvalidEdition();
+
+ GridEditorElement editor = getGridElement().getEditor();
+ TestBenchElement field = editor.getField(7);
+
+ field.click();
+ new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
+
+ assertEditorOpen();
+ assertEquals(
+ GridBasics.COLUMN_CAPTIONS[7]
+ + ": Could not convert value to Integer",
+ editor.getErrorMessage());
+ assertTrue("Field 7 should have been marked with an error after error",
+ isEditorCellErrorMarked(7));
+
+ editor.cancel();
+
+ editRow(100);
+ assertFalse("Exception should not exist",
+ isElementPresent(NotificationElement.class));
+ assertEquals("There should be no editor error message", null,
+ getGridElement().getEditor().getErrorMessage());
+ }
+
+ @Test
+ public void testSave() {
+ editRow(100);
+
+ WebElement textField = getEditor().getField(0);
+
+ textField.click();
+ // without this, the click in the middle of the field might not be after
+ // the old text on some browsers
+ new Actions(getDriver()).sendKeys(Keys.END).perform();
+
+ textField.sendKeys(" changed");
+
+ WebElement saveButton = getEditor()
+ .findElement(By.className("v-grid-editor-save"));
+
+ saveButton.click();
+
+ assertEquals("(100, 0) changed",
+ getGridElement().getCell(100, 0).getText());
+ }
+
+ @Test
+ public void testProgrammaticSave() {
+ editRow(100);
+
+ WebElement textField = getEditor().getField(0);
+
+ textField.click();
+ // without this, the click in the middle of the field might not be after
+ // the old text on some browsers
+ new Actions(getDriver()).sendKeys(Keys.END).perform();
+
+ textField.sendKeys(" changed");
+
+ selectMenuPath("Component", "Editor", "Save");
+
+ assertEquals("(100, 0) changed",
+ getGridElement().getCell(100, 0).getText());
+ }
+
+ @Test
+ public void testInvalidEdition() {
+ makeInvalidEdition();
+
+ GridEditorElement editor = getGridElement().getEditor();
+ editor.save();
+
+ assertEquals(
+ GridBasics.COLUMN_CAPTIONS[7]
+ + ": Could not convert value to Integer",
+ editor.getErrorMessage());
+ assertTrue("Field 7 should have been marked with an error after error",
+ isEditorCellErrorMarked(7));
+ editor.cancel();
+
+ editRow(100);
+ assertFalse("Exception should not exist",
+ isElementPresent(NotificationElement.class));
+ assertEquals("There should be no editor error message", null,
+ getGridElement().getEditor().getErrorMessage());
+ }
+
+ private void makeInvalidEdition() {
+ editRow(5);
+ assertFalse(logContainsText(
+ "Exception occured, java.lang.IllegalStateException"));
+
+ GridEditorElement editor = getGridElement().getEditor();
+
+ assertFalse(
+ "Field 7 should not have been marked with an error before error",
+ editor.isFieldErrorMarked(7));
+
+ WebElement intField = editor.getField(7);
+ intField.clear();
+ intField.sendKeys("banana phone");
+ editor.getField(5).click();
+ }
+
+ @Test
+ public void testEditorInDisabledGrid() {
+ int originalScrollPos = getGridVerticalScrollPos();
+
+ editRow(5);
+ assertEditorOpen();
+
+ selectMenuPath("Component", "State", "Enabled");
+ assertEditorOpen();
+
+ GridEditorElement editor = getGridElement().getEditor();
+ editor.save();
+ assertEditorOpen();
+
+ editor.cancel();
+ assertEditorOpen();
+
+ selectMenuPath("Component", "State", "Enabled");
+
+ scrollGridVerticallyTo(100);
+ assertEquals(
+ "Grid shouldn't scroll vertically while editing in buffered mode",
+ originalScrollPos, getGridVerticalScrollPos());
+ }
+
+ @Test
+ public void testCaptionChange() {
+ editRow(5);
+ assertEquals("Save button caption should've been \""
+ + GridConstants.DEFAULT_SAVE_CAPTION + "\" to begin with",
+ GridConstants.DEFAULT_SAVE_CAPTION, getSaveButton().getText());
+ assertEquals("Cancel button caption should've been \""
+ + GridConstants.DEFAULT_CANCEL_CAPTION + "\" to begin with",
+ GridConstants.DEFAULT_CANCEL_CAPTION,
+ getCancelButton().getText());
+
+ selectMenuPath("Component", "Editor", "Change save caption");
+ assertNotEquals(
+ "Save button caption should've changed while editor is open",
+ GridConstants.DEFAULT_SAVE_CAPTION, getSaveButton().getText());
+
+ getCancelButton().click();
+
+ selectMenuPath("Component", "Editor", "Change cancel caption");
+ editRow(5);
+ assertNotEquals(
+ "Cancel button caption should've changed while editor is closed",
+ GridConstants.DEFAULT_CANCEL_CAPTION,
+ getCancelButton().getText());
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testVerticalScrollLocking() {
+ editRow(5);
+ getGridElement().getCell(200, 0);
+ }
+
+ @Test
+ public void testScrollDisabledOnMouseOpen() {
+ int originalScrollPos = getGridVerticalScrollPos();
+
+ GridCellElement cell_5_0 = getGridElement().getCell(5, 0);
+ new Actions(getDriver()).doubleClick(cell_5_0).perform();
+
+ scrollGridVerticallyTo(100);
+ assertEquals(
+ "Grid shouldn't scroll vertically while editing in buffered mode",
+ originalScrollPos, getGridVerticalScrollPos());
+ }
+
+ @Test
+ public void testScrollDisabledOnKeyboardOpen() {
+ int originalScrollPos = getGridVerticalScrollPos();
+
+ GridCellElement cell_5_0 = getGridElement().getCell(5, 0);
+ cell_5_0.click();
+ new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
+
+ scrollGridVerticallyTo(100);
+ assertEquals(
+ "Grid shouldn't scroll vertically while editing in buffered mode",
+ originalScrollPos, getGridVerticalScrollPos());
+ }
+
+ @Test
+ public void testMouseOpeningClosing() {
+
+ getGridElement().getCell(4, 0).doubleClick();
+ assertEditorOpen();
+
+ getCancelButton().click();
+ assertEditorClosed();
+
+ selectMenuPath(TOGGLE_EDIT_ENABLED);
+ getGridElement().getCell(4, 0).doubleClick();
+ assertEditorClosed();
+ }
+
+ @Test
+ public void testMouseOpeningDisabledWhenOpen() {
+ editRow(5);
+
+ getGridElement().getCell(2, 0).doubleClick();
+
+ assertEquals("Editor should still edit row 5", "(5, 0)",
+ getEditor().getField(0).getAttribute("value"));
+ }
+
+ @Test
+ public void testUserSortDisabledWhenOpen() {
+ editRow(5);
+
+ getGridElement().getHeaderCell(0, 0).click();
+
+ assertEditorOpen();
+ assertEquals("(2, 0)", getGridElement().getCell(2, 0).getText());
+ }
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridEditorTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridEditorTest.java
new file mode 100644
index 0000000000..fdf1b9bba9
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridEditorTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.components.grid.basics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+
+import com.vaadin.testbench.By;
+import com.vaadin.testbench.TestBenchElement;
+import com.vaadin.testbench.elements.GridElement.GridCellElement;
+import com.vaadin.testbench.elements.GridElement.GridEditorElement;
+
+public abstract class GridEditorTest extends GridBasicsTest {
+
+ protected static final org.openqa.selenium.By BY_EDITOR_CANCEL = By
+ .className("v-grid-editor-cancel");
+ protected static final org.openqa.selenium.By BY_EDITOR_SAVE = By
+ .className("v-grid-editor-save");
+ protected static final String[] TOGGLE_EDIT_ENABLED = new String[] {
+ "Component", "Editor", "Enabled" };
+
+ @Override
+ @Before
+ public void setUp() {
+ setDebug(true);
+ openTestURL();
+ selectMenuPath(TOGGLE_EDIT_ENABLED);
+ }
+
+ @Test
+ public void testProgrammaticClosing() {
+ editRow(5);
+ assertEditorOpen();
+
+ selectMenuPath("Component", "Editor", "Cancel edit");
+ assertEditorClosed();
+ }
+
+ @Test
+ public void testKeyboardOpeningClosing() {
+
+ getGridElement().getCell(4, 0).click();
+ assertEditorClosed();
+
+ new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
+ assertEditorOpen();
+
+ new Actions(getDriver()).sendKeys(Keys.ESCAPE).perform();
+ assertEditorClosed();
+
+ // Disable Editor
+ selectMenuPath(TOGGLE_EDIT_ENABLED);
+ getGridElement().getCell(5, 0).click();
+ new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
+ assertEditorClosed();
+ }
+
+ protected void assertEditorOpen() {
+ assertTrue("Editor is supposed to be open",
+ getGridElement().isElementPresent(By.vaadin("#editor")));
+ }
+
+ protected void assertEditorClosed() {
+ assertFalse("Editor is supposed to be closed",
+ getGridElement().isElementPresent(By.vaadin("#editor")));
+ }
+
+ @Test
+ public void testFocusOnMouseOpen() {
+
+ GridCellElement cell = getGridElement().getCell(4, 0);
+
+ cell.doubleClick();
+
+ WebElement focused = getFocusedElement();
+
+ assertEquals("", "input", focused.getTagName());
+ assertEquals("", cell.getText(), focused.getAttribute("value"));
+ }
+
+ @Test
+ public void testFocusOnKeyboardOpen() {
+
+ GridCellElement cell = getGridElement().getCell(4, 0);
+
+ cell.click();
+ new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
+
+ WebElement focused = getFocusedElement();
+
+ assertEquals("", "input", focused.getTagName());
+ assertEquals("", cell.getText(), focused.getAttribute("value"));
+ }
+
+ @Test
+ public void testUneditableColumn() {
+ editRow(5);
+ assertEditorOpen();
+
+ GridEditorElement editor = getGridElement().getEditor();
+ assertFalse("Uneditable column should not have an editor widget",
+ editor.isEditable(2));
+
+ String classNames = editor
+ .findElements(By.className("v-grid-editor-cells")).get(1)
+ .findElements(By.xpath("./div")).get(2).getAttribute("class");
+
+ assertTrue("Noneditable cell should contain not-editable classname",
+ classNames.contains("not-editable"));
+
+ assertTrue("Noneditable cell should contain v-grid-cell classname",
+ classNames.contains("v-grid-cell"));
+
+ assertNoErrorNotifications();
+ }
+
+ @Test
+ public void testNoOpenFromHeaderOrFooter() {
+ selectMenuPath("Component", "Footer", "Append footer row");
+
+ getGridElement().getHeaderCell(0, 0).doubleClick();
+ assertEditorClosed();
+
+ new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
+ assertEditorClosed();
+
+ getGridElement().getFooterCell(0, 0).doubleClick();
+ assertEditorClosed();
+
+ new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
+ assertEditorClosed();
+ }
+
+ public void testEditorMoveOnResize() {
+ selectMenuPath("Component", "Size", "Height", "500px");
+ getGridElement().getCell(22, 0).doubleClick();
+ assertEditorOpen();
+
+ GridEditorElement editor = getGridElement().getEditor();
+ TestBenchElement tableWrapper = getGridElement().getTableWrapper();
+
+ int tableWrapperBottom = tableWrapper.getLocation().getY()
+ + tableWrapper.getSize().getHeight();
+ int editorBottom = editor.getLocation().getY()
+ + editor.getSize().getHeight();
+
+ assertTrue("Editor should not be initially outside grid",
+ tableWrapperBottom - editorBottom <= 2);
+
+ selectMenuPath("Component", "Size", "Height", "300px");
+ assertEditorOpen();
+
+ tableWrapperBottom = tableWrapper.getLocation().getY()
+ + tableWrapper.getSize().getHeight();
+ editorBottom = editor.getLocation().getY()
+ + editor.getSize().getHeight();
+
+ assertTrue("Editor should not be outside grid after resize",
+ tableWrapperBottom - editorBottom <= 2);
+ }
+
+ public void testEditorDoesNotMoveOnResizeIfNotNeeded() {
+ selectMenuPath("Component", "Size", "Height", "500px");
+
+ editRow(5);
+ assertEditorOpen();
+
+ GridEditorElement editor = getGridElement().getEditor();
+
+ int editorPos = editor.getLocation().getY();
+
+ selectMenuPath("Component", "Size", "Height", "300px");
+ assertEditorOpen();
+
+ assertTrue("Editor should not have moved due to resize",
+ editorPos == editor.getLocation().getY());
+ }
+
+ @Ignore("Needs programmatic sorting")
+ @Test
+ public void testEditorClosedOnSort() {
+ editRow(5);
+
+ selectMenuPath("Component", "State", "Sort by column", "Column 0, ASC");
+
+ assertEditorClosed();
+ }
+
+ @Ignore("Needs programmatic filtering")
+ @Test
+ public void testEditorClosedOnFilter() {
+ editRow(5);
+
+ selectMenuPath("Component", "Filter", "Column 1 starts with \"(23\"");
+
+ assertEditorClosed();
+ }
+
+ protected WebElement getSaveButton() {
+ return getDriver().findElement(BY_EDITOR_SAVE);
+ }
+
+ protected WebElement getCancelButton() {
+ return getDriver().findElement(BY_EDITOR_CANCEL);
+ }
+
+ protected void editRow(int rowIndex) {
+ getGridElement().getCell(rowIndex, 0).doubleClick();
+ assertEditorOpen();
+ }
+
+ protected boolean isEditorCellErrorMarked(int colIndex) {
+ WebElement editorCell = getGridElement().getEditor()
+ .findElement(By.xpath("./div/div[" + (colIndex + 1) + "]"));
+ return editorCell.getAttribute("class").contains("error");
+ }
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridEditorUnbufferedTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridEditorUnbufferedTest.java
new file mode 100644
index 0000000000..aac69d90f5
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridEditorUnbufferedTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.components.grid.basics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+
+import com.vaadin.testbench.TestBenchElement;
+import com.vaadin.testbench.elements.GridElement.GridCellElement;
+
+public class GridEditorUnbufferedTest extends GridEditorTest {
+
+ private static final String[] TOGGLE_EDITOR_BUFFERED = new String[] {
+ "Component", "Editor", "Buffered mode" };
+ private static final String[] CANCEL_EDIT = new String[] { "Component",
+ "Editor", "Cancel edit" };
+
+ @Override
+ @Before
+ public void setUp() {
+ super.setUp();
+ selectMenuPath(TOGGLE_EDITOR_BUFFERED);
+ }
+
+ @Test
+ public void testEditorShowsNoButtons() {
+ editRow(5);
+
+ assertEditorOpen();
+
+ assertFalse("Save button should not be visible in unbuffered mode.",
+ isElementPresent(BY_EDITOR_SAVE));
+
+ assertFalse("Cancel button should not be visible in unbuffered mode.",
+ isElementPresent(BY_EDITOR_CANCEL));
+ }
+
+ @Test
+ public void testToggleEditorUnbufferedWhileOpen() {
+ editRow(5);
+ assertEditorOpen();
+ selectMenuPath(TOGGLE_EDITOR_BUFFERED);
+ boolean thrown = logContainsText(
+ "Exception occured, java.lang.IllegalStateException");
+ assertTrue("IllegalStateException was not thrown", thrown);
+ }
+
+ @Test
+ public void testEditorMoveWithMouse() {
+ editRow(5);
+
+ assertEditorOpen();
+
+ String firstFieldValue = getEditor().getField(0).getAttribute("value");
+ assertEquals("Editor should be at row 5", "(5, 0)", firstFieldValue);
+
+ getGridElement().getCell(6, 0).click();
+ firstFieldValue = getEditor().getField(0).getAttribute("value");
+
+ assertEquals("Editor should be at row 6", "(6, 0)", firstFieldValue);
+ }
+
+ @Test
+ public void testEditorMoveWithKeyboard() throws InterruptedException {
+ editRow(100);
+
+ getEditor().getField(0).click();
+ new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
+
+ String firstFieldValue = getEditor().getField(0).getAttribute("value");
+ assertEquals("Editor should move to row 101", "(101, 0)",
+ firstFieldValue);
+
+ for (int i = 0; i < 10; i++) {
+ new Actions(getDriver()).keyDown(Keys.SHIFT).sendKeys(Keys.ENTER)
+ .keyUp(Keys.SHIFT).perform();
+
+ firstFieldValue = getEditor().getField(0).getAttribute("value");
+ int row = 100 - i;
+ assertEquals("Editor should move to row " + row, "(" + row + ", 0)",
+ firstFieldValue);
+ }
+ }
+
+ @Test
+ public void testValidationErrorPreventsMove() throws InterruptedException {
+ editRow(5);
+
+ getEditor().getField(7).click();
+ String faultyInt = "not a number";
+ getEditor().getField(7).sendKeys(faultyInt);
+
+ getGridElement().getCell(6, 7).click();
+
+ assertEquals("Editor should not move from row 5", "(5, 0)",
+ getEditor().getField(0).getAttribute("value"));
+
+ getEditor().getField(7).sendKeys(Keys.chord(Keys.CONTROL, "a"));
+ getEditor().getField(7).sendKeys("4");
+
+ getGridElement().getCell(7, 0).click();
+
+ assertEquals("Editor should move to row 7", "(7, 0)",
+ getEditor().getField(0).getAttribute("value"));
+
+ }
+
+ @Test
+ public void testErrorMessageWrapperHidden() {
+ editRow(5);
+
+ assertEditorOpen();
+
+ WebElement editorFooter = getEditor()
+ .findElement(By.className("v-grid-editor-footer"));
+
+ assertTrue("Editor footer should not be visible when there's no error",
+ editorFooter.getCssValue("display").equalsIgnoreCase("none"));
+ }
+
+ @Test
+ public void testScrollEnabledOnMouseOpen() {
+ int originalScrollPos = getGridVerticalScrollPos();
+
+ GridCellElement cell_5_0 = getGridElement().getCell(5, 0);
+ new Actions(getDriver()).doubleClick(cell_5_0).perform();
+
+ scrollGridVerticallyTo(100);
+ assertGreater(
+ "Grid should scroll vertically while editing in unbuffered mode",
+ getGridVerticalScrollPos(), originalScrollPos);
+ }
+
+ @Test
+ public void testScrollEnabledOnKeyboardOpen() {
+ int originalScrollPos = getGridVerticalScrollPos();
+
+ GridCellElement cell_5_0 = getGridElement().getCell(5, 0);
+ cell_5_0.click();
+ new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
+
+ scrollGridVerticallyTo(100);
+ assertGreater(
+ "Grid should scroll vertically while editing in unbuffered mode",
+ getGridVerticalScrollPos(), originalScrollPos);
+ }
+
+ @Test
+ public void testEditorInDisabledGrid() {
+ editRow(5);
+
+ selectMenuPath("Component", "State", "Enabled");
+ assertEditorOpen();
+
+ assertTrue("Editor text field should be disabled",
+ null != getEditor().getField(0).getAttribute("disabled"));
+
+ selectMenuPath("Component", "State", "Enabled");
+ assertEditorOpen();
+
+ assertFalse("Editor text field should not be disabled",
+ null != getEditor().getField(0).getAttribute("disabled"));
+ }
+
+ @Test
+ public void testMouseOpeningClosing() {
+
+ getGridElement().getCell(4, 0).doubleClick();
+ assertEditorOpen();
+
+ selectMenuPath(CANCEL_EDIT);
+ selectMenuPath(TOGGLE_EDIT_ENABLED);
+
+ getGridElement().getCell(4, 0).doubleClick();
+ assertEditorClosed();
+ }
+
+ @Ignore("Needs refresh item functionality")
+ @Test
+ public void testExternalValueChangePassesToEditor() {
+ editRow(5);
+ assertEditorOpen();
+
+ selectMenuPath("Component", "State", "ReactiveValueChanger");
+
+ getEditor().getField(0).click();
+ getEditor().getField(0).sendKeys("changing value");
+
+ // Focus another field to cause the value to be sent to the server
+ getEditor().getField(3).click();
+
+ assertEquals("Value of Column 2 in the editor was not changed",
+ "Modified", getEditor().getField(5).getAttribute("value"));
+ }
+
+ @Test
+ public void testEditorClosedOnUserSort() {
+ editRow(5);
+
+ getGridElement().getHeaderCell(0, 0).click();
+
+ assertEditorClosed();
+ }
+
+ @Test
+ public void testEditorSaveOnRowChange() {
+ // Double click sets the focus programmatically
+ getGridElement().getCell(5, 0).doubleClick();
+
+ TestBenchElement editor = getGridElement().getEditor().getField(0);
+ editor.clear();
+ // Click to ensure IE focus...
+ editor.click(5, 5);
+ editor.sendKeys("Foo", Keys.ENTER);
+
+ assertEquals("Editor did not move.", "(6, 0)",
+ getGridElement().getEditor().getField(0).getAttribute("value"));
+ assertEquals("Editor field value did not update from server.", "6",
+ getGridElement().getEditor().getField(3).getAttribute("value"));
+
+ assertEquals("Edited value was not saved.", "Foo",
+ getGridElement().getCell(5, 0).getText());
+ }
+} \ No newline at end of file