aboutsummaryrefslogtreecommitdiffstats
path: root/server/src
diff options
context:
space:
mode:
authorHenrik Paul <henrik@vaadin.com>2014-05-19 18:26:24 +0300
committerHenrik Paul <henrik@vaadin.com>2014-05-28 11:03:38 +0000
commit2eff69356b37e5c90e210e0e388d7220d18e834b (patch)
treec2f288cfa7aabb902872fc75c304e457a54b6fe1 /server/src
parentdbde68ef3d26e830cfb93e0667cedf8bf9ff8520 (diff)
downloadvaadin-framework-2eff69356b37e5c90e210e0e388d7220d18e834b.tar.gz
vaadin-framework-2eff69356b37e5c90e210e0e388d7220d18e834b.zip
Grid server-side selection (#13334)
Change-Id: I62c5a2486360fe11de8a90efabb7775ef47124cb
Diffstat (limited to 'server/src')
-rw-r--r--server/src/com/vaadin/ui/components/grid/Grid.java326
-rw-r--r--server/src/com/vaadin/ui/components/grid/selection/AbstractSelectionModel.java71
-rw-r--r--server/src/com/vaadin/ui/components/grid/selection/MultiSelectionModel.java138
-rw-r--r--server/src/com/vaadin/ui/components/grid/selection/NoSelectionModel.java54
-rw-r--r--server/src/com/vaadin/ui/components/grid/selection/SelectionChangeEvent.java73
-rw-r--r--server/src/com/vaadin/ui/components/grid/selection/SelectionChangeListener.java35
-rw-r--r--server/src/com/vaadin/ui/components/grid/selection/SelectionChangeNotifier.java43
-rw-r--r--server/src/com/vaadin/ui/components/grid/selection/SelectionModel.java234
-rw-r--r--server/src/com/vaadin/ui/components/grid/selection/SingleSelectionModel.java81
9 files changed, 1051 insertions, 4 deletions
diff --git a/server/src/com/vaadin/ui/components/grid/Grid.java b/server/src/com/vaadin/ui/components/grid/Grid.java
index da5be35d44..69beb260f4 100644
--- a/server/src/com/vaadin/ui/components/grid/Grid.java
+++ b/server/src/com/vaadin/ui/components/grid/Grid.java
@@ -17,6 +17,7 @@
package com.vaadin.ui.components.grid;
import java.io.Serializable;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -51,7 +52,14 @@ import com.vaadin.shared.ui.grid.HeightMode;
import com.vaadin.shared.ui.grid.Range;
import com.vaadin.shared.ui.grid.ScrollDestination;
import com.vaadin.ui.AbstractComponent;
-import com.vaadin.ui.Component;
+import com.vaadin.ui.components.grid.selection.MultiSelectionModel;
+import com.vaadin.ui.components.grid.selection.NoSelectionModel;
+import com.vaadin.ui.components.grid.selection.SelectionChangeEvent;
+import com.vaadin.ui.components.grid.selection.SelectionChangeListener;
+import com.vaadin.ui.components.grid.selection.SelectionChangeNotifier;
+import com.vaadin.ui.components.grid.selection.SelectionModel;
+import com.vaadin.ui.components.grid.selection.SingleSelectionModel;
+import com.vaadin.util.ReflectTools;
/**
* Data grid component
@@ -71,7 +79,7 @@ import com.vaadin.ui.Component;
* @since 7.4
* @author Vaadin Ltd
*/
-public class Grid extends AbstractComponent {
+public class Grid extends AbstractComponent implements SelectionChangeNotifier {
/**
* A helper class that handles the client-side Escalator logic relating to
@@ -350,6 +358,47 @@ public class Grid extends AbstractComponent {
}
/**
+ * Selection modes representing built-in {@link SelectionModel
+ * SelectionModels} that come bundled with {@link Grid}.
+ * <p>
+ * Passing one of these enums into
+ * {@link Grid#setSelectionMode(SelectionMode)} is equivalent to calling
+ * {@link Grid#setSelectionModel(SelectionModel)} with one of the built-in
+ * implementations of {@link SelectionModel}.
+ *
+ * @see Grid#setSelectionMode(SelectionMode)
+ * @see Grid#setSelectionModel(SelectionModel)
+ */
+ public enum SelectionMode {
+ /** A SelectionMode that maps to {@link SingleSelectionModel} */
+ SINGLE {
+ @Override
+ protected SelectionModel createModel() {
+ return new SingleSelectionModel();
+ }
+
+ },
+
+ /** A SelectionMode that maps to {@link MultiSelectionModel} */
+ MULTI {
+ @Override
+ protected SelectionModel createModel() {
+ return new MultiSelectionModel();
+ }
+ },
+
+ /** A SelectionMode that maps to {@link NoSelectionModel} */
+ NONE {
+ @Override
+ protected SelectionModel createModel() {
+ return new NoSelectionModel();
+ }
+ };
+
+ protected abstract SelectionModel createModel();
+ }
+
+ /**
* The data source attached to the grid
*/
private Container.Indexed datasource;
@@ -459,13 +508,24 @@ public class Grid extends AbstractComponent {
private final ActiveRowHandler activeRowHandler = new ActiveRowHandler();
/**
+ * The selection model that is currently in use. Never <code>null</code>
+ * after the constructor has been run.
+ */
+ private SelectionModel selectionModel;
+
+ private static final Method SELECTION_CHANGE_METHOD = ReflectTools
+ .findMethod(SelectionChangeListener.class, "selectionChange",
+ SelectionChangeEvent.class);
+
+ /**
* Creates a new Grid using the given datasource.
*
* @param datasource
* the data source for the grid
*/
public Grid(Container.Indexed datasource) {
- setContainerDatasource(datasource);
+ setContainerDataSource(datasource);
+ setSelectionMode(SelectionMode.MULTI);
registerRpc(new GridServerRpc() {
@Override
@@ -484,7 +544,7 @@ public class Grid extends AbstractComponent {
* @throws IllegalArgumentException
* if the data source is null
*/
- public void setContainerDatasource(Container.Indexed container) {
+ public void setContainerDataSource(Container.Indexed container) {
if (container == null) {
throw new IllegalArgumentException(
"Cannot set the datasource to null");
@@ -512,6 +572,14 @@ public class Grid extends AbstractComponent {
datasourceExtension = new RpcDataProviderExtension(container);
datasourceExtension.extend(this);
+ /*
+ * selectionModel == null when the invocation comes from the
+ * constructor.
+ */
+ if (selectionModel != null) {
+ selectionModel.reset();
+ }
+
// Listen to changes in properties and remove columns if needed
if (datasource instanceof PropertySetChangeNotifier) {
((PropertySetChangeNotifier) datasource)
@@ -958,4 +1026,254 @@ public class Grid extends AbstractComponent {
public HeightMode getHeightMode() {
return getState(false).heightMode;
}
+
+ /* Selection related methods: */
+
+ /**
+ * Takes a new {@link SelectionModel} into use.
+ * <p>
+ * The SelectionModel that is previously in use will have all its items
+ * deselected.
+ * <p>
+ * If the given SelectionModel is already in use, this method does nothing.
+ *
+ * @param selectionModel
+ * the new SelectionModel to use
+ * @throws IllegalArgumentException
+ * if {@code selectionModel} is <code>null</code>
+ */
+ public void setSelectionModel(SelectionModel selectionModel)
+ throws IllegalArgumentException {
+ if (selectionModel == null) {
+ throw new IllegalArgumentException(
+ "Selection model may not be null");
+ }
+
+ if (this.selectionModel != selectionModel) {
+ // this.selectionModel is null on init
+ if (this.selectionModel != null) {
+ this.selectionModel.reset();
+ this.selectionModel.setGrid(null);
+ }
+
+ this.selectionModel = selectionModel;
+ this.selectionModel.setGrid(this);
+ this.selectionModel.reset();
+ }
+ }
+
+ /**
+ * Returns the currently used {@link SelectionModel}.
+ *
+ * @return the currently used SelectionModel
+ */
+ public SelectionModel getSelectionModel() {
+ return selectionModel;
+ }
+
+ /**
+ * Changes the Grid's selection mode.
+ * <p>
+ * Grid supports three selection modes: multiselect, single select and no
+ * selection, and this is a conveniency method for choosing between one of
+ * them.
+ * <P>
+ * Technically, this method is a shortcut that can be used instead of
+ * calling {@code setSelectionModel} with a specific SelectionModel
+ * instance. Grid comes with three built-in SelectionModel classes, and the
+ * {@link SelectionMode} enum represents each of them.
+ * <p>
+ * Essentially, the two following method calls are equivalent:
+ * <p>
+ * <code><pre>
+ * grid.setSelectionMode(SelectionMode.MULTI);
+ * grid.setSelectionModel(new MultiSelectionMode());
+ * </pre></code>
+ *
+ *
+ * @param selectionMode
+ * the selection mode to switch to
+ * @return The {@link SelectionModel} instance that was taken into use
+ * @throws IllegalArgumentException
+ * if {@code selectionMode} is <code>null</code>
+ * @see SelectionModel
+ */
+ public SelectionModel setSelectionMode(final SelectionMode selectionMode)
+ throws IllegalArgumentException {
+ if (selectionMode == null) {
+ throw new IllegalArgumentException("selection mode may not be null");
+ }
+ final SelectionModel newSelectionModel = selectionMode.createModel();
+ setSelectionModel(newSelectionModel);
+ return newSelectionModel;
+ }
+
+ /**
+ * Checks whether an item is selected or not.
+ *
+ * @param itemId
+ * the item id to check for
+ * @return <code>true</code> iff the item is selected
+ */
+ // keep this javadoc in sync with SelectionModel.isSelected
+ public boolean isSelected(Object itemId) {
+ return selectionModel.isSelected(itemId);
+ }
+
+ /**
+ * Returns a collection of all the currently selected itemIds.
+ * <p>
+ * This method is a shorthand that is forwarded to the object that is
+ * returned by {@link #getSelectionModel()}.
+ *
+ * @return a collection of all the currently selected itemIds
+ */
+ // keep this javadoc in sync with SelectionModel.getSelectedRows
+ public Collection<Object> getSelectedRows() {
+ return getSelectionModel().getSelectedRows();
+ }
+
+ /**
+ * Gets the item id of the currently selected item.
+ * <p>
+ * This method is a shorthand that is forwarded to the object that is
+ * returned by {@link #getSelectionModel()}. Only
+ * {@link SelectionModel.Single} is supported.
+ *
+ * @return the item id of the currently selected item, or <code>null</code>
+ * if nothing is selected
+ * @throws IllegalStateException
+ * if the object that is returned by
+ * {@link #getSelectionModel()} is not an instance of
+ * {@link SelectionModel.Single}
+ */
+ // keep this javadoc in sync with SelectionModel.Single.getSelectedRow
+ public Object getSelectedRow() throws IllegalStateException {
+ if (selectionModel instanceof SelectionModel.Single) {
+ return ((SelectionModel.Single) selectionModel).getSelectedRow();
+ } else {
+ throw new IllegalStateException(Grid.class.getSimpleName()
+ + " does not support the 'getSelectedRow' shortcut method "
+ + "unless the selection model implements "
+ + SelectionModel.Single.class.getName()
+ + ". The current one does not ("
+ + selectionModel.getClass().getName() + ")");
+ }
+ }
+
+ /**
+ * Marks an item as selected.
+ * <p>
+ * This method is a shorthand that is forwarded to the object that is
+ * returned by {@link #getSelectionModel()}. Only
+ * {@link SelectionModel.Single} or {@link SelectionModel.Multi} are
+ * supported.
+ *
+ *
+ * @param itemIds
+ * the itemId to mark as selected
+ * @return <code>true</code> if the selection state changed.
+ * <code>false</code> if the itemId already was selected
+ * @throws IllegalArgumentException
+ * if the {@code itemId} doesn't exist in the currently active
+ * Container
+ * @throws IllegalStateException
+ * if the selection was illegal. One such reason might be that
+ * the implementation already had an item selected, and that
+ * needs to be explicitly deselected before re-selecting
+ * something
+ * @throws IllegalStateException
+ * if the object that is returned by
+ * {@link #getSelectionModel()} does not implement
+ * {@link SelectionModel.Single} or {@link SelectionModel.Multi}
+ */
+ // keep this javadoc in sync with SelectionModel.Single.select
+ public boolean select(Object itemId) throws IllegalArgumentException,
+ IllegalStateException {
+ if (selectionModel instanceof SelectionModel.Single) {
+ return ((SelectionModel.Single) selectionModel).select(itemId);
+ } else if (selectionModel instanceof SelectionModel.Multi) {
+ return ((SelectionModel.Multi) selectionModel).select(itemId);
+ } else {
+ throw new IllegalStateException(Grid.class.getSimpleName()
+ + " does not support the 'select' shortcut method "
+ + "unless the selection model implements "
+ + SelectionModel.Single.class.getName() + " or "
+ + SelectionModel.Multi.class.getName()
+ + ". The current one does not ("
+ + selectionModel.getClass().getName() + ").");
+ }
+ }
+
+ /**
+ * Marks an item as deselected.
+ * <p>
+ * This method is a shorthand that is forwarded to the object that is
+ * returned by {@link #getSelectionModel()}. Only
+ * {@link SelectionModel.Single} and {@link SelectionModel.Multi} are
+ * supported.
+ *
+ * @param itemId
+ * the itemId to remove from being selected
+ * @return <code>true</code> if the selection state changed.
+ * <code>false</code> if the itemId already was selected
+ * @throws IllegalArgumentException
+ * if the {@code itemId} doesn't exist in the currently active
+ * Container
+ * @throws IllegalStateException
+ * if the deselection was illegal. One such reason might be that
+ * the implementation already had an item selected, and that
+ * needs to be explicitly deselected before re-selecting
+ * something
+ * @throws IllegalStateException
+ * if the object that is returned by
+ * {@link #getSelectionModel()} does not implement
+ * {@link SelectionModel.Single} or {@link SelectionModel.Multi}
+ */
+ // keep this javadoc in sync with SelectionModel.Single.deselect
+ public boolean deselect(Object itemId) throws IllegalStateException {
+ if (selectionModel instanceof SelectionModel.Single) {
+ return ((SelectionModel.Single) selectionModel).deselect(itemId);
+ } else if (selectionModel instanceof SelectionModel.Multi) {
+ return ((SelectionModel.Multi) selectionModel).deselect(itemId);
+ } else {
+ throw new IllegalStateException(Grid.class.getSimpleName()
+ + " does not support the 'deselect' shortcut method "
+ + "unless the selection model implements "
+ + SelectionModel.Single.class.getName() + " or "
+ + SelectionModel.Multi.class.getName()
+ + ". The current one does not ("
+ + selectionModel.getClass().getName() + ").");
+ }
+ }
+
+ /**
+ * Fires a selection change event.
+ * <p>
+ * <strong>Note:</strong> This is not a method that should be called by
+ * application logic. This method is publicly accessible only so that
+ * {@link SelectionModel SelectionModels} would be able to inform Grid of
+ * these events.
+ *
+ * @param addedSelections
+ * the selections that were added by this event
+ * @param removedSelections
+ * the selections that were removed by this event
+ */
+ public void fireSelectionChangeEvent(Collection<Object> oldSelection,
+ Collection<Object> newSelection) {
+ fireEvent(new SelectionChangeEvent(this, oldSelection, newSelection));
+ }
+
+ @Override
+ public void addSelectionChangeListener(SelectionChangeListener listener) {
+ addListener(SelectionChangeEvent.class, listener,
+ SELECTION_CHANGE_METHOD);
+ }
+
+ @Override
+ public void removeSelectionChangeListener(SelectionChangeListener listener) {
+ removeListener(SelectionChangeEvent.class, listener,
+ SELECTION_CHANGE_METHOD);
+ }
}
diff --git a/server/src/com/vaadin/ui/components/grid/selection/AbstractSelectionModel.java b/server/src/com/vaadin/ui/components/grid/selection/AbstractSelectionModel.java
new file mode 100644
index 0000000000..3eb16d11fd
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/grid/selection/AbstractSelectionModel.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2000-2013 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.components.grid.selection;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+
+import com.vaadin.ui.components.grid.Grid;
+
+/**
+ * A base class for SelectionModels that contains some of the logic that is
+ * reusable.
+ *
+ * @since 7.4.0
+ * @author Vaadin Ltd
+ */
+public abstract class AbstractSelectionModel implements SelectionModel {
+ protected final LinkedHashSet<Object> selection = new LinkedHashSet<Object>();
+ protected Grid grid = null;
+
+ @Override
+ public boolean isSelected(final Object itemId) {
+ return selection.contains(itemId);
+ }
+
+ @Override
+ public Collection<Object> getSelectedRows() {
+ return new ArrayList<Object>(selection);
+ }
+
+ @Override
+ public void setGrid(final Grid grid) {
+ this.grid = grid;
+ }
+
+ /**
+ * Fires a {@link SelectionChangeEvent} to all the
+ * {@link SelectionChangeListener SelectionChangeListeners} currently added
+ * to the Grid in which this SelectionModel is.
+ * <p>
+ * Note that this is only a helper method, and routes the call all the way
+ * to Grid. A {@link SelectionModel} is not a
+ * {@link SelectionChangeNotifier}
+ *
+ * @param oldSelection
+ * the complete {@link Collection} of the itemIds that were
+ * selected <em>before</em> this event happened
+ * @param newSelection
+ * the complete {@link Collection} of the itemIds that are
+ * selected <em>after</em> this event happened
+ */
+ protected void fireSelectionChangeEvent(
+ final Collection<Object> oldSelection,
+ final Collection<Object> newSelection) {
+ grid.fireSelectionChangeEvent(oldSelection, newSelection);
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/grid/selection/MultiSelectionModel.java b/server/src/com/vaadin/ui/components/grid/selection/MultiSelectionModel.java
new file mode 100644
index 0000000000..ca5f34484e
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/grid/selection/MultiSelectionModel.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2000-2013 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.components.grid.selection;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+
+import com.vaadin.data.Container.Indexed;
+
+/**
+ * A default implementation of a {@link SelectionModel.Multi}
+ *
+ * @since 7.4.0
+ * @author Vaadin Ltd
+ */
+public class MultiSelectionModel extends AbstractSelectionModel implements
+ SelectionModel.Multi {
+
+ @Override
+ public boolean select(final Object... itemIds)
+ throws IllegalArgumentException {
+ if (itemIds != null) {
+ // select will fire the event
+ return select(Arrays.asList(itemIds));
+ } else {
+ throw new IllegalArgumentException(
+ "Vararg array of itemIds may not be null");
+ }
+ }
+
+ @Override
+ public boolean select(final Collection<?> itemIds)
+ throws IllegalArgumentException {
+ if (itemIds == null) {
+ throw new IllegalArgumentException("itemIds may not be null");
+ }
+
+ final boolean hasSomeDifferingElements = !selection
+ .containsAll(itemIds);
+ if (hasSomeDifferingElements) {
+ final HashSet<Object> oldSelection = new HashSet<Object>(selection);
+ selection.addAll(itemIds);
+ fireSelectionChangeEvent(oldSelection, selection);
+ }
+ return hasSomeDifferingElements;
+ }
+
+ @Override
+ public boolean deselect(final Object... itemIds)
+ throws IllegalArgumentException {
+ if (itemIds != null) {
+ // deselect will fire the event
+ return deselect(Arrays.asList(itemIds));
+ } else {
+ throw new IllegalArgumentException(
+ "Vararg array of itemIds may not be null");
+ }
+ }
+
+ @Override
+ public boolean deselect(final Collection<?> itemIds)
+ throws IllegalArgumentException {
+ if (itemIds == null) {
+ throw new IllegalArgumentException("itemIds may not be null");
+ }
+
+ final boolean hasCommonElements = !Collections.disjoint(itemIds,
+ selection);
+ if (hasCommonElements) {
+ final HashSet<Object> oldSelection = new HashSet<Object>(selection);
+ selection.removeAll(itemIds);
+ fireSelectionChangeEvent(oldSelection, selection);
+ }
+ return hasCommonElements;
+ }
+
+ @Override
+ public boolean selectAll() {
+ // select will fire the event
+ final Indexed container = grid.getContainerDatasource();
+ if (container != null) {
+ return select(container.getItemIds());
+ } else if (selection.isEmpty()) {
+ return false;
+ } else {
+ /*
+ * this should never happen (no container but has a selection), but
+ * I guess the only theoretically correct course of action...
+ */
+ return deselectAll();
+ }
+ }
+
+ @Override
+ public boolean deselectAll() {
+ // deselect will fire the event
+ return deselect(getSelectedRows());
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The returned Collection is in <strong>order of selection</strong> &ndash;
+ * the item that was first selected will be first in the collection, and so
+ * on. Should an item have been selected twice without being deselected in
+ * between, it will have remained in its original position.
+ */
+ @Override
+ public Collection<Object> getSelectedRows() {
+ // overridden only for JavaDoc
+ return super.getSelectedRows();
+ }
+
+ /**
+ * Resets the selection model.
+ * <p>
+ * Equivalent to calling {@link #deselectAll()}
+ */
+ @Override
+ public void reset() {
+ deselectAll();
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/grid/selection/NoSelectionModel.java b/server/src/com/vaadin/ui/components/grid/selection/NoSelectionModel.java
new file mode 100644
index 0000000000..6d3213a82c
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/grid/selection/NoSelectionModel.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2000-2013 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.components.grid.selection;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import com.vaadin.ui.components.grid.Grid;
+
+/**
+ * A default implementation for a {@link SelectionModel.None}
+ *
+ * @since 7.4.0
+ * @author Vaadin Ltd
+ */
+public class NoSelectionModel implements SelectionModel.None {
+ @Override
+ public void setGrid(final Grid grid) {
+ // NOOP, not needed for anything
+ }
+
+ @Override
+ public boolean isSelected(final Object itemId) {
+ return false;
+ }
+
+ @Override
+ public Collection<Object> getSelectedRows() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Semantically resets the selection model.
+ * <p>
+ * Effectively a no-op.
+ */
+ @Override
+ public void reset() {
+ // NOOP
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/grid/selection/SelectionChangeEvent.java b/server/src/com/vaadin/ui/components/grid/selection/SelectionChangeEvent.java
new file mode 100644
index 0000000000..b1097c88a6
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/grid/selection/SelectionChangeEvent.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2000-2013 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.components.grid.selection;
+
+import java.util.Collection;
+import java.util.EventObject;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
+import com.vaadin.ui.components.grid.Grid;
+
+/**
+ * An event that specifies what in a selection has changed, and where the
+ * selection took place.
+ *
+ * @since 7.4.0
+ * @author Vaadin Ltd
+ */
+public class SelectionChangeEvent extends EventObject {
+
+ private Set<Object> oldSelection;
+ private Set<Object> newSelection;
+
+ public SelectionChangeEvent(Grid source, Collection<Object> oldSelection,
+ Collection<Object> newSelection) {
+ super(source);
+ this.oldSelection = new HashSet<Object>(oldSelection);
+ this.newSelection = new HashSet<Object>(newSelection);
+ }
+
+ /**
+ * A {@link Collection} of all the itemIds that became selected.
+ * <p>
+ * <em>Note:</em> this excludes all itemIds that might have been previously
+ * selected.
+ *
+ * @return a Collection of the itemIds that became selected
+ */
+ public Set<Object> getAdded() {
+ return Sets.difference(newSelection, oldSelection);
+ }
+
+ /**
+ * A {@link Collection} of all the itemIds that became deselected.
+ * <p>
+ * <em>Note:</em> this excludes all itemIds that might have been previously
+ * deselected.
+ *
+ * @return a Collection of the itemIds that became deselected
+ */
+ public Set<Object> getRemoved() {
+ return Sets.difference(oldSelection, newSelection);
+ }
+
+ @Override
+ public Grid getSource() {
+ return (Grid) super.getSource();
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/grid/selection/SelectionChangeListener.java b/server/src/com/vaadin/ui/components/grid/selection/SelectionChangeListener.java
new file mode 100644
index 0000000000..764fee894f
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/grid/selection/SelectionChangeListener.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2013 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.components.grid.selection;
+
+import java.io.Serializable;
+
+/**
+ * The listener interface for receiving {@link SelectionChangeEvent
+ * SelectionChangeEvents}.
+ *
+ * @since 7.4.0
+ * @author Vaadin Ltd
+ */
+public interface SelectionChangeListener extends Serializable {
+ /**
+ * Notifies the listener that the selection state has changed.
+ *
+ * @param event
+ * the selection change event
+ */
+ void selectionChange(SelectionChangeEvent event);
+}
diff --git a/server/src/com/vaadin/ui/components/grid/selection/SelectionChangeNotifier.java b/server/src/com/vaadin/ui/components/grid/selection/SelectionChangeNotifier.java
new file mode 100644
index 0000000000..f61dc138c0
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/grid/selection/SelectionChangeNotifier.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2000-2013 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.components.grid.selection;
+
+import java.io.Serializable;
+
+/**
+ * The interface for adding and removing listeners for
+ * {@link SelectionChangeEvent SelectionChangeEvents}.
+ *
+ * @since 7.4.0
+ * @author Vaadin Ltd
+ */
+public interface SelectionChangeNotifier extends Serializable {
+ /**
+ * Registers a new selection change listener
+ *
+ * @param listener
+ * the listener to register
+ */
+ void addSelectionChangeListener(SelectionChangeListener listener);
+
+ /**
+ * Removes a previously registered selection change listener
+ *
+ * @param listener
+ * the listener to remove
+ */
+ void removeSelectionChangeListener(SelectionChangeListener listener);
+}
diff --git a/server/src/com/vaadin/ui/components/grid/selection/SelectionModel.java b/server/src/com/vaadin/ui/components/grid/selection/SelectionModel.java
new file mode 100644
index 0000000000..d48e72e1d3
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/grid/selection/SelectionModel.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2000-2013 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.components.grid.selection;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+import com.vaadin.ui.components.grid.Grid;
+
+/**
+ * The server-side interface that controls Grid's selection state.
+ *
+ * @since 7.4.0
+ * @author Vaadin Ltd
+ */
+public interface SelectionModel extends Serializable {
+ /**
+ * Checks whether an item is selected or not.
+ *
+ * @param itemId
+ * the item id to check for
+ * @return <code>true</code> iff the item is selected
+ */
+ boolean isSelected(Object itemId);
+
+ /**
+ * Returns a collection of all the currently selected itemIds.
+ *
+ * @return a collection of all the currently selected itemIds
+ */
+ Collection<Object> getSelectedRows();
+
+ /**
+ * Injects the current {@link Grid} instance into the SelectionModel.
+ * <p>
+ * <em>Note:</em> This method should not be called manually.
+ *
+ * @param grid
+ * the Grid in which the SelectionModel currently is, or
+ * <code>null</code> when a selection model is being detached
+ * from a Grid.
+ */
+ void setGrid(Grid grid);
+
+ /**
+ * Resets the SelectiomModel to an initial state.
+ * <p>
+ * Most often this means that the selection state is cleared, but
+ * implementations are free to interpret the "initial state" as they wish.
+ * Some, for example, may want to keep the first selected item as selected.
+ */
+ void reset();
+
+ /**
+ * A SelectionModel that supports multiple selections to be made.
+ * <p>
+ * This interface has a contract of having the same behavior, no matter how
+ * the selection model is interacted with. In other words, if something is
+ * forbidden to do in e.g. the user interface, it must also be forbidden to
+ * do in the server-side and client-side APIs.
+ */
+ public interface Multi extends SelectionModel {
+
+ /**
+ * Marks items as selected.
+ * <p>
+ * This method does not clear any previous selection state, only adds to
+ * it.
+ *
+ * @param itemIds
+ * the itemId(s) to mark as selected
+ * @return <code>true</code> if the selection state changed.
+ * <code>false</code> if all the given itemIds already were
+ * selected
+ * @throws IllegalArgumentException
+ * if the <code>itemIds</code> varargs array is
+ * <code>null</code>
+ * @see #deselect(Object...)
+ */
+ boolean select(Object... itemIds) throws IllegalArgumentException;
+
+ /**
+ * Marks items as selected.
+ * <p>
+ * This method does not clear any previous selection state, only adds to
+ * it.
+ *
+ * @param itemIds
+ * the itemIds to mark as selected
+ * @return <code>true</code> if the selection state changed.
+ * <code>false</code> if all the given itemIds already were
+ * selected
+ * @throws IllegalArgumentException
+ * if <code>itemIds</code> is <code>null</code>
+ * @see #deselect(Collection)
+ */
+ boolean select(Collection<?> itemIds) throws IllegalArgumentException;
+
+ /**
+ * Marks items as deselected.
+ *
+ * @param itemIds
+ * the itemId(s) to remove from being selected
+ * @return <code>true</code> if the selection state changed.
+ * <code>false</code> if none the given itemIds were selected
+ * previously
+ * @throws IllegalArgumentException
+ * if the <code>itemIds</code> varargs array is
+ * <code>null</code>
+ * @see #select(Object...)
+ */
+ boolean deselect(Object... itemIds) throws IllegalArgumentException;
+
+ /**
+ * Marks items as deselected.
+ *
+ * @param itemIds
+ * the itemId(s) to remove from being selected
+ * @return <code>true</code> if the selection state changed.
+ * <code>false</code> if none the given itemIds were selected
+ * previously
+ * @throws IllegalArgumentException
+ * if <code>itemIds</code> is <code>null</code>
+ * @see #select(Collection)
+ */
+ boolean deselect(Collection<?> itemIds) throws IllegalArgumentException;
+
+ /**
+ * Marks all the items in the current Container as selected
+ *
+ * @return <code>true</code> iff some items were previously not selected
+ * @see #deselectAll()
+ */
+ boolean selectAll();
+
+ /**
+ * Marks all the items in the current Container as deselected
+ *
+ * @return <code>true</code> iff some items were previously selected
+ * @see #selectAll()
+ */
+ boolean deselectAll();
+ }
+
+ /**
+ * A SelectionModel that supports for only single rows to be selected at a
+ * time.
+ * <p>
+ * This interface has a contract of having the same behavior, no matter how
+ * the selection model is interacted with. In other words, if something is
+ * forbidden to do in e.g. the user interface, it must also be forbidden to
+ * do in the server-side and client-side APIs.
+ */
+ public interface Single extends SelectionModel {
+ /**
+ * Marks an item as selected.
+ *
+ * @param itemIds
+ * the itemId to mark as selected
+ * @return <code>true</code> if the selection state changed.
+ * <code>false</code> if the itemId already was selected
+ * @throws IllegalStateException
+ * if the selection was illegal. One such reason might be
+ * that the implementation already had an item selected, and
+ * that needs to be explicitly deselected before
+ * re-selecting something
+ * @see #deselect(Object)
+ */
+ boolean select(Object itemId) throws IllegalStateException;
+
+ /**
+ * Marks an item as deselected.
+ *
+ * @param itemId
+ * the itemId to remove from being selected
+ * @return <code>true</code> if the selection state changed.
+ * <code>false</code> if the itemId already was selected
+ * @throws IllegalStateException
+ * if the deselection was illegal. One such reason might be
+ * that the implementation enforces that an item is always
+ * selected
+ * @see #select(Object)
+ */
+ boolean deselect(Object itemId) throws IllegalStateException;
+
+ /**
+ * Gets the item id of the currently selected item.
+ *
+ * @return the item id of the currently selected item, or
+ * <code>null</code> if nothing is selected
+ */
+ Object getSelectedRow();
+ }
+
+ /**
+ * A SelectionModel that does not allow for rows to be selected.
+ * <p>
+ * This interface has a contract of having the same behavior, no matter how
+ * the selection model is interacted with. In other words, if the developer
+ * is unable to select something programmatically, it is not allowed for the
+ * end-user to select anything, either.
+ */
+ public interface None extends SelectionModel {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return always <code>false</code>.
+ */
+ @Override
+ public boolean isSelected(Object itemId);
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return always an empty collection.
+ */
+ @Override
+ public Collection<Object> getSelectedRows();
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/grid/selection/SingleSelectionModel.java b/server/src/com/vaadin/ui/components/grid/selection/SingleSelectionModel.java
new file mode 100644
index 0000000000..dfac1d777c
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/grid/selection/SingleSelectionModel.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2000-2013 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.components.grid.selection;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * A default implementation of a {@link SelectionModel.Single}
+ *
+ * @since 7.4.0
+ * @author Vaadin Ltd
+ */
+public class SingleSelectionModel extends AbstractSelectionModel implements
+ SelectionModel.Single {
+ @Override
+ public boolean select(final Object itemId) {
+ final Object selectedRow = getSelectedRow();
+ final boolean modified = selection.add(itemId);
+ if (modified) {
+ final Collection<Object> deselected;
+ if (selectedRow != null) {
+ deselectInternal(selectedRow, false);
+ deselected = Collections.singleton(selectedRow);
+ } else {
+ deselected = Collections.emptySet();
+ }
+
+ fireSelectionChangeEvent(deselected, selection);
+ }
+
+ return modified;
+ }
+
+ @Override
+ public boolean deselect(final Object itemId) {
+ return deselectInternal(itemId, true);
+ }
+
+ private boolean deselectInternal(final Object itemId,
+ boolean fireEventIfNeeded) {
+ final boolean modified = selection.remove(itemId);
+ if (fireEventIfNeeded && modified) {
+ fireSelectionChangeEvent(Collections.singleton(itemId),
+ Collections.emptySet());
+ }
+ return modified;
+ }
+
+ @Override
+ public Object getSelectedRow() {
+ if (selection.isEmpty()) {
+ return null;
+ } else {
+ return selection.iterator().next();
+ }
+ }
+
+ /**
+ * Resets the selection state.
+ * <p>
+ * If an item is selected, it will become deselected.
+ */
+ @Override
+ public void reset() {
+ deselect(getSelectedRow());
+ }
+}