From: Henrik Paul Date: Thu, 5 Feb 2015 21:50:31 +0000 (+0200) Subject: Highlights erroneous cells in Grid editor (#16575) X-Git-Tag: 7.5.0.alpha1~123 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=0e141e31bb30a0ab6726129f3c9fa892c92573e4;p=vaadin-framework.git Highlights erroneous cells in Grid editor (#16575) Change-Id: Ie1f9d738db7a03ddb01b968782ad5e4877af1d7e --- diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index a79420f7a9..e4a4a1d920 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -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 { diff --git a/WebContent/VAADIN/themes/valo/components/_grid.scss b/WebContent/VAADIN/themes/valo/components/_grid.scss index c481f127a8..4cac9c5e43 100644 --- a/WebContent/VAADIN/themes/valo/components/_grid.scss +++ b/WebContent/VAADIN/themes/valo/components/_grid.scss @@ -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, diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index 8d383ab0ae..60a730c80e 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -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 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 errorColumnsIds) { + endRequest(saveSucceeded, errorColumnsIds); } }); } @@ -295,24 +297,34 @@ public class GridConnector extends AbstractHasComponentsConnector implements } } - private void startRequest(EditorRequest request) { + private void startRequest(EditorRequest request) { assert currentRequest == null : "Earlier request not yet finished"; currentRequest = request; } - private void endRequest(boolean succeeded) { + private void endRequest(boolean succeeded, List 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 request = currentRequest; currentRequest = null; if (succeeded) { request.success(); } else { - request.fail(); + Collection> errorColumns; + if (errorColumnsIds != null) { + errorColumns = new ArrayList>(); + for (String colId : errorColumnsIds) { + errorColumns.add(columnIdToColumn.get(colId)); + } + } else { + errorColumns = null; + } + + request.failure(errorColumns); } } } diff --git a/client/src/com/vaadin/client/widget/grid/EditorHandler.java b/client/src/com/vaadin/client/widget/grid/EditorHandler.java index 07ec1b231c..1d152c708c 100644 --- a/client/src/com/vaadin/client/widget/grid/EditorHandler.java +++ b/client/src/com/vaadin/client/widget/grid/EditorHandler.java @@ -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 { * @param * the row data type */ - public static class EditorRequest { - - /** - * A callback interface used to notify the invoker of the editor handler - * of completed editor requests. - * - * @param - * the row data type - */ - public interface RequestCallback { - /** - * The method that must be called when the request has been - * processed correctly. - * - * @param request - * the original request object - */ - public void onSuccess(EditorRequest 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 request); - } - - private Grid grid; - private int rowIndex; - private RequestCallback 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 grid, int rowIndex, - RequestCallback callback) { - this.grid = grid; - this.rowIndex = rowIndex; - this.callback = callback; - } - + public interface EditorRequest { /** * 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 getGrid() { - return grid; - } + public Grid getGrid(); /** * Returns the editor widget used to edit the values of the given @@ -128,59 +75,30 @@ public interface EditorHandler { * the column whose widget to get * @return the widget related to the column */ - public Widget getWidget(Grid.Column 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 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 null if no columns should + * be marked as erroneous. */ - public void fail() { - complete(); - if (callback != null) { - callback.onError(this); - } - } + public void failure(Collection> errorColumns); /** * Checks whether the request is completed or not. * * @return true iff the request is completed */ - public boolean isCompleted() { - return completed; - } + public boolean isCompleted(); } /** @@ -188,8 +106,9 @@ public interface EditorHandler { * opened for editing. *

* The implementation must 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 diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index fb7ec3abb5..0f3ffc696a 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -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 extends ResizeComposite implements } } + private static class EditorRequestImpl implements EditorRequest { + + /** + * A callback interface used to notify the invoker of the editor handler + * of completed editor requests. + * + * @param + * the row data type + */ + public static interface RequestCallback { + /** + * The method that must be called when the request has been + * processed correctly. + * + * @param request + * the original request object + */ + public void onSuccess(EditorRequest 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 request); + } + + private Grid grid; + private int rowIndex; + private RequestCallback callback; + private boolean completed = false; + + public EditorRequestImpl(Grid grid, int rowIndex, + RequestCallback 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 getGrid() { + return grid; + } + + @Override + public Widget getWidget(Grid.Column column) { + Widget w = grid.getEditorWidget(column); + assert w != null; + return w; + } + + private void complete(Collection> errorColumns) { + if (completed) { + throw new IllegalStateException( + "An EditorRequest must be completed exactly once"); + } + completed = true; + + grid.getEditor().clearEditorColumnErrors(); + if (errorColumns != null) { + for (Column column : errorColumns) { + grid.getEditor().setEditorColumnError(column, true); + } + } + } + + @Override + public void success() { + complete(null); + if (callback != null) { + callback.onSuccess(this); + } + } + + @Override + public void failure(Collection> 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 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 extends ResizeComposite implements } }; - private final RequestCallback saveRequestCallback = new RequestCallback() { + private final EditorRequestImpl.RequestCallback saveRequestCallback = new EditorRequestImpl.RequestCallback() { @Override public void onSuccess(EditorRequest request) { if (state == State.SAVING) { @@ -1027,7 +1128,7 @@ public class Grid extends ResizeComposite implements + " remember to call success() or fail()?"); } }; - private final RequestCallback bindRequestCallback = new RequestCallback() { + private final EditorRequestImpl.RequestCallback bindRequestCallback = new EditorRequestImpl.RequestCallback() { @Override public void onSuccess(EditorRequest request) { if (state == State.BINDING) { @@ -1055,6 +1156,9 @@ public class Grid extends ResizeComposite implements } }; + /** A set of all the columns that display an error flag. */ + private final Set> columnErrors = new HashSet>(); + public Editor() { saveButton = new Button(); saveButton.setText(GridConstants.DEFAULT_SAVE_CAPTION); @@ -1132,7 +1236,7 @@ public class Grid extends ResizeComposite implements hideOverlay(); grid.getEscalator().setScrollLocked(Direction.VERTICAL, false); - EditorRequest request = new EditorRequest(grid, rowIndex, + EditorRequest request = new EditorRequestImpl(grid, rowIndex, null); handler.cancel(request); state = State.INACTIVE; @@ -1159,7 +1263,7 @@ public class Grid extends ResizeComposite implements state = State.SAVING; setButtonsEnabled(false); saveTimeout.schedule(SAVE_TIMEOUT_MS); - EditorRequest request = new EditorRequest(grid, rowIndex, + EditorRequest request = new EditorRequestImpl(grid, rowIndex, saveRequestCallback); handler.save(request); } @@ -1222,8 +1326,8 @@ public class Grid extends ResizeComposite implements if (state == State.ACTIVATING) { state = State.BINDING; bindTimeout.schedule(BIND_TIMEOUT_MS); - EditorRequest request = new EditorRequest(grid, rowIndex, - bindRequestCallback); + EditorRequest request = new EditorRequestImpl(grid, + rowIndex, bindRequestCallback); handler.bind(request); grid.getEscalator().setScrollLocked(Direction.VERTICAL, true); } @@ -1372,6 +1476,8 @@ public class Grid extends ResizeComposite implements editorOverlay.removeFromParent(); scrollHandler.removeHandler(); + + clearEditorColumnErrors(); } protected void setStylePrimaryName(String primaryName) { @@ -1480,6 +1586,46 @@ public class Grid extends ResizeComposite implements public String getCancelCaption() { return cancelButton.getText(); } + + public void setEditorColumnError(Column 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 column) { + return columnErrors.contains(column); + } } public static abstract class AbstractGridKeyEvent @@ -3215,7 +3361,7 @@ public class Grid extends ResizeComposite implements * * @return {@code true} if this column is editable, {@code false} * otherwise - * + * * @see #setEditable(boolean) */ public boolean isEditable() { diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index 69e23ecd92..ef97f9b336 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -260,6 +260,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, private CommitException cause; + private Set errorColumns = new HashSet(); + 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 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 - // ": " + 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 + * ": " + */ + 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 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(); + 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) { diff --git a/shared/src/com/vaadin/shared/ui/grid/EditorClientRpc.java b/shared/src/com/vaadin/shared/ui/grid/EditorClientRpc.java index 82e08999b4..6fb0b7a069 100644 --- a/shared/src/com/vaadin/shared/ui/grid/EditorClientRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/EditorClientRpc.java @@ -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 * true iff the save action was successful + * @param errorColumnsIds + * a list of column keys that should get error markers, or + * null if there should be no error markers */ - void confirmSave(boolean saveSucceeded); + void confirmSave(boolean saveSucceeded, List errorColumnsIds); } diff --git a/uitest/src/com/vaadin/testbench/elements/GridElement.java b/uitest/src/com/vaadin/testbench/elements/GridElement.java index 6b1279d2c2..5d85de4eb6 100644 --- a/uitest/src/com/vaadin/testbench/elements/GridElement.java +++ b/uitest/src/com/vaadin/testbench/elements/GridElement.java @@ -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 true 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. *

diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java index 30481ebc65..2d8c9eb763 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java @@ -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")); } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java index 5e11af1ca5..e1567b4286 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java @@ -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); diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java index 41d74a11a2..7509054957 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java @@ -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> request) { + if (secondEditorError) { + log.setText("Syntethic fail of editor in column 2"); + request.failure(Collections.>> singleton(grid + .getColumn(2))); + return; + } try { log.setText("Row " + request.getRowIndex() + " edit committed"); List 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> ds; private final ListSorter> 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) {