aboutsummaryrefslogtreecommitdiffstats
path: root/server/src
diff options
context:
space:
mode:
authorPekka Hyvönen <pekka@vaadin.com>2016-11-11 09:41:43 +0200
committerVaadin Code Review <review@vaadin.com>2016-11-29 10:18:11 +0000
commitf2d8f812efa067b4baa7e27c0ea76f7596b291e6 (patch)
tree8e35e6c4eef4ffc5f8006d30989914da1deeab1b /server/src
parent13443562ccbd633ceb561bb87893014f65437ad1 (diff)
downloadvaadin-framework-f2d8f812efa067b4baa7e27c0ea76f7596b291e6.tar.gz
vaadin-framework-f2d8f812efa067b4baa7e27c0ea76f7596b291e6.zip
Add MultiSelect support for Grid
Still missing following things coming in next patches: - select all checkbox - firing an event when data provider is changed in grid - read only selection models for grid Part 1 for vaadin/framework8-issues#232 Change-Id: Ib2c7c81a838f43cb7c521a56d50139c91961f54a
Diffstat (limited to 'server/src')
-rw-r--r--server/src/main/java/com/vaadin/data/SelectionModel.java27
-rw-r--r--server/src/main/java/com/vaadin/event/selection/MultiSelectionEvent.java33
-rw-r--r--server/src/main/java/com/vaadin/ui/Grid.java75
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/MultiSelectionModelImpl.java277
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/SingleSelectionModelImpl.java (renamed from server/src/main/java/com/vaadin/ui/components/grid/SingleSelectionModel.java)34
-rw-r--r--server/src/test/java/com/vaadin/data/GridAsMultiSelectInBinder.java270
-rw-r--r--server/src/test/java/com/vaadin/data/GridAsSingleSelectInBinder.java14
-rw-r--r--server/src/test/java/com/vaadin/tests/components/grid/GridMultiSelectionModelTest.java543
-rw-r--r--server/src/test/java/com/vaadin/tests/components/grid/GridSingleSelectionModelTest.java113
9 files changed, 1329 insertions, 57 deletions
diff --git a/server/src/main/java/com/vaadin/data/SelectionModel.java b/server/src/main/java/com/vaadin/data/SelectionModel.java
index 98a068993a..cf4fb846b6 100644
--- a/server/src/main/java/com/vaadin/data/SelectionModel.java
+++ b/server/src/main/java/com/vaadin/data/SelectionModel.java
@@ -72,10 +72,15 @@ public interface SelectionModel<T> extends Serializable {
if (item != null) {
select(item);
} else {
- deselectAll();
+ getSelectedItem().ifPresent(this::deselect);
}
}
+ @Override
+ public default void deselectAll() {
+ setSelectedItem(null);
+ }
+
/**
* Returns a singleton set of the currently selected item or an empty
* set if no item is selected.
@@ -145,6 +150,12 @@ public interface SelectionModel<T> extends Serializable {
Collections.emptySet());
}
+ @SuppressWarnings("unchecked")
+ @Override
+ public default void deselect(T item) {
+ deselectItems(item);
+ }
+
/**
* Removes the given items from the set of currently selected items.
* <p>
@@ -183,6 +194,14 @@ public interface SelectionModel<T> extends Serializable {
default Optional<T> getFirstSelectedItem() {
return getSelectedItems().stream().findFirst();
}
+
+ /**
+ * Deselects all currently selected items.
+ */
+ @Override
+ public default void deselectAll() {
+ updateSelection(Collections.emptySet(), getSelectedItems());
+ }
}
/**
@@ -227,11 +246,9 @@ public interface SelectionModel<T> extends Serializable {
public void deselect(T item);
/**
- * Deselects all currently selected items.
+ * Deselects all currently selected items, if any.
*/
- public default void deselectAll() {
- getSelectedItems().forEach(this::deselect);
- }
+ public void deselectAll();
/**
* Returns whether the given item is currently selected.
diff --git a/server/src/main/java/com/vaadin/event/selection/MultiSelectionEvent.java b/server/src/main/java/com/vaadin/event/selection/MultiSelectionEvent.java
index 5fb91bbcbe..7718b5a1a6 100644
--- a/server/src/main/java/com/vaadin/event/selection/MultiSelectionEvent.java
+++ b/server/src/main/java/com/vaadin/event/selection/MultiSelectionEvent.java
@@ -21,6 +21,8 @@ import java.util.Set;
import com.vaadin.data.HasValue.ValueChangeEvent;
import com.vaadin.ui.AbstractMultiSelect;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.MultiSelect;
/**
* Event fired when the the selection changes in a
@@ -56,12 +58,31 @@ public class MultiSelectionEvent<T> extends ValueChangeEvent<Set<T>>
}
/**
+ * Creates a new selection change event in a multiselect component.
+ *
+ * @param component
+ * the component
+ * @param source
+ * the multiselect source
+ * @param oldSelection
+ * the old set of selected items
+ * @param userOriginated
+ * {@code true} if this event originates from the client,
+ * {@code false} otherwise.
+ */
+ public MultiSelectionEvent(Component component, MultiSelect<T> source,
+ Set<T> oldSelection, boolean userOriginated) {
+ super(component, source, userOriginated);
+ this.oldSelection = oldSelection;
+ }
+
+ /**
* Gets the new selection.
* <p>
* The result is the current selection of the source
* {@link AbstractMultiSelect} object. So it's always exactly the same as
* {@link AbstractMultiSelect#getValue()}
- *
+ *
* @see #getValue()
*
* @return a set of items selected after the selection was changed
@@ -83,4 +104,14 @@ public class MultiSelectionEvent<T> extends ValueChangeEvent<Set<T>>
public Optional<T> getFirstSelected() {
return getValue().stream().findFirst();
}
+
+ /**
+ * The multiselect on which the Event initially occurred.
+ *
+ * @return the multiselect on which the Event initially occurred.
+ */
+ @Override
+ public MultiSelect<T> getSource() {
+ return (MultiSelect<T>) super.getSource();
+ }
}
diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java
index e6830f3b95..5010387377 100644
--- a/server/src/main/java/com/vaadin/ui/Grid.java
+++ b/server/src/main/java/com/vaadin/ui/Grid.java
@@ -72,7 +72,7 @@ import com.vaadin.ui.components.grid.EditorImpl;
import com.vaadin.ui.components.grid.Footer;
import com.vaadin.ui.components.grid.Header;
import com.vaadin.ui.components.grid.Header.Row;
-import com.vaadin.ui.components.grid.SingleSelectionModel;
+import com.vaadin.ui.components.grid.SingleSelectionModelImpl;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.ui.renderers.AbstractRenderer;
import com.vaadin.ui.renderers.Renderer;
@@ -133,9 +133,12 @@ public class Grid<T> extends AbstractListing<T>
/**
* The server-side interface that controls Grid's selection state.
* SelectionModel should extend {@link AbstractGridExtension}.
+ * <p>
*
* @param <T>
* the grid bean type
+ * @see SingleSelectionModel
+ * @see MultiSelectionModel
*/
public interface GridSelectionModel<T>
extends SelectionModel<T>, Extension {
@@ -152,6 +155,42 @@ public class Grid<T> extends AbstractListing<T>
}
/**
+ * Single selection model interface for Grid.
+ *
+ * @param <T>
+ * the type of items in grid
+ */
+ public interface SingleSelectionModel<T> extends GridSelectionModel<T>,
+ com.vaadin.data.SelectionModel.Single<T> {
+
+ /**
+ * Gets a wrapper to use this single selection model as a single select
+ * in {@link Binder}.
+ *
+ * @return the single select wrapper
+ */
+ SingleSelect<T> asSingleSelect();
+ }
+
+ /**
+ * Multiselection model interface for Grid.
+ *
+ * @param <T>
+ * the type of items in grid
+ */
+ public interface MultiSelectionModel<T> extends GridSelectionModel<T>,
+ com.vaadin.data.SelectionModel.Multi<T> {
+
+ /**
+ * Gets a wrapper to use this multiselection model as a multiselect in
+ * {@link Binder}.
+ *
+ * @return the multiselect wrapper
+ */
+ MultiSelect<T> asMultiSelect();
+ }
+
+ /**
* An event listener for column resize events in the Grid.
*/
@FunctionalInterface
@@ -2030,7 +2069,7 @@ public class Grid<T> extends AbstractListing<T>
setDefaultHeaderRow(appendHeaderRow());
- selectionModel = new SingleSelectionModel<>(this);
+ selectionModel = new SingleSelectionModelImpl<>(this);
detailsManager = new DetailsManager<>();
addExtension(detailsManager);
@@ -2896,15 +2935,18 @@ public class Grid<T> extends AbstractListing<T>
/**
* Use this grid as a single select in {@link Binder}.
* <p>
- * Sets the grid to single select mode, if not yet so.
+ * Throws {@link IllegalStateException} if the grid is not using a
+ * {@link SingleSelectionModel}.
*
* @return the single select wrapper that can be used in binder
+ * @throws IllegalStateException
+ * if not using a single selection model
*/
public SingleSelect<T> asSingleSelect() {
GridSelectionModel<T> model = getSelectionModel();
if (!(model instanceof SingleSelectionModel)) {
- model = new SingleSelectionModel<>(this);
- setSelectionModel(model);
+ throw new IllegalStateException(
+ "Grid is not in single select mode, it needs to be explicitly set to such with setSelectionModel(SingleSelectionModel) before being able to use single selection features.");
}
return ((SingleSelectionModel<T>) model).asSingleSelect();
@@ -2915,14 +2957,33 @@ public class Grid<T> extends AbstractListing<T>
}
/**
+ * User this grid as a multiselect in {@link Binder}.
+ * <p>
+ * Throws {@link IllegalStateException} if the grid is not using a
+ * {@link MultiSelectionModel}.
+ *
+ * @return the multiselect wrapper that can be used in binder
+ * @throws IllegalStateException
+ * if not using a multiselection model
+ */
+ public MultiSelect<T> asMultiSelect() {
+ GridSelectionModel<T> model = getSelectionModel();
+ if (!(model instanceof MultiSelectionModel)) {
+ throw new IllegalStateException(
+ "Grid is not in multiselect mode, it needs to be explicitly set to such with setSelectionModel(MultiSelectionModel) before being able to use multiselection features.");
+ }
+ return ((MultiSelectionModel<T>) model).asMultiSelect();
+ }
+
+ /**
* Sets the selection model for this listing.
* <p>
- * The default selection model is {@link SingleSelectionModel}.
+ * The default selection model is {@link SingleSelectionModelImpl}.
*
* @param model
* the selection model to use, not {@code null}
*/
- protected void setSelectionModel(GridSelectionModel<T> model) {
+ public void setSelectionModel(GridSelectionModel<T> model) {
Objects.requireNonNull(model, "selection model cannot be null");
selectionModel.remove();
selectionModel = model;
diff --git a/server/src/main/java/com/vaadin/ui/components/grid/MultiSelectionModelImpl.java b/server/src/main/java/com/vaadin/ui/components/grid/MultiSelectionModelImpl.java
new file mode 100644
index 0000000000..286edd5f2b
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/components/grid/MultiSelectionModelImpl.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.grid;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import com.vaadin.event.selection.MultiSelectionEvent;
+import com.vaadin.event.selection.MultiSelectionListener;
+import com.vaadin.shared.Registration;
+import com.vaadin.shared.data.DataCommunicatorConstants;
+import com.vaadin.shared.data.selection.GridMultiSelectServerRpc;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.Grid.AbstractGridExtension;
+import com.vaadin.ui.Grid.MultiSelectionModel;
+import com.vaadin.ui.MultiSelect;
+import com.vaadin.util.ReflectTools;
+
+import elemental.json.JsonObject;
+
+/**
+ * Multiselection model for grid.
+ * <p>
+ * Shows a column of checkboxes as the first column of grid. Each checkbox
+ * triggers the selection for that row.
+ *
+ * @author Vaadin Ltd.
+ * @since 8.0
+ *
+ * @param <T>
+ * the type of the selected item in grid.
+ */
+public class MultiSelectionModelImpl<T> extends AbstractGridExtension<T>
+ implements MultiSelectionModel<T> {
+
+ private class GridMultiSelectServerRpcImpl
+ implements GridMultiSelectServerRpc {
+
+ @Override
+ public void select(String key) {
+ MultiSelectionModelImpl.this.updateSelection(
+ new LinkedHashSet<>(Arrays.asList(getData(key))),
+ Collections.emptySet(), true);
+ }
+
+ @Override
+ public void deselect(String key) {
+ MultiSelectionModelImpl.this.updateSelection(Collections.emptySet(),
+ new LinkedHashSet<>(Arrays.asList(getData(key))), true);
+ }
+
+ @Override
+ public void selectAll() {
+ // TODO will be added in another patch
+ throw new UnsupportedOperationException("Select all not supported");
+ }
+
+ @Override
+ public void deselectAll() {
+ // TODO will be added in another patch
+ throw new UnsupportedOperationException(
+ "Deelect all not supported");
+ }
+ }
+
+ @Deprecated
+ private static final Method SELECTION_CHANGE_METHOD = ReflectTools
+ .findMethod(MultiSelectionListener.class, "accept",
+ MultiSelectionEvent.class);
+
+ private final Grid<T> grid;
+
+ private Set<T> selection = new LinkedHashSet<>();
+
+ /**
+ * Constructs a new multiselection model for the given grid.
+ *
+ * @param grid
+ * the grid to bind the selection model into
+ */
+ public MultiSelectionModelImpl(Grid<T> grid) {
+ this.grid = grid;
+ extend(grid);
+
+ registerRpc(new GridMultiSelectServerRpcImpl());
+ }
+
+ @Override
+ public void remove() {
+ updateSelection(Collections.emptySet(), getSelectedItems(), false);
+
+ super.remove();
+ }
+
+ /**
+ * Adds a selection listener that will be called when the selection is
+ * changed either by the user or programmatically.
+ *
+ * @param listener
+ * the value change listener, not {@code null}
+ * @return a registration for the listener
+ */
+ public Registration addSelectionListener(
+ MultiSelectionListener<T> listener) {
+ addListener(MultiSelectionEvent.class, listener,
+ SELECTION_CHANGE_METHOD);
+ return () -> removeListener(MultiSelectionEvent.class, listener);
+ }
+
+ @Override
+ public void generateData(T item, JsonObject jsonObject) {
+ // in case of all items selected, don't write individual items as
+ // seleted
+ if (isSelected(item)) {
+ jsonObject.put(DataCommunicatorConstants.SELECTED, true);
+ }
+ }
+
+ @Override
+ public Set<T> getSelectedItems() {
+ return Collections.unmodifiableSet(new LinkedHashSet<>(selection));
+ }
+
+ @Override
+ public void updateSelection(Set<T> addedItems, Set<T> removedItems) {
+ updateSelection(addedItems, removedItems, false);
+ }
+
+ /**
+ * Gets a wrapper for using this grid as a multiselect in a binder.
+ *
+ * @return a multiselect wrapper for grid
+ */
+ @Override
+ public MultiSelect<T> asMultiSelect() {
+ return new MultiSelect<T>() {
+
+ @Override
+ public void setValue(Set<T> value) {
+ Objects.requireNonNull(value);
+ Set<T> copy = value.stream().map(Objects::requireNonNull)
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+
+ updateSelection(copy, new LinkedHashSet<>(getSelectedItems()));
+ }
+
+ @Override
+ public Set<T> getValue() {
+ return getSelectedItems();
+ }
+
+ @Override
+ public Registration addValueChangeListener(
+ com.vaadin.data.HasValue.ValueChangeListener<Set<T>> listener) {
+ return addSelectionListener(event -> listener.accept(event));
+ }
+
+ @Override
+ public void setRequiredIndicatorVisible(
+ boolean requiredIndicatorVisible) {
+ // TODO support required indicator for grid ?
+ throw new UnsupportedOperationException(
+ "Required indicator is not supported in grid.");
+ }
+
+ @Override
+ public boolean isRequiredIndicatorVisible() {
+ // TODO support required indicator for grid ?
+ throw new UnsupportedOperationException(
+ "Required indicator is not supported in grid.");
+ }
+
+ @Override
+ public void setReadOnly(boolean readOnly) {
+ // TODO support read only in grid ?
+ throw new UnsupportedOperationException(
+ "Read only mode is not supported for grid.");
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ // TODO support read only in grid ?
+ throw new UnsupportedOperationException(
+ "Read only mode is not supported for grid.");
+ }
+
+ @Override
+ public void updateSelection(Set<T> addedItems,
+ Set<T> removedItems) {
+ MultiSelectionModelImpl.this.updateSelection(addedItems,
+ removedItems);
+ }
+
+ @Override
+ public Set<T> getSelectedItems() {
+ return MultiSelectionModelImpl.this.getSelectedItems();
+ }
+
+ @Override
+ public Registration addSelectionListener(
+ MultiSelectionListener<T> listener) {
+ return MultiSelectionModelImpl.this
+ .addSelectionListener(listener);
+ }
+ };
+ }
+
+ /**
+ * Updates the selection by adding and removing the given items.
+ * <p>
+ * All selection updates should go through this method, since it handles
+ * incorrect parameters, removing duplicates, notifying data communicator
+ * and and firing events.
+ *
+ * @param addedItems
+ * the items added to selection, not {@code} null
+ * @param removedItems
+ * the items removed from selection, not {@code} null
+ * @param userOriginated
+ * {@code true} if this was used originated, {@code false} if not
+ */
+ protected void updateSelection(Set<T> addedItems, Set<T> removedItems,
+ boolean userOriginated) {
+ Objects.requireNonNull(addedItems);
+ Objects.requireNonNull(removedItems);
+
+ // if there are duplicates, some item is both added & removed, just
+ // discard that and leave things as was before
+ addedItems.removeIf(item -> removedItems.remove(item));
+
+ if (selection.containsAll(addedItems)
+ && Collections.disjoint(selection, removedItems)) {
+ return;
+ }
+
+ doUpdateSelection(set -> {
+ // order of add / remove does not matter since no duplicates
+ set.removeAll(removedItems);
+ set.addAll(addedItems);
+ removedItems.forEach(grid.getDataCommunicator()::refresh);
+ addedItems.forEach(grid.getDataCommunicator()::refresh);
+ }, userOriginated);
+ }
+
+ private void doUpdateSelection(Consumer<Set<T>> handler,
+ boolean userOriginated) {
+ if (getParent() == null) {
+ throw new IllegalStateException(
+ "Trying to update selection for grid selection model that has been detached from the grid.");
+ }
+
+ LinkedHashSet<T> oldSelection = new LinkedHashSet<>(selection);
+ handler.accept(selection);
+
+ fireEvent(new MultiSelectionEvent<>(grid, asMultiSelect(), oldSelection,
+ userOriginated));
+ }
+}
diff --git a/server/src/main/java/com/vaadin/ui/components/grid/SingleSelectionModel.java b/server/src/main/java/com/vaadin/ui/components/grid/SingleSelectionModelImpl.java
index d095504768..e68487f358 100644
--- a/server/src/main/java/com/vaadin/ui/components/grid/SingleSelectionModel.java
+++ b/server/src/main/java/com/vaadin/ui/components/grid/SingleSelectionModelImpl.java
@@ -23,7 +23,6 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import com.vaadin.data.SelectionModel;
import com.vaadin.event.selection.SingleSelectionEvent;
import com.vaadin.event.selection.SingleSelectionListener;
import com.vaadin.shared.Registration;
@@ -32,7 +31,7 @@ import com.vaadin.shared.data.selection.SelectionServerRpc;
import com.vaadin.ui.Component;
import com.vaadin.ui.Grid;
import com.vaadin.ui.Grid.AbstractGridExtension;
-import com.vaadin.ui.Grid.GridSelectionModel;
+import com.vaadin.ui.Grid.SingleSelectionModel;
import com.vaadin.ui.SingleSelect;
import com.vaadin.util.ReflectTools;
@@ -47,8 +46,8 @@ import elemental.json.JsonObject;
* @param <T>
* the type of the selected item in grid.
*/
-public class SingleSelectionModel<T> extends AbstractGridExtension<T>
- implements GridSelectionModel<T>, SelectionModel.Single<T> {
+public class SingleSelectionModelImpl<T> extends AbstractGridExtension<T>
+ implements SingleSelectionModel<T> {
private static final Method SELECTION_CHANGE_METHOD = ReflectTools
.findMethod(SingleSelectionListener.class, "accept",
@@ -63,7 +62,7 @@ public class SingleSelectionModel<T> extends AbstractGridExtension<T>
* @param grid
* the grid to bind the selection model into
*/
- public SingleSelectionModel(Grid<T> grid) {
+ public SingleSelectionModelImpl(Grid<T> grid) {
this.grid = grid;
extend(grid);
registerRpc(new SelectionServerRpc() {
@@ -83,15 +82,14 @@ public class SingleSelectionModel<T> extends AbstractGridExtension<T>
}
/**
- * Adds a selection change listener to this select. The listener is called
- * when the value of this select is changed either by the user or
- * programmatically.
+ * Adds a selection listener to this select. The listener is called when the
+ * value of this select is changed either by the user or programmatically.
*
* @param listener
* the value change listener, not null
* @return a registration for the listener
*/
- public Registration addSelectionChangeListener(
+ public Registration addSelectionListener(
SingleSelectionListener<T> listener) {
return addListener(SingleSelectionEvent.class, listener,
SELECTION_CHANGE_METHOD);
@@ -148,6 +146,11 @@ public class SingleSelectionModel<T> extends AbstractGridExtension<T>
* selection
*/
protected void doSetSelectedKey(String key) {
+ if (getParent() == null) {
+ throw new IllegalStateException(
+ "Trying to update selection for grid selection model that has been detached from the grid.");
+ }
+
if (selectedItem != null) {
grid.getDataCommunicator().refresh(selectedItem);
}
@@ -233,8 +236,7 @@ public class SingleSelectionModel<T> extends AbstractGridExtension<T>
// when selection model changes, firing an event for selection change
// event fired before removing so that parent is still intact (in case
// needed)
- selectedItem = null;
- fireEvent(new SingleSelectionEvent<>(grid, asSingleSelect(), false));
+ setSelectedFromServer(null);
super.remove();
}
@@ -244,24 +246,26 @@ public class SingleSelectionModel<T> extends AbstractGridExtension<T>
*
* @return a single select wrapper for grid
*/
+ @Override
public SingleSelect<T> asSingleSelect() {
return new SingleSelect<T>() {
@Override
public void setValue(T value) {
- SingleSelectionModel.this.setSelectedFromServer(value);
+ SingleSelectionModelImpl.this.setSelectedFromServer(value);
}
@Override
public T getValue() {
- return SingleSelectionModel.this.getSelectedItem().orElse(null);
+ return SingleSelectionModelImpl.this.getSelectedItem()
+ .orElse(null);
}
@Override
public Registration addValueChangeListener(
com.vaadin.data.HasValue.ValueChangeListener<T> listener) {
- return SingleSelectionModel.this.addSelectionChangeListener(
- event -> listener.accept(event));
+ return SingleSelectionModelImpl.this
+ .addSelectionListener(event -> listener.accept(event));
}
@Override
diff --git a/server/src/test/java/com/vaadin/data/GridAsMultiSelectInBinder.java b/server/src/test/java/com/vaadin/data/GridAsMultiSelectInBinder.java
new file mode 100644
index 0000000000..e31b2ab188
--- /dev/null
+++ b/server/src/test/java/com/vaadin/data/GridAsMultiSelectInBinder.java
@@ -0,0 +1,270 @@
+package com.vaadin.data;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.data.util.converter.Converter;
+import com.vaadin.data.util.converter.ValueContext;
+import com.vaadin.tests.data.bean.BeanWithEnums;
+import com.vaadin.tests.data.bean.Sex;
+import com.vaadin.tests.data.bean.TestEnum;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.MultiSelect;
+import com.vaadin.ui.components.grid.MultiSelectionModelImpl;
+import com.vaadin.ui.components.grid.SingleSelectionModelImpl;
+
+public class GridAsMultiSelectInBinder
+ extends BinderTestBase<Binder<BeanWithEnums>, BeanWithEnums> {
+ public class TestEnumSetToStringConverter
+ implements Converter<Set<TestEnum>, String> {
+ @Override
+ public Result<String> convertToModel(Set<TestEnum> value,
+ ValueContext context) {
+ return Result.ok(value.stream().map(TestEnum::name)
+ .collect(Collectors.joining(",")));
+ }
+
+ @Override
+ public Set<TestEnum> convertToPresentation(String value,
+ ValueContext context) {
+ return Stream.of(value.split(","))
+ .filter(string -> !string.isEmpty()).map(TestEnum::valueOf)
+ .collect(Collectors.toSet());
+ }
+ }
+
+ private class CustomMultiSelectModel extends MultiSelectionModelImpl<Sex> {
+
+ public CustomMultiSelectModel(Grid<Sex> grid) {
+ super(grid);
+ }
+
+ @Override
+ public void updateSelection(Set<Sex> addedItems, Set<Sex> removedItems,
+ boolean userOriginated) {
+ super.updateSelection(addedItems, removedItems, userOriginated);
+ }
+
+ }
+
+ private Binder<AtomicReference<String>> converterBinder = new Binder<>();
+ private Grid<TestEnum> grid;
+ private MultiSelect<TestEnum> select;
+
+ @Before
+ public void setUp() {
+ binder = new Binder<>();
+ item = new BeanWithEnums();
+ grid = new Grid<>();
+ grid.setItems(TestEnum.values());
+ grid.setSelectionModel(new MultiSelectionModelImpl<>(grid));
+ select = grid.asMultiSelect();
+
+ converterBinder.forField(select)
+ .withConverter(new TestEnumSetToStringConverter())
+ .bind(AtomicReference::get, AtomicReference::set);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void boundGridInBinder_selectionModelChanged_throws() {
+ grid.setSelectionModel(new SingleSelectionModelImpl<>(grid));
+
+ select.select(TestEnum.ONE);
+ }
+
+ @Test
+ public void beanBound_bindSelectByShortcut_selectionUpdated() {
+ item.setEnums(Collections.singleton(TestEnum.ONE));
+ binder.setBean(item);
+ binder.bind(select, BeanWithEnums::getEnums, BeanWithEnums::setEnums);
+
+ assertEquals(Collections.singleton(TestEnum.ONE),
+ select.getSelectedItems());
+ }
+
+ @Test
+ public void beanBound_bindSelect_selectionUpdated() {
+ item.setEnums(Collections.singleton(TestEnum.TWO));
+ binder.setBean(item);
+ binder.forField(select).bind(BeanWithEnums::getEnums,
+ BeanWithEnums::setEnums);
+
+ assertEquals(Collections.singleton(TestEnum.TWO),
+ select.getSelectedItems());
+ }
+
+ @Test
+ public void selectBound_bindBeanWithoutEnums_selectedItemNotPresent() {
+ bindEnum();
+
+ assertTrue(select.getSelectedItems().isEmpty());
+ }
+
+ @Test
+ public void selectBound_bindBean_selectionUpdated() {
+ item.setEnums(Collections.singleton(TestEnum.ONE));
+ bindEnum();
+
+ assertEquals(Collections.singleton(TestEnum.ONE),
+ select.getSelectedItems());
+ }
+
+ @Test
+ public void bound_setSelection_beanValueUpdated() {
+ bindEnum();
+
+ select.select(TestEnum.TWO);
+
+ assertEquals(Collections.singleton(TestEnum.TWO), item.getEnums());
+ }
+
+ @Test
+ public void bound_setSelection_beanValueIsACopy() {
+ bindEnum();
+
+ select.select(TestEnum.TWO);
+
+ Set<TestEnum> enums = item.getEnums();
+
+ binder.setBean(new BeanWithEnums());
+ select.select(TestEnum.ONE);
+
+ assertEquals(Collections.singleton(TestEnum.TWO), enums);
+ }
+
+ @Test
+ public void bound_deselect_beanValueUpdatedToNull() {
+ item.setEnums(Collections.singleton(TestEnum.ONE));
+ bindEnum();
+
+ select.deselect(TestEnum.ONE);
+
+ assertTrue(item.getEnums().isEmpty());
+ }
+
+ @Test
+ public void unbound_changeSelection_beanValueNotUpdated() {
+ item.setEnums(Collections.singleton(TestEnum.ONE));
+ bindEnum();
+ binder.removeBean();
+
+ select.select(TestEnum.TWO);
+
+ assertEquals(Collections.singleton(TestEnum.ONE), item.getEnums());
+ }
+
+ @Test
+ public void withConverter_load_selectUpdated() {
+ converterBinder.readBean(new AtomicReference<>("TWO"));
+
+ assertEquals(Collections.singleton(TestEnum.TWO),
+ select.getSelectedItems());
+ }
+
+ @Test
+ public void withConverter_save_referenceUpdated() {
+ select.select(TestEnum.ONE);
+ select.select(TestEnum.TWO);
+
+ AtomicReference<String> reference = new AtomicReference<>("");
+ converterBinder.writeBeanIfValid(reference);
+
+ assertEquals("ONE,TWO", reference.get());
+ }
+
+ @Test
+ public void withValidator_validate_validatorUsed() {
+ binder.forField(select)
+ .withValidator(selection -> selection.size() % 2 == 1,
+ "Must select odd number of items")
+ .bind(BeanWithEnums::getEnums, BeanWithEnums::setEnums);
+ binder.setBean(item);
+
+ assertFalse(binder.validate().isOk());
+
+ select.select(TestEnum.TWO);
+
+ assertTrue(binder.validate().isOk());
+ }
+
+ @Test
+ public void addValueChangeListener_selectionUpdated_eventTriggeredForMultiSelect() {
+ Grid<Sex> grid = new Grid<>();
+ CustomMultiSelectModel model = new CustomMultiSelectModel(grid);
+ grid.setSelectionModel(model);
+ grid.setItems(Sex.values());
+ MultiSelect<Sex> select = grid.asMultiSelect();
+
+ List<Sex> selected = new ArrayList<>();
+ List<Boolean> userOriginated = new ArrayList<>();
+ select.addValueChangeListener(event -> {
+ selected.addAll(event.getValue());
+ userOriginated.add(event.isUserOriginated());
+ assertSame(grid, event.getComponent());
+ // cannot compare that the event source is the select since a new
+ // MultiSelect wrapper object has been created for the event
+
+ assertEquals(select.getValue(), event.getValue());
+ });
+
+ select.select(Sex.UNKNOWN);
+
+ assertEquals(Arrays.asList(Sex.UNKNOWN), selected);
+
+ model.updateSelection(new LinkedHashSet<>(Arrays.asList(Sex.MALE)),
+ Collections.emptySet(), true); // simulate client side selection
+ assertEquals(Arrays.asList(Sex.UNKNOWN, Sex.UNKNOWN, Sex.MALE),
+ selected);
+ selected.clear();
+
+ select.select(Sex.MALE); // NOOP
+ assertEquals(Arrays.asList(), selected);
+ selected.clear();
+
+ model.updateSelection(Collections.emptySet(),
+ new LinkedHashSet<>(Arrays.asList(Sex.UNKNOWN)), true); // client
+ // side
+ // deselect
+ assertEquals(Arrays.asList(Sex.MALE), selected);
+ selected.clear();
+
+ select.deselect(Sex.UNKNOWN); // NOOP
+ assertEquals(Arrays.asList(), selected);
+ selected.clear();
+
+ select.deselect(Sex.FEMALE, Sex.MALE); // partly NOOP
+ assertEquals(Arrays.asList(), selected);
+
+ model.selectItems(Sex.FEMALE, Sex.MALE);
+ assertEquals(Arrays.asList(Sex.FEMALE, Sex.MALE), selected);
+ selected.clear();
+
+ model.updateSelection(new LinkedHashSet<>(Arrays.asList(Sex.FEMALE)),
+ Collections.emptySet(), true); // client side NOOP
+ assertEquals(Arrays.asList(), selected);
+
+ assertEquals(Arrays.asList(false, true, true, false, false),
+ userOriginated);
+ }
+
+ protected void bindEnum() {
+ binder.forField(select).bind(BeanWithEnums::getEnums,
+ BeanWithEnums::setEnums);
+ binder.setBean(item);
+ }
+}
diff --git a/server/src/test/java/com/vaadin/data/GridAsSingleSelectInBinder.java b/server/src/test/java/com/vaadin/data/GridAsSingleSelectInBinder.java
index 7b34c78f25..cf649384e0 100644
--- a/server/src/test/java/com/vaadin/data/GridAsSingleSelectInBinder.java
+++ b/server/src/test/java/com/vaadin/data/GridAsSingleSelectInBinder.java
@@ -16,7 +16,8 @@ import com.vaadin.tests.data.bean.Person;
import com.vaadin.tests.data.bean.Sex;
import com.vaadin.ui.Grid;
import com.vaadin.ui.SingleSelect;
-import com.vaadin.ui.components.grid.SingleSelectionModel;
+import com.vaadin.ui.components.grid.MultiSelectionModelImpl;
+import com.vaadin.ui.components.grid.SingleSelectionModelImpl;
public class GridAsSingleSelectInBinder
extends BinderTestBase<Binder<Person>, Person> {
@@ -29,7 +30,7 @@ public class GridAsSingleSelectInBinder
}
}
- private class CustomSingleSelectModel extends SingleSelectionModel<Sex> {
+ private class CustomSingleSelectModel extends SingleSelectionModelImpl<Sex> {
public CustomSingleSelectModel(Grid<Sex> grid) {
super(grid);
@@ -52,6 +53,13 @@ public class GridAsSingleSelectInBinder
select = grid.asSingleSelect();
}
+ @Test(expected = IllegalStateException.class)
+ public void boundGridInBinder_selectionModelChanged_throws() {
+ grid.setSelectionModel(new MultiSelectionModelImpl<>(grid));
+
+ select.setValue(Sex.MALE);
+ }
+
@Test
public void personBound_bindSelectByShortcut_selectionUpdated() {
item.setSex(Sex.FEMALE);
@@ -117,8 +125,6 @@ public class GridAsSingleSelectInBinder
@Test
public void addValueChangeListener_selectionUpdated_eventTriggeredForSelect() {
- binder = new Binder<>();
- item = new Person();
GridWithCustomSingleSelectionModel grid = new GridWithCustomSingleSelectionModel();
CustomSingleSelectModel model = new CustomSingleSelectModel(grid);
grid.setSelectionModel(model);
diff --git a/server/src/test/java/com/vaadin/tests/components/grid/GridMultiSelectionModelTest.java b/server/src/test/java/com/vaadin/tests/components/grid/GridMultiSelectionModelTest.java
new file mode 100644
index 0000000000..d7a087be54
--- /dev/null
+++ b/server/src/test/java/com/vaadin/tests/components/grid/GridMultiSelectionModelTest.java
@@ -0,0 +1,543 @@
+package com.vaadin.tests.components.grid;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.easymock.Capture;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import com.vaadin.data.HasValue.ValueChangeEvent;
+import com.vaadin.event.selection.SingleSelectionEvent;
+import com.vaadin.event.selection.SingleSelectionListener;
+import com.vaadin.server.data.provider.bov.Person;
+import com.vaadin.shared.Registration;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.Grid.GridSelectionModel;
+import com.vaadin.ui.components.grid.MultiSelectionModelImpl;
+import com.vaadin.ui.components.grid.SingleSelectionModelImpl;
+
+import elemental.json.JsonObject;
+
+public class GridMultiSelectionModelTest {
+
+ public static final Person PERSON_C = new Person("c", 3);
+ public static final Person PERSON_B = new Person("b", 2);
+ public static final Person PERSON_A = new Person("a", 1);
+
+ private Grid<Person> grid;
+ private MultiSelectionModelImpl<Person> selectionModel;
+ private Capture<List<Person>> currentSelectionCapture;
+ private Capture<List<Person>> oldSelectionCapture;
+ private AtomicInteger events;
+
+ public static class CustomMultiSelectionModel
+ extends MultiSelectionModelImpl<String> {
+ public final Map<String, Boolean> generatedData = new LinkedHashMap<>();
+
+ public CustomMultiSelectionModel(Grid<String> grid) {
+ super(grid);
+ }
+
+ @Override
+ public void generateData(String item, JsonObject jsonObject) {
+ super.generateData(item, jsonObject);
+ // capture updated row
+ generatedData.put(item, isSelected(item));
+ }
+
+ }
+
+ @Before
+ public void setUp() {
+ grid = new Grid<>();
+ selectionModel = new MultiSelectionModelImpl<>(grid);
+ grid.setSelectionModel(selectionModel);
+ grid.setItems(PERSON_A, PERSON_B, PERSON_C);
+
+ currentSelectionCapture = new Capture<>();
+ oldSelectionCapture = new Capture<>();
+ events = new AtomicInteger();
+
+ selectionModel.addSelectionListener(event -> {
+ currentSelectionCapture
+ .setValue(new ArrayList<>(event.getNewSelection()));
+ oldSelectionCapture
+ .setValue(new ArrayList<>(event.getOldSelection()));
+ events.incrementAndGet();
+ });
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void selectionModelChanged_usingPreviousSelectionModel_throws() {
+ grid.setSelectionModel(new SingleSelectionModelImpl<>(grid));
+
+ selectionModel.select(PERSON_A);
+ }
+
+ @Test
+ public void changingSelectionModel_firesSelectionEvent() {
+ Grid<String> customGrid = new Grid<>();
+ customGrid.setSelectionModel(new MultiSelectionModelImpl<>(customGrid));
+ customGrid.setItems("Foo", "Bar", "Baz");
+
+ List<String> selectionChanges = new ArrayList<>();
+ Capture<List<String>> oldSelectionCapture = new Capture<>();
+ ((MultiSelectionModelImpl<String>) customGrid.getSelectionModel())
+ .addSelectionListener(e -> {
+ selectionChanges.addAll(e.getValue());
+ oldSelectionCapture
+ .setValue(new ArrayList<>(e.getOldSelection()));
+ });
+
+ customGrid.getSelectionModel().select("Foo");
+ assertEquals(Arrays.asList("Foo"), selectionChanges);
+ selectionChanges.clear();
+
+ customGrid.getSelectionModel().select("Bar");
+ assertEquals("Foo",
+ customGrid.getSelectionModel().getFirstSelectedItem().get());
+ assertEquals(Arrays.asList("Foo", "Bar"), selectionChanges);
+ selectionChanges.clear();
+
+ customGrid.setSelectionModel(new SingleSelectionModelImpl<>(customGrid));
+ assertFalse(customGrid.getSelectionModel().getFirstSelectedItem()
+ .isPresent());
+ assertEquals(Arrays.asList(), selectionChanges);
+ assertEquals(Arrays.asList("Foo", "Bar"),
+ oldSelectionCapture.getValue());
+ }
+
+ @Test
+ public void serverSideSelection_GridChangingSelectionModel_sendsUpdatedRowsToClient() {
+ Grid<String> customGrid = new Grid<>();
+ customGrid.setItems("Foo", "Bar", "Baz");
+
+ CustomMultiSelectionModel customModel = new CustomMultiSelectionModel(
+ customGrid);
+ customGrid.setSelectionModel(customModel);
+ customGrid.getDataCommunicator().beforeClientResponse(true);
+
+ Assert.assertFalse("Item should have been updated as selected",
+ customModel.generatedData.get("Foo"));
+ Assert.assertFalse("Item should have been updated as NOT selected",
+ customModel.generatedData.get("Bar"));
+ Assert.assertFalse("Item should have been updated as NOT selected",
+ customModel.generatedData.get("Baz"));
+
+ customModel.generatedData.clear();
+
+ customGrid.getSelectionModel().select("Foo");
+ customGrid.getDataCommunicator().beforeClientResponse(false);
+
+ Assert.assertTrue("Item should have been updated as selected",
+ customModel.generatedData.get("Foo"));
+ Assert.assertFalse("Item should have NOT been updated",
+ customModel.generatedData.containsKey("Bar"));
+ Assert.assertFalse("Item should have NOT been updated",
+ customModel.generatedData.containsKey("Baz"));
+
+ customModel.generatedData.clear();
+
+ customModel.updateSelection(asSet("Bar"), asSet("Foo"));
+ customGrid.getDataCommunicator().beforeClientResponse(false);
+
+ Assert.assertFalse("Item should have been updated as NOT selected",
+ customModel.generatedData.get("Foo"));
+ Assert.assertTrue("Item should have been updated as selected",
+ customModel.generatedData.get("Bar"));
+ Assert.assertFalse("Item should have NOT been updated",
+ customModel.generatedData.containsKey("Baz"));
+
+ // switch to single to cause event
+ customModel.generatedData.clear();
+ customGrid.setSelectionModel(new SingleSelectionModelImpl<>(customGrid));
+ customGrid.getDataCommunicator().beforeClientResponse(false);
+
+ // changing selection model should trigger row updates, but the old
+ // selection model is not triggered as it has been removed
+ Assert.assertTrue(customModel.generatedData.isEmpty()); // not triggered
+ }
+
+ @Test
+ public void select_gridWithStrings() {
+ Grid<String> gridWithStrings = new Grid<>();
+ gridWithStrings
+ .setSelectionModel(new MultiSelectionModelImpl<>(gridWithStrings));
+ gridWithStrings.setItems("Foo", "Bar", "Baz");
+
+ GridSelectionModel<String> model = gridWithStrings.getSelectionModel();
+ Assert.assertFalse(model.isSelected("Foo"));
+
+ model.select("Foo");
+ Assert.assertTrue(model.isSelected("Foo"));
+ Assert.assertEquals(Optional.of("Foo"), model.getFirstSelectedItem());
+
+ model.select("Bar");
+ Assert.assertTrue(model.isSelected("Foo"));
+ Assert.assertTrue(model.isSelected("Bar"));
+ Assert.assertEquals(Arrays.asList("Foo", "Bar"),
+ new ArrayList<>(model.getSelectedItems()));
+
+ model.deselect("Bar");
+ Assert.assertFalse(model.isSelected("Bar"));
+ Assert.assertTrue(model.getFirstSelectedItem().isPresent());
+ Assert.assertEquals(Arrays.asList("Foo"),
+ new ArrayList<>(model.getSelectedItems()));
+ }
+
+ @Test
+ public void select() {
+ selectionModel.select(PERSON_B);
+
+ assertEquals(PERSON_B,
+ selectionModel.getFirstSelectedItem().orElse(null));
+ assertEquals(Optional.of(PERSON_B),
+ selectionModel.getFirstSelectedItem());
+
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertTrue(selectionModel.isSelected(PERSON_B));
+ assertFalse(selectionModel.isSelected(PERSON_C));
+
+ assertEquals(Arrays.asList(PERSON_B),
+ currentSelectionCapture.getValue());
+
+ selectionModel.select(PERSON_A);
+ assertEquals(PERSON_B,
+ selectionModel.getFirstSelectedItem().orElse(null));
+
+ assertTrue(selectionModel.isSelected(PERSON_A));
+ assertTrue(selectionModel.isSelected(PERSON_B));
+ assertFalse(selectionModel.isSelected(PERSON_C));
+
+ assertEquals(Arrays.asList(PERSON_B, PERSON_A),
+ currentSelectionCapture.getValue());
+ assertEquals(2, events.get());
+ }
+
+ @Test
+ public void deselect() {
+ selectionModel.select(PERSON_B);
+ selectionModel.deselect(PERSON_B);
+
+ assertFalse(selectionModel.getFirstSelectedItem().isPresent());
+
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertFalse(selectionModel.isSelected(PERSON_B));
+ assertFalse(selectionModel.isSelected(PERSON_C));
+
+ assertEquals(Arrays.asList(), currentSelectionCapture.getValue());
+ assertEquals(2, events.get());
+ }
+
+ @Test
+ public void selectItems() {
+ selectionModel.selectItems(PERSON_C, PERSON_B);
+
+ assertEquals(PERSON_C,
+ selectionModel.getFirstSelectedItem().orElse(null));
+ assertEquals(Optional.of(PERSON_C),
+ selectionModel.getFirstSelectedItem());
+
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertTrue(selectionModel.isSelected(PERSON_B));
+ assertTrue(selectionModel.isSelected(PERSON_C));
+
+ assertEquals(Arrays.asList(PERSON_C, PERSON_B),
+ currentSelectionCapture.getValue());
+
+ selectionModel.selectItems(PERSON_A, PERSON_C); // partly NOOP
+ assertTrue(selectionModel.isSelected(PERSON_A));
+ assertTrue(selectionModel.isSelected(PERSON_B));
+ assertTrue(selectionModel.isSelected(PERSON_C));
+
+ assertEquals(Arrays.asList(PERSON_C, PERSON_B, PERSON_A),
+ currentSelectionCapture.getValue());
+ assertEquals(2, events.get());
+ }
+
+ @Test
+ public void deselectItems() {
+ selectionModel.selectItems(PERSON_C, PERSON_A, PERSON_B);
+
+ selectionModel.deselectItems(PERSON_A);
+ assertEquals(PERSON_C,
+ selectionModel.getFirstSelectedItem().orElse(null));
+ assertEquals(Optional.of(PERSON_C),
+ selectionModel.getFirstSelectedItem());
+
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertTrue(selectionModel.isSelected(PERSON_B));
+ assertTrue(selectionModel.isSelected(PERSON_C));
+
+ assertEquals(Arrays.asList(PERSON_C, PERSON_B),
+ currentSelectionCapture.getValue());
+
+ selectionModel.deselectItems(PERSON_A, PERSON_B, PERSON_C);
+ assertNull(selectionModel.getFirstSelectedItem().orElse(null));
+ assertEquals(Optional.empty(), selectionModel.getFirstSelectedItem());
+
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertFalse(selectionModel.isSelected(PERSON_B));
+ assertFalse(selectionModel.isSelected(PERSON_C));
+
+ assertEquals(Arrays.asList(), currentSelectionCapture.getValue());
+ assertEquals(3, events.get());
+ }
+
+ @Test
+ public void selectionEvent_newSelection_oldSelection() {
+ selectionModel.selectItems(PERSON_C, PERSON_A, PERSON_B);
+
+ assertEquals(Arrays.asList(PERSON_C, PERSON_A, PERSON_B),
+ currentSelectionCapture.getValue());
+ assertEquals(Arrays.asList(), oldSelectionCapture.getValue());
+
+ selectionModel.deselect(PERSON_A);
+
+ assertEquals(Arrays.asList(PERSON_C, PERSON_B),
+ currentSelectionCapture.getValue());
+ assertEquals(Arrays.asList(PERSON_C, PERSON_A, PERSON_B),
+ oldSelectionCapture.getValue());
+
+ selectionModel.deselectItems(PERSON_A, PERSON_B, PERSON_C);
+ assertEquals(Arrays.asList(), currentSelectionCapture.getValue());
+ assertEquals(Arrays.asList(PERSON_C, PERSON_B),
+ oldSelectionCapture.getValue());
+
+ selectionModel.selectItems(PERSON_A);
+ assertEquals(Arrays.asList(PERSON_A),
+ currentSelectionCapture.getValue());
+ assertEquals(Arrays.asList(), oldSelectionCapture.getValue());
+
+ selectionModel.updateSelection(
+ new LinkedHashSet<>(Arrays.asList(PERSON_B, PERSON_C)),
+ new LinkedHashSet<>(Arrays.asList(PERSON_A)));
+ assertEquals(Arrays.asList(PERSON_B, PERSON_C),
+ currentSelectionCapture.getValue());
+ assertEquals(Arrays.asList(PERSON_A), oldSelectionCapture.getValue());
+
+ selectionModel.deselectAll();
+ assertEquals(Arrays.asList(), currentSelectionCapture.getValue());
+ assertEquals(Arrays.asList(PERSON_B, PERSON_C),
+ oldSelectionCapture.getValue());
+
+ selectionModel.select(PERSON_C);
+ assertEquals(Arrays.asList(PERSON_C),
+ currentSelectionCapture.getValue());
+ assertEquals(Arrays.asList(), oldSelectionCapture.getValue());
+
+ selectionModel.deselect(PERSON_C);
+ assertEquals(Arrays.asList(), currentSelectionCapture.getValue());
+ assertEquals(Arrays.asList(PERSON_C), oldSelectionCapture.getValue());
+ }
+
+ @Test
+ public void deselectAll() {
+ selectionModel.selectItems(PERSON_A, PERSON_C, PERSON_B);
+
+ assertTrue(selectionModel.isSelected(PERSON_A));
+ assertTrue(selectionModel.isSelected(PERSON_B));
+ assertTrue(selectionModel.isSelected(PERSON_C));
+ assertEquals(Arrays.asList(PERSON_A, PERSON_C, PERSON_B),
+ currentSelectionCapture.getValue());
+ assertEquals(1, events.get());
+
+ selectionModel.deselectAll();
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertFalse(selectionModel.isSelected(PERSON_B));
+ assertFalse(selectionModel.isSelected(PERSON_C));
+ assertEquals(Arrays.asList(), currentSelectionCapture.getValue());
+ assertEquals(Arrays.asList(PERSON_A, PERSON_C, PERSON_B),
+ oldSelectionCapture.getValue());
+ assertEquals(2, events.get());
+
+ selectionModel.select(PERSON_C);
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertFalse(selectionModel.isSelected(PERSON_B));
+ assertTrue(selectionModel.isSelected(PERSON_C));
+ assertEquals(Arrays.asList(PERSON_C),
+ currentSelectionCapture.getValue());
+ assertEquals(Arrays.asList(), oldSelectionCapture.getValue());
+ assertEquals(3, events.get());
+
+ selectionModel.deselectAll();
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertFalse(selectionModel.isSelected(PERSON_B));
+ assertFalse(selectionModel.isSelected(PERSON_C));
+ assertEquals(Arrays.asList(), currentSelectionCapture.getValue());
+ assertEquals(Arrays.asList(PERSON_C), oldSelectionCapture.getValue());
+ assertEquals(4, events.get());
+
+ selectionModel.deselectAll();
+ assertEquals(4, events.get());
+ }
+
+ @Test
+ public void updateSelection() {
+ selectionModel.updateSelection(asSet(PERSON_A), Collections.emptySet());
+
+ assertTrue(selectionModel.isSelected(PERSON_A));
+ assertFalse(selectionModel.isSelected(PERSON_B));
+ assertFalse(selectionModel.isSelected(PERSON_C));
+ assertEquals(Arrays.asList(PERSON_A),
+ currentSelectionCapture.getValue());
+ assertEquals(1, events.get());
+
+ selectionModel.updateSelection(asSet(PERSON_B), asSet(PERSON_A));
+
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertTrue(selectionModel.isSelected(PERSON_B));
+ assertFalse(selectionModel.isSelected(PERSON_C));
+ assertEquals(Arrays.asList(PERSON_B),
+ currentSelectionCapture.getValue());
+ assertEquals(Arrays.asList(PERSON_A), oldSelectionCapture.getValue());
+ assertEquals(2, events.get());
+
+ selectionModel.updateSelection(asSet(PERSON_B), asSet(PERSON_A)); // NOOP
+
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertTrue(selectionModel.isSelected(PERSON_B));
+ assertFalse(selectionModel.isSelected(PERSON_C));
+ assertEquals(Arrays.asList(PERSON_B),
+ currentSelectionCapture.getValue());
+ assertEquals(Arrays.asList(PERSON_A), oldSelectionCapture.getValue());
+ assertEquals(2, events.get());
+
+ selectionModel.updateSelection(asSet(PERSON_A, PERSON_C),
+ asSet(PERSON_A)); // partly NOOP
+
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertTrue(selectionModel.isSelected(PERSON_B));
+ assertTrue(selectionModel.isSelected(PERSON_C));
+ assertEquals(Arrays.asList(PERSON_B, PERSON_C),
+ currentSelectionCapture.getValue());
+ assertEquals(Arrays.asList(PERSON_B), oldSelectionCapture.getValue());
+ assertEquals(3, events.get());
+
+ selectionModel.updateSelection(asSet(PERSON_B, PERSON_A),
+ asSet(PERSON_B)); // partly NOOP
+
+ assertTrue(selectionModel.isSelected(PERSON_A));
+ assertTrue(selectionModel.isSelected(PERSON_B));
+ assertTrue(selectionModel.isSelected(PERSON_C));
+ assertEquals(Arrays.asList(PERSON_B, PERSON_C, PERSON_A),
+ currentSelectionCapture.getValue());
+ assertEquals(Arrays.asList(PERSON_B, PERSON_C),
+ oldSelectionCapture.getValue());
+ assertEquals(4, events.get());
+
+ selectionModel.updateSelection(asSet(),
+ asSet(PERSON_B, PERSON_A, PERSON_C));
+
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertFalse(selectionModel.isSelected(PERSON_B));
+ assertFalse(selectionModel.isSelected(PERSON_C));
+ assertEquals(Arrays.asList(), currentSelectionCapture.getValue());
+ assertEquals(Arrays.asList(PERSON_B, PERSON_C, PERSON_A),
+ oldSelectionCapture.getValue());
+ assertEquals(5, events.get());
+ }
+
+ private <T> Set<T> asSet(@SuppressWarnings("unchecked") T... people) {
+ return new LinkedHashSet<>(Arrays.asList(people));
+ }
+
+ @Test
+ public void selectTwice() {
+ selectionModel.select(PERSON_C);
+ selectionModel.select(PERSON_C);
+
+ assertEquals(PERSON_C,
+ selectionModel.getFirstSelectedItem().orElse(null));
+
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertFalse(selectionModel.isSelected(PERSON_B));
+ assertTrue(selectionModel.isSelected(PERSON_C));
+
+ assertEquals(Optional.of(PERSON_C),
+ selectionModel.getFirstSelectedItem());
+
+ assertEquals(Arrays.asList(PERSON_C),
+ currentSelectionCapture.getValue());
+ assertEquals(1, events.get());
+ }
+
+ @Test
+ public void deselectTwice() {
+ selectionModel.select(PERSON_C);
+ assertEquals(Arrays.asList(PERSON_C),
+ currentSelectionCapture.getValue());
+ assertEquals(1, events.get());
+
+ selectionModel.deselect(PERSON_C);
+
+ assertFalse(selectionModel.getFirstSelectedItem().isPresent());
+ assertFalse(selectionModel.isSelected(PERSON_C));
+ assertEquals(Arrays.asList(), currentSelectionCapture.getValue());
+ assertEquals(2, events.get());
+
+ selectionModel.deselect(PERSON_C);
+
+ assertFalse(selectionModel.getFirstSelectedItem().isPresent());
+ assertFalse(selectionModel.isSelected(PERSON_A));
+ assertFalse(selectionModel.isSelected(PERSON_B));
+ assertFalse(selectionModel.isSelected(PERSON_C));
+ assertEquals(Arrays.asList(), currentSelectionCapture.getValue());
+ assertEquals(2, events.get());
+ }
+
+ @SuppressWarnings({ "serial" })
+ @Test
+ public void addValueChangeListener() {
+ AtomicReference<SingleSelectionListener<String>> selectionListener = new AtomicReference<>();
+ Registration registration = Mockito.mock(Registration.class);
+ Grid<String> grid = new Grid<>();
+ grid.setItems("foo", "bar");
+ String value = "foo";
+ SingleSelectionModelImpl<String> select = new SingleSelectionModelImpl<String>(
+ grid) {
+ @Override
+ public Registration addSelectionListener(
+ SingleSelectionListener<String> listener) {
+ selectionListener.set(listener);
+ return registration;
+ }
+
+ @Override
+ public Optional<String> getSelectedItem() {
+ return Optional.of(value);
+ }
+ };
+
+ AtomicReference<ValueChangeEvent<?>> event = new AtomicReference<>();
+ Registration actualRegistration = select.addSelectionListener(evt -> {
+ Assert.assertNull(event.get());
+ event.set(evt);
+ });
+ Assert.assertSame(registration, actualRegistration);
+
+ selectionListener.get().accept(new SingleSelectionEvent<>(grid,
+ select.asSingleSelect(), true));
+
+ Assert.assertEquals(grid, event.get().getComponent());
+ Assert.assertEquals(value, event.get().getValue());
+ Assert.assertTrue(event.get().isUserOriginated());
+ }
+}
diff --git a/server/src/test/java/com/vaadin/tests/components/grid/GridSingleSelectionModelTest.java b/server/src/test/java/com/vaadin/tests/components/grid/GridSingleSelectionModelTest.java
index 67b35c5208..17b409c1a6 100644
--- a/server/src/test/java/com/vaadin/tests/components/grid/GridSingleSelectionModelTest.java
+++ b/server/src/test/java/com/vaadin/tests/components/grid/GridSingleSelectionModelTest.java
@@ -6,7 +6,9 @@ import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
@@ -20,62 +22,124 @@ import com.vaadin.event.selection.SingleSelectionEvent;
import com.vaadin.event.selection.SingleSelectionListener;
import com.vaadin.server.data.provider.bov.Person;
import com.vaadin.shared.Registration;
-import com.vaadin.shared.data.DataCommunicatorClientRpc;
import com.vaadin.ui.Grid;
import com.vaadin.ui.Grid.GridSelectionModel;
-import com.vaadin.ui.components.grid.SingleSelectionModel;
+import com.vaadin.ui.components.grid.MultiSelectionModelImpl;
+import com.vaadin.ui.components.grid.SingleSelectionModelImpl;
+
+import elemental.json.JsonObject;
public class GridSingleSelectionModelTest {
public static final Person PERSON_C = new Person("c", 3);
public static final Person PERSON_B = new Person("b", 2);
public static final Person PERSON_A = new Person("a", 1);
- public static final String RPC_INTERFACE = DataCommunicatorClientRpc.class
- .getName();
- private class CustomSelectionModelGrid extends Grid<String> {
- public void switchSelectionModel() {
- // just switch selection model to cause event
- setSelectionModel(new SingleSelectionModel(this));
+ public static class CustomSingleSelectionModel
+ extends SingleSelectionModelImpl<String> {
+ public final Map<String, Boolean> generatedData = new LinkedHashMap<>();
+
+ public CustomSingleSelectionModel(Grid<String> grid) {
+ super(grid);
}
+
+ @Override
+ public void generateData(String item, JsonObject jsonObject) {
+ super.generateData(item, jsonObject);
+ // capture updated row
+ generatedData.put(item, isSelected(item));
+ }
+
}
private List<Person> selectionChanges;
private Grid<Person> grid;
- private SingleSelectionModel<Person> selectionModel;
+ private SingleSelectionModelImpl<Person> selectionModel;
@Before
public void setUp() {
grid = new Grid<>();
grid.setItems(PERSON_A, PERSON_B, PERSON_C);
- selectionModel = (SingleSelectionModel<Person>) grid
+ selectionModel = (SingleSelectionModelImpl<Person>) grid
.getSelectionModel();
selectionChanges = new ArrayList<>();
- selectionModel.addSelectionChangeListener(
- e -> selectionChanges.add(e.getValue()));
+ selectionModel
+ .addSelectionListener(e -> selectionChanges.add(e.getValue()));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void selectionModelChanged_usingPreviousSelectionModel_throws() {
+ grid.setSelectionModel(new MultiSelectionModelImpl<>(grid));
+
+ selectionModel.select(PERSON_A);
}
@Test
- public void testGridChangingSelectionModel_firesSelectionChangeEvent() {
- CustomSelectionModelGrid customGrid = new CustomSelectionModelGrid();
+ public void gridChangingSelectionModel_firesSelectionChangeEvent() {
+ Grid<String> customGrid = new Grid<>();
customGrid.setItems("Foo", "Bar", "Baz");
List<String> selectionChanges = new ArrayList<>();
- ((SingleSelectionModel<String>) customGrid.getSelectionModel())
- .addSelectionChangeListener(
- e -> selectionChanges.add(e.getValue()));
+ ((SingleSelectionModelImpl<String>) customGrid.getSelectionModel())
+ .addSelectionListener(e -> selectionChanges.add(e.getValue()));
customGrid.getSelectionModel().select("Foo");
assertEquals("Foo",
customGrid.getSelectionModel().getFirstSelectedItem().get());
assertEquals(Arrays.asList("Foo"), selectionChanges);
- customGrid.switchSelectionModel();
+ customGrid
+ .setSelectionModel(new CustomSingleSelectionModel(customGrid));
assertEquals(Arrays.asList("Foo", null), selectionChanges);
}
@Test
+ public void serverSideSelection_GridChangingSelectionModel_sendsUpdatedRowsToClient() {
+ Grid<String> customGrid = new Grid<>();
+ customGrid.setItems("Foo", "Bar", "Baz");
+
+ CustomSingleSelectionModel customModel = new CustomSingleSelectionModel(
+ customGrid);
+ customGrid.setSelectionModel(customModel);
+
+ customGrid.getDataCommunicator().beforeClientResponse(true);
+
+ Assert.assertFalse("Item should have been updated as selected",
+ customModel.generatedData.get("Foo"));
+ Assert.assertFalse("Item should have been updated as NOT selected",
+ customModel.generatedData.get("Bar"));
+ Assert.assertFalse("Item should have been updated as NOT selected",
+ customModel.generatedData.get("Baz"));
+
+ customModel.generatedData.clear();
+
+ customGrid.getSelectionModel().select("Foo");
+ customGrid.getDataCommunicator().beforeClientResponse(false);
+
+ Assert.assertTrue("Item should have been updated as selected",
+ customModel.generatedData.get("Foo"));
+ Assert.assertFalse("Item should have NOT been updated",
+ customModel.generatedData.containsKey("Bar"));
+ Assert.assertFalse("Item should have NOT been updated",
+ customModel.generatedData.containsKey("Baz"));
+
+ // switch to another selection model to cause event
+ customModel.generatedData.clear();
+ customGrid.setSelectionModel(new SingleSelectionModelImpl<>(customGrid));
+ customGrid.getDataCommunicator().beforeClientResponse(false);
+
+ // since the selection model has been removed, it is no longer a data
+ // generator for the data communicator, would need to verify somehow
+ // that row is not marked as selected anymore ? (done in UI tests)
+ Assert.assertTrue(customModel.generatedData.isEmpty()); // at least
+ // removed
+ // selection
+ // model is not
+ // triggered
+ }
+
+ @Test
public void testGridWithSingleSelection() {
Grid<String> gridWithStrings = new Grid<>();
gridWithStrings.setItems("Foo", "Bar", "Baz");
@@ -210,10 +274,10 @@ public class GridSingleSelectionModelTest {
Grid<String> grid = new Grid<>();
grid.setItems("foo", "bar");
String value = "foo";
- SingleSelectionModel<String> select = new SingleSelectionModel<String>(
+ SingleSelectionModelImpl<String> select = new SingleSelectionModelImpl<String>(
grid) {
@Override
- public Registration addSelectionChangeListener(
+ public Registration addSelectionListener(
SingleSelectionListener<String> listener) {
selectionListener.set(listener);
return registration;
@@ -226,11 +290,10 @@ public class GridSingleSelectionModelTest {
};
AtomicReference<ValueChangeEvent<?>> event = new AtomicReference<>();
- Registration actualRegistration = select
- .addSelectionChangeListener(evt -> {
- Assert.assertNull(event.get());
- event.set(evt);
- });
+ Registration actualRegistration = select.addSelectionListener(evt -> {
+ Assert.assertNull(event.get());
+ event.set(evt);
+ });
Assert.assertSame(registration, actualRegistration);
selectionListener.get().accept(new SingleSelectionEvent<>(grid,