From 131e4f34bddf99d61a76fcd49890490c78c3efa8 Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 30 Jan 2017 13:47:55 +0200 Subject: [PATCH] Make it possible to disallow user selection in Grid (#8144) Fixes #7880 --- .../MultiSelectionModelConnector.java | 45 ++++- .../SingleSelectionModelConnector.java | 36 +++- .../grid/selection/ClickSelectHandler.java | 4 + .../selection/HasUserSelectionAllowed.java | 44 +++++ .../selection/MultiSelectionRenderer.java | 6 +- .../grid/selection/SelectionModelMulti.java | 13 +- .../grid/selection/SelectionModelSingle.java | 13 +- .../grid/selection/SpaceSelectHandler.java | 4 + .../java/com/vaadin/client/widgets/Grid.java | 57 ++++++- .../components/components-grid.asciidoc | 13 ++ server/src/main/java/com/vaadin/ui/Grid.java | 127 ++++++++++++-- .../grid/MultiSelectionModelTest.java | 41 +++++ .../grid/SingleSelectionModelTest.java | 13 ++ .../java/com/vaadin/ui/ComponentTest.java | 119 +++++++++++++ .../selection/MultiSelectionModelState.java | 1 + .../selection/SingleSelectionModelState.java | 1 + .../grid/basicfeatures/GridBasicFeatures.java | 30 +++- .../server/GridSelectionTest.java | 157 ++++++++++++++++++ 18 files changed, 694 insertions(+), 30 deletions(-) create mode 100644 client/src/main/java/com/vaadin/client/widget/grid/selection/HasUserSelectionAllowed.java create mode 100644 server/src/test/java/com/vaadin/ui/ComponentTest.java diff --git a/client/src/main/java/com/vaadin/client/connectors/MultiSelectionModelConnector.java b/client/src/main/java/com/vaadin/client/connectors/MultiSelectionModelConnector.java index 748e9b1acf..5c0a84bd5f 100644 --- a/client/src/main/java/com/vaadin/client/connectors/MultiSelectionModelConnector.java +++ b/client/src/main/java/com/vaadin/client/connectors/MultiSelectionModelConnector.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.logging.Logger; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.ui.CheckBox; @@ -35,12 +36,15 @@ import com.vaadin.client.widget.grid.DataAvailableEvent; import com.vaadin.client.widget.grid.DataAvailableHandler; import com.vaadin.client.widget.grid.events.SelectAllEvent; import com.vaadin.client.widget.grid.events.SelectAllHandler; +import com.vaadin.client.widget.grid.selection.HasUserSelectionAllowed; import com.vaadin.client.widget.grid.selection.MultiSelectionRenderer; import com.vaadin.client.widget.grid.selection.SelectionModel; import com.vaadin.client.widget.grid.selection.SelectionModel.Multi; import com.vaadin.client.widget.grid.selection.SpaceSelectHandler; import com.vaadin.client.widgets.Grid; +import com.vaadin.client.widgets.Grid.Column; import com.vaadin.client.widgets.Grid.HeaderCell; +import com.vaadin.client.widgets.Grid.SelectionColumn; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.grid.GridState; import com.vaadin.shared.ui.grid.Range; @@ -94,8 +98,30 @@ public class MultiSelectionModelConnector extends } } + @OnStateChange("userSelectionAllowed") + void updateUserSelectionAllowed() { + if (selectionModel instanceof HasUserSelectionAllowed) { + ((HasUserSelectionAllowed) selectionModel) + .setUserSelectionAllowed(getState().userSelectionAllowed); + } else { + getLogger().warning("userSelectionAllowed set to " + + getState().userSelectionAllowed + + " but the selection model does not implement " + + HasUserSelectionAllowed.class.getSimpleName()); + } + } + + private static Logger getLogger() { + return Logger.getLogger(MultiSelectionModelConnector.class.getName()); + } + + /** + * The default multi selection model used for this connector. + * + */ protected class MultiSelectionModel extends AbstractSelectionModel - implements SelectionModel.Multi.Batched { + implements SelectionModel.Multi.Batched, + HasUserSelectionAllowed { private ComplexRenderer renderer = null; private Set> selected = new HashSet>(); @@ -104,6 +130,7 @@ public class MultiSelectionModelConnector extends private HandlerRegistration dataAvailable; private Range availableRows; private boolean batchSelect = false; + private boolean userSelectionAllowed = true; @Override public void setGrid(Grid grid) { @@ -382,5 +409,21 @@ public class MultiSelectionModelConnector extends public Collection getDeselectedRowsBatch() { return Collections.unmodifiableSet(getRows(deselected)); } + + @Override + public boolean isUserSelectionAllowed() { + return userSelectionAllowed; + } + + @Override + public void setUserSelectionAllowed(boolean userSelectionAllowed) { + this.userSelectionAllowed = userSelectionAllowed; + for (Column c : getGrid().getColumns()) { + if (c instanceof SelectionColumn) { + ((SelectionColumn) c) + .setUserSelectionAllowed(userSelectionAllowed); + } + } + } } } diff --git a/client/src/main/java/com/vaadin/client/connectors/SingleSelectionModelConnector.java b/client/src/main/java/com/vaadin/client/connectors/SingleSelectionModelConnector.java index 55c1eddf61..7cd30e40ef 100644 --- a/client/src/main/java/com/vaadin/client/connectors/SingleSelectionModelConnector.java +++ b/client/src/main/java/com/vaadin/client/connectors/SingleSelectionModelConnector.java @@ -15,11 +15,14 @@ */ package com.vaadin.client.connectors; +import java.util.logging.Logger; + import com.vaadin.client.ServerConnector; import com.vaadin.client.annotations.OnStateChange; import com.vaadin.client.data.DataSource.RowHandle; import com.vaadin.client.renderers.Renderer; import com.vaadin.client.widget.grid.selection.ClickSelectHandler; +import com.vaadin.client.widget.grid.selection.HasUserSelectionAllowed; import com.vaadin.client.widget.grid.selection.SelectionModel; import com.vaadin.client.widget.grid.selection.SelectionModel.Single; import com.vaadin.client.widget.grid.selection.SpaceSelectHandler; @@ -75,14 +78,34 @@ public class SingleSelectionModelConnector extends selectionModel.setDeselectAllowed(getState().deselectAllowed); } + @OnStateChange("userSelectionAllowed") + void updateUserSelectionAllowed() { + + if (selectionModel instanceof HasUserSelectionAllowed) { + ((HasUserSelectionAllowed) selectionModel) + .setUserSelectionAllowed(getState().userSelectionAllowed); + } else { + getLogger().warning("userSelectionAllowed set to " + + getState().userSelectionAllowed + + " but the selection model does not implement " + + HasUserSelectionAllowed.class.getSimpleName()); + } + } + + private static Logger getLogger() { + return Logger.getLogger(SingleSelectionModelConnector.class.getName()); + } + /** * SingleSelectionModel without a selection column renderer. */ public class SingleSelectionModel extends AbstractSelectionModel - implements SelectionModel.Single { + implements SelectionModel.Single, + HasUserSelectionAllowed { private RowHandle selectedRow; private boolean deselectAllowed; + private boolean userSelectionAllowed = true; @Override public Renderer getSelectionColumnRenderer() { @@ -182,5 +205,16 @@ public class SingleSelectionModelConnector extends public boolean isDeselectAllowed() { return deselectAllowed; } + + @Override + public boolean isUserSelectionAllowed() { + return userSelectionAllowed; + } + + @Override + public void setUserSelectionAllowed(boolean userSelectionAllowed) { + this.userSelectionAllowed = userSelectionAllowed; + } + } } \ No newline at end of file diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/ClickSelectHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/ClickSelectHandler.java index e7be08f141..7ace1eb4a4 100644 --- a/client/src/main/java/com/vaadin/client/widget/grid/selection/ClickSelectHandler.java +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/ClickSelectHandler.java @@ -36,6 +36,10 @@ public class ClickSelectHandler { @Override public void onClick(GridClickEvent event) { + if (!grid.isUserSelectionAllowed()) { + return; + } + T row = (T) event.getTargetCell().getRow(); if (!grid.isSelected(row)) { grid.select(row); diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/HasUserSelectionAllowed.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/HasUserSelectionAllowed.java new file mode 100644 index 0000000000..de4eda36ca --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/HasUserSelectionAllowed.java @@ -0,0 +1,44 @@ +/* + * Copyright 2000-2014 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.widget.grid.selection; + +/** + * Interface implemented by selection models which support disabling client side + * selection while still allowing programmatic selection on the server. + * + * @param + * Grid's row type + */ +public interface HasUserSelectionAllowed extends SelectionModel { + + /** + * Checks if the user is allowed to change the selection. + * + * @return true if the user is allowed to change the selection, + * false otherwise + */ + public boolean isUserSelectionAllowed(); + + /** + * Sets whether the user is allowed to change the selection. + * + * @param userSelectionAllowed + * true if the user is allowed to change the + * selection, false otherwise + */ + public void setUserSelectionAllowed(boolean userSelectionAllowed); + +} diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java index b27a4a2eed..9c8f60c0cd 100644 --- a/client/src/main/java/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java @@ -632,7 +632,8 @@ public class MultiSelectionRenderer public void render(final RendererCellReference cell, final Boolean data, CheckBox checkBox) { checkBox.setValue(data, false); - checkBox.setEnabled(grid.isEnabled() && !grid.isEditorActive()); + checkBox.setEnabled(grid.isEnabled() && !grid.isEditorActive() + && grid.isUserSelectionAllowed()); } @Override @@ -770,6 +771,9 @@ public class MultiSelectionRenderer } protected void setSelected(final int logicalRow, final boolean select) { + if (!grid.isUserSelectionAllowed()) { + return; + } T row = grid.getDataSource().getRow(logicalRow); if (select) { grid.select(row); diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelMulti.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelMulti.java index 00c115c5c2..7874e03006 100644 --- a/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelMulti.java +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelMulti.java @@ -33,7 +33,7 @@ import com.vaadin.client.widgets.Grid; * @since 7.4 */ public class SelectionModelMulti extends AbstractRowHandleSelectionModel - implements SelectionModel.Multi.Batched { + implements SelectionModel.Multi.Batched, HasUserSelectionAllowed { private final LinkedHashSet> selectedRows; private Renderer renderer; @@ -45,6 +45,7 @@ public class SelectionModelMulti extends AbstractRowHandleSelectionModel /* Event handling for selection with space key */ private SpaceSelectHandler spaceSelectHandler; + private boolean userSelectionAllowed = true; public SelectionModelMulti() { grid = null; @@ -270,4 +271,14 @@ public class SelectionModelMulti extends AbstractRowHandleSelectionModel } return rows; } + + @Override + public boolean isUserSelectionAllowed() { + return userSelectionAllowed; + } + + @Override + public void setUserSelectionAllowed(boolean userSelectionAllowed) { + this.userSelectionAllowed = userSelectionAllowed; + } } diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelSingle.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelSingle.java index f3df892623..217682bcd1 100644 --- a/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelSingle.java +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelSingle.java @@ -29,7 +29,7 @@ import com.vaadin.client.widgets.Grid; * @since 7.4 */ public class SelectionModelSingle extends AbstractRowHandleSelectionModel - implements SelectionModel.Single { + implements SelectionModel.Single, HasUserSelectionAllowed { private Grid grid; private RowHandle selectedRow; @@ -41,6 +41,7 @@ public class SelectionModelSingle extends AbstractRowHandleSelectionModel private ClickSelectHandler clickSelectHandler; private boolean deselectAllowed = true; + private boolean userSelectionAllowed = true; @Override public boolean isSelected(T row) { @@ -172,4 +173,14 @@ public class SelectionModelSingle extends AbstractRowHandleSelectionModel } } + @Override + public boolean isUserSelectionAllowed() { + return userSelectionAllowed; + } + + @Override + public void setUserSelectionAllowed(boolean userSelectionAllowed) { + this.userSelectionAllowed = userSelectionAllowed; + } + } diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/SpaceSelectHandler.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/SpaceSelectHandler.java index 456f08c5b3..476f342838 100644 --- a/client/src/main/java/com/vaadin/client/widget/grid/selection/SpaceSelectHandler.java +++ b/client/src/main/java/com/vaadin/client/widget/grid/selection/SpaceSelectHandler.java @@ -44,6 +44,10 @@ public class SpaceSelectHandler { @Override public void onKeyDown(GridKeyDownEvent event) { + if (!grid.isUserSelectionAllowed()) { + return; + } + if (event.getNativeKeyCode() != KeyCodes.KEY_SPACE || spaceDown) { return; } 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 f796529aa7..d215b3c565 100755 --- a/client/src/main/java/com/vaadin/client/widgets/Grid.java +++ b/client/src/main/java/com/vaadin/client/widgets/Grid.java @@ -158,6 +158,7 @@ import com.vaadin.client.widget.grid.events.ScrollHandler; import com.vaadin.client.widget.grid.events.SelectAllEvent; import com.vaadin.client.widget.grid.events.SelectAllHandler; import com.vaadin.client.widget.grid.selection.HasSelectionHandlers; +import com.vaadin.client.widget.grid.selection.HasUserSelectionAllowed; import com.vaadin.client.widget.grid.selection.MultiSelectionRenderer; import com.vaadin.client.widget.grid.selection.SelectionEvent; import com.vaadin.client.widget.grid.selection.SelectionHandler; @@ -1922,6 +1923,9 @@ public class Grid extends ResizeComposite implements HasSelectionHandlers, checkBox.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { + if (!grid.isUserSelectionAllowed()) { + return; + } T row = pinnedRowHandle.getRow(); if (grid.isSelected(row)) { grid.deselect(row); @@ -2885,6 +2889,8 @@ public class Grid extends ResizeComposite implements HasSelectionHandlers, private boolean initDone = false; private boolean selected = false; private CheckBox selectAllCheckBox; + private boolean userSelectionAllowed = true; + private boolean enabled = true; SelectionColumn(final Renderer selectColumnRenderer) { super(selectColumnRenderer); @@ -2915,6 +2921,7 @@ public class Grid extends ResizeComposite implements HasSelectionHandlers, if (selectAllCheckBox == null) { selectAllCheckBox = GWT.create(CheckBox.class); + selectAllCheckBox.setEnabled(enabled && userSelectionAllowed); selectAllCheckBox.setStylePrimaryName( getStylePrimaryName() + SELECT_ALL_CHECKBOX_CLASSNAME); selectAllCheckBox.addValueChangeHandler( @@ -2923,6 +2930,9 @@ public class Grid extends ResizeComposite implements HasSelectionHandlers, @Override public void onValueChange( ValueChangeEvent event) { + if (!isUserSelectionAllowed()) { + return; + } if (event.getValue()) { fireEvent(new SelectAllEvent(model)); selected = true; @@ -2937,6 +2947,10 @@ public class Grid extends ResizeComposite implements HasSelectionHandlers, addHeaderClickHandler(new HeaderClickHandler() { @Override public void onClick(GridClickEvent event) { + if (!userSelectionAllowed) { + return; + } + CellReference targetCell = event.getTargetCell(); int defaultRowIndex = getHeader().getRows() .indexOf(getDefaultHeaderRow()); @@ -2956,6 +2970,10 @@ public class Grid extends ResizeComposite implements HasSelectionHandlers, if (event.getNativeKeyCode() != KeyCodes.KEY_SPACE) { return; } + if (!isUserSelectionAllowed()) { + return; + } + HeaderRow targetHeaderRow = getHeader() .getRow(event.getFocusedCell().getRowIndex()); if (!targetHeaderRow.isDefault()) { @@ -3051,8 +3069,9 @@ public class Grid extends ResizeComposite implements HasSelectionHandlers, * to disable it. */ public void setEnabled(boolean enabled) { + this.enabled = enabled; if (selectAllCheckBox != null) { - selectAllCheckBox.setEnabled(enabled); + selectAllCheckBox.setEnabled(enabled && userSelectionAllowed); } } @@ -3060,6 +3079,26 @@ public class Grid extends ResizeComposite implements HasSelectionHandlers, public void onEnabled(boolean enabled) { setEnabled(enabled); } + + /** + * Sets whether the user is allowed to change the selection. + * + * @param userSelectionAllowed + * true if the user is allowed to change the + * selection, false otherwise + */ + public void setUserSelectionAllowed(boolean userSelectionAllowed) { + if (userSelectionAllowed == this.userSelectionAllowed) { + return; + } + + this.userSelectionAllowed = userSelectionAllowed; + // Update checkbox state + setEnabled(enabled); + // Re-render select checkboxes + getEscalator().getBody().refreshRows(0, + getEscalator().getBody().getRowCount()); + } } /** @@ -9205,4 +9244,20 @@ public class Grid extends ResizeComposite implements HasSelectionHandlers, } return null; } + + /** + * Checks if selection by the user is allowed in the grid. + * + * @return true if selection by the user is allowed by the + * selection model (the default), false otherwise + */ + public boolean isUserSelectionAllowed() { + if (!(getSelectionModel() instanceof HasUserSelectionAllowed)) { + // Selection model does not support toggling user selection allowed + // - old default is to always allow selection + return true; + } + return ((HasUserSelectionAllowed) getSelectionModel()) + .isUserSelectionAllowed(); + } } diff --git a/documentation/components/components-grid.asciidoc b/documentation/components/components-grid.asciidoc index 9b465a71b0..931c50ee2a 100644 --- a/documentation/components/components-grid.asciidoc +++ b/documentation/components/components-grid.asciidoc @@ -327,6 +327,19 @@ grid.addSelectionListener(selection -> { // Java 8 }); ---- +[[components.grid.selection.disallowuser]] +=== Disallowing User Selection +It is possible to prevent the user from changing the selection in grid for both single- and multi-selection models: + +[source, java] +---- +HasUserSelectionAllowed model = (HasUserSelectionAllowed) grid.getSelectionModel(); +model.setUserSelectionAllowed(false); +---- + +[NOTE] +Both `SingleSelectionModel` and `MultiSelectModel` implement `HasUserSelectionAllowed` so the cast is generally safe. + [[components.grid.selection.clicks]] === Focus and Clicks diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java index 1efc091104..f96b485cc0 100644 --- a/server/src/main/java/com/vaadin/ui/Grid.java +++ b/server/src/main/java/com/vaadin/ui/Grid.java @@ -105,6 +105,7 @@ import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState; import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc; import com.vaadin.shared.ui.grid.selection.SingleSelectionModelState; import com.vaadin.shared.util.SharedUtil; +import com.vaadin.ui.Grid.SelectionModel.HasUserSelectionAllowed; import com.vaadin.ui.declarative.DesignAttributeHandler; import com.vaadin.ui.declarative.DesignContext; import com.vaadin.ui.declarative.DesignException; @@ -569,11 +570,10 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, } } - private void bindFields(List> fields, - Item itemDataSource) { + private void bindFields(List> fields, Item itemDataSource) { for (Field field : fields) { - if (itemDataSource.getItemProperty(getPropertyId(field)) - != null) { + if (itemDataSource + .getItemProperty(getPropertyId(field)) != null) { bind(field, getPropertyId(field)); } } @@ -1105,6 +1105,34 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * SelectionModel should extend {@link AbstractGridExtension}. */ public interface SelectionModel extends Serializable, Extension { + + /** + * Interface implemented by selection models which support disabling + * client side selection while still allowing programmatic selection on + * the server. + * + */ + public interface HasUserSelectionAllowed extends SelectionModel { + + /** + * Checks if the user is allowed to change the selection. + * + * @return true if the user is allowed to change the + * selection, false otherwise + */ + public boolean isUserSelectionAllowed(); + + /** + * Sets whether the user is allowed to change the selection. + * + * @param userSelectionAllowed + * true if the user is allowed to change the + * selection, false otherwise + */ + public void setUserSelectionAllowed(boolean userSelectionAllowed); + + } + /** * Checks whether an item is selected or not. * @@ -1464,7 +1492,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * A default implementation of a {@link SelectionModel.Single} */ public static class SingleSelectionModel extends AbstractSelectionModel - implements SelectionModel.Single { + implements SelectionModel.Single, HasUserSelectionAllowed { @Override protected void extend(AbstractClientConnector target) { @@ -1473,6 +1501,11 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, @Override public void select(String rowKey) { + if (!isUserSelectionAllowed()) { + throw new IllegalStateException( + "Client tried to select '" + rowKey + + "' although user selection is disallowed"); + } SingleSelectionModel.this.select(getItemId(rowKey), false); } }); @@ -1563,6 +1596,21 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, protected SingleSelectionModelState getState() { return (SingleSelectionModelState) super.getState(); } + + @Override + protected SingleSelectionModelState getState(boolean markAsDirty) { + return (SingleSelectionModelState) super.getState(markAsDirty); + } + + @Override + public boolean isUserSelectionAllowed() { + return getState(false).userSelectionAllowed; + } + + @Override + public void setUserSelectionAllowed(boolean userSelectionAllowed) { + getState().userSelectionAllowed = userSelectionAllowed; + } } /** @@ -1590,13 +1638,15 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, public void reset() { // NOOP } + } /** * A default implementation of a {@link SelectionModel.Multi} */ public static class MultiSelectionModel extends AbstractSelectionModel - implements SelectionModel.Multi { + implements SelectionModel.Multi, + SelectionModel.HasUserSelectionAllowed { /** * The default selection size limit. @@ -1614,6 +1664,12 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, @Override public void select(List rowKeys) { + if (!isUserSelectionAllowed()) { + throw new IllegalStateException( + "Client tried to select '" + rowKeys + + "' although user selection is disallowed"); + } + List items = new ArrayList(); for (String rowKey : rowKeys) { items.add(getItemId(rowKey)); @@ -1623,6 +1679,12 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, @Override public void deselect(List rowKeys) { + if (!isUserSelectionAllowed()) { + throw new IllegalStateException( + "Client tried to deselect '" + rowKeys + + "' although user selection is disallowed"); + } + List items = new ArrayList(); for (String rowKey : rowKeys) { items.add(getItemId(rowKey)); @@ -1632,11 +1694,21 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, @Override public void selectAll() { + if (!isUserSelectionAllowed()) { + throw new IllegalStateException( + "Client tried to select all although user selection is disallowed"); + } + MultiSelectionModel.this.selectAll(false); } @Override public void deselectAll() { + if (!isUserSelectionAllowed()) { + throw new IllegalStateException( + "Client tried to deselect all although user selection is disallowed"); + } + MultiSelectionModel.this.deselectAll(false); } }); @@ -1920,6 +1992,21 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, protected MultiSelectionModelState getState() { return (MultiSelectionModelState) super.getState(); } + + @Override + protected MultiSelectionModelState getState(boolean markAsDirty) { + return (MultiSelectionModelState) super.getState(markAsDirty); + } + + @Override + public boolean isUserSelectionAllowed() { + return getState(false).userSelectionAllowed; + } + + @Override + public void setUserSelectionAllowed(boolean userSelectionAllowed) { + getState().userSelectionAllowed = userSelectionAllowed; + } } /** @@ -2228,8 +2315,8 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, Item item = cell.getItem(); Property itemProperty = item.getItemProperty(cell.getPropertyId()); - Object modelValue = - itemProperty == null ? null : itemProperty.getValue(); + Object modelValue = itemProperty == null ? null + : itemProperty.getValue(); data.put(columnKeys.key(cell.getPropertyId()), AbstractRenderer .encodeValue(modelValue, renderer, converter, getLocale())); @@ -4563,8 +4650,8 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, private FieldGroup editorFieldGroup = new CustomFieldGroup(); /** - * Poperty ID to Field mapping that stores editor fields set by {@link - * #setEditorField(Object, Field)}. + * Poperty ID to Field mapping that stores editor fields set by + * {@link #setEditorField(Object, Field)}. */ private Map> editorFields = new HashMap>(); @@ -5271,10 +5358,12 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, } /** - * Sets the column resize mode to use. The default mode is {@link ColumnResizeMode#ANIMATED}. + * Sets the column resize mode to use. The default mode is + * {@link ColumnResizeMode#ANIMATED}. * - * @param mode a ColumnResizeMode value - + * @param mode + * a ColumnResizeMode value + * * @since 7.7.5 */ public void setColumnResizeMode(ColumnResizeMode mode) { @@ -5282,7 +5371,8 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, } /** - * Returns the current column resize mode. The default mode is {@link ColumnResizeMode#ANIMATED}. + * Returns the current column resize mode. The default mode is + * {@link ColumnResizeMode#ANIMATED}. * * @return a ColumnResizeMode value * @@ -6873,7 +6963,8 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, Field editor = editorFieldGroup.getField(propertyId); - // If field group has no field for this property, see if we have it stored + // If field group has no field for this property, see if we have it + // stored if (editor == null) { editor = editorFields.get(propertyId); if (editor != null) { @@ -6937,9 +7028,9 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, editorFieldGroup.setItemDataSource(item); for (Column column : getColumns()) { - column.getState().editorConnector = - item.getItemProperty(column.getPropertyId()) == null - ? null : getEditorField(column.getPropertyId()); + column.getState().editorConnector = item + .getItemProperty(column.getPropertyId()) == null ? null + : getEditorField(column.getPropertyId()); } editorActive = true; diff --git a/server/src/test/java/com/vaadin/tests/server/component/grid/MultiSelectionModelTest.java b/server/src/test/java/com/vaadin/tests/server/component/grid/MultiSelectionModelTest.java index f07c740b6c..49856dffa9 100644 --- a/server/src/test/java/com/vaadin/tests/server/component/grid/MultiSelectionModelTest.java +++ b/server/src/test/java/com/vaadin/tests/server/component/grid/MultiSelectionModelTest.java @@ -17,6 +17,7 @@ package com.vaadin.tests.server.component.grid; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.junit.After; @@ -28,8 +29,11 @@ import com.vaadin.data.Container; import com.vaadin.data.util.IndexedContainer; import com.vaadin.event.SelectionEvent; import com.vaadin.event.SelectionEvent.SelectionListener; +import com.vaadin.shared.ui.grid.selection.MultiSelectionModelServerRpc; import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState; +import com.vaadin.ui.ComponentTest; import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.SelectionModel.HasUserSelectionAllowed; public class MultiSelectionModelTest { @@ -187,4 +191,41 @@ public class MultiSelectionModelTest { } Assert.fail("Not all items were correctly selected"); } + + @Test(expected = IllegalStateException.class) + public void refuseSelectWhenUserSelectionDisallowed() { + ((HasUserSelectionAllowed) grid.getSelectionModel()) + .setUserSelectionAllowed(false); + MultiSelectionModelServerRpc serverRpc = ComponentTest.getRpcProxy( + grid.getSelectionModel(), MultiSelectionModelServerRpc.class); + serverRpc.select(Collections.singletonList("a")); + } + + @Test(expected = IllegalStateException.class) + public void refuseDeselectWhenUserSelectionDisallowed() { + ((HasUserSelectionAllowed) grid.getSelectionModel()) + .setUserSelectionAllowed(false); + MultiSelectionModelServerRpc serverRpc = ComponentTest.getRpcProxy( + grid.getSelectionModel(), MultiSelectionModelServerRpc.class); + serverRpc.deselect(Collections.singletonList("a")); + } + + @Test(expected = IllegalStateException.class) + public void refuseSelectAllWhenUserSelectionDisallowed() { + ((HasUserSelectionAllowed) grid.getSelectionModel()) + .setUserSelectionAllowed(false); + MultiSelectionModelServerRpc serverRpc = ComponentTest.getRpcProxy( + grid.getSelectionModel(), MultiSelectionModelServerRpc.class); + serverRpc.selectAll(); + } + + @Test(expected = IllegalStateException.class) + public void refuseDeselectAllWhenUserSelectionDisallowed() { + ((HasUserSelectionAllowed) grid.getSelectionModel()) + .setUserSelectionAllowed(false); + MultiSelectionModelServerRpc serverRpc = ComponentTest.getRpcProxy( + grid.getSelectionModel(), MultiSelectionModelServerRpc.class); + serverRpc.deselectAll(); + } + } diff --git a/server/src/test/java/com/vaadin/tests/server/component/grid/SingleSelectionModelTest.java b/server/src/test/java/com/vaadin/tests/server/component/grid/SingleSelectionModelTest.java index 8b66ab625d..e3331cd0be 100644 --- a/server/src/test/java/com/vaadin/tests/server/component/grid/SingleSelectionModelTest.java +++ b/server/src/test/java/com/vaadin/tests/server/component/grid/SingleSelectionModelTest.java @@ -24,8 +24,11 @@ import com.vaadin.data.Container; import com.vaadin.data.util.IndexedContainer; import com.vaadin.event.SelectionEvent; import com.vaadin.event.SelectionEvent.SelectionListener; +import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc; +import com.vaadin.ui.ComponentTest; import com.vaadin.ui.Grid; import com.vaadin.ui.Grid.SelectionMode; +import com.vaadin.ui.Grid.SelectionModel.HasUserSelectionAllowed; import com.vaadin.ui.Grid.SingleSelectionModel; public class SingleSelectionModelTest { @@ -150,4 +153,14 @@ public class SingleSelectionModelTest { } }); } + + @Test(expected = IllegalStateException.class) + public void refuseSelectionWhenUserSelectionDisallowed() { + ((HasUserSelectionAllowed) grid.getSelectionModel()) + .setUserSelectionAllowed(false); + SingleSelectionModelServerRpc serverRpc = ComponentTest.getRpcProxy( + grid.getSelectionModel(), SingleSelectionModelServerRpc.class); + serverRpc.select("a"); + } + } diff --git a/server/src/test/java/com/vaadin/ui/ComponentTest.java b/server/src/test/java/com/vaadin/ui/ComponentTest.java new file mode 100644 index 0000000000..7077f8b22e --- /dev/null +++ b/server/src/test/java/com/vaadin/ui/ComponentTest.java @@ -0,0 +1,119 @@ +/* + * 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; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashSet; + +import org.junit.Assert; + +import com.vaadin.server.ClientConnector; +import com.vaadin.server.ServerRpcManager; +import com.vaadin.shared.communication.ServerRpc; + +import elemental.json.JsonObject; + +/** + * Base class for component unit tests, providing helper methods for e.g. + * invoking RPC and updating diff state. + */ +public class ComponentTest { + + /** + * Perform operations on the component similar to what would be done when + * the component state is communicated to the client, e.g. update diff state + * and mark as clean. + * + * @param component + * the component to update + */ + public static void syncToClient(AbstractComponent component) { + updateDiffState(component); + component.getUI().getConnectorTracker().markClean(component); + } + + /** + * Checks if the connector has been marked dirty. + * + * @param connector + * the connector to check + * @return true if the connector has been marked dirty, + * false otherwise + */ + public static boolean isDirty(ClientConnector connector) { + return connector.getUI().getConnectorTracker().isDirty(connector); + } + + /** + * Updates the stored diff state from the current component state. + * + * @param rta + * the component to update + */ + public static void updateDiffState(AbstractComponent component) { + component.getUI().getSession().getCommunicationManager() + .encodeState(component, component.getState()); + + } + + /** + * Gets the server rpc handler registered for a component. + * + * @param connector + * the connector which listens to the RPC + * @param serverRpcClass + * the server RPC class + * @return the server RPC handler + */ + public static T getRpcProxy(ClientConnector connector, + Class serverRpcClass) { + try { + ServerRpcManager rpcManager = connector + .getRpcManager(serverRpcClass.getName()); + Method method = ServerRpcManager.class + .getDeclaredMethod("getImplementation"); + method.setAccessible(true); + return serverRpcClass.cast(method.invoke(rpcManager)); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + /** + * Asserts the set of properties that would be sent as state changes for the + * given connector. + * + * @param connector + * the connector that has state changes + * @param message + * the message to show if the properties are not as expected + * @param expectedProperties + * names of the expected properties + */ + public static void assertEncodedStateProperties(ClientConnector connector, + String message, String... expectedProperties) { + assert connector.isAttached(); + + JsonObject encodeState = connector.encodeState(); + + // Collect to HashSet so that order doesn't matter + Assert.assertEquals(message, + new HashSet(Arrays.asList(expectedProperties)), + new HashSet(Arrays.asList(encodeState.keys()))); + } + +} \ No newline at end of file diff --git a/shared/src/main/java/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java b/shared/src/main/java/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java index 35da1d1e0b..3fd685c98e 100644 --- a/shared/src/main/java/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java +++ b/shared/src/main/java/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java @@ -27,5 +27,6 @@ public class MultiSelectionModelState extends SharedState { /* Select All -checkbox status */ public boolean allSelected; + public boolean userSelectionAllowed = true; } diff --git a/shared/src/main/java/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java b/shared/src/main/java/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java index de06dfc61a..6355ddec7e 100644 --- a/shared/src/main/java/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java +++ b/shared/src/main/java/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java @@ -27,4 +27,5 @@ public class SingleSelectionModelState extends SharedState { /* Allow deselecting rows */ public boolean deselectAllowed = true; + public boolean userSelectionAllowed = true; } diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/main/java/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index e3fdd68ed6..6f1986720b 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/main/java/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -75,6 +75,7 @@ import com.vaadin.ui.Grid.RowReference; import com.vaadin.ui.Grid.RowStyleGenerator; import com.vaadin.ui.Grid.SelectionMode; import com.vaadin.ui.Grid.SelectionModel; +import com.vaadin.ui.Grid.SelectionModel.HasUserSelectionAllowed; import com.vaadin.ui.Label; import com.vaadin.ui.Notification; import com.vaadin.ui.Panel; @@ -108,6 +109,7 @@ public class GridBasicFeatures extends AbstractComponentTest { private int containerDelay = 0; private boolean singleSelectAllowDeselect = true; + private boolean allowUserSelection = true; private IndexedContainer ds; private Grid grid; @@ -509,6 +511,9 @@ public class GridBasicFeatures extends AbstractComponentTest { } else { grid.removeSelectionListener(selectionListener); } + + ((HasUserSelectionAllowed) grid.getSelectionModel()) + .setUserSelectionAllowed(allowUserSelection); } }); @@ -804,6 +809,17 @@ public class GridBasicFeatures extends AbstractComponentTest { } } }); + createBooleanAction("Allow user selection", "State", allowUserSelection, + new Command() { + @Override + public void execute(Grid c, Boolean value, Object data) { + allowUserSelection = value.booleanValue(); + + SelectionModel model = c.getSelectionModel(); + ((HasUserSelectionAllowed) model) + .setUserSelectionAllowed(allowUserSelection); + } + }); createBooleanAction("Column Reordering Allowed", "State", false, new Command() { @@ -1268,12 +1284,14 @@ public class GridBasicFeatures extends AbstractComponentTest { } }, null); - createBooleanAction("Simple resize mode", "Columns", false, new Command() { - @Override - public void execute(Grid g, Boolean value, Object data) { - g.setColumnResizeMode(value ? ColumnResizeMode.SIMPLE : ColumnResizeMode.ANIMATED); - } - }); + createBooleanAction("Simple resize mode", "Columns", false, + new Command() { + @Override + public void execute(Grid g, Boolean value, Object data) { + g.setColumnResizeMode(value ? ColumnResizeMode.SIMPLE + : ColumnResizeMode.ANIMATED); + } + }); } private static String getColumnProperty(int c) { diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/basicfeatures/server/GridSelectionTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/basicfeatures/server/GridSelectionTest.java index 642514393e..b185d52c5b 100644 --- a/uitest/src/test/java/com/vaadin/tests/components/grid/basicfeatures/server/GridSelectionTest.java +++ b/uitest/src/test/java/com/vaadin/tests/components/grid/basicfeatures/server/GridSelectionTest.java @@ -483,6 +483,10 @@ public class GridSelectionTest extends GridBasicFeaturesTest { selectMenuPath("Component", "Body rows", "Select first row"); } + private void toggleUserSelectionAllowed() { + selectMenuPath("Component", "State", "Allow user selection"); + } + private GridRowElement getRow(int i) { return getGridElement().getRow(i); } @@ -526,4 +530,157 @@ public class GridSelectionTest extends GridBasicFeaturesTest { getGridElement().getCell(0, 0).click(); assertTrue("row should become selected", getRow(0).isSelected()); } + + @Test + public void singleSelectUserSelectionDisallowedSpaceSelectionNoOp() { + openTestURL(); + setSelectionModelSingle(); + getGridElement().focus(); + getGridElement().sendKeys(Keys.DOWN, Keys.SPACE); + assertTrue("row was selected when selection was allowed", + getRow(1).isSelected()); + toggleUserSelectionAllowed(); + getGridElement().sendKeys(Keys.SPACE); + assertTrue("deselect disallowed", getRow(1).isSelected()); + getGridElement().sendKeys(Keys.DOWN, Keys.SPACE); + assertFalse("select disallowed", getRow(2).isSelected()); + assertTrue("old selection remains", getRow(1).isSelected()); + toggleUserSelectionAllowed(); + getGridElement().sendKeys(Keys.SPACE); + assertTrue("select allowed again", getRow(2).isSelected()); + assertFalse("old selection removed", getRow(1).isSelected()); + + } + + @Test + public void singleSelectUserSelectionDisallowedClickSelectionNoOp() { + openTestURL(); + setSelectionModelSingle(); + getGridElement().getCell(1, 0).click(); + assertTrue("selection allowed, should have been selected", + getRow(1).isSelected()); + toggleUserSelectionAllowed(); + getGridElement().getCell(1, 0).click(); + assertTrue("deselect disallowed, should remain selected", + getRow(1).isSelected()); + getGridElement().getCell(2, 0).click(); + assertFalse("select disallowed, should not have been selected", + getRow(2).isSelected()); + assertTrue("select disallowed, old selection should have remained", + getRow(1).isSelected()); + toggleUserSelectionAllowed(); + getGridElement().getCell(2, 0).click(); + assertTrue("select allowed again, row should have been selected", + getRow(2).isSelected()); + assertFalse("old selection removed", getRow(1).isSelected()); + + } + + @Test + public void multiSelectUserSelectionDisallowedSpaceSelectionNoOp() { + openTestURL(); + setSelectionModelMulti(); + getGridElement().focus(); + getGridElement().sendKeys(Keys.DOWN, Keys.SPACE); + assertTrue("selection allowed, should have been selected", + getRow(1).isSelected()); + toggleUserSelectionAllowed(); + getGridElement().sendKeys(Keys.SPACE); + assertTrue("deselect disallowed, should remain selected", + getRow(1).isSelected()); + getGridElement().sendKeys(Keys.DOWN, Keys.SPACE); + assertFalse("select disallowed, should not have been selected", + getRow(2).isSelected()); + assertTrue("select disallowed, old selection should have remained", + getRow(1).isSelected()); + + toggleUserSelectionAllowed(); + getGridElement().sendKeys(Keys.SPACE); + assertTrue("select allowed again, row should have been selected", + getRow(2).isSelected()); + assertTrue( + "select allowed again but old selection should have remained", + getRow(1).isSelected()); + } + + @Test + public void multiSelectUserSelectionDisallowedCheckboxSelectionNoOp() { + openTestURL(); + setSelectionModelMulti(); + assertTrue(getSelectionCheckbox(0).isEnabled()); + toggleUserSelectionAllowed(); + assertFalse(getSelectionCheckbox(0).isEnabled()); + + // Select by clicking on checkbox (should always fail as it is disabled) + getSelectionCheckbox(0).click(); + assertFalse(getGridElement().getRow(0).isSelected()); + // Select by clicking on cell (should fail) + getGridElement().getCell(0, 0).click(); + assertFalse(getGridElement().getRow(0).isSelected()); + + toggleUserSelectionAllowed(); + assertTrue(getSelectionCheckbox(0).isEnabled()); + getSelectionCheckbox(0).click(); + assertTrue(getGridElement().getRow(0).isSelected()); + } + + @Test + public void multiSelectUserSelectionDisallowedCheckboxSelectAllNoOp() { + openTestURL(); + setSelectionModelMulti(); + + assertTrue(getSelectAllCheckbox().isEnabled()); + toggleUserSelectionAllowed(); + assertFalse(getSelectAllCheckbox().isEnabled()); + + // Select all by clicking on checkbox (should not select) + getSelectAllCheckbox().click(); + assertFalse(getSelectAllCheckbox().isSelected()); + assertFalse(getGridElement().getRow(0).isSelected()); + assertFalse(getGridElement().getRow(10).isSelected()); + + // Select all by clicking on header cell (should not select) + getGridElement().getHeaderCell(0, 0).click(); + assertFalse(getSelectAllCheckbox().isSelected()); + assertFalse(getGridElement().getRow(0).isSelected()); + assertFalse(getGridElement().getRow(10).isSelected()); + + toggleUserSelectionAllowed(); + + assertTrue(getSelectAllCheckbox().isEnabled()); + getSelectAllCheckbox().click(); + assertTrue(getGridElement().getRow(0).isSelected()); + assertTrue(getGridElement().getRow(10).isSelected()); + } + + @Test + public void singleSelectUserSelectionDisallowedServerSelect() { + openTestURL(); + setSelectionModelSingle(); + toggleUserSelectionAllowed(); + + toggleFirstRowSelection(); + assertTrue(getGridElement().getRow(0).isSelected()); + } + + @Test + public void multiSelectUserSelectionDisallowedServerSelect() { + openTestURL(); + setSelectionModelMulti(); + toggleUserSelectionAllowed(); + + toggleFirstRowSelection(); + assertTrue(getGridElement().getRow(0).isSelected()); + } + + private WebElement getSelectAllCheckbox() { + return getGridElement().getHeaderCell(0, 0) + .findElement(By.tagName("input")); + } + + private WebElement getSelectionCheckbox(int row) { + return getGridElement().getCell(row, 0) + .findElement(By.tagName("input")); + } + } -- 2.39.5