summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohannes Dahlström <johannesd@vaadin.com>2016-08-23 15:45:51 +0300
committerVaadin Code Review <review@vaadin.com>2016-08-25 09:55:50 +0000
commitc0117c3baf907c09781c4b56a64d29b4e43c9281 (patch)
tree0ad56e594dc04d6aebf04e9f7fa8f84f58087f5f
parent6439a2e0c9a37da5a34f021a31eb8d61c902fd69 (diff)
downloadvaadin-framework-c0117c3baf907c09781c4b56a64d29b4e43c9281.tar.gz
vaadin-framework-c0117c3baf907c09781c4b56a64d29b4e43c9281.zip
Add SelectionModel interface and selection API to Listing
Concrete selection models not implemented in this patch. Change-Id: Ibcd64817efa704b6dd664bfaccb2d8c5110720fb
-rw-r--r--server/src/main/java/com/vaadin/data/Listing.java109
-rw-r--r--server/src/main/java/com/vaadin/data/selection/SelectionModel.java141
-rw-r--r--server/src/main/java/com/vaadin/server/data/DataCommunicator.java29
-rw-r--r--server/src/main/java/com/vaadin/ui/AbstractListing.java113
-rw-r--r--server/src/main/java/com/vaadin/ui/Grid.java21
-rw-r--r--server/src/test/java/com/vaadin/tests/server/component/abstractlisting/AbstractListingTest.java44
6 files changed, 367 insertions, 90 deletions
diff --git a/server/src/main/java/com/vaadin/data/Listing.java b/server/src/main/java/com/vaadin/data/Listing.java
index ce81490989..818389f8b7 100644
--- a/server/src/main/java/com/vaadin/data/Listing.java
+++ b/server/src/main/java/com/vaadin/data/Listing.java
@@ -17,49 +17,116 @@ package com.vaadin.data;
import java.io.Serializable;
import java.util.Collection;
+import java.util.Set;
+import com.vaadin.data.selection.SelectionModel;
import com.vaadin.server.data.DataSource;
/**
- * Generic interface for Components that show a list of data.
+ * A generic interface for components that show a list of data.
*
+ * @author Vaadin Ltd.
* @param <T>
- * data type for listing
+ * the item data type
+ * @param <SELECTIONMODEL>
+ * the selection logic supported by this listing
+ * @since
*/
-public interface Listing<T> extends Serializable {
+public interface Listing<T, SELECTIONMODEL extends SelectionModel<T>> extends
+ Serializable {
/**
- * Sets the {@link DataSource} used by this Listing.
+ * Returns the source of data items used by this listing.
*
- * @param data
- * data source
+ * @return the data source, not null
*/
- void setDataSource(DataSource<T> data);
+ DataSource<T> getDataSource();
/**
- * Sets the options available for this Listing.
+ * Sets the source of data items used by this listing. The data source is
+ * queried for displayed items as needed.
*
- * @param data
- * collection of data
+ * @param dataSource
+ * the data source, not null
*/
- default void setItems(Collection<T> data) {
- setDataSource(DataSource.create(data));
- }
+ void setDataSource(DataSource<T> dataSource);
+
+ /**
+ * Returns the selection model for this listing.
+ *
+ * @return the selection model, not null
+ */
+ SELECTIONMODEL getSelectionModel();
/**
- * Sets the options available for this Listing.
+ * Sets the collection of data items of this listing.
*
- * @param data
- * array of data
+ * @param items
+ * the data items to display
+ *
*/
- default void setItems(T... data) {
- setDataSource(DataSource.create(data));
+ default void setItems(Collection<T> items) {
+ setDataSource(DataSource.create(items));
}
/**
- * Returns the {@link DataSource} of this Listing.
+ * Sets the data items of this listing.
*
- * @return data source
+ * @param items
+ * the data items to display
*/
- DataSource<T> getDataSource();
+ default void setItems(T... items) {
+ setDataSource(DataSource.create(items));
+ }
+
+ /* SelectionModel helper methods */
+
+ /**
+ * Returns an immutable set of the currently selected items. The iteration
+ * order of the items in the returned set is specified by the
+ * {@linkplain #getSelectionModel() selection model} used.
+ *
+ * @return the current selection
+ *
+ * @see SelectionModel#getSelectedItems
+ */
+ default Set<T> getSelectedItems() {
+ return getSelectionModel().getSelectedItems();
+ }
+
+ /**
+ * Selects the given item. If the item is already selected, does nothing.
+ *
+ * @param item
+ * the item to select, not null
+ *
+ * @see SelectionModel#select
+ */
+ default void select(T item) {
+ getSelectionModel().select(item);
+ }
+
+ /**
+ * Deselects the given item. If the item is not currently selected, does
+ * nothing.
+ *
+ * @param item
+ * the item to deselect, not null
+ *
+ * @see SelectionModel#deselect
+ */
+ default void deselect(T item) {
+ getSelectionModel().deselect(item);
+ }
+
+ /**
+ * Returns whether the given item is currently selected.
+ *
+ * @param item
+ * the item to check, not null
+ * @return {@code true} if the item is selected, {@code false} otherwise
+ */
+ default boolean isSelected(T item) {
+ return getSelectionModel().isSelected(item);
+ }
}
diff --git a/server/src/main/java/com/vaadin/data/selection/SelectionModel.java b/server/src/main/java/com/vaadin/data/selection/SelectionModel.java
new file mode 100644
index 0000000000..1730d26319
--- /dev/null
+++ b/server/src/main/java/com/vaadin/data/selection/SelectionModel.java
@@ -0,0 +1,141 @@
+/*
+ * 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.data.selection;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
+
+import com.vaadin.data.HasValue;
+import com.vaadin.data.Listing;
+
+/**
+ * Models the selection logic of a {@code Listing} component. Determines how
+ * items can be selected and deselected.
+ *
+ * @author Vaadin Ltd.
+ *
+ * @param <T>
+ * the type of the items to select
+ * @since
+ *
+ * @see Listing
+ */
+public interface SelectionModel<T> extends Serializable {
+
+ /**
+ * A selection model in which a single item can be selected at a time.
+ * Selecting another item deselects the originally selected item.
+ *
+ * @param <T>
+ * the type of the items to select
+ */
+ public interface Single<T> extends SelectionModel<T>, HasValue<T> {
+
+ /**
+ * Selects the given item. If another item was already selected, that
+ * item is deselected.
+ */
+ @Override
+ public void select(T item);
+
+ /**
+ * Returns the currently selected item, or an empty optional if no item
+ * is selected.
+ *
+ * @return an optional of the selected item if any, an empty optional
+ * otherwise
+ */
+ public Optional<T> getSelectedItem();
+
+ /**
+ * Returns a singleton set of the currently selected item or an empty
+ * set if no item is selected.
+ *
+ * @return a singleton set of the selected item if any, an empty set
+ * otherwise
+ */
+ @Override
+ default Set<T> getSelectedItems() {
+ return getSelectedItem().map(Collections::singleton)
+ .orElse(Collections.emptySet());
+ }
+ }
+
+ /**
+ * A selection model in which multiple items can be selected at the same
+ * time. Selecting an item adds it to the selection.
+ *
+ * @param <T>
+ * the type of the items to select
+ */
+ public interface Multi<T> extends SelectionModel<T>,
+ HasValue<Collection<T>> {
+
+ /**
+ * Adds the given items to the set of currently selected items.
+ */
+ @Override
+ public void select(T item);
+
+ /**
+ * Adds the given items to the set of currently selected items.
+ */
+ public void select(@SuppressWarnings("unchecked") T... items);
+ }
+
+ /**
+ * Returns an immutable set of the currently selected item.
+ * <p>
+ * <i>Implementation note:</i> the iteration order of the items in the
+ * returned set should be well-defined and documented by the implementing
+ * class.
+ *
+ * @return the items in the current selection, not null
+ */
+ public Set<T> getSelectedItems();
+
+ /**
+ * Selects the given item. Depending on the implementation, may cause other
+ * items to be deselected. If the item is already selected, does nothing.
+ *
+ * @param item
+ * the item to select, not null
+ */
+ public void select(T item);
+
+ /**
+ * Deselects the given item. If the item is not currently selected, does
+ * nothing.
+ *
+ * @param item
+ * the item to deselect, not null
+ */
+ public void deselect(T item);
+
+ /**
+ * Returns whether the given item is currently selected.
+ *
+ * @param item
+ * the item to check, not null
+ * @return {@code true} if the item is selected, {@code false} otherwise
+ */
+ public default boolean isSelected(T item) {
+ return getSelectedItems().contains(item);
+ }
+}
diff --git a/server/src/main/java/com/vaadin/server/data/DataCommunicator.java b/server/src/main/java/com/vaadin/server/data/DataCommunicator.java
index 8da23b42d1..9669b4367f 100644
--- a/server/src/main/java/com/vaadin/server/data/DataCommunicator.java
+++ b/server/src/main/java/com/vaadin/server/data/DataCommunicator.java
@@ -23,6 +23,7 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -87,13 +88,13 @@ public class DataCommunicator<T> extends AbstractExtension {
/**
* Set of key strings for currently active data objects
*/
- private final Set<String> activeData = new HashSet<String>();
+ private final Set<String> activeData = new HashSet<>();
/**
* Set of key strings for data objects dropped on the client. This set
* is used to clean up old data when it's no longer needed.
*/
- private final Set<String> droppedData = new HashSet<String>();
+ private final Set<String> droppedData = new HashSet<>();
/**
* Adds given objects as currently active objects.
@@ -148,7 +149,7 @@ public class DataCommunicator<T> extends AbstractExtension {
* @return collection of active data objects
*/
public Collection<T> getActiveData() {
- HashSet<T> hashSet = new HashSet<T>();
+ HashSet<T> hashSet = new HashSet<>();
for (String key : activeData) {
hashSet.add(getKeyMapper().get(key));
}
@@ -171,14 +172,14 @@ public class DataCommunicator<T> extends AbstractExtension {
}
}
- private Collection<TypedDataGenerator<T>> generators = new LinkedHashSet<TypedDataGenerator<T>>();
+ private Collection<TypedDataGenerator<T>> generators = new LinkedHashSet<>();
private ActiveDataHandler handler = new ActiveDataHandler();
private DataSource<T> dataSource;
private DataKeyMapper<T> keyMapper;
private boolean reset = false;
- private final Set<T> updatedData = new HashSet<T>();
+ private final Set<T> updatedData = new HashSet<>();
private Range pushRows = Range.withLength(0, 40);
private Comparator<T> inMemorySorting;
@@ -248,22 +249,27 @@ public class DataCommunicator<T> extends AbstractExtension {
}
/**
- * Adds a {@link TypedDataGenerator} to this {@link DataCommunicator}.
+ * Adds a data generator to this data communicator. Data generators can be
+ * used to insert custom data to the rows sent to the client. If the data
+ * generator is already added, does nothing.
*
* @param generator
- * typed data generator
+ * the data generator to add, not null
*/
public void addDataGenerator(TypedDataGenerator<T> generator) {
+ Objects.requireNonNull(generator, "generator cannot be null");
generators.add(generator);
}
/**
- * Removes a {@link TypedDataGenerator} from this {@link DataCommunicator}.
+ * Removes a data generator from this data communicator. If there is no such
+ * data generator, does nothing.
*
* @param generator
- * typed data generator
+ * the data generator to remove, not null
*/
public void removeDataGenerator(TypedDataGenerator<T> generator) {
+ Objects.requireNonNull(generator, "generator cannot be null");
generators.remove(generator);
}
@@ -395,7 +401,7 @@ public class DataCommunicator<T> extends AbstractExtension {
* @return key mapper
*/
protected DataKeyMapper<T> createKeyMapper() {
- return new KeyMapper<T>();
+ return new KeyMapper<>();
}
/**
@@ -422,9 +428,10 @@ public class DataCommunicator<T> extends AbstractExtension {
* Sets the current data source for this DataCommunicator.
*
* @param dataSource
- * the data source to set
+ * the data source to set, not null
*/
public void setDataSource(DataSource<T> dataSource) {
+ Objects.requireNonNull(dataSource, "data source cannot be null");
this.dataSource = dataSource;
reset();
}
diff --git a/server/src/main/java/com/vaadin/ui/AbstractListing.java b/server/src/main/java/com/vaadin/ui/AbstractListing.java
index b5780f57e9..1167e64505 100644
--- a/server/src/main/java/com/vaadin/ui/AbstractListing.java
+++ b/server/src/main/java/com/vaadin/ui/AbstractListing.java
@@ -18,50 +18,44 @@ package com.vaadin.ui;
import java.util.Objects;
import com.vaadin.data.Listing;
+import com.vaadin.data.selection.SelectionModel;
import com.vaadin.server.AbstractExtension;
import com.vaadin.server.data.DataCommunicator;
import com.vaadin.server.data.DataSource;
import com.vaadin.server.data.TypedDataGenerator;
/**
- * Base class for {@link Listing} components. Provides common handling for
- * {@link DataCommunicator} and {@link TypedDataGenerator}s.
- *
+ * A base class for listing components. Provides common handling for fetching
+ * backend data items, selection logic, and server-client communication.
+ *
* @param <T>
- * listing data type
+ * the item data type
+ * @param <SELECTIONMODEL>
+ * the selection logic supported by this listing
*/
-public abstract class AbstractListing<T> extends AbstractComponent
- implements Listing<T> {
+public abstract class AbstractListing<T, SELECTIONMODEL extends SelectionModel<T>>
+ extends AbstractComponent implements Listing<T, SELECTIONMODEL> {
/**
- * Helper base class for creating extensions for Listing components. This
+ * A helper base class for creating extensions for Listing components. This
* class provides helpers for accessing the underlying parts of the
- * component and its communicational mechanism.
+ * component and its communication mechanism.
*
* @param <T>
- * listing data type
+ * the listing item type
*/
public abstract static class AbstractListingExtension<T>
extends AbstractExtension implements TypedDataGenerator<T> {
/**
- * {@inheritDoc}
- * <p>
- * Note: AbstractListingExtensions need parent to be of type
- * AbstractListing.
- *
- * @throws IllegalArgument
- * if parent is not an AbstractListing
+ * Adds this extension to the given parent listing.
+ *
+ * @param listing
+ * the parent component to add to
*/
- public void extend(Listing<T> listing) {
- if (listing instanceof AbstractListing) {
- AbstractListing<T> parent = (AbstractListing<T>) listing;
- super.extend(parent);
- parent.addDataGenerator(this);
- } else {
- throw new IllegalArgumentException(
- "Parent needs to extend AbstractListing");
- }
+ public void extend(AbstractListing<T, ?> listing) {
+ super.extend(listing);
+ listing.addDataGenerator(this);
}
@Override
@@ -84,46 +78,56 @@ public abstract class AbstractListing<T> extends AbstractComponent
@Override
@SuppressWarnings("unchecked")
- public AbstractListing<T> getParent() {
- return (AbstractListing<T>) super.getParent();
+ public AbstractListing<T, ?> getParent() {
+ return (AbstractListing<T, ?>) super.getParent();
}
/**
- * Helper method for refreshing a single data object.
+ * A helper method for refreshing the client-side representation of a
+ * single data item.
*
- * @param data
- * data object to refresh
+ * @param item
+ * the item to refresh
*/
- protected void refresh(T data) {
- getParent().getDataCommunicator().refresh(data);
+ protected void refresh(T item) {
+ getParent().getDataCommunicator().refresh(item);
}
}
- /* DataCommunicator for this Listing component */
private final DataCommunicator<T> dataCommunicator;
+ private SELECTIONMODEL selectionModel;
+
/**
- * Constructs an {@link AbstractListing}, extending it with a
- * {@link DataCommunicator}.
+ * Creates a new {@code AbstractListing} using the given selection model.
+ *
+ * @param selectionModel
+ * the selection model to use, not null
*/
- protected AbstractListing() {
- this(new DataCommunicator<>());
+ protected AbstractListing(SELECTIONMODEL selectionModel) {
+ this(selectionModel, new DataCommunicator<>());
}
/**
- * Constructs an {@link AbstractListing}, extending it with given
- * {@link DataCommunicator}.
+ * Creates a new {@code AbstractListing} with the given selection model and
+ * data communicator.
* <p>
* <strong>Note:</strong> This method is for creating an
* {@link AbstractListing} with a custom {@link DataCommunicator}. In the
* common case {@link AbstractListing#AbstractListing()} should be used.
*
+ * @param selectionModel
+ * the selection model to use, not null
* @param dataCommunicator
- * a customized data communicator instance
+ * the custom data communicator to use, not null
*/
- protected AbstractListing(DataCommunicator<T> dataCommunicator) {
+ protected AbstractListing(SELECTIONMODEL selectionModel,
+ DataCommunicator<T> dataCommunicator) {
+ Objects.requireNonNull(selectionModel, "selectionModel cannot be null");
Objects.requireNonNull(dataCommunicator,
- "The data communicator can't be null");
+ "dataCommunicator cannot be null");
+
+ this.selectionModel = selectionModel;
this.dataCommunicator = dataCommunicator;
addExtension(dataCommunicator);
}
@@ -138,32 +142,37 @@ public abstract class AbstractListing<T> extends AbstractComponent
return getDataCommunicator().getDataSource();
}
+ @Override
+ public SELECTIONMODEL getSelectionModel() {
+ return selectionModel;
+ }
+
/**
- * Adds a {@link TypedDataGenerator} for the {@link DataCommunicator} of
- * this Listing component.
+ * Adds the given data generator to this listing. If the generator was
+ * already added, does nothing.
*
* @param generator
- * typed data generator
+ * the data generator to add, not null
*/
protected void addDataGenerator(TypedDataGenerator<T> generator) {
- dataCommunicator.addDataGenerator(generator);
+ getDataCommunicator().addDataGenerator(generator);
}
/**
- * Removed a {@link TypedDataGenerator} from the {@link DataCommunicator} of
- * this Listing component.
+ * Removes the given data generator from this listing. If this listing does
+ * not have the generator, does nothing.
*
* @param generator
- * typed data generator
+ * the data generator to remove, not null
*/
protected void removeDataGenerator(TypedDataGenerator<T> generator) {
- dataCommunicator.removeDataGenerator(generator);
+ getDataCommunicator().removeDataGenerator(generator);
}
/**
- * Get the {@link DataCommunicator} of this Listing component.
+ * Returns the data communicator of this listing.
*
- * @return data provider
+ * @return the data communicator, not null
*/
public DataCommunicator<T> getDataCommunicator() {
return dataCommunicator;
diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java
index 553d5e0761..2bd4158023 100644
--- a/server/src/main/java/com/vaadin/ui/Grid.java
+++ b/server/src/main/java/com/vaadin/ui/Grid.java
@@ -27,6 +27,7 @@ import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
+import com.vaadin.data.selection.SelectionModel;
import com.vaadin.server.AbstractExtension;
import com.vaadin.server.KeyMapper;
import com.vaadin.server.data.DataSource;
@@ -51,7 +52,7 @@ import elemental.json.JsonObject;
* @param <T>
* the grid bean type
*/
-public class Grid<T> extends AbstractListing<T> {
+public class Grid<T> extends AbstractListing<T, SelectionModel<T>> {
private final class GridServerRpcImpl implements GridServerRpc {
@Override
@@ -159,6 +160,7 @@ public class Grid<T> extends AbstractListing<T> {
if (Comparable.class.isAssignableFrom(valueType)) {
comparator = (a, b) -> {
+ @SuppressWarnings("unchecked")
Comparable<V> comp = (Comparable<V>) valueProvider.apply(a);
return comp.compareTo(valueProvider.apply(b));
};
@@ -351,6 +353,21 @@ public class Grid<T> extends AbstractListing<T> {
* Constructor for the {@link Grid} component.
*/
public Grid() {
+ super(new SelectionModel<T>() {
+ // Stub no-op selection model until selection models are implemented
+ @Override
+ public Set<T> getSelectedItems() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void select(T item) {
+ }
+
+ @Override
+ public void deselect(T item) {
+ }
+ });
setDataSource(DataSource.create());
registerRpc(new GridServerRpcImpl());
}
@@ -372,7 +389,7 @@ public class Grid<T> extends AbstractListing<T> {
*/
public <V> Column<T, V> addColumn(String caption, Class<V> valueType,
Function<T, V> valueProvider) {
- Column<T, V> c = new Column<T, V>(caption, valueType, valueProvider);
+ Column<T, V> c = new Column<>(caption, valueType, valueProvider);
c.extend(this);
c.setId(columnKeys.key(c));
diff --git a/server/src/test/java/com/vaadin/tests/server/component/abstractlisting/AbstractListingTest.java b/server/src/test/java/com/vaadin/tests/server/component/abstractlisting/AbstractListingTest.java
index 2bc13dc4f3..3ec889f301 100644
--- a/server/src/test/java/com/vaadin/tests/server/component/abstractlisting/AbstractListingTest.java
+++ b/server/src/test/java/com/vaadin/tests/server/component/abstractlisting/AbstractListingTest.java
@@ -2,17 +2,21 @@ package com.vaadin.tests.server.component.abstractlisting;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.stream.Stream;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import com.vaadin.data.selection.SelectionModel;
import com.vaadin.server.data.BackEndDataSource;
import com.vaadin.server.data.DataSource;
import com.vaadin.server.data.ListDataSource;
import com.vaadin.server.data.Query;
+import com.vaadin.server.data.TypedDataGenerator;
import com.vaadin.ui.AbstractListing;
import com.vaadin.ui.AbstractListing.AbstractListingExtension;
@@ -20,7 +24,39 @@ import elemental.json.JsonObject;
public class AbstractListingTest {
- private final class TestListing extends AbstractListing<String> {
+ private final class TestListing extends
+ AbstractListing<String, SelectionModel<String>> {
+
+ protected TestListing() {
+ // Stub for now, implement (and test) when adding concrete
+ // SelectionModels
+ super(new SelectionModel<String>() {
+
+ @Override
+ public Set<String> getSelectedItems() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void select(String item) {
+ }
+
+ @Override
+ public void deselect(String item) {
+ }
+ });
+ }
+
+ @Override
+ public void addDataGenerator(TypedDataGenerator<String> generator) {
+ super.addDataGenerator(generator);
+ }
+
+ @Override
+ public void removeDataGenerator(TypedDataGenerator<String> generator) {
+ super.removeDataGenerator(generator);
+ }
+
/**
* Used to execute data generation
*/
@@ -95,7 +131,7 @@ public class AbstractListingTest {
}
@Test
- public void testAddDataGeneartorBeforeDataSource() {
+ public void testAddDataGeneratorBeforeDataSource() {
CountGenerator generator = new CountGenerator();
generator.extend(listing);
listing.setItems("Foo");
@@ -105,7 +141,7 @@ public class AbstractListingTest {
}
@Test
- public void testAddDataGeneartorAfterDataSource() {
+ public void testAddDataGeneratorAfterDataSource() {
CountGenerator generator = new CountGenerator();
listing.setItems("Foo");
generator.extend(listing);
@@ -128,7 +164,7 @@ public class AbstractListingTest {
}
@Test
- public void testRemoveDataGeneartor() {
+ public void testRemoveDataGenerator() {
listing.setItems("Foo");
CountGenerator generator = new CountGenerator();
generator.extend(listing);