diff options
author | Johannes Dahlström <johannesd@vaadin.com> | 2016-08-23 15:45:51 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2016-08-25 09:55:50 +0000 |
commit | c0117c3baf907c09781c4b56a64d29b4e43c9281 (patch) | |
tree | 0ad56e594dc04d6aebf04e9f7fa8f84f58087f5f | |
parent | 6439a2e0c9a37da5a34f021a31eb8d61c902fd69 (diff) | |
download | vaadin-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
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); |