diff options
author | Artur <artur@vaadin.com> | 2017-01-30 13:47:55 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-01-30 13:47:55 +0200 |
commit | 131e4f34bddf99d61a76fcd49890490c78c3efa8 (patch) | |
tree | 1276e94ddbec6a8fc612bedf87835cd366e5f0a5 /server | |
parent | 870a4d9ab386de41b0c5e344d16ebfd9c13b2951 (diff) | |
download | vaadin-framework-131e4f34bddf99d61a76fcd49890490c78c3efa8.tar.gz vaadin-framework-131e4f34bddf99d61a76fcd49890490c78c3efa8.zip |
Make it possible to disallow user selection in Grid (#8144)
Fixes #7880
Diffstat (limited to 'server')
4 files changed, 282 insertions, 18 deletions
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<Field<?>> fields, - Item itemDataSource) { + private void bindFields(List<Field<?>> 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 <code>true</code> if the user is allowed to change the + * selection, <code>false</code> otherwise + */ + public boolean isUserSelectionAllowed(); + + /** + * Sets whether the user is allowed to change the selection. + * + * @param userSelectionAllowed + * <code>true</code> if the user is allowed to change the + * selection, <code>false</code> 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<String> rowKeys) { + if (!isUserSelectionAllowed()) { + throw new IllegalStateException( + "Client tried to select '" + rowKeys + + "' although user selection is disallowed"); + } + List<Object> items = new ArrayList<Object>(); for (String rowKey : rowKeys) { items.add(getItemId(rowKey)); @@ -1623,6 +1679,12 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, @Override public void deselect(List<String> rowKeys) { + if (!isUserSelectionAllowed()) { + throw new IllegalStateException( + "Client tried to deselect '" + rowKeys + + "' although user selection is disallowed"); + } + List<Object> items = new ArrayList<Object>(); 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<Object, Field<?>> editorFields = new HashMap<Object, Field<?>>(); @@ -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 <code>true</code> if the connector has been marked dirty, + * <code>false</code> 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 extends ServerRpc> T getRpcProxy(ClientConnector connector, + Class<T> 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<String>(Arrays.asList(expectedProperties)), + new HashSet<String>(Arrays.asList(encodeState.keys()))); + } + +}
\ No newline at end of file |