]> source.dussan.org Git - vaadin-framework.git/commitdiff
Clean up old Grid selection models
authorTeemu Suo-Anttila <teemusa@vaadin.com>
Thu, 1 Sep 2016 07:31:19 +0000 (10:31 +0300)
committerTeemu Suo-Anttila <teemusa@vaadin.com>
Tue, 6 Sep 2016 07:38:26 +0000 (10:38 +0300)
This patch removes old Grid selection model APIs
in favor of the new common SelectionModel API.

Change-Id: Iab8f2921930a575012c7da6226811d14a7145271

24 files changed:
client/src/main/java/com/vaadin/client/connectors/AbstractListingConnector.java
client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java
client/src/main/java/com/vaadin/client/connectors/selection/AbstractSelectionConnector.java
client/src/main/java/com/vaadin/client/connectors/selection/SingleSelectionConnector.java
client/src/main/java/com/vaadin/client/widget/grid/AutoScroller.java
client/src/main/java/com/vaadin/client/widget/grid/datasources/ListDataSource.java
client/src/main/java/com/vaadin/client/widget/grid/events/SelectAllEvent.java
client/src/main/java/com/vaadin/client/widget/grid/selection/AbstractRowHandleSelectionModel.java [deleted file]
client/src/main/java/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java
client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModel.java [deleted file]
client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelMulti.java [deleted file]
client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelNone.java [deleted file]
client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelSingle.java [deleted file]
client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelWithSelectionColumn.java [new file with mode: 0644]
client/src/main/java/com/vaadin/client/widgets/Grid.java
server/src/main/java/com/vaadin/data/selection/SingleSelection.java
server/src/main/java/com/vaadin/server/data/DataCommunicator.java
server/src/main/java/com/vaadin/ui/AbstractListing.java
server/src/main/java/com/vaadin/ui/Grid.java
server/src/test/java/com/vaadin/tests/components/grid/GridSelectionTest.java [new file with mode: 0644]
shared/src/main/java/com/vaadin/shared/data/selection/SelectionModel.java
uitest/src/main/java/com/vaadin/tests/components/grid/basics/DataObject.java
uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java
uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicSelectionTest.java [new file with mode: 0644]

index c00807ed2c6273d21a8e703bff24cc8f43d50449..938df199da0d7d0202aba8a3d3ae26fa2f1c40d4 100644 (file)
@@ -33,7 +33,7 @@ public abstract class AbstractListingConnector
 
     private DataSource<JsonObject> dataSource = null;
 
-    private SelectionModel<String> selectionModel = null;
+    private SelectionModel<JsonObject> selectionModel = null;
 
     @Override
     public void setDataSource(DataSource<JsonObject> dataSource) {
@@ -47,15 +47,15 @@ public abstract class AbstractListingConnector
 
     /**
      * Sets the selection model to use. Passing {@code null} disables selection.
-     * 
+     *
      * @param selectionModel
      *            the selection model or null to disable
      */
-    public void setSelectionModel(SelectionModel<String> selectionModel) {
+    public void setSelectionModel(SelectionModel<JsonObject> selectionModel) {
         this.selectionModel = selectionModel;
     }
 
-    public SelectionModel<String> getSelectionModel() {
+    public SelectionModel<JsonObject> getSelectionModel() {
         return selectionModel;
     }
 }
index 2259f47647d3766cb4f1dd599fee0b06e8a7099a..31f6cc38ab89dcf5bf16ba110093c58862d9de5a 100644 (file)
@@ -31,10 +31,13 @@ import com.vaadin.client.connectors.AbstractListingConnector;
 import com.vaadin.client.data.DataSource;
 import com.vaadin.client.ui.SimpleManagedLayout;
 import com.vaadin.client.widget.grid.selection.ClickSelectHandler;
+import com.vaadin.client.widget.grid.selection.SpaceSelectHandler;
 import com.vaadin.client.widget.grid.sort.SortEvent;
 import com.vaadin.client.widget.grid.sort.SortOrder;
 import com.vaadin.client.widgets.Grid;
 import com.vaadin.client.widgets.Grid.Column;
+import com.vaadin.shared.data.selection.SelectionModel;
+import com.vaadin.shared.data.selection.SelectionModel.Single;
 import com.vaadin.shared.data.sort.SortDirection;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.grid.GridServerRpc;
@@ -56,6 +59,8 @@ public class GridConnector extends AbstractListingConnector
     private Map<Column<?, JsonObject>, String> columnToIdMap = new HashMap<>();
     /* Child component list for HasComponentsConnector */
     private List<ComponentConnector> childComponents;
+    private SpaceSelectHandler<JsonObject> spaceSelectHandler;
+    private ClickSelectHandler<JsonObject> clickSelectHandler;
 
     @Override
     public Grid<JsonObject> getWidget() {
@@ -66,7 +71,8 @@ public class GridConnector extends AbstractListingConnector
     protected void init() {
         super.init();
 
-        new ClickSelectHandler<>(getWidget());
+        // Default selection style is space key.
+        spaceSelectHandler = new SpaceSelectHandler<JsonObject>(getWidget());
         getWidget().addSortHandler(this::handleSortEvent);
 
         layout();
@@ -74,9 +80,23 @@ public class GridConnector extends AbstractListingConnector
 
     @Override
     public void setDataSource(DataSource<JsonObject> dataSource) {
+        super.setDataSource(dataSource);
         getWidget().setDataSource(dataSource);
     }
 
+    @Override
+    public void setSelectionModel(SelectionModel<JsonObject> selectionModel) {
+        removeClickHandler();
+
+        super.setSelectionModel(selectionModel);
+        getWidget().setSelectionModel(selectionModel);
+
+        if (selectionModel instanceof Single) {
+            // Single selection should be moved by a click.
+            clickSelectHandler = new ClickSelectHandler<>(getWidget());
+        }
+    }
+
     /**
      * Adds a column to the Grid widget. For each column a communication id
      * stored for client to server communication.
@@ -112,6 +132,12 @@ public class GridConnector extends AbstractListingConnector
         super.onUnregister();
 
         columnToIdMap.clear();
+        removeClickHandler();
+
+        if (spaceSelectHandler != null) {
+            spaceSelectHandler.removeHandler();
+            spaceSelectHandler = null;
+        }
     }
 
     @Override
@@ -177,4 +203,11 @@ public class GridConnector extends AbstractListingConnector
     public GridState getState() {
         return (GridState) super.getState();
     }
+
+    private void removeClickHandler() {
+        if (clickSelectHandler != null) {
+            clickSelectHandler.removeHandler();
+            clickSelectHandler = null;
+        }
+    }
 }
index b295fb0cc366dd88766462e5297da5ed4a6a8cd1..7edc944a224de03a86c22d9fa8dad215c693b4d3 100644 (file)
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -18,19 +18,22 @@ package com.vaadin.client.connectors.selection;
 import com.vaadin.client.ServerConnector;
 import com.vaadin.client.connectors.AbstractListingConnector;
 import com.vaadin.client.extensions.AbstractExtensionConnector;
+import com.vaadin.shared.data.DataCommunicatorConstants;
 import com.vaadin.shared.data.selection.SelectionModel;
 
+import elemental.json.JsonObject;
+
 /**
  * The client-side connector for selection extensions.
- * 
+ *
  * @author Vaadin Ltd.
- * 
+ *
  * @since
  */
-public abstract class AbstractSelectionConnector extends
-        AbstractExtensionConnector {
+public abstract class AbstractSelectionConnector
+        extends AbstractExtensionConnector {
 
-    private SelectionModel<String> model = null;
+    private SelectionModel<JsonObject> model = null;
 
     @Override
     protected void extend(ServerConnector target) {
@@ -45,10 +48,10 @@ public abstract class AbstractSelectionConnector extends
 
     /**
      * Creates a selection model object to be used by the Connector.
-     * 
+     *
      * @return created selection model
      */
-    protected abstract SelectionModel<String> createSelectionModel();
+    protected abstract SelectionModel<JsonObject> createSelectionModel();
 
     @Override
     public AbstractListingConnector getParent() {
@@ -57,10 +60,41 @@ public abstract class AbstractSelectionConnector extends
 
     /**
      * Returns the client-side selection model associated with this connector.
-     * 
+     *
      * @return the selection model in use
      */
-    protected SelectionModel<String> getSelectionModel() {
+    protected SelectionModel<JsonObject> getSelectionModel() {
         return model;
     }
+
+    /**
+     * Gets the selected state from a given json object. This is a helper method
+     * for selection model connectors.
+     *
+     * @param item
+     *            a json object
+     * @return {@code true} if the json object is marked as selected;
+     *         {@code false} if not
+     */
+    public static boolean isItemSelected(JsonObject item) {
+        return item.hasKey(DataCommunicatorConstants.SELECTED)
+                && item.getBoolean(DataCommunicatorConstants.SELECTED);
+    }
+
+    /**
+     * Gets the item key from given json object. This is a helper method for
+     * selection model connectors.
+     *
+     * @param item
+     *            a json object
+     * @return item key; {@code null} if there is no key
+     */
+    public static String getKey(JsonObject item) {
+        if (item.hasKey(DataCommunicatorConstants.KEY)) {
+            return item.getString(DataCommunicatorConstants.KEY);
+        } else {
+            return null;
+        }
+    }
+
 }
index 70844d1ae756b6b520300a7835244acec2ff1e49..912b20310e4c3d4a13ad33d7d5c4033cbff1f74e 100644 (file)
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -23,18 +23,19 @@ import com.vaadin.shared.data.selection.SelectionModel;
 import com.vaadin.shared.data.selection.SelectionServerRpc;
 import com.vaadin.shared.ui.Connect;
 
+import elemental.json.JsonObject;
+
 /**
  * A connector for single selection extensions.
- * 
+ *
  * @author Vaadin Ltd.
  */
 @Connect(com.vaadin.data.selection.SingleSelection.class)
 public class SingleSelectionConnector extends AbstractSelectionConnector {
 
-    private static class SingleSelection implements
-            SelectionModel.Single<String> {
+    private static class SingleSelection
+            implements SelectionModel.Single<JsonObject> {
 
-        private String value;
         private SelectionServerRpc rpc;
 
         SingleSelection(SelectionServerRpc rpc) {
@@ -42,29 +43,28 @@ public class SingleSelectionConnector extends AbstractSelectionConnector {
         }
 
         @Override
-        public void select(String item) {
-            if (item != null && !item.equals(value)) {
-                rpc.select(item);
-                value = item;
+        public void select(JsonObject item) {
+            if (!isSelected(item)) {
+                rpc.select(getKey(item));
             }
         }
 
         @Override
-        public void deselect(String item) {
-            if (item != null && item.equals(value)) {
-                rpc.deselect(item);
-                value = null;
+        public void deselect(JsonObject item) {
+            if (isSelected(item)) {
+                rpc.deselect(getKey(item));
             }
         }
 
         @Override
-        public boolean isSelected(String item) {
-            return value != null && value.equals(item);
+        public boolean isSelected(JsonObject item) {
+            return isItemSelected(item);
         }
 
         @Override
-        public Optional<String> getSelectedItem() {
-            return Optional.ofNullable(value);
+        public Optional<JsonObject> getSelectedItem() {
+            throw new UnsupportedOperationException(
+                    "A client-side selection model does not know the full selection");
         }
     }
 
@@ -85,7 +85,7 @@ public class SingleSelectionConnector extends AbstractSelectionConnector {
     }
 
     @Override
-    protected SelectionModel<String> createSelectionModel() {
+    protected SelectionModel<JsonObject> createSelectionModel() {
         return new SingleSelection(getRpcProxy(SelectionServerRpc.class));
     }
 }
index db77701b92bcbc75ca17de40b6a64f4143547248..826105d6237572fcb4af2f0b0ae57ae85181a09b 100644 (file)
@@ -27,6 +27,7 @@ import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.Event.NativePreviewEvent;
 import com.google.gwt.user.client.Event.NativePreviewHandler;
 import com.vaadin.client.WidgetUtil;
+import com.vaadin.client.widget.grid.selection.SelectionModelWithSelectionColumn;
 import com.vaadin.client.widgets.Grid;
 
 /**
@@ -625,8 +626,8 @@ public class AutoScroller {
     private int getRealFrozenColumnCount() {
         if (grid.getFrozenColumnCount() < 0) {
             return 0;
-        } else if (grid.getSelectionModel()
-                .getSelectionColumnRenderer() != null) {
+        } else if (grid
+                .getSelectionModel() instanceof SelectionModelWithSelectionColumn) {
             // includes the selection column
             return grid.getFrozenColumnCount() + 1;
         } else {
index b5b6f42f3a04ac438887a27a7060f58704fa962c..9767a230bbe8791ef7a8cf15354ec98ae7afb22f 100644 (file)
@@ -452,7 +452,7 @@ public class ListDataSource<T> implements DataSource<T> {
         return new SelectAllHandler<T>() {
             @Override
             public void onSelectAll(SelectAllEvent<T> event) {
-                event.getSelectionModel().select(asList());
+                asList().forEach(event.getSelectionModel()::select);
             }
         };
     }
@@ -461,5 +461,4 @@ public class ListDataSource<T> implements DataSource<T> {
         Set<DataChangeHandler> copy = new LinkedHashSet<>(changeHandlers);
         return copy.stream();
     }
-
 }
index a21adfd2a8c938a25611b46b993a9d0254578555..cd86dbe99313d2b9413046ab319bbdc559824f0a 100644 (file)
@@ -16,7 +16,7 @@
 package com.vaadin.client.widget.grid.events;
 
 import com.google.gwt.event.shared.GwtEvent;
-import com.vaadin.client.widget.grid.selection.SelectionModel;
+import com.vaadin.shared.data.selection.SelectionModel;
 
 /**
  * A select all event, fired by the Grid when it needs all rows in data source
diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/AbstractRowHandleSelectionModel.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/AbstractRowHandleSelectionModel.java
deleted file mode 100644 (file)
index 9bc5397..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.client.widget.grid.selection;
-
-import com.vaadin.client.data.DataSource.RowHandle;
-
-/**
- * An abstract class that adds a consistent API for common methods that's needed
- * by Vaadin's server-based selection models to work.
- * <p>
- * <em>Note:</em> This should be an interface instead of an abstract class, if
- * only we could define protected methods in an interface.
- *
- * @author Vaadin Ltd
- * @param <T>
- *            The grid's row type
- * @since 7.4
- */
-public abstract class AbstractRowHandleSelectionModel<T>
-        implements SelectionModel<T> {
-    /**
-     * Select a row, based on its
-     * {@link com.vaadin.client.data.DataSource.RowHandle RowHandle}.
-     * <p>
-     * <em>Note:</em> this method may not fire selection change events.
-     *
-     * @param handle
-     *            the handle to select by
-     * @return <code>true</code> iff the selection state was changed by this
-     *         call
-     * @throws UnsupportedOperationException
-     *             if the selection model does not support either handles or
-     *             selection
-     */
-    protected abstract boolean selectByHandle(RowHandle<T> handle);
-
-    /**
-     * Deselect a row, based on its
-     * {@link com.vaadin.client.data.DataSource.RowHandle RowHandle}.
-     * <p>
-     * <em>Note:</em> this method may not fire selection change events.
-     *
-     * @param handle
-     *            the handle to deselect by
-     * @return <code>true</code> iff the selection state was changed by this
-     *         call
-     * @throws UnsupportedOperationException
-     *             if the selection model does not support either handles or
-     *             deselection
-     */
-    protected abstract boolean deselectByHandle(RowHandle<T> handle)
-            throws UnsupportedOperationException;
-}
index 2ae8b0579d232767d10b6c71e1251504e30c6e3c..e1f9ebb18ed1703198d702a1f2924d894644d6a8 100644 (file)
@@ -44,7 +44,6 @@ import com.vaadin.client.widget.grid.CellReference;
 import com.vaadin.client.widget.grid.RendererCellReference;
 import com.vaadin.client.widget.grid.events.GridEnabledEvent;
 import com.vaadin.client.widget.grid.events.GridEnabledHandler;
-import com.vaadin.client.widget.grid.selection.SelectionModel.Multi.Batched;
 import com.vaadin.client.widgets.Grid;
 
 /**
@@ -523,13 +522,6 @@ public class MultiSelectionRenderer<T>
         private int gradientArea;
 
         public void start(int logicalRowIndex) {
-
-            SelectionModel<T> model = grid.getSelectionModel();
-            if (model instanceof Batched) {
-                Batched<?> batchedModel = (Batched<?>) model;
-                batchedModel.startBatchSelect();
-            }
-
             /*
              * bounds are updated whenever the autoscroll cycle starts, to make
              * sure that the widget hasn't changed in size, moved around, or
@@ -576,12 +568,6 @@ public class MultiSelectionRenderer<T>
                 autoScroller = null;
             }
 
-            SelectionModel<T> model = grid.getSelectionModel();
-            if (model instanceof Batched) {
-                Batched<?> batchedModel = (Batched<?>) model;
-                batchedModel.commitBatchSelect();
-            }
-
             removeNativeHandler();
         }
     }
diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModel.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModel.java
deleted file mode 100644 (file)
index 3d2c7d4..0000000
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.client.widget.grid.selection;
-
-import java.util.Collection;
-
-import com.vaadin.client.renderers.Renderer;
-import com.vaadin.client.widgets.Grid;
-
-/**
- * Common interface for all selection models.
- * <p>
- * Selection models perform tracking of selected rows in the Grid, as well as
- * dispatching events when the selection state changes.
- *
- * @author Vaadin Ltd
- * @param <T>
- *            Grid's row type
- * @since 7.4
- */
-public interface SelectionModel<T> {
-
-    /**
-     * Return true if the provided row is considered selected under the
-     * implementing selection model.
-     *
-     * @param row
-     *            row object instance
-     * @return <code>true</code>, if the row given as argument is considered
-     *         selected.
-     */
-    public boolean isSelected(T row);
-
-    /**
-     * Return the {@link Renderer} responsible for rendering the selection
-     * column.
-     *
-     * @return a renderer instance. If null is returned, a selection column will
-     *         not be drawn.
-     */
-    public Renderer<Boolean> getSelectionColumnRenderer();
-
-    /**
-     * Tells this SelectionModel which Grid it belongs to.
-     * <p>
-     * Implementations are free to have this be a no-op. This method is called
-     * internally by Grid.
-     *
-     * @param grid
-     *            a {@link Grid} instance; <code>null</code> when removing from
-     *            Grid
-     */
-    public void setGrid(Grid<T> grid);
-
-    /**
-     * Resets the SelectionModel to the initial state.
-     * <p>
-     * This method can be called internally, for example, when the attached
-     * Grid's data source changes.
-     */
-    public void reset();
-
-    /**
-     * Returns a Collection containing all selected rows.
-     *
-     * @return a non-null collection.
-     */
-    public Collection<T> getSelectedRows();
-
-    /**
-     * Selection model that allows a maximum of one row to be selected at any
-     * one time.
-     *
-     * @param <T>
-     *            type parameter corresponding with Grid row type
-     */
-    public interface Single<T> extends SelectionModel<T> {
-
-        /**
-         * Selects a row.
-         *
-         * @param row
-         *            a {@link Grid} row object
-         * @return true, if this row as not previously selected.
-         */
-        public boolean select(T row);
-
-        /**
-         * Deselects a row.
-         * <p>
-         * This is a no-op unless {@link row} is the currently selected row.
-         *
-         * @param row
-         *            a {@link Grid} row object
-         * @return true, if the currently selected row was deselected.
-         */
-        public boolean deselect(T row);
-
-        /**
-         * Returns the currently selected row.
-         *
-         * @return a {@link Grid} row object or null, if nothing is selected.
-         */
-        public T getSelectedRow();
-
-        /**
-         * Sets whether it's allowed to deselect the selected row through the
-         * UI. Deselection is allowed by default.
-         *
-         * @param deselectAllowed
-         *            <code>true</code> if the selected row can be deselected
-         *            without selecting another row instead; otherwise
-         *            <code>false</code>.
-         */
-        public void setDeselectAllowed(boolean deselectAllowed);
-
-        /**
-         * Sets whether it's allowed to deselect the selected row through the
-         * UI.
-         *
-         * @return <code>true</code> if deselection is allowed; otherwise
-         *         <code>false</code>
-         */
-        public boolean isDeselectAllowed();
-
-    }
-
-    /**
-     * Selection model that allows for several rows to be selected at once.
-     *
-     * @param <T>
-     *            type parameter corresponding with Grid row type
-     */
-    public interface Multi<T> extends SelectionModel<T> {
-
-        /**
-         * A multi selection model that can send selections and deselections in
-         * a batch, instead of committing them one-by-one.
-         *
-         * @param <T>
-         *            type parameter corresponding with Grid row type
-         */
-        public interface Batched<T> extends Multi<T> {
-            /**
-             * Starts a batch selection.
-             * <p>
-             * Any commands to any select or deselect method will be batched
-             * into one, and a final selection event will be fired when
-             * {@link #commitBatchSelect()} is called.
-             * <p>
-             * <em>Note:</em> {@link SelectionEvent SelectionChangeEvents} will
-             * still be fired for each selection/deselection. You should check
-             * whether the event is a part of a batch or not with
-             * {@link SelectionEvent#isBatchedSelection()}.
-             */
-            public void startBatchSelect();
-
-            /**
-             * Commits and ends a batch selection.
-             * <p>
-             * Any and all selections and deselections since the last invocation
-             * of {@link #startBatchSelect()} will be fired at once as one
-             * collated {@link SelectionEvent}.
-             */
-            public void commitBatchSelect();
-
-            /**
-             * Checks whether or not a batch has been started.
-             *
-             * @return <code>true</code> iff a batch has been started
-             */
-            public boolean isBeingBatchSelected();
-
-            /**
-             * Gets all the rows that would become selected in this batch.
-             *
-             * @return a collection of the rows that would become selected
-             */
-            public Collection<T> getSelectedRowsBatch();
-
-            /**
-             * Gets all the rows that would become deselected in this batch.
-             *
-             * @return a collection of the rows that would become deselected
-             */
-            public Collection<T> getDeselectedRowsBatch();
-        }
-
-        /**
-         * Selects one or more rows.
-         *
-         * @param rows
-         *            {@link Grid} row objects
-         * @return true, if the set of selected rows was changed.
-         */
-        public boolean select(T... rows);
-
-        /**
-         * Deselects one or more rows.
-         *
-         * @param rows
-         *            Grid row objects
-         * @return true, if the set of selected rows was changed.
-         */
-        public boolean deselect(T... rows);
-
-        /**
-         * De-selects all rows.
-         *
-         * @return true, if any row was previously selected.
-         */
-        public boolean deselectAll();
-
-        /**
-         * Select all rows in a {@link Collection}.
-         *
-         * @param rows
-         *            a collection of Grid row objects
-         * @return true, if the set of selected rows was changed.
-         */
-        public boolean select(Collection<T> rows);
-
-        /**
-         * Deselect all rows in a {@link Collection}.
-         *
-         * @param rows
-         *            a collection of Grid row objects
-         * @return true, if the set of selected rows was changed.
-         */
-        public boolean deselect(Collection<T> rows);
-
-    }
-
-    /**
-     * Interface for a selection model that does not allow anything to be
-     * selected.
-     *
-     * @param <T>
-     *            type parameter corresponding with Grid row type
-     */
-    public interface None<T> extends SelectionModel<T> {
-
-    }
-
-}
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
deleted file mode 100644 (file)
index 3c39556..0000000
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.client.widget.grid.selection;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-import com.vaadin.client.data.DataSource.RowHandle;
-import com.vaadin.client.renderers.Renderer;
-import com.vaadin.client.widgets.Grid;
-
-/**
- * Multi-row selection model.
- *
- * @author Vaadin Ltd
- * @since 7.4
- */
-public class SelectionModelMulti<T> extends AbstractRowHandleSelectionModel<T>
-        implements SelectionModel.Multi.Batched<T> {
-
-    private final LinkedHashSet<RowHandle<T>> selectedRows;
-    private Renderer<Boolean> renderer;
-    private Grid<T> grid;
-
-    private boolean batchStarted = false;
-    private final LinkedHashSet<RowHandle<T>> selectionBatch = new LinkedHashSet<>();
-    private final LinkedHashSet<RowHandle<T>> deselectionBatch = new LinkedHashSet<>();
-
-    /* Event handling for selection with space key */
-    private SpaceSelectHandler<T> spaceSelectHandler;
-
-    public SelectionModelMulti() {
-        grid = null;
-        renderer = null;
-        selectedRows = new LinkedHashSet<>();
-    }
-
-    @Override
-    public boolean isSelected(T row) {
-        return isSelectedByHandle(grid.getDataSource().getHandle(row));
-    }
-
-    @Override
-    public Renderer<Boolean> getSelectionColumnRenderer() {
-        return renderer;
-    }
-
-    @Override
-    public void setGrid(Grid<T> grid) {
-        if (this.grid != null && grid != null) {
-            // Trying to replace grid
-            throw new IllegalStateException(
-                    "Selection model is already attached to a grid. "
-                            + "Remove the selection model first from "
-                            + "the grid and then add it.");
-        }
-
-        this.grid = grid;
-        if (this.grid != null) {
-            spaceSelectHandler = new SpaceSelectHandler<>(grid);
-            this.renderer = new MultiSelectionRenderer<>(grid);
-        } else {
-            spaceSelectHandler.removeHandler();
-            spaceSelectHandler = null;
-            this.renderer = null;
-        }
-
-    }
-
-    @Override
-    public boolean select(T... rows) {
-        if (rows == null) {
-            throw new IllegalArgumentException("Rows cannot be null");
-        }
-        return select(Arrays.asList(rows));
-    }
-
-    @Override
-    public boolean deselect(T... rows) {
-        if (rows == null) {
-            throw new IllegalArgumentException("Rows cannot be null");
-        }
-        return deselect(Arrays.asList(rows));
-    }
-
-    @Override
-    public boolean deselectAll() {
-        if (selectedRows.size() > 0) {
-
-            @SuppressWarnings("unchecked")
-            final LinkedHashSet<RowHandle<T>> selectedRowsClone = (LinkedHashSet<RowHandle<T>>) selectedRows
-                    .clone();
-            SelectionEvent<T> event = new SelectionEvent<>(grid, null,
-                    getSelectedRows(), isBeingBatchSelected());
-            selectedRows.clear();
-
-            if (isBeingBatchSelected()) {
-                selectionBatch.clear();
-                deselectionBatch.clear();
-                deselectionBatch.addAll(selectedRowsClone);
-            }
-
-            grid.fireEvent(event);
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean select(Collection<T> rows) {
-        if (rows == null) {
-            throw new IllegalArgumentException("Rows cannot be null");
-        }
-
-        Set<T> added = new LinkedHashSet<>();
-
-        for (T row : rows) {
-            RowHandle<T> handle = grid.getDataSource().getHandle(row);
-            if (selectByHandle(handle)) {
-                added.add(row);
-            }
-        }
-
-        if (added.size() > 0) {
-            grid.fireEvent(new SelectionEvent<>(grid, added, null,
-                    isBeingBatchSelected()));
-
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean deselect(Collection<T> rows) {
-        if (rows == null) {
-            throw new IllegalArgumentException("Rows cannot be null");
-        }
-
-        Set<T> removed = new LinkedHashSet<>();
-
-        for (T row : rows) {
-            RowHandle<T> handle = grid.getDataSource().getHandle(row);
-            if (deselectByHandle(handle)) {
-                removed.add(row);
-            }
-        }
-
-        if (removed.size() > 0) {
-            grid.fireEvent(new SelectionEvent<>(grid, null, removed,
-                    isBeingBatchSelected()));
-            return true;
-        }
-        return false;
-    }
-
-    protected boolean isSelectedByHandle(RowHandle<T> handle) {
-        return selectedRows.contains(handle);
-    }
-
-    @Override
-    protected boolean selectByHandle(RowHandle<T> handle) {
-        if (selectedRows.add(handle)) {
-            handle.pin();
-
-            if (isBeingBatchSelected()) {
-                deselectionBatch.remove(handle);
-                selectionBatch.add(handle);
-            }
-
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    protected boolean deselectByHandle(RowHandle<T> handle) {
-        if (selectedRows.remove(handle)) {
-
-            if (!isBeingBatchSelected()) {
-                handle.unpin();
-            } else {
-                selectionBatch.remove(handle);
-                deselectionBatch.add(handle);
-            }
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public Collection<T> getSelectedRows() {
-        Set<T> selected = new LinkedHashSet<>();
-        for (RowHandle<T> handle : selectedRows) {
-            selected.add(handle.getRow());
-        }
-        return Collections.unmodifiableSet(selected);
-    }
-
-    @Override
-    public void reset() {
-        deselectAll();
-    }
-
-    @Override
-    public void startBatchSelect() {
-        assert !isBeingBatchSelected() : "Batch has already been started";
-        batchStarted = true;
-    }
-
-    @Override
-    public void commitBatchSelect() {
-        assert isBeingBatchSelected() : "Batch was never started";
-        if (!isBeingBatchSelected()) {
-            return;
-        }
-
-        batchStarted = false;
-
-        final Collection<T> added = getSelectedRowsBatch();
-        selectionBatch.clear();
-
-        final Collection<T> removed = getDeselectedRowsBatch();
-
-        // unpin deselected rows
-        for (RowHandle<T> handle : deselectionBatch) {
-            handle.unpin();
-        }
-        deselectionBatch.clear();
-
-        grid.fireEvent(new SelectionEvent<>(grid, added, removed,
-                isBeingBatchSelected()));
-    }
-
-    @Override
-    public boolean isBeingBatchSelected() {
-        return batchStarted;
-    }
-
-    @Override
-    public Collection<T> getSelectedRowsBatch() {
-        return rowHandlesToRows(selectionBatch);
-    }
-
-    @Override
-    public Collection<T> getDeselectedRowsBatch() {
-        return rowHandlesToRows(deselectionBatch);
-    }
-
-    private ArrayList<T> rowHandlesToRows(Collection<RowHandle<T>> rowHandles) {
-        ArrayList<T> rows = new ArrayList<>(rowHandles.size());
-        for (RowHandle<T> handle : rowHandles) {
-            rows.add(handle.getRow());
-        }
-        return rows;
-    }
-}
diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelNone.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelNone.java
deleted file mode 100644 (file)
index 3aca419..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.client.widget.grid.selection;
-
-import java.util.Collection;
-import java.util.Collections;
-
-import com.vaadin.client.data.DataSource.RowHandle;
-import com.vaadin.client.renderers.Renderer;
-import com.vaadin.client.widgets.Grid;
-
-/**
- * No-row selection model.
- *
- * @author Vaadin Ltd
- * @since 7.4
- */
-public class SelectionModelNone<T> extends AbstractRowHandleSelectionModel<T>
-        implements SelectionModel.None<T> {
-
-    @Override
-    public boolean isSelected(T row) {
-        return false;
-    }
-
-    @Override
-    public Renderer<Boolean> getSelectionColumnRenderer() {
-        return null;
-    }
-
-    @Override
-    public void setGrid(Grid<T> grid) {
-        // noop
-    }
-
-    @Override
-    public void reset() {
-        // noop
-    }
-
-    @Override
-    public Collection<T> getSelectedRows() {
-        return Collections.emptySet();
-    }
-
-    @Override
-    protected boolean selectByHandle(RowHandle<T> handle)
-            throws UnsupportedOperationException {
-        throw new UnsupportedOperationException(
-                "This selection model " + "does not support selection");
-    }
-
-    @Override
-    protected boolean deselectByHandle(RowHandle<T> handle)
-            throws UnsupportedOperationException {
-        throw new UnsupportedOperationException(
-                "This selection model " + "does not support deselection");
-    }
-
-}
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
deleted file mode 100644 (file)
index 1214e3f..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright 2000-2016 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.client.widget.grid.selection;
-
-import java.util.Collection;
-import java.util.Collections;
-
-import com.vaadin.client.data.DataSource.RowHandle;
-import com.vaadin.client.renderers.Renderer;
-import com.vaadin.client.widgets.Grid;
-
-/**
- * Single-row selection model.
- *
- * @author Vaadin Ltd
- * @since 7.4
- */
-public class SelectionModelSingle<T> extends AbstractRowHandleSelectionModel<T>
-        implements SelectionModel.Single<T> {
-
-    private Grid<T> grid;
-    private RowHandle<T> selectedRow;
-
-    /** Event handling for selection with space key */
-    private SpaceSelectHandler<T> spaceSelectHandler;
-
-    /** Event handling for selection by clicking cells */
-    private ClickSelectHandler<T> clickSelectHandler;
-
-    private boolean deselectAllowed = true;
-
-    @Override
-    public boolean isSelected(T row) {
-        return selectedRow != null
-                && selectedRow.equals(grid.getDataSource().getHandle(row));
-    }
-
-    @Override
-    public Renderer<Boolean> getSelectionColumnRenderer() {
-        // No Selection column renderer for single selection
-        return null;
-    }
-
-    @Override
-    public void setGrid(Grid<T> grid) {
-        if (this.grid != null && grid != null) {
-            // Trying to replace grid
-            throw new IllegalStateException(
-                    "Selection model is already attached to a grid. "
-                            + "Remove the selection model first from "
-                            + "the grid and then add it.");
-        }
-
-        this.grid = grid;
-        if (this.grid != null) {
-            spaceSelectHandler = new SpaceSelectHandler<>(grid);
-            clickSelectHandler = new ClickSelectHandler<>(grid);
-            updateHandlerDeselectAllowed();
-        } else {
-            spaceSelectHandler.removeHandler();
-            clickSelectHandler.removeHandler();
-            spaceSelectHandler = null;
-            clickSelectHandler = null;
-        }
-    }
-
-    @Override
-    public boolean select(T row) {
-
-        if (row == null) {
-            throw new IllegalArgumentException("Row cannot be null");
-        }
-
-        T removed = getSelectedRow();
-        if (selectByHandle(grid.getDataSource().getHandle(row))) {
-            grid.fireEvent(new SelectionEvent<>(grid, row, removed, false));
-
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean deselect(T row) {
-
-        if (row == null) {
-            throw new IllegalArgumentException("Row cannot be null");
-        }
-
-        if (isSelected(row)) {
-            deselectByHandle(selectedRow);
-            grid.fireEvent(new SelectionEvent<>(grid, null, row, false));
-            return true;
-        }
-
-        return false;
-    }
-
-    @Override
-    public T getSelectedRow() {
-        return (selectedRow != null ? selectedRow.getRow() : null);
-    }
-
-    @Override
-    public void reset() {
-        if (selectedRow != null) {
-            deselect(getSelectedRow());
-        }
-    }
-
-    @Override
-    public Collection<T> getSelectedRows() {
-        if (getSelectedRow() != null) {
-            return Collections.singleton(getSelectedRow());
-        }
-        return Collections.emptySet();
-    }
-
-    @Override
-    protected boolean selectByHandle(RowHandle<T> handle) {
-        if (handle != null && !handle.equals(selectedRow)) {
-            deselectByHandle(selectedRow);
-            selectedRow = handle;
-            selectedRow.pin();
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    @Override
-    protected boolean deselectByHandle(RowHandle<T> handle) {
-        if (handle != null && handle.equals(selectedRow)) {
-            selectedRow.unpin();
-            selectedRow = null;
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    @Override
-    public void setDeselectAllowed(boolean deselectAllowed) {
-        this.deselectAllowed = deselectAllowed;
-        updateHandlerDeselectAllowed();
-    }
-
-    @Override
-    public boolean isDeselectAllowed() {
-        return deselectAllowed;
-    }
-
-    private void updateHandlerDeselectAllowed() {
-        if (spaceSelectHandler != null) {
-            spaceSelectHandler.setDeselectAllowed(deselectAllowed);
-        }
-        if (clickSelectHandler != null) {
-            clickSelectHandler.setDeselectAllowed(deselectAllowed);
-        }
-    }
-
-}
diff --git a/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelWithSelectionColumn.java b/client/src/main/java/com/vaadin/client/widget/grid/selection/SelectionModelWithSelectionColumn.java
new file mode 100644 (file)
index 0000000..eeb9991
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.widget.grid.selection;
+
+import com.vaadin.client.renderers.Renderer;
+import com.vaadin.shared.data.selection.SelectionModel;
+
+/**
+ * Interface for SelectionModels that wants Grid to display a selection column.
+ *
+ * @author Vaadin Ltd
+ * @since
+ *
+ * @param <T>
+ *            selected item type
+ *
+ * @see Renderer
+ */
+public interface SelectionModelWithSelectionColumn<T>
+        extends SelectionModel<T> {
+
+    /**
+     * Returns a new instance of the Renderer for selection column.
+     *
+     * @return selection column renderer
+     */
+    public Renderer<Boolean> getRenderer();
+
+}
index 92dc0d704329c3b8c028c67fdc8ff1aefcb41a8c..9a87d82363af1fbf8b70b339ad40550c4005c852 100644 (file)
@@ -159,12 +159,7 @@ import com.vaadin.client.widget.grid.selection.HasSelectionHandlers;
 import com.vaadin.client.widget.grid.selection.MultiSelectionRenderer;
 import com.vaadin.client.widget.grid.selection.SelectionEvent;
 import com.vaadin.client.widget.grid.selection.SelectionHandler;
-import com.vaadin.client.widget.grid.selection.SelectionModel;
-import com.vaadin.client.widget.grid.selection.SelectionModel.Multi;
-import com.vaadin.client.widget.grid.selection.SelectionModel.Single;
-import com.vaadin.client.widget.grid.selection.SelectionModelMulti;
-import com.vaadin.client.widget.grid.selection.SelectionModelNone;
-import com.vaadin.client.widget.grid.selection.SelectionModelSingle;
+import com.vaadin.client.widget.grid.selection.SelectionModelWithSelectionColumn;
 import com.vaadin.client.widget.grid.sort.Sort;
 import com.vaadin.client.widget.grid.sort.SortEvent;
 import com.vaadin.client.widget.grid.sort.SortHandler;
@@ -176,6 +171,8 @@ import com.vaadin.client.widgets.Grid.StaticSection.StaticCell;
 import com.vaadin.client.widgets.Grid.StaticSection.StaticRow;
 import com.vaadin.shared.Range;
 import com.vaadin.shared.Registration;
+import com.vaadin.shared.data.selection.SelectionModel;
+import com.vaadin.shared.data.selection.SelectionModel.Multi;
 import com.vaadin.shared.data.sort.SortDirection;
 import com.vaadin.shared.ui.grid.GridConstants;
 import com.vaadin.shared.ui.grid.GridConstants.Section;
@@ -4445,7 +4442,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
         private int getSelectionAndFrozenColumnCount() {
             // no matter if selection column is frozen or not, it is considered
             // frozen for column dnd reorder
-            if (getSelectionModel().getSelectionColumnRenderer() != null) {
+            if (getSelectionModel() instanceof SelectionModelWithSelectionColumn) {
                 return Math.max(0, getFrozenColumnCount()) + 1;
             } else {
                 return Math.max(0, getFrozenColumnCount());
@@ -4563,47 +4560,6 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
 
     };
 
-    /**
-     * Enumeration for easy setting of selection mode.
-     */
-    public enum SelectionMode {
-
-        /**
-         * Shortcut for {@link SelectionModelSingle}.
-         */
-        SINGLE {
-
-            @Override
-            protected <T> SelectionModel<T> createModel() {
-                return GWT.create(SelectionModelSingle.class);
-            }
-        },
-
-        /**
-         * Shortcut for {@link SelectionModelMulti}.
-         */
-        MULTI {
-
-            @Override
-            protected <T> SelectionModel<T> createModel() {
-                return GWT.create(SelectionModelMulti.class);
-            }
-        },
-
-        /**
-         * Shortcut for {@link SelectionModelNone}.
-         */
-        NONE {
-
-            @Override
-            protected <T> SelectionModel<T> createModel() {
-                return GWT.create(SelectionModelNone.class);
-            }
-        };
-
-        protected abstract <T> SelectionModel<T> createModel();
-    }
-
     /**
      * Base class for grid columns internally used by the Grid. The user should
      * use {@link Column} when creating new columns.
@@ -4776,7 +4732,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
         }
 
         /**
-         * Returns the current header caption for this column
+         * Returns the current header caption for this column.
          *
          * @since 7.6
          * @return the header caption string
@@ -4888,6 +4844,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
          *
          * @param pixels
          *            the width in pixels or negative for auto sizing
+         * @return this column
          */
         public Column<C, T> setWidth(double pixels) {
             if (!WidgetUtil.pixelValuesEqual(widthUser, pixels)) {
@@ -4990,6 +4947,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
          * @param resizable
          *            {@code true} if this column should be resizable,
          *            {@code false} otherwise
+         * @return this column
          */
         public Column<C, T> setResizable(boolean resizable) {
             if (this.resizable != resizable) {
@@ -5026,6 +4984,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
          * @param hidden
          *            <code>true</code> to hide the column, <code>false</code>
          *            to show
+         * @return this column
          */
         public Column<C, T> setHidden(boolean hidden) {
             setHidden(hidden, false);
@@ -5089,6 +5048,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
          * @param hidable
          *            {@code true} the user can hide this column, {@code false}
          *            otherwise
+         * @return this column
          */
         public Column<C, T> setHidable(boolean hidable) {
             if (this.hidable != hidable) {
@@ -5124,6 +5084,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
          * @since 7.5.0
          * @param hidingToggleCaption
          *            the caption for the hiding toggle for this column
+         * @return this column
          */
         public Column<C, T> setHidingToggleCaption(String hidingToggleCaption) {
             this.hidingToggleCaption = hidingToggleCaption;
@@ -5211,11 +5172,6 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
          *
          * @param pixels
          *            the maximum width
-         * @param immediately
-         *            <code>true</code> if the widths should be executed
-         *            immediately (ignoring lazy loading completely), or
-         *            <code>false</code> if the command should be run after a
-         *            while (duplicate non-immediately invocations are ignored).
          * @return this column
          */
         public Column<C, T> setMaximumWidth(double pixels) {
@@ -5257,7 +5213,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
          * returns. This is done to reduce overhead of unintentionally always
          * recalculate all columns, when modifying several columns at once.
          *
-         * @param expandRatio
+         * @param ratio
          *            the expand ratio of this column. {@code 0} to not have it
          *            expand at all. A negative number to clear the expand
          *            value.
@@ -5919,7 +5875,22 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
 
         editor.setGrid(this);
 
-        setSelectionMode(SelectionMode.SINGLE);
+        setSelectionModel(new SelectionModel<T>() {
+
+            @Override
+            public Set<T> getSelectedItems() {
+                return Collections.emptySet();
+            }
+
+            @Override
+            public void select(T item) {
+            }
+
+            @Override
+            public void deselect(T item) {
+            }
+
+        });
 
         escalator.getBody().setSpacerUpdater(gridSpacerUpdater);
 
@@ -6478,7 +6449,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
     /**
      * Removes the row at the given position from the header section.
      *
-     * @param index
+     * @param rowIndex
      *            the position of the row
      *
      * @throws IllegalArgumentException
@@ -6627,7 +6598,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
     /**
      * Removes the row at the given position from the footer section.
      *
-     * @param index
+     * @param rowIndex
      *            the position of the row
      *
      * @throws IllegalArgumentException
@@ -6700,8 +6671,6 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
             throw new IllegalArgumentException("dataSource can't be null.");
         }
 
-        selectionModel.reset();
-
         if (changeHandler != null) {
             changeHandler.remove();
             changeHandler = null;
@@ -6990,7 +6959,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
     }
 
     /**
-     * Gets the vertical scroll offset
+     * Gets the vertical scroll offset.
      *
      * @return the number of pixels this grid is scrolled down
      */
@@ -6999,7 +6968,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
     }
 
     /**
-     * Sets the horizontal scroll offset
+     * Sets the horizontal scroll offset.
      *
      * @since 7.5.0
      * @param px
@@ -7010,7 +6979,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
     }
 
     /**
-     * Gets the horizontal scroll offset
+     * Gets the horizontal scroll offset.
      *
      * @return the number of pixels this grid is scrolled to the right
      */
@@ -7622,15 +7591,14 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
             throw new IllegalArgumentException("Selection model can't be null");
         }
 
-        if (this.selectionModel != null) {
-            // Detach selection model from Grid.
-            this.selectionModel.setGrid(null);
-        }
-
         this.selectionModel = selectionModel;
-        selectionModel.setGrid(this);
-        setSelectColumnRenderer(
-                this.selectionModel.getSelectionColumnRenderer());
+        if (selectionModel instanceof SelectionModelWithSelectionColumn) {
+            setSelectColumnRenderer(
+                    ((SelectionModelWithSelectionColumn<T>) selectionModel)
+                            .getRenderer());
+        } else {
+            setSelectColumnRenderer(null);
+        }
 
         // Refresh rendered rows to update selection, if it has changed
         refreshBody();
@@ -7646,33 +7614,19 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
     }
 
     /**
-     * Sets current selection mode.
-     * <p>
-     * This is a shorthand method for {@link Grid#setSelectionModel}.
-     *
-     * @param mode
-     *            a selection mode value
-     * @see {@link SelectionMode}.
-     */
-    public void setSelectionMode(SelectionMode mode) {
-        SelectionModel<T> model = mode.createModel();
-        setSelectionModel(model);
-    }
-
-    /**
-     * Test if a row is selected.
+     * Returns if a row is selected.
      *
      * @param row
      *            a row object
-     * @return true, if the current selection model considers the provided row
-     *         object selected.
+     * @return {@code true}, if the current selection model considers the
+     *         provided row object selected.
      */
     public boolean isSelected(T row) {
         return selectionModel.isSelected(row);
     }
 
     /**
-     * Select a row using the current selection model.
+     * Selects a row using the current selection model.
      * <p>
      * Only selection models implementing {@link SelectionModel.Single} and
      * {@link SelectionModel.Multi} are supported; for anything else, an
@@ -7680,24 +7634,16 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
      *
      * @param row
      *            a row object
-     * @return <code>true</code> iff the current selection changed
      * @throws IllegalStateException
      *             if the current selection model is not an instance of
      *             {@link SelectionModel.Single} or {@link SelectionModel.Multi}
      */
-    public boolean select(T row) {
-        if (selectionModel instanceof SelectionModel.Single<?>) {
-            return ((SelectionModel.Single<T>) selectionModel).select(row);
-        } else if (selectionModel instanceof SelectionModel.Multi<?>) {
-            return ((SelectionModel.Multi<T>) selectionModel)
-                    .select(Collections.singleton(row));
-        } else {
-            throw new IllegalStateException("Unsupported selection model");
-        }
+    public void select(T row) {
+        getSelectionModel().select(row);
     }
 
     /**
-     * Deselect a row using the current selection model.
+     * Deselects a row using the current selection model.
      * <p>
      * Only selection models implementing {@link SelectionModel.Single} and
      * {@link SelectionModel.Multi} are supported; for anything else, an
@@ -7705,45 +7651,23 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
      *
      * @param row
      *            a row object
-     * @return <code>true</code> iff the current selection changed
      * @throws IllegalStateException
      *             if the current selection model is not an instance of
      *             {@link SelectionModel.Single} or {@link SelectionModel.Multi}
      */
-    public boolean deselect(T row) {
-        if (selectionModel instanceof SelectionModel.Single<?>) {
-            return ((SelectionModel.Single<T>) selectionModel).deselect(row);
-        } else if (selectionModel instanceof SelectionModel.Multi<?>) {
-            return ((SelectionModel.Multi<T>) selectionModel)
-                    .deselect(Collections.singleton(row));
-        } else {
-            throw new IllegalStateException("Unsupported selection model");
-        }
+    public void deselect(T row) {
+        getSelectionModel().deselect(row);
     }
 
     /**
-     * Deselect all rows using the current selection model.
+     * Deselects all rows using the current selection model.
      *
-     * @param row
-     *            a row object
-     * @return <code>true</code> iff the current selection changed
      * @throws IllegalStateException
      *             if the current selection model is not an instance of
      *             {@link SelectionModel.Single} or {@link SelectionModel.Multi}
      */
-    public boolean deselectAll() {
-        if (selectionModel instanceof SelectionModel.Single<?>) {
-            Single<T> single = ((SelectionModel.Single<T>) selectionModel);
-            if (single.getSelectedRow() != null) {
-                return single.deselect(single.getSelectedRow());
-            } else {
-                return false;
-            }
-        } else if (selectionModel instanceof SelectionModel.Multi<?>) {
-            return ((SelectionModel.Multi<T>) selectionModel).deselectAll();
-        } else {
-            throw new IllegalStateException("Unsupported selection model");
-        }
+    public void deselectAll() {
+        getSelectionModel().deselectAll();
     }
 
     /**
@@ -7759,12 +7683,8 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
      *             {@link SelectionModel.Single}
      */
     public T getSelectedRow() {
-        if (selectionModel instanceof SelectionModel.Single<?>) {
-            return ((SelectionModel.Single<T>) selectionModel).getSelectedRow();
-        } else {
-            throw new IllegalStateException(
-                    "Unsupported selection model; can not get single selected row");
-        }
+        return getSelectionModel().getSelectedItems().stream().findFirst()
+                .orElse(null);
     }
 
     /**
@@ -7773,7 +7693,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
      * @return a non-null collection containing all currently selected rows.
      */
     public Collection<T> getSelectedRows() {
-        return selectionModel.getSelectedRows();
+        return getSelectionModel().getSelectedItems();
     }
 
     @Override
@@ -7884,6 +7804,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
      *
      * @param handler
      *            a select all event handler
+     * @return the registration for the event
      */
     public HandlerRegistration addSelectAllHandler(
             SelectAllHandler<T> handler) {
@@ -8318,7 +8239,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
     }
 
     /**
-     * Sets the style generator that is used for generating styles for cells
+     * Sets the style generator that is used for generating styles for cells.
      *
      * @param cellStyleGenerator
      *            the cell style generator to set, or <code>null</code> to
@@ -8331,7 +8252,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
     }
 
     /**
-     * Gets the style generator that is used for generating styles for cells
+     * Gets the style generator that is used for generating styles for cells.
      *
      * @return the cell style generator, or <code>null</code> if no generator is
      *         set
@@ -8341,7 +8262,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
     }
 
     /**
-     * Sets the style generator that is used for generating styles for rows
+     * Sets the style generator that is used for generating styles for rows.
      *
      * @param rowStyleGenerator
      *            the row style generator to set, or <code>null</code> to remove
@@ -8353,7 +8274,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
     }
 
     /**
-     * Gets the style generator that is used for generating styles for rows
+     * Gets the style generator that is used for generating styles for rows.
      *
      * @return the row style generator, or <code>null</code> if no generator is
      *         set
@@ -8443,7 +8364,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
      * Sets the handler responsible for binding data and editor widgets to the
      * editor.
      *
-     * @param rowHandler
+     * @param handler
      *            the new editor handler
      *
      * @throws IllegalStateException
@@ -8802,9 +8723,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
         if (visible && !isVisible) {
             escalator.getBody().setSpacer(rowIndex, DETAILS_ROW_INITIAL_HEIGHT);
             visibleDetails.add(rowIndexInteger);
-        }
-
-        else if (!visible && isVisible) {
+        } else if (!visible && isVisible) {
             escalator.getBody().setSpacer(rowIndex, -1);
             visibleDetails.remove(rowIndexInteger);
         }
@@ -8911,7 +8830,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
      * Sets the buffered editor mode.
      *
      * @since 7.6
-     * @param editorUnbuffered
+     * @param editorBuffered
      *            <code>true</code> to enable buffered editor,
      *            <code>false</code> to disable it
      */
index fb6d3b1cacf56e445f02bbb4035c603ba01d6078..54f0240b373adbac24fee388d2ca0224595a0f4a 100644 (file)
@@ -88,8 +88,8 @@ public class SingleSelection<T> extends AbstractSelectionModel<T>
      * @see SingleSelectionChange
      */
     @FunctionalInterface
-    public interface SingleSelectionListener<T> extends
-            EventListener<SingleSelectionChange<T>> {
+    public interface SingleSelectionListener<T>
+            extends EventListener<SingleSelectionChange<T>> {
 
         @Override
         public void accept(SingleSelectionChange<T> event);
@@ -112,12 +112,14 @@ public class SingleSelection<T> extends AbstractSelectionModel<T>
 
             @Override
             public void select(String key) {
-                doSelect(getData(key), true);
+                if (!Objects.equals(selectedItem, getData(key))) {
+                    doSelect(getData(key), true);
+                }
             }
 
             @Override
             public void deselect(String key) {
-                if (getData(key).equals(selectedItem)) {
+                if (Objects.equals(selectedItem, getData(key))) {
                     doSelect(null, true);
                 }
             }
@@ -133,14 +135,15 @@ public class SingleSelection<T> extends AbstractSelectionModel<T>
     }
 
     @Override
-    public void select(T value) {
-        doSelect(value, false);
+    public void select(T item) {
+        doSelect(item, false);
     }
 
     @Override
     public void deselect(T value) {
-        if(Objects.equals(selectedItem,value))
+        if (Objects.equals(selectedItem, value)) {
             doSelect(null, false);
+        }
     }
 
     @Override
@@ -172,16 +175,22 @@ public class SingleSelection<T> extends AbstractSelectionModel<T>
      * Selects the given item or deselects the current one if given
      * {@code null}.
      *
-     * @param value
+     * @param item
      *            the item to select or {@code null} to deselect
      * @param userOriginated
      *            {@code true} if this event originates from the client,
      *            {@code false} otherwise.
      */
-    protected void doSelect(T value, boolean userOriginated) {
-        if (!Objects.equals(value, this.selectedItem)) {
-            this.selectedItem = value;
-            fireEvent(new SingleSelectionChange<>(getParent(), value,
+    protected void doSelect(T item, boolean userOriginated) {
+        if (!Objects.equals(item, selectedItem)) {
+            if (selectedItem != null) {
+                refresh(selectedItem);
+            }
+            selectedItem = item;
+            if (selectedItem != null) {
+                refresh(selectedItem);
+            }
+            fireEvent(new SingleSelectionChange<>(getParent(), selectedItem,
                     userOriginated));
         }
     }
index 8c92ad50e316dd58abad949b92d94bd42514852b..5aceb9b305145828876319475f08b492cd6f2c85 100644 (file)
@@ -373,6 +373,11 @@ public class DataCommunicator<T> extends AbstractExtension {
      *            updated data object
      */
     public void refresh(T data) {
+        if (!handler.getActiveData().contains(data)) {
+            // Item is not currently available at the client-side
+            return;
+        }
+
         if (updatedData.isEmpty()) {
             markAsDirty();
         }
index 285490346390378558e8ef319522908ef2340690..5996b52c9b49aea2c85f2640a3f6bdbf6ef1154d 100644 (file)
@@ -160,6 +160,11 @@ public abstract class AbstractListing<T, SELECTIONMODEL extends SelectionModel<T
      *            the selection model to use, not null
      */
     protected void setSelectionModel(SELECTIONMODEL model) {
+        if (selectionModel != null) {
+            throw new IllegalStateException(
+                    "A selection model can't be changed.");
+        }
+
         Objects.requireNonNull(model, "selection model cannot be null");
         selectionModel = model;
     }
index ae7c550172651fba30e178c4ec28d102f88ef383..387cd2227574ef1b71bd558ae8974889ac647ce8 100644 (file)
@@ -34,9 +34,9 @@ import java.util.stream.Stream;
 import com.vaadin.data.selection.SingleSelection;
 import com.vaadin.server.AbstractExtension;
 import com.vaadin.server.KeyMapper;
+import com.vaadin.server.data.DataGenerator;
 import com.vaadin.server.data.DataSource;
 import com.vaadin.server.data.SortOrder;
-import com.vaadin.server.data.DataGenerator;
 import com.vaadin.shared.MouseEventDetails;
 import com.vaadin.shared.data.DataCommunicatorConstants;
 import com.vaadin.shared.data.selection.SelectionModel;
diff --git a/server/src/test/java/com/vaadin/tests/components/grid/GridSelectionTest.java b/server/src/test/java/com/vaadin/tests/components/grid/GridSelectionTest.java
new file mode 100644 (file)
index 0000000..0522e26
--- /dev/null
@@ -0,0 +1,34 @@
+package com.vaadin.tests.components.grid;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.ui.Grid;
+
+public class GridSelectionTest {
+
+    Grid<String> grid;
+
+    @Before
+    public void setUp() {
+        grid = new Grid<>();
+        grid.setItems("Foo", "Bar");
+    }
+
+    @Test
+    public void testGridWithSingleSelection() {
+        Assert.assertFalse(grid.isSelected("Foo"));
+        grid.select("Foo");
+        Assert.assertTrue(grid.isSelected("Foo"));
+        Assert.assertEquals(1, grid.getSelectedItems().size());
+        Assert.assertEquals("Foo", grid.getSelectedItems().iterator().next());
+        grid.select("Bar");
+        Assert.assertFalse(grid.isSelected("Foo"));
+        Assert.assertTrue(grid.isSelected("Bar"));
+        grid.deselect("Bar");
+        Assert.assertFalse(grid.isSelected("Bar"));
+        Assert.assertEquals(0, grid.getSelectedItems().size());
+    }
+
+}
index 8fff6ee434b95b6f3b610779e2b903045fda211b..270b964a79179c155f99dee7a526fc285b16a78d 100644 (file)
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -23,9 +23,9 @@ import java.util.Set;
 /**
  * Models the selection logic of a {@code Listing} component. Determines how
  * items can be selected and deselected.
- * 
+ *
  * @author Vaadin Ltd.
- * 
+ *
  * @param <T>
  *            the type of the items to select
  * @since
@@ -51,7 +51,7 @@ public interface SelectionModel<T> extends Serializable {
         /**
          * Returns the currently selected item, or an empty optional if no item
          * is selected.
-         * 
+         *
          * @return an optional of the selected item if any, an empty optional
          *         otherwise
          */
@@ -60,7 +60,7 @@ public interface SelectionModel<T> extends Serializable {
         /**
          * Returns a singleton set of the currently selected item or an empty
          * set if no item is selected.
-         * 
+         *
          * @return a singleton set of the selected item if any, an empty set
          *         otherwise
          */
@@ -85,6 +85,7 @@ public interface SelectionModel<T> extends Serializable {
          */
         @Override
         public void select(T item);
+
     }
 
     /**
@@ -93,7 +94,7 @@ public interface SelectionModel<T> extends Serializable {
      * <i>Implementation note:</i> the iteration order of the items in the
      * returned set should be well-defined and documented by the implementing
      * class.
-     * 
+     *
      * @return the items in the current selection, not null
      */
     public Set<T> getSelectedItems();
@@ -101,7 +102,7 @@ public interface SelectionModel<T> extends Serializable {
     /**
      * Selects the given item. Depending on the implementation, may cause other
      * items to be deselected. If the item is already selected, does nothing.
-     * 
+     *
      * @param item
      *            the item to select, not null
      */
@@ -110,15 +111,22 @@ public interface SelectionModel<T> extends Serializable {
     /**
      * Deselects the given item. If the item is not currently selected, does
      * nothing.
-     * 
+     *
      * @param item
      *            the item to deselect, not null
      */
     public void deselect(T item);
 
+    /**
+     * Deselects all currently selected items.
+     */
+    public default void deselectAll() {
+        getSelectedItems().forEach(this::deselect);
+    }
+
     /**
      * Returns whether the given item is currently selected.
-     * 
+     *
      * @param item
      *            the item to check, not null
      * @return {@code true} if the item is selected, {@code false} otherwise
index ebcdc34d7f20ebc0be3d2968898452da081743e0..ff272689915de56dca68d1f85f630f9aec80917c 100644 (file)
@@ -64,6 +64,11 @@ class DataObject {
         this.date = date;
     }
 
+    @Override
+    public String toString() {
+        return "DataObject[" + rowNumber + "]";
+    }
+
     static List<DataObject> generateObjects() {
         List<DataObject> data = new ArrayList<>();
 
index 3445a3e3f3102bc1e90d1583c6add83ed2425c29..b78e5272fec3ddf766bde96453e0d63321194c1e 100644 (file)
@@ -10,6 +10,7 @@ import java.util.function.Consumer;
 import java.util.stream.Stream;
 
 import com.vaadin.annotations.Widgetset;
+import com.vaadin.data.selection.SingleSelection;
 import com.vaadin.server.VaadinRequest;
 import com.vaadin.shared.ui.grid.HeightMode;
 import com.vaadin.tests.components.AbstractTestUIWithLog;
@@ -112,6 +113,10 @@ public class GridBasics extends AbstractTestUIWithLog {
         grid = new Grid<>();
         grid.setItems(data);
 
+        grid.addColumn("Column 0", String.class,
+                dataObj -> "(" + dataObj.getRowNumber() + ", 0)");
+        grid.addColumn("Column 1", String.class,
+                dataObj -> "(" + dataObj.getRowNumber() + ", 1)");
         grid.addColumn("Row Number", Integer.class, DataObject::getRowNumber);
         grid.addColumn("Date", Date.class, DataObject::getDate);
         grid.addColumn("HTML String", String.class, DataObject::getHtmlString);
@@ -119,6 +124,9 @@ public class GridBasics extends AbstractTestUIWithLog {
         grid.addColumn("Small Random", Integer.class,
                 DataObject::getSmallRandom);
 
+        ((SingleSelection<DataObject>) grid.getSelectionModel())
+                .addSelectionListener(e -> log("Selected: " + e.getValue()));
+
         layout.addComponent(createMenu());
         layout.addComponent(grid);
         addComponent(layout);
@@ -130,6 +138,7 @@ public class GridBasics extends AbstractTestUIWithLog {
         createStateMenu(componentMenu.addItem("State", null));
         createSizeMenu(componentMenu.addItem("Size", null));
         createDetailsMenu(componentMenu.addItem("Details", null));
+        createBodyMenu(componentMenu.addItem("Body rows", null));
         return menu;
     }
 
@@ -163,6 +172,17 @@ public class GridBasics extends AbstractTestUIWithLog {
         parent.addItem(name, menuItem -> method.accept(value));
     }
 
+    private void createBodyMenu(MenuItem rowMenu) {
+        rowMenu.addItem("Toggle first row selection", menuItem -> {
+            DataObject item = data.get(0);
+            if (grid.isSelected(item)) {
+                grid.deselect(item);
+            } else {
+                grid.select(item);
+            }
+        });
+    }
+
     /* DetailsGenerator related things */
 
     private void createDetailsMenu(MenuItem detailsMenu) {
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicSelectionTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicSelectionTest.java
new file mode 100644 (file)
index 0000000..2c329ca
--- /dev/null
@@ -0,0 +1,84 @@
+package com.vaadin.tests.components.grid.basics;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.interactions.Actions;
+
+import com.vaadin.testbench.customelements.GridElement;
+import com.vaadin.testbench.elements.GridElement.GridRowElement;
+
+public class GridBasicSelectionTest extends GridBasicsTest {
+
+    @Test
+    public void testKeyboardWithSingleSelection() {
+
+        GridElement grid = getGridElement();
+        grid.getCell(3, 1).click();
+
+        assertTrue("Grid row 3 was not selected with clicking.",
+                grid.getRow(3).isSelected());
+
+        new Actions(getDriver()).sendKeys(Keys.SPACE).perform();
+
+        assertTrue("Grid row 3 was not deselected with space key.",
+                !grid.getRow(3).isSelected());
+
+        new Actions(getDriver()).sendKeys(Keys.SPACE).perform();
+
+        assertTrue("Grid row 3 was not selected with space key.",
+                grid.getRow(3).isSelected());
+
+        grid.scrollToRow(500);
+
+        new Actions(getDriver()).sendKeys(Keys.SPACE).perform();
+
+        assertTrue("Grid row 3 was not deselected with space key.",
+                !grid.getRow(3).isSelected());
+    }
+
+    @Test
+    public void testSingleSelectionUpdatesFromServer() {
+        GridElement grid = getGridElement();
+        assertFalse("First row was selected from start",
+                grid.getRow(0).isSelected());
+        toggleFirstRowSelection();
+        assertTrue("First row was not selected.", getRow(0).isSelected());
+        assertTrue("Selection event was not correct",
+                logContainsText("Selected: DataObject[0]"));
+        grid.getCell(5, 0).click();
+        assertTrue("Fifth row was not selected.", getRow(5).isSelected());
+        assertFalse("First row was still selected.", getRow(0).isSelected());
+        assertTrue("Selection event was not correct",
+                logContainsText("Selected: DataObject[5]"));
+        grid.getCell(0, 6).click();
+        assertTrue("Selection event was not correct",
+                logContainsText("Selected: DataObject[0]"));
+        toggleFirstRowSelection();
+        assertTrue("Selection event was not correct",
+                logContainsText("Selected: null"));
+        assertFalse("First row was still selected.", getRow(0).isSelected());
+        assertFalse("Fifth row was still selected.", getRow(5).isSelected());
+
+        grid.scrollToRow(600);
+        grid.getCell(595, 3).click();
+        assertTrue("Row 595 was not selected.", getRow(595).isSelected());
+        assertTrue("Selection event was not correct",
+                logContainsText("Selected: DataObject[595]"));
+        toggleFirstRowSelection();
+        assertFalse("Row 595 was still selected.", getRow(595).isSelected());
+        assertTrue("First row was not selected.", getRow(0).isSelected());
+        assertTrue("Selection event was not correct",
+                logContainsText("Selected: DataObject[0]"));
+    }
+
+    private void toggleFirstRowSelection() {
+        selectMenuPath("Component", "Body rows", "Toggle first row selection");
+    }
+
+    private GridRowElement getRow(int i) {
+        return getGridElement().getRow(i);
+    }
+}