]> source.dussan.org Git - vaadin-framework.git/commitdiff
Highlights erroneous cells in Grid editor (#16575)
authorHenrik Paul <henrik@vaadin.com>
Thu, 5 Feb 2015 21:50:31 +0000 (23:50 +0200)
committerLeif Åstrand <leif@vaadin.com>
Fri, 6 Feb 2015 09:19:17 +0000 (11:19 +0200)
Change-Id: Ie1f9d738db7a03ddb01b968782ad5e4877af1d7e

WebContent/VAADIN/themes/base/grid/grid.scss
WebContent/VAADIN/themes/valo/components/_grid.scss
client/src/com/vaadin/client/connectors/GridConnector.java
client/src/com/vaadin/client/widget/grid/EditorHandler.java
client/src/com/vaadin/client/widgets/Grid.java
server/src/com/vaadin/ui/Grid.java
shared/src/com/vaadin/shared/ui/grid/EditorClientRpc.java
uitest/src/com/vaadin/testbench/elements/GridElement.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java
uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java
uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java

index a79420f7a91ca62ab71b03e4607fb3b80c1ea3af..e4a4a1d920b7d6d9489fd22a9b0bb6396179c54e 100644 (file)
@@ -266,6 +266,21 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default;
         max-width: 100%;
       }
     }
+
+    .error::before {
+      position: absolute;
+      display: block;
+      height: 0;
+      width: 0;
+      content: "";
+      border-top: 5px solid red;
+      border-right: 5px solid transparent;
+    }
+
+    .error,
+    .error > input {
+      background-color: #fee;
+    }
   }
 
   .#{$primaryStyleName}-editor-footer {
index c481f127a85533f3b64cb861825e7be55b0e9850..4cac9c5e43bb1162f46a266af4fbff2ede8cbb90 100644 (file)
@@ -102,6 +102,17 @@ $v-grid-animations-enabled: $v-animations-enabled !default;
       height: 100%;
       vertical-align: middle;
     }
+    
+    .error::before {
+      border-top: round($v-unit-size / 4) solid $v-error-indicator-color;
+      border-right: round($v-unit-size / 4) solid transparent;
+    }
+    
+    .error,
+    .error > input {
+      // taken from @mixin valo-textfield-error-style()
+      background-color: scale-color($v-error-indicator-color, $lightness: 98%);
+    }
 
     .v-textfield,
     .v-textfield-focus,
index 8d383ab0aedfc1a4fc0d1ae71bae49ebdf7f0a2f..60a730c80e1d64f42b7283c6c8193c2592701473 100644 (file)
@@ -18,6 +18,7 @@ package com.vaadin.client.connectors;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -202,7 +203,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements
 
         private EditorServerRpc rpc = getRpcProxy(EditorServerRpc.class);
 
-        private EditorRequest<?> currentRequest = null;
+        private EditorRequest<JsonObject> currentRequest = null;
         private boolean serverInitiated = false;
 
         public CustomEditorHandler() {
@@ -227,12 +228,13 @@ public class GridConnector extends AbstractHasComponentsConnector implements
 
                 @Override
                 public void confirmBind(final boolean bindSucceeded) {
-                    endRequest(bindSucceeded);
+                    endRequest(bindSucceeded, null);
                 }
 
                 @Override
-                public void confirmSave(boolean saveSucceeded) {
-                    endRequest(saveSucceeded);
+                public void confirmSave(boolean saveSucceeded,
+                        List<String> errorColumnsIds) {
+                    endRequest(saveSucceeded, errorColumnsIds);
                 }
             });
         }
@@ -295,24 +297,34 @@ public class GridConnector extends AbstractHasComponentsConnector implements
             }
         }
 
-        private void startRequest(EditorRequest<?> request) {
+        private void startRequest(EditorRequest<JsonObject> request) {
             assert currentRequest == null : "Earlier request not yet finished";
 
             currentRequest = request;
         }
 
-        private void endRequest(boolean succeeded) {
+        private void endRequest(boolean succeeded, List<String> errorColumnsIds) {
             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<?> request = currentRequest;
+            EditorRequest<JsonObject> request = currentRequest;
             currentRequest = null;
             if (succeeded) {
                 request.success();
             } else {
-                request.fail();
+                Collection<Column<?, JsonObject>> errorColumns;
+                if (errorColumnsIds != null) {
+                    errorColumns = new ArrayList<Grid.Column<?, JsonObject>>();
+                    for (String colId : errorColumnsIds) {
+                        errorColumns.add(columnIdToColumn.get(colId));
+                    }
+                } else {
+                    errorColumns = null;
+                }
+
+                request.failure(errorColumns);
             }
         }
     }
index 07ec1b231c104c28bd81776bd041bb011823d3bf..1d152c708c5e3069fb1e74b56e720019a0ea6683 100644 (file)
@@ -15,6 +15,8 @@
  */
 package com.vaadin.client.widget.grid;
 
+import java.util.Collection;
+
 import com.google.gwt.user.client.ui.Widget;
 import com.vaadin.client.widgets.Grid;
 
@@ -43,82 +45,27 @@ public interface EditorHandler<T> {
      * @param <T>
      *            the row data type
      */
-    public static class EditorRequest<T> {
-
-        /**
-         * A callback interface used to notify the invoker of the editor handler
-         * of completed editor requests.
-         * 
-         * @param <T>
-         *            the row data type
-         */
-        public interface RequestCallback<T> {
-            /**
-             * The method that must be called when the request has been
-             * processed correctly.
-             * 
-             * @param request
-             *            the original request object
-             */
-            public void onSuccess(EditorRequest<T> request);
-
-            /**
-             * The method that must be called when processing the request has
-             * produced an aborting error.
-             * 
-             * @param request
-             *            the original request object
-             */
-            public void onError(EditorRequest<T> request);
-        }
-
-        private Grid<T> grid;
-        private int rowIndex;
-        private RequestCallback<T> callback;
-        private boolean completed = false;
-
-        /**
-         * Creates a new editor request.
-         * 
-         * @param rowIndex
-         *            the index of the edited row
-         * @param callback
-         *            the callback invoked when the request is ready, or null if
-         *            no need to call back
-         */
-        public EditorRequest(Grid<T> grid, int rowIndex,
-                RequestCallback<T> callback) {
-            this.grid = grid;
-            this.rowIndex = rowIndex;
-            this.callback = callback;
-        }
-
+    public interface EditorRequest<T> {
         /**
          * Returns the index of the row being requested.
          * 
          * @return the row index
          */
-        public int getRowIndex() {
-            return rowIndex;
-        }
+        public int getRowIndex();
 
         /**
          * Returns the row data related to the row being requested.
          * 
          * @return the row data
          */
-        public T getRow() {
-            return grid.getDataSource().getRow(rowIndex);
-        }
+        public T getRow();
 
         /**
          * Returns the grid instance related to this editor request.
          * 
          * @return the grid instance
          */
-        public Grid<T> getGrid() {
-            return grid;
-        }
+        public Grid<T> getGrid();
 
         /**
          * Returns the editor widget used to edit the values of the given
@@ -128,59 +75,30 @@ public interface EditorHandler<T> {
          *            the column whose widget to get
          * @return the widget related to the column
          */
-        public Widget getWidget(Grid.Column<?, T> column) {
-            Widget w = grid.getEditorWidget(column);
-            assert w != null;
-            return w;
-        }
-
-        /**
-         * Completes this request. The request can only be completed once. This
-         * method should only be called by an EditorHandler implementer if the
-         * request handling is asynchronous in nature and {@link #startAsync()}
-         * is previously invoked for this request. Synchronous requests are
-         * completed automatically by the editor.
-         * 
-         * @throws IllegalStateException
-         *             if the request is already completed
-         */
-        private void complete() {
-            if (completed) {
-                throw new IllegalStateException(
-                        "An EditorRequest must be completed exactly once");
-            }
-            completed = true;
-        }
+        public Widget getWidget(Grid.Column<?, T> column);
 
         /**
          * Informs Grid that the editor request was a success.
          */
-        public void success() {
-            complete();
-            if (callback != null) {
-                callback.onSuccess(this);
-            }
-        }
+        public void success();
 
         /**
          * Informs Grid that an error occurred while trying to process the
          * request.
+         * 
+         * @param errorColumns
+         *            a collection of columns for which an error indicator
+         *            should be shown, or <code>null</code> if no columns should
+         *            be marked as erroneous.
          */
-        public void fail() {
-            complete();
-            if (callback != null) {
-                callback.onError(this);
-            }
-        }
+        public void failure(Collection<Grid.Column<?, T>> errorColumns);
 
         /**
          * Checks whether the request is completed or not.
          * 
          * @return <code>true</code> iff the request is completed
          */
-        public boolean isCompleted() {
-            return completed;
-        }
+        public boolean isCompleted();
     }
 
     /**
@@ -188,8 +106,9 @@ public interface EditorHandler<T> {
      * opened for editing.
      * <p>
      * The implementation <em>must</em> call either
-     * {@link EditorRequest#success()} or {@link EditorRequest#fail()} to signal
-     * a successful or a failed (respectively) bind action.
+     * {@link EditorRequest#success()} or
+     * {@link EditorRequest#failure(Collection)} to signal a successful or a
+     * failed (respectively) bind action.
      * 
      * @param request
      *            the data binding request
index fb7ec3abb5a6db79f9096241b952c04166372317..0f3ffc696ace6b5fc92ddeb06183dacab9fc604c 100644 (file)
@@ -83,7 +83,6 @@ import com.vaadin.client.widget.grid.DataAvailableEvent;
 import com.vaadin.client.widget.grid.DataAvailableHandler;
 import com.vaadin.client.widget.grid.EditorHandler;
 import com.vaadin.client.widget.grid.EditorHandler.EditorRequest;
-import com.vaadin.client.widget.grid.EditorHandler.EditorRequest.RequestCallback;
 import com.vaadin.client.widget.grid.EventCellReference;
 import com.vaadin.client.widget.grid.RendererCellReference;
 import com.vaadin.client.widget.grid.RowReference;
@@ -936,6 +935,106 @@ public class Grid<T> extends ResizeComposite implements
         }
     }
 
+    private static class EditorRequestImpl<T> implements EditorRequest<T> {
+
+        /**
+         * A callback interface used to notify the invoker of the editor handler
+         * of completed editor requests.
+         * 
+         * @param <T>
+         *            the row data type
+         */
+        public static interface RequestCallback<T> {
+            /**
+             * The method that must be called when the request has been
+             * processed correctly.
+             * 
+             * @param request
+             *            the original request object
+             */
+            public void onSuccess(EditorRequest<T> request);
+
+            /**
+             * The method that must be called when processing the request has
+             * produced an aborting error.
+             * 
+             * @param request
+             *            the original request object
+             */
+            public void onError(EditorRequest<T> request);
+        }
+
+        private Grid<T> grid;
+        private int rowIndex;
+        private RequestCallback<T> callback;
+        private boolean completed = false;
+
+        public EditorRequestImpl(Grid<T> grid, int rowIndex,
+                RequestCallback<T> callback) {
+            this.grid = grid;
+            this.rowIndex = rowIndex;
+            this.callback = callback;
+        }
+
+        @Override
+        public int getRowIndex() {
+            return rowIndex;
+        }
+
+        @Override
+        public T getRow() {
+            return grid.getDataSource().getRow(rowIndex);
+        }
+
+        @Override
+        public Grid<T> getGrid() {
+            return grid;
+        }
+
+        @Override
+        public Widget getWidget(Grid.Column<?, T> column) {
+            Widget w = grid.getEditorWidget(column);
+            assert w != null;
+            return w;
+        }
+
+        private void complete(Collection<Column<?, T>> errorColumns) {
+            if (completed) {
+                throw new IllegalStateException(
+                        "An EditorRequest must be completed exactly once");
+            }
+            completed = true;
+
+            grid.getEditor().clearEditorColumnErrors();
+            if (errorColumns != null) {
+                for (Column<?, T> column : errorColumns) {
+                    grid.getEditor().setEditorColumnError(column, true);
+                }
+            }
+        }
+
+        @Override
+        public void success() {
+            complete(null);
+            if (callback != null) {
+                callback.onSuccess(this);
+            }
+        }
+
+        @Override
+        public void failure(Collection<Grid.Column<?, T>> errorColumns) {
+            complete(errorColumns);
+            if (callback != null) {
+                callback.onError(this);
+            }
+        }
+
+        @Override
+        public boolean isCompleted() {
+            return completed;
+        }
+    }
+
     /**
      * An editor UI for Grid rows. A single Grid row at a time can be opened for
      * editing.
@@ -945,6 +1044,8 @@ public class Grid<T> extends ResizeComposite implements
         public static final int KEYCODE_SHOW = KeyCodes.KEY_ENTER;
         public static final int KEYCODE_HIDE = KeyCodes.KEY_ESCAPE;
 
+        private static final String ERROR_CLASS_NAME = "error";
+
         protected enum State {
             INACTIVE, ACTIVATING, BINDING, ACTIVE, SAVING
         }
@@ -988,7 +1089,7 @@ public class Grid<T> extends ResizeComposite implements
             }
         };
 
-        private final RequestCallback<T> saveRequestCallback = new RequestCallback<T>() {
+        private final EditorRequestImpl.RequestCallback<T> saveRequestCallback = new EditorRequestImpl.RequestCallback<T>() {
             @Override
             public void onSuccess(EditorRequest<T> request) {
                 if (state == State.SAVING) {
@@ -1027,7 +1128,7 @@ public class Grid<T> extends ResizeComposite implements
                                 + " remember to call success() or fail()?");
             }
         };
-        private final RequestCallback<T> bindRequestCallback = new RequestCallback<T>() {
+        private final EditorRequestImpl.RequestCallback<T> bindRequestCallback = new EditorRequestImpl.RequestCallback<T>() {
             @Override
             public void onSuccess(EditorRequest<T> request) {
                 if (state == State.BINDING) {
@@ -1055,6 +1156,9 @@ public class Grid<T> extends ResizeComposite implements
             }
         };
 
+        /** A set of all the columns that display an error flag. */
+        private final Set<Column<?, T>> columnErrors = new HashSet<Grid.Column<?, T>>();
+
         public Editor() {
             saveButton = new Button();
             saveButton.setText(GridConstants.DEFAULT_SAVE_CAPTION);
@@ -1132,7 +1236,7 @@ public class Grid<T> extends ResizeComposite implements
             hideOverlay();
             grid.getEscalator().setScrollLocked(Direction.VERTICAL, false);
 
-            EditorRequest<T> request = new EditorRequest<T>(grid, rowIndex,
+            EditorRequest<T> request = new EditorRequestImpl<T>(grid, rowIndex,
                     null);
             handler.cancel(request);
             state = State.INACTIVE;
@@ -1159,7 +1263,7 @@ public class Grid<T> extends ResizeComposite implements
             state = State.SAVING;
             setButtonsEnabled(false);
             saveTimeout.schedule(SAVE_TIMEOUT_MS);
-            EditorRequest<T> request = new EditorRequest<T>(grid, rowIndex,
+            EditorRequest<T> request = new EditorRequestImpl<T>(grid, rowIndex,
                     saveRequestCallback);
             handler.save(request);
         }
@@ -1222,8 +1326,8 @@ public class Grid<T> extends ResizeComposite implements
             if (state == State.ACTIVATING) {
                 state = State.BINDING;
                 bindTimeout.schedule(BIND_TIMEOUT_MS);
-                EditorRequest<T> request = new EditorRequest<T>(grid, rowIndex,
-                        bindRequestCallback);
+                EditorRequest<T> request = new EditorRequestImpl<T>(grid,
+                        rowIndex, bindRequestCallback);
                 handler.bind(request);
                 grid.getEscalator().setScrollLocked(Direction.VERTICAL, true);
             }
@@ -1372,6 +1476,8 @@ public class Grid<T> extends ResizeComposite implements
             editorOverlay.removeFromParent();
 
             scrollHandler.removeHandler();
+
+            clearEditorColumnErrors();
         }
 
         protected void setStylePrimaryName(String primaryName) {
@@ -1480,6 +1586,46 @@ public class Grid<T> extends ResizeComposite implements
         public String getCancelCaption() {
             return cancelButton.getText();
         }
+
+        public void setEditorColumnError(Column<?, T> column, boolean hasError) {
+            if (state != State.ACTIVE && state != State.SAVING) {
+                throw new IllegalStateException("Cannot set cell error "
+                        + "status: editor is neither active nor saving.");
+            }
+
+            if (isEditorColumnError(column) == hasError) {
+                return;
+            }
+
+            Element editorCell = getWidget(column).getElement()
+                    .getParentElement();
+            if (hasError) {
+                editorCell.addClassName(ERROR_CLASS_NAME);
+                columnErrors.add(column);
+            } else {
+                editorCell.removeClassName(ERROR_CLASS_NAME);
+                columnErrors.remove(column);
+            }
+        }
+
+        public void clearEditorColumnErrors() {
+
+            /*
+             * editorOverlay has no children if it's not active, effectively
+             * making this loop a NOOP.
+             */
+            Element e = editorOverlay.getFirstChildElement();
+            while (e != null) {
+                e.removeClassName(ERROR_CLASS_NAME);
+                e = e.getNextSiblingElement();
+            }
+
+            columnErrors.clear();
+        }
+
+        public boolean isEditorColumnError(Column<?, T> column) {
+            return columnErrors.contains(column);
+        }
     }
 
     public static abstract class AbstractGridKeyEvent<HANDLER extends AbstractGridKeyEventHandler>
@@ -3215,7 +3361,7 @@ public class Grid<T> extends ResizeComposite implements
          * 
          * @return {@code true} if this column is editable, {@code false}
          *         otherwise
-         *
+         * 
          * @see #setEditable(boolean)
          */
         public boolean isEditable() {
index 69e23ecd92f4df5e962a6555852c9a12f7a857d6..ef97f9b33680781db6bc0e333144627695e351c3 100644 (file)
@@ -260,6 +260,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
 
         private CommitException cause;
 
+        private Set<Column> errorColumns = new HashSet<Column>();
+
         public CommitErrorEvent(Grid grid, CommitException cause) {
             super(grid);
             this.cause = cause;
@@ -288,6 +290,26 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
             return cause.getCause() instanceof InvalidValueException;
         }
 
+        /**
+         * Marks that an error indicator should be shown for the editor of a
+         * column.
+         * 
+         * @param column
+         *            the column to show an error for
+         */
+        public void addErrorColumn(Column column) {
+            errorColumns.add(column);
+        }
+
+        /**
+         * Gets all the columns that have been marked as erroneous.
+         * 
+         * @return an umodifiable collection of erroneous columns
+         */
+        public Collection<Column> getErrorColumns() {
+            return Collections.unmodifiableCollection(errorColumns);
+        }
+
     }
 
     /**
@@ -302,19 +324,36 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
                     .getCause().getInvalidFields();
 
             if (!invalidFields.isEmpty()) {
-                // Validation error, show first failure as
-                // "<Column header>: <message>"
+                Object firstErrorPropertyId = null;
+                Field<?> firstErrorField = null;
+
                 FieldGroup fieldGroup = event.getCause().getFieldGroup();
-                Object propertyId = getFirstPropertyId(fieldGroup,
-                        invalidFields.keySet());
-                Field<?> field = fieldGroup.getField(propertyId);
-                String caption = getColumn(propertyId).getHeaderCaption();
-                // TODO This should be shown in the editor component once
-                // there is a place for that. Optionally, all errors should be
-                // shown
-                Notification.show(caption + ": "
-                        + invalidFields.get(field).getLocalizedMessage(),
-                        Type.ERROR_MESSAGE);
+                for (Column column : getColumns()) {
+                    Object propertyId = column.getPropertyId();
+                    Field<?> field = fieldGroup.getField(propertyId);
+                    if (invalidFields.keySet().contains(field)) {
+                        event.addErrorColumn(column);
+
+                        if (firstErrorPropertyId == null) {
+                            firstErrorPropertyId = propertyId;
+                            firstErrorField = field;
+                        }
+                    }
+                }
+
+                /*
+                 * Validation error, show first failure as
+                 * "<Column header>: <message>"
+                 */
+                String caption = getColumn(firstErrorPropertyId)
+                        .getHeaderCaption();
+                String message = invalidFields.get(firstErrorField)
+                        .getLocalizedMessage();
+                /*
+                 * TODO This should be shown in the editor component once there
+                 * is a place for that. Optionally, all errors should be shown
+                 */
+                Notification.show(caption + ": " + message, Type.ERROR_MESSAGE);
 
             } else {
                 com.vaadin.server.ErrorEvent.findErrorHandler(Grid.this).error(
@@ -3016,14 +3055,21 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
 
             @Override
             public void save(int rowIndex) {
+                List<String> errorColumnIds = null;
                 boolean success = false;
                 try {
                     saveEditor();
                     success = true;
                 } catch (CommitException e) {
                     try {
-                        getEditorErrorHandler().commitError(
-                                new CommitErrorEvent(Grid.this, e));
+                        CommitErrorEvent event = new CommitErrorEvent(
+                                Grid.this, e);
+                        getEditorErrorHandler().commitError(event);
+
+                        errorColumnIds = new ArrayList<String>();
+                        for (Column column : event.getErrorColumns()) {
+                            errorColumnIds.add(column.state.id);
+                        }
                     } catch (Exception ee) {
                         // A badly written error handler can throw an exception,
                         // which would lock up the Grid
@@ -3032,7 +3078,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier,
                 } catch (Exception e) {
                     handleError(e);
                 }
-                getEditorRpc().confirmSave(success);
+                getEditorRpc().confirmSave(success, errorColumnIds);
             }
 
             private void handleError(Exception e) {
index 82e08999b49d78f9b1fc2029a02967466b8c7a24..6fb0b7a0691612f5da550f21e296173df980c891 100644 (file)
@@ -15,6 +15,8 @@
  */
 package com.vaadin.shared.ui.grid;
 
+import java.util.List;
+
 import com.vaadin.shared.communication.ClientRpc;
 
 /**
@@ -56,6 +58,9 @@ public interface EditorClientRpc extends ClientRpc {
      * 
      * @param saveSucceeded
      *            <code>true</code> iff the save action was successful
+     * @param errorColumnsIds
+     *            a list of column keys that should get error markers, or
+     *            <code>null</code> if there should be no error markers
      */
-    void confirmSave(boolean saveSucceeded);
+    void confirmSave(boolean saveSucceeded, List<String> errorColumnsIds);
 }
index 6b1279d2c22b2264a8e5aaf5f5cbb118d301730b..5d85de4eb6da3bf00ea3279197f906a5fab2556c 100644 (file)
@@ -99,6 +99,17 @@ public class GridElement extends AbstractComponentElement {
                     .isElementPresent(By.vaadin("#editor[" + colIndex + "]"));
         }
 
+        /**
+         * Checks whether a field is marked with an error.
+         * 
+         * @param colIndex
+         *            column index
+         * @return <code>true</code> iff the field is marked with an error
+         */
+        public boolean isFieldErrorMarked(int colIndex) {
+            return getField(colIndex).getAttribute("class").contains("error");
+        }
+
         /**
          * Saves the fields of this editor.
          * <p>
index 30481ebc651ea126df6d0ebd8f6e1da6ac9922e5..2d8c9eb763709315d1a6361aa4fe6676a53766b2 100644 (file)
@@ -187,6 +187,7 @@ public class GridEditorClientTest extends GridBasicClientFeaturesTest {
                         .getText());
     }
 
+    @Test
     public void testUneditableColumn() {
         selectMenuPath("Component", "Editor", "Edit row 5");
 
@@ -194,6 +195,19 @@ public class GridEditorClientTest extends GridBasicClientFeaturesTest {
                 getGridElement().getEditor().isEditable(3));
     }
 
+    @Test
+    public void testErrorField() {
+        selectMenuPath(EDIT_ROW_5);
+
+        assertTrue("No errors should be present",
+                getEditor().findElements(By.className("error")).isEmpty());
+        selectMenuPath("Component", "Editor", "Toggle second editor error");
+        getSaveButton().click();
+
+        assertEquals("Unexpected amount of error fields", 1, getEditor()
+                .findElements(By.className("error")).size());
+    }
+
     protected WebElement getSaveButton() {
         return getEditor().findElement(By.className("v-grid-editor-save"));
     }
index 5e11af1ca50d1e342440c480aa94da52a63b88d6..e1567b4286be4f9f3aaa8bf24beed40acb7e1209 100644 (file)
@@ -212,6 +212,10 @@ public class GridEditorTest extends GridBasicFeaturesTest {
 
         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");
@@ -220,6 +224,8 @@ public class GridEditorTest extends GridBasicFeaturesTest {
         assertEquals("Column 7: Could not convert value to Integer",
                 n.getCaption());
         n.close();
+        assertTrue("Field 7 should have been marked with an error after error",
+                editor.isFieldErrorMarked(7));
         editor.cancel();
 
         selectMenuPath(EDIT_ITEM_100);
index 41d74a11a2ad6b318f14ab67e566e6a8646d9e5e..7509054957aa3a79829dabed56d1cf6e0efd946b 100644 (file)
@@ -16,6 +16,7 @@
 package com.vaadin.tests.widgetset.client.grid;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
@@ -67,6 +68,7 @@ import com.vaadin.client.widget.grid.events.ScrollEvent;
 import com.vaadin.client.widget.grid.events.ScrollHandler;
 import com.vaadin.client.widget.grid.selection.SelectionModel.None;
 import com.vaadin.client.widgets.Grid;
+import com.vaadin.client.widgets.Grid.Column;
 import com.vaadin.client.widgets.Grid.FooterRow;
 import com.vaadin.client.widgets.Grid.HeaderRow;
 import com.vaadin.client.widgets.Grid.SelectionMode;
@@ -122,6 +124,12 @@ public class GridBasicClientFeaturesWidget extends
 
         @Override
         public void save(EditorRequest<List<Data>> request) {
+            if (secondEditorError) {
+                log.setText("Syntethic fail of editor in column 2");
+                request.failure(Collections.<Column<?, List<Data>>> singleton(grid
+                        .getColumn(2)));
+                return;
+            }
             try {
                 log.setText("Row " + request.getRowIndex() + " edit committed");
                 List<Data> rowData = ds.getRow(request.getRowIndex());
@@ -145,7 +153,7 @@ public class GridBasicClientFeaturesWidget extends
                 request.success();
             } catch (Exception e) {
                 Logger.getLogger(getClass().getName()).warning(e.toString());
-                request.fail();
+                request.failure(null);
             }
         }
 
@@ -179,6 +187,8 @@ public class GridBasicClientFeaturesWidget extends
     private final ListDataSource<List<Data>> ds;
     private final ListSorter<List<Data>> sorter;
 
+    private boolean secondEditorError = false;
+
     /**
      * Our basic data object
      */
@@ -972,6 +982,12 @@ public class GridBasicClientFeaturesWidget extends
             }
         }, "Component", "Editor");
 
+        addMenuCommand("Toggle second editor error", new ScheduledCommand() {
+            @Override
+            public void execute() {
+                secondEditorError = !secondEditorError;
+            }
+        }, "Component", "Editor");
     }
 
     private void configureFooterRow(final FooterRow row) {