瀏覽代碼

Grid server-side selection (#13334)

Change-Id: I62c5a2486360fe11de8a90efabb7775ef47124cb
tags/7.4.0.alpha2
Henrik Paul 10 年之前
父節點
當前提交
2eff69356b

+ 322
- 4
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
@@ -349,6 +357,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
*/
@@ -458,6 +507,16 @@ 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.
*
@@ -465,7 +524,8 @@ public class Grid extends AbstractComponent {
* 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);
}
}

+ 71
- 0
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);
}
}

+ 138
- 0
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();
}
}

+ 54
- 0
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
}
}

+ 73
- 0
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();
}
}

+ 35
- 0
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);
}

+ 43
- 0
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);
}

+ 234
- 0
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();
}
}

+ 81
- 0
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());
}
}

+ 306
- 0
server/tests/src/com/vaadin/tests/server/component/grid/GridSelection.java 查看文件

@@ -0,0 +1,306 @@
/*
* 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.tests.server.component.grid;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.Collection;

import org.junit.Before;
import org.junit.Test;

import com.vaadin.data.util.IndexedContainer;
import com.vaadin.ui.components.grid.Grid;
import com.vaadin.ui.components.grid.Grid.SelectionMode;
import com.vaadin.ui.components.grid.selection.SelectionChangeEvent;
import com.vaadin.ui.components.grid.selection.SelectionChangeListener;
import com.vaadin.ui.components.grid.selection.SelectionModel;

/**
*
* @since
* @author Vaadin Ltd
*/
public class GridSelection {

private static class MockSelectionChangeListener implements
SelectionChangeListener {
private SelectionChangeEvent event;

@Override
public void selectionChange(final SelectionChangeEvent event) {
this.event = event;
}

public Collection<?> getAdded() {
return event.getAdded();
}

public Collection<?> getRemoved() {
return event.getRemoved();
}

public void clearEvent() {
/*
* This method is not strictly needed as the event will simply be
* overridden, but it's good practice, and makes the code more
* obvious.
*/
event = null;
}

public boolean eventHasHappened() {
return event != null;
}
}

private Grid grid;
private MockSelectionChangeListener mockListener;

private final Object itemId1Present = "itemId1Present";
private final Object itemId2Present = "itemId2Present";

private final Object itemId1NotPresent = "itemId1NotPresent";
private final Object itemId2NotPresent = "itemId2NotPresent";

@Before
public void setup() {
final IndexedContainer container = new IndexedContainer();
container.addItem(itemId1Present);
container.addItem(itemId2Present);
for (int i = 2; i < 10; i++) {
container.addItem(new Object());
}

assertEquals("init size", 10, container.size());
assertTrue("itemId1Present", container.containsId(itemId1Present));
assertTrue("itemId2Present", container.containsId(itemId2Present));
assertFalse("itemId1NotPresent",
container.containsId(itemId1NotPresent));
assertFalse("itemId2NotPresent",
container.containsId(itemId2NotPresent));

grid = new Grid(container);

mockListener = new MockSelectionChangeListener();
grid.addSelectionChangeListener(mockListener);

assertFalse("eventHasHappened", mockListener.eventHasHappened());
}

@Test
public void defaultSelectionModeIsMulti() {
assertTrue(grid.getSelectionModel() instanceof SelectionModel.Multi);
}

@Test(expected = IllegalStateException.class)
public void getSelectedRowThrowsExceptionMulti() {
grid.setSelectionMode(SelectionMode.MULTI);
grid.getSelectedRow();
}

@Test(expected = IllegalStateException.class)
public void getSelectedRowThrowsExceptionNone() {
grid.setSelectionMode(SelectionMode.NONE);
grid.getSelectedRow();
}

@Test(expected = IllegalStateException.class)
public void selectThrowsExceptionNone() {
grid.setSelectionMode(SelectionMode.NONE);
grid.select(itemId1Present);
}

@Test(expected = IllegalStateException.class)
public void deselectRowThrowsExceptionNone() {
grid.setSelectionMode(SelectionMode.NONE);
grid.deselect(itemId1Present);
}

@Test
public void selectionModeMapsToMulti() {
assertTrue(grid.setSelectionMode(SelectionMode.MULTI) instanceof SelectionModel.Multi);
}

@Test
public void selectionModeMapsToSingle() {
assertTrue(grid.setSelectionMode(SelectionMode.SINGLE) instanceof SelectionModel.Single);
}

@Test
public void selectionModeMapsToNone() {
assertTrue(grid.setSelectionMode(SelectionMode.NONE) instanceof SelectionModel.None);
}

@Test(expected = IllegalArgumentException.class)
public void selectionModeNullThrowsException() {
grid.setSelectionMode(null);
}

@Test
public void noSelectModel_isSelected() {
grid.setSelectionMode(SelectionMode.NONE);
assertFalse("itemId1Present", grid.isSelected(itemId1Present));
assertFalse("itemId1NotPresent", grid.isSelected(itemId1NotPresent));
}

@Test(expected = IllegalStateException.class)
public void noSelectModel_getSelectedRow() {
grid.setSelectionMode(SelectionMode.NONE);
grid.getSelectedRow();
}

@Test
public void noSelectModel_getSelectedRows() {
grid.setSelectionMode(SelectionMode.NONE);
assertTrue(grid.getSelectedRows().isEmpty());
}

@Test
public void selectionCallsListenerMulti() {
grid.setSelectionMode(SelectionMode.MULTI);
selectionCallsListener();
}

@Test
public void selectionCallsListenerSingle() {
grid.setSelectionMode(SelectionMode.SINGLE);
selectionCallsListener();
}

private void selectionCallsListener() {
grid.select(itemId1Present);
assertEquals("added size", 1, mockListener.getAdded().size());
assertEquals("added item", itemId1Present, mockListener.getAdded()
.iterator().next());
assertEquals("removed size", 0, mockListener.getRemoved().size());
}

@Test
public void deselectionCallsListenerMulti() {
grid.setSelectionMode(SelectionMode.MULTI);
deselectionCallsListener();
}

@Test
public void deselectionCallsListenerSingle() {
grid.setSelectionMode(SelectionMode.SINGLE);
deselectionCallsListener();
}

private void deselectionCallsListener() {
grid.select(itemId1Present);
mockListener.clearEvent();

grid.deselect(itemId1Present);
assertEquals("removed size", 1, mockListener.getRemoved().size());
assertEquals("removed item", itemId1Present, mockListener.getRemoved()
.iterator().next());
assertEquals("removed size", 0, mockListener.getAdded().size());
}

@Test
public void deselectPresentButNotSelectedItemIdShouldntFireListenerMulti() {
grid.setSelectionMode(SelectionMode.MULTI);
deselectPresentButNotSelectedItemIdShouldntFireListener();
}

@Test
public void deselectPresentButNotSelectedItemIdShouldntFireListenerSingle() {
grid.setSelectionMode(SelectionMode.SINGLE);
deselectPresentButNotSelectedItemIdShouldntFireListener();
}

private void deselectPresentButNotSelectedItemIdShouldntFireListener() {
grid.deselect(itemId1Present);
assertFalse(mockListener.eventHasHappened());
}

@Test
public void deselectNotPresentItemIdShouldNotThrowExceptionMulti() {
grid.setSelectionMode(SelectionMode.MULTI);
grid.deselect(itemId1NotPresent);
}

@Test
public void deselectNotPresentItemIdShouldNotThrowExceptionSingle() {
grid.setSelectionMode(SelectionMode.SINGLE);
grid.deselect(itemId1NotPresent);
}

@Test
public void selectNotPresentItemIdShouldNotThrowExceptionMulti() {
grid.setSelectionMode(SelectionMode.MULTI);
grid.select(itemId1NotPresent);
}

@Test
public void selectNotPresentItemIdShouldNotThrowExceptionSingle() {
grid.setSelectionMode(SelectionMode.SINGLE);
grid.select(itemId1NotPresent);
}

@Test
public void selectAllMulti() {
grid.setSelectionMode(SelectionMode.MULTI);
final SelectionModel.Multi select = (SelectionModel.Multi) grid
.getSelectionModel();
select.selectAll();
assertEquals("added size", 10, mockListener.getAdded().size());
assertEquals("removed size", 0, mockListener.getRemoved().size());
assertTrue("itemId1Present",
mockListener.getAdded().contains(itemId1Present));
assertTrue("itemId2Present",
mockListener.getAdded().contains(itemId2Present));
}

@Test
public void deselectAllMulti() {
grid.setSelectionMode(SelectionMode.MULTI);
final SelectionModel.Multi select = (SelectionModel.Multi) grid
.getSelectionModel();
select.selectAll();
mockListener.clearEvent();

select.deselectAll();
assertEquals("removed size", 10, mockListener.getRemoved().size());
assertEquals("added size", 0, mockListener.getAdded().size());
assertTrue("itemId1Present",
mockListener.getRemoved().contains(itemId1Present));
assertTrue("itemId2Present",
mockListener.getRemoved().contains(itemId2Present));
assertTrue("selectedRows is empty", grid.getSelectedRows().isEmpty());
}

@Test
public void reselectionDeselectsPreviousSingle() {
grid.setSelectionMode(SelectionMode.SINGLE);
grid.select(itemId1Present);
mockListener.clearEvent();

grid.select(itemId2Present);
assertEquals("added size", 1, mockListener.getAdded().size());
assertEquals("removed size", 1, mockListener.getRemoved().size());
assertEquals("added item", itemId2Present, mockListener.getAdded()
.iterator().next());
assertEquals("removed item", itemId1Present, mockListener.getRemoved()
.iterator().next());
assertEquals("selectedRows is correct", itemId2Present,
grid.getSelectedRow());
}
}

Loading…
取消
儲存