diff options
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())); - } -} |