diff options
author | Pekka Hyvönen <pekka@vaadin.com> | 2016-09-22 14:19:34 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2016-09-27 07:23:36 +0000 |
commit | 7e78d52dfe72678cf275585bc552b1612844da44 (patch) | |
tree | a6d3454fdb59f6a2e69c0bab125c228dca93a169 /server | |
parent | 0052d59a318075a3ce8202b2eab84e9d643fc544 (diff) | |
download | vaadin-framework-7e78d52dfe72678cf275585bc552b1612844da44.tar.gz vaadin-framework-7e78d52dfe72678cf275585bc552b1612844da44.zip |
TwinColSelect with new databinding API
Removes feature for adding new items.
Introduces a AbstractMultiSelect-abstraction layer,
which is used in server side by TwinColSelect & CheckBoxGroup and
on client side only TwinColSelect for now. Plan is to use it for
ListSelect too.
Further improvement would be to make AbstractMultiSelect use
SelectionModel that extends AbstractSelectionModel and is thus used
as an extension both as client & server side.
Updates to JUnit 4.12 for easier use of @Parameterized test..
Change-Id: I64258c2229b9514d382693748e2ca562a1e448d4
Diffstat (limited to 'server')
8 files changed, 820 insertions, 330 deletions
diff --git a/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java b/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java index 1d17841812..3d2cb151b8 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java +++ b/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java @@ -16,16 +16,33 @@ package com.vaadin.ui; import java.lang.reflect.Method; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Collectors; import com.vaadin.event.selection.MultiSelectionEvent; import com.vaadin.event.selection.MultiSelectionListener; +import com.vaadin.server.Resource; +import com.vaadin.server.ResourceReference; +import com.vaadin.server.data.DataGenerator; import com.vaadin.shared.Registration; +import com.vaadin.shared.data.selection.MultiSelectServerRpc; +import com.vaadin.shared.data.selection.SelectionModel; import com.vaadin.shared.data.selection.SelectionModel.Multi; +import com.vaadin.shared.ui.ListingJsonConstants; import com.vaadin.util.ReflectTools; +import elemental.json.JsonObject; + /** * Base class for listing components that allow selecting multiple items. + * <p> + * Sends selection information individually for each item. * * @param <T> * item type @@ -35,16 +52,240 @@ import com.vaadin.util.ReflectTools; public abstract class AbstractMultiSelect<T> extends AbstractListing<T, Multi<T>> { + /** + * Simple implementation of multiselectmodel. + */ + protected class SimpleMultiSelectModel implements SelectionModel.Multi<T> { + + private Set<T> selection = new LinkedHashSet<>(); + + @Override + public void select(T item) { + // Not user originated + select(item, false); + } + + /** + * 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 + * @param userOriginated + * {@code true} if this was used originated, {@code false} if + * not + */ + protected void select(T item, boolean userOriginated) { + if (selection.contains(item)) { + return; + } + + updateSelection(set -> set.add(item), userOriginated); + } + + @Override + public void updateSelection(Set<T> addedItems, Set<T> removedItems) { + updateSelection(addedItems, removedItems, false); + } + + /** + * Updates the selection by adding and removing the given items. + * + * @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; + } + + updateSelection(set -> { + // order of add / remove does not matter since no duplicates + set.removeAll(removedItems); + set.addAll(addedItems); + }, userOriginated); + } + + @Override + public Set<T> getSelectedItems() { + return Collections.unmodifiableSet(new LinkedHashSet<>(selection)); + } + + @Override + public void deselect(T item) { + // Not user originated + deselect(item, false); + } + + /** + * Deselects the given item. If the item is not currently selected, does + * nothing. + * + * @param item + * the item to deselect, not null + * @param userOriginated + * {@code true} if this was used originated, {@code false} if + * not + */ + protected void deselect(T item, boolean userOriginated) { + if (!selection.contains(item)) { + return; + } + + updateSelection(set -> set.remove(item), userOriginated); + } + + /** + * Removes the given items. Any item that is not currently selected, is + * ignored. If none of the items are selected, does nothing. + * + * @param items + * the items to deselect, not {@code null} + * @param userOriginated + * {@code true} if this was used originated, {@code false} if + * not + */ + protected void deselect(Set<T> items, boolean userOriginated) { + Objects.requireNonNull(items); + if (items.stream().noneMatch(i -> isSelected(i))) { + return; + } + + updateSelection(set -> set.removeAll(items), userOriginated); + } + + @Override + public void deselectAll() { + if (selection.isEmpty()) { + return; + } + + updateSelection(Set::clear, false); + } + + private void updateSelection(Consumer<Set<T>> handler, + boolean userOriginated) { + LinkedHashSet<T> oldSelection = new LinkedHashSet<>(selection); + handler.accept(selection); + LinkedHashSet<T> newSelection = new LinkedHashSet<>(selection); + + fireEvent(new MultiSelectionEvent<>(AbstractMultiSelect.this, + oldSelection, newSelection, userOriginated)); + + getDataCommunicator().reset(); + } + + @Override + public boolean isSelected(T item) { + return selection.contains(item); + } + } + + private class MultiSelectServerRpcImpl implements MultiSelectServerRpc { + @Override + public void updateSelection(Set<String> selectedItemKeys, + Set<String> deselectedItemKeys) { + getSelectionModel().updateSelection( + getItemsForSelectionChange(selectedItemKeys), + getItemsForSelectionChange(deselectedItemKeys), true); + } + + private Set<T> getItemsForSelectionChange(Set<String> keys) { + return keys.stream().map(key -> getItemForSelectionChange(key)) + .filter(Optional::isPresent).map(Optional::get) + .collect(Collectors.toSet()); + } + + private Optional<T> getItemForSelectionChange(String key) { + T item = getDataCommunicator().getKeyMapper().get(key); + if (item == null || !getItemEnabledProvider().test(item)) { + return Optional.empty(); + } + + return Optional.of(item); + } + + private SimpleMultiSelectModel getSelectionModel() { + return (SimpleMultiSelectModel) AbstractMultiSelect.this + .getSelectionModel(); + } + } + + private class MultiSelectDataGenerator implements DataGenerator<T> { + @Override + public void generateData(T data, JsonObject jsonObject) { + jsonObject.put(ListingJsonConstants.JSONKEY_ITEM_VALUE, + getItemCaptionGenerator().apply(data)); + Resource icon = getItemIconGenerator().apply(data); + if (icon != null) { + String iconUrl = ResourceReference + .create(icon, AbstractMultiSelect.this, null).getURL(); + jsonObject.put(ListingJsonConstants.JSONKEY_ITEM_ICON, iconUrl); + } + if (!getItemEnabledProvider().test(data)) { + jsonObject.put(ListingJsonConstants.JSONKEY_ITEM_DISABLED, + true); + } + + if (getSelectionModel().isSelected(data)) { + jsonObject.put(ListingJsonConstants.JSONKEY_ITEM_SELECTED, + true); + } + } + + @Override + public void destroyData(T data) { + } + } + @Deprecated private static final Method SELECTION_CHANGE_METHOD = ReflectTools .findMethod(MultiSelectionListener.class, "accept", MultiSelectionEvent.class); /** + * The item icon caption provider. + */ + private ItemCaptionGenerator<T> itemCaptionGenerator = String::valueOf; + + /** + * The item icon provider. It is up to the implementing class to support + * this or not. + */ + private IconGenerator<T> itemIconGenerator = item -> null; + + /** + * The item enabled status provider. It is up to the implementing class to + * support this or not. + */ + private Predicate<T> itemEnabledProvider = item -> true; + + /** * Creates a new multi select with an empty data source. */ protected AbstractMultiSelect() { - super(); + setSelectionModel(new SimpleMultiSelectModel()); + + registerRpc(new MultiSelectServerRpcImpl()); + + // #FIXME it should be the responsibility of the SelectionModel + // (AbstractSelectionModel) to add selection data for item + addDataGenerator(new MultiSelectDataGenerator()); } /** @@ -52,7 +293,7 @@ public abstract class AbstractMultiSelect<T> * changed either by the user or programmatically. * * @param listener - * the value change listener, not <code>null</code> + * the value change listener, not {@code null} * @return a registration for the listener */ public Registration addSelectionListener( @@ -63,4 +304,99 @@ public abstract class AbstractMultiSelect<T> return () -> removeListener(MultiSelectionEvent.class, listener); } + /** + * Gets the item caption generator that is used to produce the strings shown + * in the select for each item. + * + * @return the item caption generator used, not {@code null} + * @see #setItemCaptionGenerator(ItemCaptionGenerator) + */ + public ItemCaptionGenerator<T> getItemCaptionGenerator() { + return itemCaptionGenerator; + } + + /** + * Sets the item caption generator that is used to produce the strings shown + * in the select for each item. By default, {@link String#valueOf(Object)} + * is used. + * + * @param itemCaptionGenerator + * the item caption generator to use, not {@code null} + */ + public void setItemCaptionGenerator( + ItemCaptionGenerator<T> itemCaptionGenerator) { + Objects.requireNonNull(itemCaptionGenerator); + this.itemCaptionGenerator = itemCaptionGenerator; + getDataCommunicator().reset(); + } + + /** + * Returns the item icon generator for this multiselect. + * <p> + * <em>Implementation note:</em> Override this method and + * {@link #setItemIconGenerator(IconGenerator)} as {@code public} and invoke + * {@code super} methods to support this feature in the multiselect + * component. + * + * @return the item icon generator, not {@code null} + * @see #setItemIconGenerator(IconGenerator) + */ + protected IconGenerator<T> getItemIconGenerator() { + return itemIconGenerator; + } + + /** + * Sets the item icon generator for this multiselect. The icon generator is + * queried for each item to optionally display an icon next to the item + * caption. If the generator returns null for an item, no icon is displayed. + * The default provider always returns null (no icons). + * <p> + * <em>Implementation note:</em> Override this method and + * {@link #getItemIconGenerator()} as {@code public} and invoke + * {@code super} methods to support this feature in the multiselect + * component. + * + * @param itemIconGenerator + * the item icon generator to set, not {@code null} + */ + protected void setItemIconGenerator(IconGenerator<T> itemIconGenerator) { + Objects.requireNonNull(itemIconGenerator); + this.itemIconGenerator = itemIconGenerator; + } + + /** + * Returns the item enabled provider for this multiselect. + * <p> + * <em>Implementation note:</em> Override this method and + * {@link #setItemEnabledProvider(Predicate)} as {@code public} and invoke + * {@code super} methods to support this feature in the multiselect + * component. + * + * @return the item enabled provider, not {@code null} + * @see #setItemEnabledProvider(Predicate) + */ + protected Predicate<T> getItemEnabledProvider() { + return itemEnabledProvider; + } + + /** + * Sets the item enabled predicate for this multiselect. The predicate is + * applied to each item to determine whether the item should be enabled + * ({@code true}) or disabled ({@code false}). Disabled items are displayed + * as grayed out and the user cannot select them. The default predicate + * always returns {@code true} (all the items are enabled). + * <p> + * <em>Implementation note:</em> Override this method and + * {@link #getItemEnabledProvider()} as {@code public} and invoke + * {@code super} methods to support this feature in the multiselect + * component. + * + * @param itemEnabledProvider + * the item enabled provider to set, not {@code null} + */ + protected void setItemEnabledProvider(Predicate<T> itemEnabledProvider) { + Objects.requireNonNull(itemEnabledProvider); + this.itemEnabledProvider = itemEnabledProvider; + } + }
\ No newline at end of file diff --git a/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java b/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java index d5b2c5a851..0a1e4c71af 100644 --- a/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java +++ b/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java @@ -17,28 +17,12 @@ package com.vaadin.ui; import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Predicate; import com.vaadin.data.Listing; -import com.vaadin.event.selection.MultiSelectionEvent; -import com.vaadin.server.Resource; -import com.vaadin.server.ResourceReference; -import com.vaadin.server.data.DataGenerator; import com.vaadin.server.data.DataSource; -import com.vaadin.shared.data.selection.SelectionModel; -import com.vaadin.shared.data.selection.SelectionServerRpc; -import com.vaadin.shared.ui.optiongroup.CheckBoxGroupConstants; import com.vaadin.shared.ui.optiongroup.CheckBoxGroupState; -import elemental.json.JsonObject; - /** * A group of Checkboxes. Individual checkboxes are made from items supplied by * a {@link DataSource}. Checkboxes may have captions and icons. @@ -50,77 +34,6 @@ import elemental.json.JsonObject; */ public class CheckBoxGroup<T> extends AbstractMultiSelect<T> { - private final class SimpleMultiSelectModel - implements SelectionModel.Multi<T> { - - private Set<T> selection = new LinkedHashSet<>(); - - @Override - public void select(T item) { - // Not user originated - select(item, false); - } - - private void select(T item, boolean userOriginated) { - if (selection.contains(item)) { - return; - } - - updateSelection(set -> set.add(item), userOriginated); - } - - @Override - public Set<T> getSelectedItems() { - return Collections.unmodifiableSet(new LinkedHashSet<>(selection)); - } - - @Override - public void deselect(T item) { - // Not user originated - deselect(item, false); - } - - private void deselect(T item, boolean userOriginated) { - if (!selection.contains(item)) { - return; - } - - updateSelection(set -> set.remove(item), userOriginated); - } - - @Override - public void deselectAll() { - if (selection.isEmpty()) { - return; - } - - updateSelection(Set::clear, false); - } - - private void updateSelection(Consumer<Set<T>> handler, - boolean userOriginated) { - LinkedHashSet<T> oldSelection = new LinkedHashSet<>(selection); - handler.accept(selection); - LinkedHashSet<T> newSelection = new LinkedHashSet<>(selection); - - fireEvent(new MultiSelectionEvent<>(CheckBoxGroup.this, - oldSelection, newSelection, userOriginated)); - - getDataCommunicator().reset(); - } - - @Override - public boolean isSelected(T item) { - return selection.contains(item); - } - } - - private Function<T, Resource> itemIconProvider = item -> null; - - private Function<T, String> itemCaptionProvider = String::valueOf; - - private Predicate<T> itemEnabledProvider = item -> true; - /** * Constructs a new CheckBoxGroup with caption. * @@ -167,65 +80,6 @@ public class CheckBoxGroup<T> extends AbstractMultiSelect<T> { * @see Listing#setDataSource(DataSource) */ public CheckBoxGroup() { - setSelectionModel(new SimpleMultiSelectModel()); - - registerRpc(new SelectionServerRpc() { - - @Override - public void select(String key) { - getItemForSelectionChange(key).ifPresent( - item -> getSelectionModel().select(item, true)); - } - - @Override - public void deselect(String key) { - getItemForSelectionChange(key).ifPresent( - item -> getSelectionModel().deselect(item, true)); - } - - private Optional<T> getItemForSelectionChange(String key) { - T item = getDataCommunicator().getKeyMapper().get(key); - if (item == null || !itemEnabledProvider.test(item)) { - return Optional.empty(); - } - - return Optional.of(item); - } - - private SimpleMultiSelectModel getSelectionModel() { - return (SimpleMultiSelectModel) CheckBoxGroup.this - .getSelectionModel(); - } - }); - - addDataGenerator(new DataGenerator<T>() { - @Override - public void generateData(T data, JsonObject jsonObject) { - jsonObject.put(CheckBoxGroupConstants.JSONKEY_ITEM_VALUE, - itemCaptionProvider.apply(data)); - Resource icon = itemIconProvider.apply(data); - if (icon != null) { - String iconUrl = ResourceReference - .create(icon, CheckBoxGroup.this, null).getURL(); - jsonObject.put(CheckBoxGroupConstants.JSONKEY_ITEM_ICON, - iconUrl); - } - if (!itemEnabledProvider.test(data)) { - jsonObject.put(CheckBoxGroupConstants.JSONKEY_ITEM_DISABLED, - true); - } - - if (getSelectionModel().isSelected(data)) { - jsonObject.put(CheckBoxGroupConstants.JSONKEY_ITEM_SELECTED, - true); - } - } - - @Override - public void destroyData(T data) { - } - }); - } /** @@ -263,77 +117,23 @@ public class CheckBoxGroup<T> extends AbstractMultiSelect<T> { return (CheckBoxGroupState) super.getState(markAsDirty); } - /** - * Returns the item icons provider. - * - * @return the icons provider for items - * @see #setItemIconProvider - */ - public Function<T, Resource> getItemIconProvider() { - return itemIconProvider; - } - - /** - * Sets the item icon provider for this checkbox group. The icon provider is - * queried for each item to optionally display an icon next to the item - * caption. If the provider returns null for an item, no icon is displayed. - * The default provider always returns null (no icons). - * - * @param itemIconProvider - * icons provider, not null - */ - public void setItemIconProvider(Function<T, Resource> itemIconProvider) { - Objects.nonNull(itemIconProvider); - this.itemIconProvider = itemIconProvider; - } - - /** - * Returns the item caption provider. - * - * @return the captions provider - * @see #setItemCaptionProvider - */ - public Function<T, String> getItemCaptionProvider() { - return itemCaptionProvider; + @Override + public IconGenerator<T> getItemIconGenerator() { + return super.getItemIconGenerator(); } - /** - * Sets the item caption provider for this checkbox group. The caption - * provider is queried for each item to optionally display an item textual - * representation. The default provider returns - * {@code String.valueOf(item)}. - * - * @param itemCaptionProvider - * the item caption provider, not null - */ - public void setItemCaptionProvider( - Function<T, String> itemCaptionProvider) { - Objects.nonNull(itemCaptionProvider); - this.itemCaptionProvider = itemCaptionProvider; + @Override + public void setItemIconGenerator(IconGenerator<T> itemIconGenerator) { + super.setItemIconGenerator(itemIconGenerator); } - /** - * Returns the item enabled predicate. - * - * @return the item enabled predicate - * @see #setItemEnabledProvider - */ + @Override public Predicate<T> getItemEnabledProvider() { - return itemEnabledProvider; + return super.getItemEnabledProvider(); } - /** - * Sets the item enabled predicate for this checkbox group. The predicate is - * applied to each item to determine whether the item should be enabled - * (true) or disabled (false). Disabled items are displayed as grayed out - * and the user cannot select them. The default predicate always returns - * true (all the items are enabled). - * - * @param itemEnabledProvider - * the item enable predicate, not null - */ + @Override public void setItemEnabledProvider(Predicate<T> itemEnabledProvider) { - Objects.nonNull(itemEnabledProvider); - this.itemEnabledProvider = itemEnabledProvider; + super.setItemEnabledProvider(itemEnabledProvider); } } diff --git a/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java b/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java index e3cc1892b0..f22e4a9535 100644 --- a/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java +++ b/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java @@ -21,7 +21,7 @@ import com.vaadin.server.Resource; import com.vaadin.server.ResourceReference; import com.vaadin.server.data.DataGenerator; import com.vaadin.server.data.DataSource; -import com.vaadin.shared.ui.optiongroup.RadioButtonGroupConstants; +import com.vaadin.shared.ui.ListingJsonConstants; import com.vaadin.shared.ui.optiongroup.RadioButtonGroupState; import elemental.json.JsonObject; @@ -98,22 +98,22 @@ public class RadioButtonGroup<T> extends AbstractSingleSelect<T> { addDataGenerator(new DataGenerator<T>() { @Override public void generateData(T data, JsonObject jsonObject) { - jsonObject.put(RadioButtonGroupConstants.JSONKEY_ITEM_VALUE, + jsonObject.put(ListingJsonConstants.JSONKEY_ITEM_VALUE, itemCaptionProvider.apply(data)); Resource icon = itemIconProvider.apply(data); if (icon != null) { String iconUrl = ResourceReference .create(icon, RadioButtonGroup.this, null).getURL(); - jsonObject.put(RadioButtonGroupConstants.JSONKEY_ITEM_ICON, + jsonObject.put(ListingJsonConstants.JSONKEY_ITEM_ICON, iconUrl); } if (!itemEnabledProvider.test(data)) { - jsonObject.put(RadioButtonGroupConstants.JSONKEY_ITEM_DISABLED, + jsonObject.put(ListingJsonConstants.JSONKEY_ITEM_DISABLED, true); } if (getSelectionModel().isSelected(data)) { - jsonObject.put(RadioButtonGroupConstants.JSONKEY_ITEM_SELECTED, + jsonObject.put(ListingJsonConstants.JSONKEY_ITEM_SELECTED, true); } } diff --git a/server/src/main/java/com/vaadin/ui/TwinColSelect.java b/server/src/main/java/com/vaadin/ui/TwinColSelect.java new file mode 100644 index 0000000000..4b321559a3 --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/TwinColSelect.java @@ -0,0 +1,158 @@ +/* + * 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; + +import java.util.Collection; + +import com.vaadin.server.data.DataSource; +import com.vaadin.shared.ui.twincolselect.TwinColSelectState; + +/** + * Multiselect component with two lists: left side for available items and right + * side for selected items. + * + * @author Vaadin Ltd + * + * @param <T> + * item type + */ +public class TwinColSelect<T> extends AbstractMultiSelect<T> { + + /** + * Constructs a new TwinColSelect. + */ + public TwinColSelect() { + } + + /** + * Constructs a new TwinColSelect with the given caption. + * + * @param caption + * the caption to set, can be {@code null} + */ + public TwinColSelect(String caption) { + this(); + setCaption(caption); + } + + /** + * Constructs a new TwinColSelect with caption and data source for options. + * + * @param caption + * the caption to set, can be {@code null} + * @param dataSource + * the data source, not {@code null} + */ + public TwinColSelect(String caption, DataSource<T> dataSource) { + this(caption); + setDataSource(dataSource); + } + + /** + * Constructs a new TwinColSelect with caption and the given options. + * + * @param caption + * the caption to set, can be {@code null} + * @param options + * the options, cannot be {@code null} + */ + public TwinColSelect(String caption, Collection<T> options) { + this(caption, DataSource.create(options)); + } + + /** + * Returns the number of rows in the selects. + * + * @return the number of rows visible + */ + public int getRows() { + return getState(false).rows; + } + + /** + * Sets the number of rows in the selects. If the number of rows is set to 0 + * or less, the actual number of displayed rows is determined implicitly by + * the selects. + * <p> + * If a height if set (using {@link #setHeight(String)} or + * {@link #setHeight(float, int)}) it overrides the number of rows. Leave + * the height undefined to use this method. + * + * @param rows + * the number of rows to set. + */ + public void setRows(int rows) { + if (rows < 0) { + rows = 0; + } + if (getState(false).rows != rows) { + getState().rows = rows; + } + } + + /** + * Sets the text shown above the right column. {@code null} clears the + * caption. + * + * @param rightColumnCaption + * The text to show, {@code null} to clear + */ + public void setRightColumnCaption(String rightColumnCaption) { + getState().rightColumnCaption = rightColumnCaption; + } + + /** + * Returns the text shown above the right column. + * + * @return The text shown or {@code null} if not set. + */ + public String getRightColumnCaption() { + return getState(false).rightColumnCaption; + } + + /** + * Sets the text shown above the left column. {@code null} clears the + * caption. + * + * @param leftColumnCaption + * The text to show, {@code null} to clear + */ + public void setLeftColumnCaption(String leftColumnCaption) { + getState().leftColumnCaption = leftColumnCaption; + markAsDirty(); + } + + /** + * Returns the text shown above the left column. + * + * @return The text shown or {@code null} if not set. + */ + public String getLeftColumnCaption() { + return getState(false).leftColumnCaption; + } + + @Override + protected TwinColSelectState getState() { + return (TwinColSelectState) super.getState(); + } + + @Override + protected TwinColSelectState getState(boolean markAsDirty) { + return (TwinColSelectState) super.getState(markAsDirty); + } + +} diff --git a/server/src/test/java/com/vaadin/tests/server/component/twincolselect/TwinColSelectDeclarativeTest.java b/server/src/test/java/com/vaadin/tests/server/component/twincolselect/TwinColSelectDeclarativeTest.java new file mode 100644 index 0000000000..6ec4f16f86 --- /dev/null +++ b/server/src/test/java/com/vaadin/tests/server/component/twincolselect/TwinColSelectDeclarativeTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.server.component.twincolselect; + +import org.junit.Test; + +import com.vaadin.tests.design.DeclarativeTestBase; +import com.vaadin.ui.TwinColSelect; + +/** + * Test cases for reading the properties of selection components. + * + * @author Vaadin Ltd + */ +public class TwinColSelectDeclarativeTest + extends DeclarativeTestBase<TwinColSelect<String>> { + + public String getBasicDesign() { + return "<vaadin-twin-col-select rows=5 right-column-caption='Selected values' left-column-caption='Unselected values'>\n" + + " <option>First item</option>\n" + + " <option selected>Second item</option>\n" + + " <option selected>Third item</option>\n" + + "</vaadin-twin-col-select>"; + + } + + public TwinColSelect<String> getBasicExpected() { + TwinColSelect<String> s = new TwinColSelect<>(); + s.setRightColumnCaption("Selected values"); + s.setLeftColumnCaption("Unselected values"); + s.setItems("First item", "Second item", "Third item"); + s.getSelectionModel().select("Second item"); + s.getSelectionModel().select("Third item"); + s.setRows(5); + return s; + } + + @Test + public void testReadBasic() { + testRead(getBasicDesign(), getBasicExpected()); + } + + @Test + public void testWriteBasic() { + testWrite(stripOptionTags(getBasicDesign()), getBasicExpected()); + } + + @Test + public void testReadEmpty() { + testRead("<vaadin-twin-col-select />", new TwinColSelect()); + } + + @Test + public void testWriteEmpty() { + testWrite("<vaadin-twin-col-select />", new TwinColSelect()); + } + +}
\ No newline at end of file diff --git a/server/src/test/java/com/vaadin/ui/AbstractMultiSelectTest.java b/server/src/test/java/com/vaadin/ui/AbstractMultiSelectTest.java new file mode 100644 index 0000000000..3505e4ec38 --- /dev/null +++ b/server/src/test/java/com/vaadin/ui/AbstractMultiSelectTest.java @@ -0,0 +1,235 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import com.vaadin.server.data.DataSource; +import com.vaadin.shared.Registration; +import com.vaadin.shared.data.selection.MultiSelectServerRpc; +import com.vaadin.shared.data.selection.SelectionModel.Multi; + +@RunWith(Parameterized.class) +public class AbstractMultiSelectTest { + + @Parameters(name = "{0}") + public static Iterable<AbstractMultiSelect<String>> multiSelects() { + return Arrays.asList(new CheckBoxGroup<>(), new TwinColSelect<>()); + } + + @Parameter + public AbstractMultiSelect<String> selectToTest; + + private Multi<String> selectionModel; + private MultiSelectServerRpc rpc; + + private Registration registration; + + @Before + public void setUp() { + selectToTest.getSelectionModel().deselectAll(); + // Intentional deviation from upcoming selection order + selectToTest.setDataSource( + DataSource.create("3", "2", "1", "5", "8", "7", "4", "6")); + selectionModel = selectToTest.getSelectionModel(); + rpc = ComponentTest.getRpcProxy(selectToTest, + MultiSelectServerRpc.class); + } + + @After + public void tearDown() { + if (registration != null) { + registration.remove(); + registration = null; + } + } + + @Test + public void stableSelectionOrder() { + selectionModel.select("1"); + selectionModel.select("2"); + selectionModel.select("3"); + + assertSelectionOrder(selectionModel, "1", "2", "3"); + + selectionModel.deselect("1"); + assertSelectionOrder(selectionModel, "2", "3"); + + selectionModel.select("1"); + assertSelectionOrder(selectionModel, "2", "3", "1"); + + selectionModel.selectItems("7", "8", "4"); + assertSelectionOrder(selectionModel, "2", "3", "1", "7", "8", "4"); + + selectionModel.deselectItems("2", "1", "4", "5"); + assertSelectionOrder(selectionModel, "3", "7", "8"); + } + + @Test + public void apiSelectionChange_notUserOriginated() { + AtomicInteger listenerCount = new AtomicInteger(0); + listenerCount.set(0); + + registration = selectToTest.addSelectionListener(event -> { + listenerCount.incrementAndGet(); + Assert.assertFalse(event.isUserOriginated()); + }); + + selectToTest.select("1"); + selectToTest.select("2"); + + selectToTest.deselect("2"); + selectToTest.getSelectionModel().deselectAll(); + + selectToTest.getSelectionModel().selectItems("2", "3", "4"); + selectToTest.getSelectionModel().deselectItems("1", "4"); + + Assert.assertEquals(6, listenerCount.get()); + + // select partly selected + selectToTest.getSelectionModel().selectItems("2", "3", "4"); + Assert.assertEquals(7, listenerCount.get()); + + // select completely selected + selectToTest.getSelectionModel().selectItems("2", "3", "4"); + Assert.assertEquals(7, listenerCount.get()); + + // deselect partly not selected + selectToTest.getSelectionModel().selectItems("1", "4"); + Assert.assertEquals(8, listenerCount.get()); + + // deselect completely not selected + selectToTest.getSelectionModel().selectItems("1", "4"); + Assert.assertEquals(8, listenerCount.get()); + } + + @Test + public void rpcSelectionChange_userOriginated() { + AtomicInteger listenerCount = new AtomicInteger(0); + + registration = selectToTest.addSelectionListener(event -> { + listenerCount.incrementAndGet(); + Assert.assertTrue(event.isUserOriginated()); + }); + + rpcSelect("1"); + assertSelectionOrder(selectionModel, "1"); + + rpcSelect("2"); + assertSelectionOrder(selectionModel, "1", "2"); + rpcDeselect("2"); + assertSelectionOrder(selectionModel, "1"); + rpcSelect("3", "6"); + assertSelectionOrder(selectionModel, "1", "3", "6"); + rpcDeselect("1", "3"); + assertSelectionOrder(selectionModel, "6"); + + Assert.assertEquals(5, listenerCount.get()); + + // select partly selected + rpcSelect("2", "3", "4"); + Assert.assertEquals(6, listenerCount.get()); + assertSelectionOrder(selectionModel, "6", "2", "3", "4"); + + // select completely selected + rpcSelect("2", "3", "4"); + Assert.assertEquals(6, listenerCount.get()); + assertSelectionOrder(selectionModel, "6", "2", "3", "4"); + + // deselect partly not selected + rpcDeselect("1", "4"); + Assert.assertEquals(7, listenerCount.get()); + assertSelectionOrder(selectionModel, "6", "2", "3"); + + // deselect completely not selected + rpcDeselect("1", "4"); + Assert.assertEquals(7, listenerCount.get()); + assertSelectionOrder(selectionModel, "6", "2", "3"); + + // select completely selected and deselect completely not selected + rpcUpdateSelection(new String[] { "3" }, new String[] { "1", "4" }); + Assert.assertEquals(7, listenerCount.get()); + assertSelectionOrder(selectionModel, "6", "2", "3"); + + // select partly selected and deselect completely not selected + rpcUpdateSelection(new String[] { "4", "2" }, + new String[] { "1", "8" }); + Assert.assertEquals(8, listenerCount.get()); + assertSelectionOrder(selectionModel, "6", "2", "3", "4"); + + // select completely selected and deselect partly not selected + rpcUpdateSelection(new String[] { "4", "3" }, + new String[] { "1", "2" }); + Assert.assertEquals(9, listenerCount.get()); + assertSelectionOrder(selectionModel, "6", "3", "4"); + + // duplicate case - ignored + rpcUpdateSelection(new String[] { "2" }, new String[] { "2" }); + Assert.assertEquals(9, listenerCount.get()); + assertSelectionOrder(selectionModel, "6", "3", "4"); + + // duplicate case - duplicate removed + rpcUpdateSelection(new String[] { "2" }, new String[] { "2", "3" }); + Assert.assertEquals(10, listenerCount.get()); + assertSelectionOrder(selectionModel, "6", "4"); + + // duplicate case - duplicate removed + rpcUpdateSelection(new String[] { "6", "8" }, new String[] { "6" }); + Assert.assertEquals(11, listenerCount.get()); + assertSelectionOrder(selectionModel, "6", "4", "8"); + } + + private void rpcSelect(String... keysToSelect) { + rpcUpdateSelection(keysToSelect, new String[] {}); + } + + private void rpcDeselect(String... keysToDeselect) { + rpcUpdateSelection(new String[] {}, keysToDeselect); + } + + private void rpcUpdateSelection(String[] added, String[] removed) { + rpc.updateSelection( + new LinkedHashSet<>(Stream.of(added).map(this::getItemKey) + .collect(Collectors.toList())), + new LinkedHashSet<>(Stream.of(removed).map(this::getItemKey) + .collect(Collectors.toList()))); + } + + private String getItemKey(String dataObject) { + return selectToTest.getDataCommunicator().getKeyMapper() + .key(dataObject); + } + + private static void assertSelectionOrder(Multi<String> selectionModel, + String... selectionOrder) { + Assert.assertEquals(Arrays.asList(selectionOrder), + new ArrayList<>(selectionModel.getSelectedItems())); + } +} diff --git a/server/src/test/java/com/vaadin/ui/CheckBoxGroupBoVTest.java b/server/src/test/java/com/vaadin/ui/CheckBoxGroupBoVTest.java index e8594757d0..c89d95a4b2 100644 --- a/server/src/test/java/com/vaadin/ui/CheckBoxGroupBoVTest.java +++ b/server/src/test/java/com/vaadin/ui/CheckBoxGroupBoVTest.java @@ -23,24 +23,19 @@ import java.util.EnumSet; * @author Vaadin Ltd * @since 8.0 */ -public class CheckBoxGroupBoVTest -{ +public class CheckBoxGroupBoVTest { public enum Status { - STATE_A, - STATE_B, - STATE_C, - STATE_D; + STATE_A, STATE_B, STATE_C, STATE_D; public String getCaption() { return "** " + toString(); } } - public void createOptionGroup() { CheckBoxGroup<Status> s = new CheckBoxGroup<>(); s.setItems(EnumSet.allOf(Status.class)); - s.setItemCaptionProvider(Status::getCaption); + s.setItemCaptionGenerator(Status::getCaption); } } diff --git a/server/src/test/java/com/vaadin/ui/CheckBoxGroupTest.java b/server/src/test/java/com/vaadin/ui/CheckBoxGroupTest.java deleted file mode 100644 index 192dcb3d52..0000000000 --- a/server/src/test/java/com/vaadin/ui/CheckBoxGroupTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import com.vaadin.server.data.DataSource; -import com.vaadin.shared.data.selection.SelectionModel.Multi; -import com.vaadin.shared.data.selection.SelectionServerRpc; - -public class CheckBoxGroupTest { - private CheckBoxGroup<String> checkBoxGroup; - private Multi<String> selectionModel; - - @Before - public void setUp() { - checkBoxGroup = new CheckBoxGroup<>(); - // Intentional deviation from upcoming selection order - checkBoxGroup - .setDataSource(DataSource.create("Third", "Second", "First")); - selectionModel = checkBoxGroup.getSelectionModel(); - } - - @Test - public void stableSelectionOrder() { - selectionModel.select("First"); - selectionModel.select("Second"); - selectionModel.select("Third"); - - assertSelectionOrder(selectionModel, "First", "Second", "Third"); - - selectionModel.deselect("First"); - assertSelectionOrder(selectionModel, "Second", "Third"); - - selectionModel.select("First"); - assertSelectionOrder(selectionModel, "Second", "Third", "First"); - } - - @Test - public void apiSelectionChange_notUserOriginated() { - AtomicInteger listenerCount = new AtomicInteger(0); - - checkBoxGroup.addSelectionListener(event -> { - listenerCount.incrementAndGet(); - Assert.assertFalse(event.isUserOriginated()); - }); - - checkBoxGroup.select("First"); - checkBoxGroup.select("Second"); - - checkBoxGroup.deselect("Second"); - checkBoxGroup.getSelectionModel().deselectAll(); - - Assert.assertEquals(4, listenerCount.get()); - } - - @Test - public void rpcSelectionChange_userOriginated() { - AtomicInteger listenerCount = new AtomicInteger(0); - - checkBoxGroup.addSelectionListener(event -> { - listenerCount.incrementAndGet(); - Assert.assertTrue(event.isUserOriginated()); - }); - - SelectionServerRpc rpc = ComponentTest.getRpcProxy(checkBoxGroup, - SelectionServerRpc.class); - - rpc.select(getItemKey("First")); - rpc.select(getItemKey("Second")); - rpc.deselect(getItemKey("Second")); - - Assert.assertEquals(3, listenerCount.get()); - } - - private String getItemKey(String dataObject) { - return checkBoxGroup.getDataCommunicator().getKeyMapper() - .key(dataObject); - } - - private static void assertSelectionOrder(Multi<String> selectionModel, - String... selectionOrder) { - Assert.assertEquals(Arrays.asList(selectionOrder), - new ArrayList<>(selectionModel.getSelectedItems())); - } -} |