diff options
10 files changed, 471 insertions, 123 deletions
diff --git a/client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java b/client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java index 3b6c73950c..3fde2f6998 100644 --- a/client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java +++ b/client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java @@ -16,9 +16,16 @@ package com.vaadin.client.ui; +import static com.vaadin.shared.ui.optiongroup.CheckBoxGroupConstants.JSONKEY_ITEM_DISABLED; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + import com.google.gwt.aria.client.Roles; -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.ui.Composite; @@ -32,16 +39,8 @@ import com.vaadin.client.ApplicationConnection; import com.vaadin.client.WidgetUtil; import com.vaadin.shared.Registration; import com.vaadin.shared.ui.optiongroup.CheckBoxGroupConstants; -import elemental.json.JsonObject; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import static com.vaadin.shared.ui.optiongroup.CheckBoxGroupConstants.JSONKEY_ITEM_DISABLED; +import elemental.json.JsonObject; /** * The client-side widget for the {@code CheckBoxGroup} component. @@ -49,8 +48,7 @@ import static com.vaadin.shared.ui.optiongroup.CheckBoxGroupConstants.JSONKEY_IT * @author Vaadin Ltd. * @since 8.0 */ -public class VCheckBoxGroup extends Composite - implements Field, ClickHandler, ChangeHandler, +public class VCheckBoxGroup extends Composite implements Field, ClickHandler, com.vaadin.client.Focusable, HasEnabled { public static final String CLASSNAME = "v-select-optiongroup"; @@ -62,10 +60,7 @@ public class VCheckBoxGroup extends Composite * For internal use only. May be removed or replaced in the future. */ public ApplicationConnection client; - /** - * For internal use only. May be removed or replaced in the future. - */ - public JsonObject selected; //TODO replace with SelectionModel + /** * Widget holding the different options (e.g. ListBox or Panel for radio * buttons) (optional, fallbacks to container Panel) @@ -78,7 +73,7 @@ public class VCheckBoxGroup extends Composite private boolean enabled; private boolean readonly; - private List<Consumer<JsonObject>> selectionChangeListeners; + private List<BiConsumer<JsonObject, Boolean>> selectionChangeListeners; public VCheckBoxGroup() { optionsContainer = new FlowPanel(); @@ -102,15 +97,15 @@ public class VCheckBoxGroup extends Composite Roles.getRadiogroupRole().set(getElement()); optionsContainer.clear(); for (JsonObject item : items) { - String itemHtml = - item.getString(CheckBoxGroupConstants.JSONKEY_ITEM_VALUE); + String itemHtml = item + .getString(CheckBoxGroupConstants.JSONKEY_ITEM_VALUE); if (!isHtmlContentAllowed()) { itemHtml = WidgetUtil.escapeHTML(itemHtml); } VCheckBox checkBox = new VCheckBox(); - String iconUrl = - item.getString(CheckBoxGroupConstants.JSONKEY_ITEM_ICON); + String iconUrl = item + .getString(CheckBoxGroupConstants.JSONKEY_ITEM_ICON); if (iconUrl != null && iconUrl.length() != 0) { checkBox.icon = client.getIcon(iconUrl); } @@ -118,7 +113,8 @@ public class VCheckBoxGroup extends Composite checkBox.addStyleName(CLASSNAME_OPTION); checkBox.addClickHandler(this); checkBox.setHTML(itemHtml); - checkBox.setValue(true);//TODO selection model here + checkBox.setValue(item + .getBoolean(CheckBoxGroupConstants.JSONKEY_ITEM_SELECTED)); boolean optionEnabled = !item.getBoolean(JSONKEY_ITEM_DISABLED); boolean enabled = optionEnabled && !isReadonly() && isEnabled(); checkBox.setEnabled(enabled); @@ -138,13 +134,13 @@ public class VCheckBoxGroup extends Composite return; } - final boolean selected = source.getValue(); - JsonObject item = optionsToItems.get(source); //TODO SelectionModel - if (selected) { - this.selected = item; - } else { - this.selected = null; - } + Boolean selected = source.getValue(); + + JsonObject item = optionsToItems.get(source); + assert item != null; + + new ArrayList<>(selectionChangeListeners) + .forEach(listener -> listener.accept(item, selected)); } } @@ -163,8 +159,8 @@ public class VCheckBoxGroup extends Composite .entrySet()) { VCheckBox checkBox = entry.getKey(); JsonObject value = entry.getValue(); - Boolean isOptionEnabled = !value.getBoolean( - CheckBoxGroupConstants.JSONKEY_ITEM_DISABLED); + Boolean isOptionEnabled = !value + .getBoolean(CheckBoxGroupConstants.JSONKEY_ITEM_DISABLED); checkBox.setEnabled(optionGroupEnabled && isOptionEnabled); } } @@ -194,11 +190,6 @@ public class VCheckBoxGroup extends Composite return readonly; } - @Override - public void onChange(ChangeEvent event) { - //TODO selectionModel - } - public void setReadonly(boolean readonly) { if (this.readonly != readonly) { this.readonly = readonly; @@ -214,8 +205,8 @@ public class VCheckBoxGroup extends Composite } } - public Registration addNotifyHandler( - Consumer<JsonObject> selectionChanged) { + public Registration addSelectionChangeHandler( + BiConsumer<JsonObject, Boolean> selectionChanged) { selectionChangeListeners.add(selectionChanged); return (Registration) () -> selectionChangeListeners .remove(selectionChanged); diff --git a/client/src/main/java/com/vaadin/client/ui/optiongroup/CheckBoxGroupConnector.java b/client/src/main/java/com/vaadin/client/ui/optiongroup/CheckBoxGroupConnector.java index dbd531382d..a60a1b111f 100644 --- a/client/src/main/java/com/vaadin/client/ui/optiongroup/CheckBoxGroupConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/optiongroup/CheckBoxGroupConnector.java @@ -23,8 +23,8 @@ import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.connectors.AbstractListingConnector; import com.vaadin.client.data.DataSource; import com.vaadin.client.ui.VCheckBoxGroup; -import com.vaadin.shared.Registration; import com.vaadin.shared.data.selection.SelectionModel; +import com.vaadin.shared.data.selection.SelectionServerRpc; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.optiongroup.CheckBoxGroupState; import com.vaadin.ui.CheckBoxGroup; @@ -32,20 +32,25 @@ import com.vaadin.ui.CheckBoxGroup; import elemental.json.JsonObject; @Connect(CheckBoxGroup.class) +// We don't care about the framework-provided selection model at this point public class CheckBoxGroupConnector - extends AbstractListingConnector<SelectionModel.Multi<JsonObject>> { - - private Registration selectionChangeRegistration; + extends AbstractListingConnector<SelectionModel<?>> { @Override protected void init() { super.init(); - selectionChangeRegistration = - getWidget().addNotifyHandler(this::selectionChanged); + getWidget().addSelectionChangeHandler(this::selectionChanged); } - private void selectionChanged(JsonObject newSelection) { - getSelectionModel().select(newSelection); + private void selectionChanged(JsonObject changedItem, Boolean selected) { + SelectionServerRpc rpc = getRpcProxy(SelectionServerRpc.class); + String key = getRowKey(changedItem); + + if (Boolean.TRUE.equals(selected)) { + rpc.select(key); + } else { + rpc.deselect(key); + } } @Override diff --git a/server/src/main/java/com/vaadin/event/selection/MultiSelectionEvent.java b/server/src/main/java/com/vaadin/event/selection/MultiSelectionEvent.java new file mode 100644 index 0000000000..81c8478721 --- /dev/null +++ b/server/src/main/java/com/vaadin/event/selection/MultiSelectionEvent.java @@ -0,0 +1,77 @@ +/* + * 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.event.selection; + +import java.util.Collections; +import java.util.Set; + +import com.vaadin.event.ConnectorEvent; +import com.vaadin.shared.data.selection.SelectionModel; +import com.vaadin.ui.AbstractListing; + +/** + * Event fired when the the selection changes in a + * {@link com.vaadin.shared.data.selection.SelectionModel.Multi}. + * + * @author Vaadin Ltd + * + * @since 8.0 + * + * @param <T> + * the data type of the selection model + */ +public class MultiSelectionEvent<T> extends ConnectorEvent { + + private Set<T> oldSelection; + private Set<T> newSelection; + + /** + * Creates a new event. + * + * @param source + * the listing component in which the selection changed + * @param oldSelection + * the old set of selected items + * @param newSelection + * the new set of selected items + */ + public MultiSelectionEvent( + AbstractListing<T, SelectionModel.Multi<T>> source, + Set<T> oldSelection, Set<T> newSelection) { + super(source); + this.oldSelection = oldSelection; + this.newSelection = newSelection; + } + + /** + * Gets the new selection. + * + * @return a set of items selected after the selection was changed + */ + public Set<T> getNewSelection() { + return Collections.unmodifiableSet(newSelection); + } + + /** + * Gets the old selection. + * + * @return a set of items selected before the selection was changed + */ + public Set<T> getOldSelection() { + return Collections.unmodifiableSet(oldSelection); + } + +} diff --git a/server/src/main/java/com/vaadin/event/selection/MultiSelectionListener.java b/server/src/main/java/com/vaadin/event/selection/MultiSelectionListener.java new file mode 100644 index 0000000000..44c317532d --- /dev/null +++ b/server/src/main/java/com/vaadin/event/selection/MultiSelectionListener.java @@ -0,0 +1,36 @@ +/* + * 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.event.selection; + +import com.vaadin.event.EventListener; + +/** + * Listens to changes from a + * {@link com.vaadin.shared.data.selection.SelectionModel.Multi}. + * + * @author Vaadin Ltd + * + * @since 8.0 + * + * @param <T> + * the data type of the selection model + */ +public interface MultiSelectionListener<T> + extends EventListener<MultiSelectionEvent<T>> { + @Override + // Explicitly defined to make reflection logic happy + void accept(MultiSelectionEvent<T> event); +} diff --git a/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java b/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java index 2ad72c5e2c..8687aa2b92 100644 --- a/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java +++ b/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java @@ -16,33 +16,103 @@ package com.vaadin.ui; +import java.lang.reflect.Method; +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.event.selection.MultiSelectionListener; 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.Registration; 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; +import com.vaadin.util.ReflectTools; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; -import java.util.function.Predicate; +import elemental.json.JsonObject; /** - * A group of Checkboxes. Individual checkboxes are made from items given to - * supplied by {@code Datasource}. Checkboxes my have captions and icons. + * A group of Checkboxes. Individual checkboxes are made from items supplied by + * a {@link DataSource}. Checkboxes may have captions and icons. * * @param <T> - * item type + * item type * @author Vaadin Ltd * @since 8.0 */ -public class CheckBoxGroup<T> extends AbstractListing<T, SelectionModel<T>> { +public class CheckBoxGroup<T> + extends AbstractListing<T, SelectionModel.Multi<T>> { + + private final class SimpleMultiSelectModel + implements SelectionModel.Multi<T> { + + private Set<T> selection = new LinkedHashSet<>(); + + @Override + public void select(T item) { + if (selection.contains(item)) { + return; + } + + updateSelection(set -> set.add(item)); + } + + @Override + public Set<T> getSelectedItems() { + return Collections.unmodifiableSet(selection); + } + + @Override + public void deselect(T item) { + if (!selection.contains(item)) { + return; + } + + updateSelection(set -> set.remove(item)); + } + + @Override + public void deselectAll() { + if (selection.isEmpty()) { + return; + } + + updateSelection(Set::clear); + } + + private void updateSelection(Consumer<Set<T>> handler) { + LinkedHashSet<T> oldSelection = new LinkedHashSet<>(selection); + handler.accept(selection); + LinkedHashSet<T> newSelection = new LinkedHashSet<>(selection); + + fireEvent(new MultiSelectionEvent<>(CheckBoxGroup.this, + oldSelection, newSelection)); + + getDataCommunicator().reset(); + } + + @Override + public boolean isSelected(T item) { + return selection.contains(item); + } + } + + @Deprecated + private static final Method SELECTION_CHANGE_METHOD = ReflectTools + .findMethod(MultiSelectionListener.class, "accept", + MultiSelectionEvent.class); private Function<T, Resource> itemIconProvider = item -> null; @@ -54,7 +124,7 @@ public class CheckBoxGroup<T> extends AbstractListing<T, SelectionModel<T>> { * Constructs a new CheckBoxGroup with caption. * * @param caption - * caption text + * caption text * @see Listing#setDataSource(DataSource) */ public CheckBoxGroup(String caption) { @@ -66,9 +136,9 @@ public class CheckBoxGroup<T> extends AbstractListing<T, SelectionModel<T>> { * Constructs a new CheckBoxGroup with caption and DataSource. * * @param caption - * the caption text + * the caption text * @param dataSource - * the data source, not null + * the data source, not null * @see Listing#setDataSource(DataSource) */ public CheckBoxGroup(String caption, DataSource<T> dataSource) { @@ -81,9 +151,9 @@ public class CheckBoxGroup<T> extends AbstractListing<T, SelectionModel<T>> { * given items. * * @param caption - * the caption text + * the caption text * @param items - * the data items to use, not null + * the data items to use, not null * @see Listing#setDataSource(DataSource) */ public CheckBoxGroup(String caption, Collection<T> items) { @@ -96,51 +166,52 @@ public class CheckBoxGroup<T> extends AbstractListing<T, SelectionModel<T>> { * @see Listing#setDataSource(DataSource) */ public CheckBoxGroup() { - //TODO Fix when MultiSelection is ready - // SingleSelection<T> model = new SingleSelection<>(this); - // setSelectionModel(model); - // model.addSelectionListener(event -> beforeClientResponse(false)); - setSelectionModel(new SelectionModel.Multi<T>() { - @Override - public void select(T item) { + setSelectionModel(new SimpleMultiSelectModel()); - } + registerRpc(new SelectionServerRpc() { @Override - public Set<T> getSelectedItems() { - return Collections.emptySet(); + public void select(String key) { + getItemForSelectionChange(key) + .ifPresent(getSelectionModel()::select); } @Override - public void deselect(T item) { - + public void deselect(String key) { + getItemForSelectionChange(key) + .ifPresent(getSelectionModel()::deselect); } - @Override - public void deselectAll() { - - } + private Optional<T> getItemForSelectionChange(String key) { + T item = getDataCommunicator().getKeyMapper().get(key); + if (item == null || !itemEnabledProvider.test(item)) { + return Optional.empty(); + } - @Override - public boolean isSelected(T item) { - return false; + return Optional.of(item); } }); + addDataGenerator(new DataGenerator<T>() { @Override public void generateData(T data, JsonObject jsonObject) { jsonObject.put(CheckBoxGroupConstants.JSONKEY_ITEM_VALUE, - itemCaptionProvider.apply(data)); + 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); + iconUrl); } if (!itemEnabledProvider.test(data)) { jsonObject.put(CheckBoxGroupConstants.JSONKEY_ITEM_DISABLED, - true); + true); + } + + if (getSelectionModel().isSelected(data)) { + jsonObject.put(CheckBoxGroupConstants.JSONKEY_ITEM_SELECTED, + true); } } @@ -158,8 +229,8 @@ public class CheckBoxGroup<T> extends AbstractListing<T, SelectionModel<T>> { * content is passed to the browser as plain text. * * @param htmlContentAllowed - * true if the captions are used as html, false if used as plain - * text + * true if the captions are used as html, false if used as plain + * text */ public void setHtmlContentAllowed(boolean htmlContentAllowed) { getState().htmlContentAllowed = htmlContentAllowed; @@ -169,7 +240,7 @@ public class CheckBoxGroup<T> extends AbstractListing<T, SelectionModel<T>> { * Checks whether captions are interpreted as html or plain text. * * @return true if the captions are used as html, false if used as plain - * text + * text * @see #setHtmlContentAllowed(boolean) */ public boolean isHtmlContentAllowed() { @@ -197,13 +268,13 @@ public class CheckBoxGroup<T> extends AbstractListing<T, SelectionModel<T>> { } /** - * 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). + * 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 + * icons provider, not null */ public void setItemIconProvider(Function<T, Resource> itemIconProvider) { Objects.nonNull(itemIconProvider); @@ -222,12 +293,12 @@ public class CheckBoxGroup<T> extends AbstractListing<T, SelectionModel<T>> { /** * 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 + * 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 + * the item caption provider, not null */ public void setItemCaptionProvider( Function<T, String> itemCaptionProvider) { @@ -246,17 +317,33 @@ public class CheckBoxGroup<T> extends AbstractListing<T, SelectionModel<T>> { } /** - * 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). + * 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 + * the item enable predicate, not null */ public void setItemEnabledProvider(Predicate<T> itemEnabledProvider) { Objects.nonNull(itemEnabledProvider); this.itemEnabledProvider = itemEnabledProvider; } + + /** + * Adds a selection listener that will be called when the selection is + * changed either by the user or programmatically. + * + * @param listener + * the value change listener, not <code>null</code> + * @return a registration for the listener + */ + public Registration addSelectionListener( + MultiSelectionListener<T> listener) { + Objects.requireNonNull(listener, "listener cannot be null"); + addListener(MultiSelectionEvent.class, listener, + SELECTION_CHANGE_METHOD); + return () -> removeListener(MultiSelectionEvent.class, listener); + } } diff --git a/server/src/test/java/com/vaadin/ui/CheckBoxGroupTest.java b/server/src/test/java/com/vaadin/ui/CheckBoxGroupTest.java new file mode 100644 index 0000000000..0ad1801e8b --- /dev/null +++ b/server/src/test/java/com/vaadin/ui/CheckBoxGroupTest.java @@ -0,0 +1,54 @@ +/* + * 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 org.junit.Assert; +import org.junit.Test; + +import com.vaadin.server.data.DataSource; +import com.vaadin.shared.data.selection.SelectionModel.Multi; + +public class CheckBoxGroupTest { + @Test + public void stableSelectionOrder() { + CheckBoxGroup<String> checkBoxGroup = new CheckBoxGroup<>(); + // Intentional deviation from upcoming selection order + checkBoxGroup + .setDataSource(DataSource.create("Third", "Second", "First")); + Multi<String> selectionModel = checkBoxGroup.getSelectionModel(); + + 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"); + } + + private static void assertSelectionOrder(Multi<String> selectionModel, + String... selectionOrder) { + Assert.assertEquals(Arrays.asList(selectionOrder), + new ArrayList<>(selectionModel.getSelectedItems())); + } +} diff --git a/shared/src/main/java/com/vaadin/shared/ui/optiongroup/CheckBoxGroupConstants.java b/shared/src/main/java/com/vaadin/shared/ui/optiongroup/CheckBoxGroupConstants.java index 2747dd4c99..6bca43852a 100644 --- a/shared/src/main/java/com/vaadin/shared/ui/optiongroup/CheckBoxGroupConstants.java +++ b/shared/src/main/java/com/vaadin/shared/ui/optiongroup/CheckBoxGroupConstants.java @@ -25,4 +25,6 @@ public class CheckBoxGroupConstants implements Serializable { public static final String JSONKEY_ITEM_VALUE = "v"; public static final String JSONKEY_ITEM_KEY = "k"; + + public static final String JSONKEY_ITEM_SELECTED = "s"; } diff --git a/uitest-common/src/main/java/com/vaadin/testbench/customelements/CheckBoxGroupElement.java b/uitest-common/src/main/java/com/vaadin/testbench/customelements/CheckBoxGroupElement.java index b070ece852..b2dd93a7b0 100644 --- a/uitest-common/src/main/java/com/vaadin/testbench/customelements/CheckBoxGroupElement.java +++ b/uitest-common/src/main/java/com/vaadin/testbench/customelements/CheckBoxGroupElement.java @@ -1,12 +1,12 @@ /* * 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 @@ -32,13 +32,13 @@ import com.vaadin.testbench.elementsbase.ServerClass; @ServerClass("com.vaadin.ui.CheckBoxGroup") public class CheckBoxGroupElement extends AbstractSelectElement { - private static org.openqa.selenium.By byButtonSpan = - By.className("v-select-option"); + private static org.openqa.selenium.By byButtonSpan = By + .className("v-select-option"); private static org.openqa.selenium.By byLabel = By.tagName("label"); private static org.openqa.selenium.By byInput = By.tagName("input"); public List<String> getOptions() { - List<String> optionTexts = new ArrayList<String>(); + List<String> optionTexts = new ArrayList<>(); List<WebElement> options = findElements(byButtonSpan); for (WebElement option : options) { optionTexts.add(option.findElement(byLabel).getText()); @@ -51,9 +51,17 @@ public class CheckBoxGroupElement extends AbstractSelectElement { throw new ReadOnlyException(); } List<WebElement> options = findElements(byButtonSpan); - for (WebElement option : options) { + for (int i = 0; i < options.size(); i++) { + WebElement option = options.get(i); if (text.equals(option.findElement(byLabel).getText())) { option.findElement(byInput).click(); + + // Seems like this is needed because of #19753 + waitForVaadin(); + + // Toggling selection causes the DOM to be rebuilt, so fetch new + // items and continue iterating from the same index + options = findElements(byButtonSpan); } } } @@ -70,8 +78,8 @@ public class CheckBoxGroupElement extends AbstractSelectElement { WebElement checkedItem; checkedItem = option.findElement(By.tagName("input")); String checked = checkedItem.getAttribute("checked"); - if (checked != null && - checkedItem.getAttribute("checked").equals("true")) { + if (checked != null + && checkedItem.getAttribute("checked").equals("true")) { values.add(option.findElement(By.tagName("label")).getText()); } } @@ -82,7 +90,8 @@ public class CheckBoxGroupElement extends AbstractSelectElement { * Select option in the checkbox group with the specified value * * @param chars - * value of the option in the checkbox group which will be selected + * value of the option in the checkbox group which will be + * selected */ public void selectOption(CharSequence chars) throws ReadOnlyException { selectByText((String) chars); @@ -91,8 +100,7 @@ public class CheckBoxGroupElement extends AbstractSelectElement { @Override public void clear() { throw new UnsupportedOperationException( - "Clear operation is not supported for CheckBoxGroup." + - " This operation has no effect on the element."); + "Clear operation is not supported for CheckBoxGroup." + + " This operation has no effect on the element."); } } - diff --git a/uitest/src/main/java/com/vaadin/tests/components/checkbox/CheckBoxGroupTestUI.java b/uitest/src/main/java/com/vaadin/tests/components/checkbox/CheckBoxGroupTestUI.java index 343faf3930..fef23f56d3 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/checkbox/CheckBoxGroupTestUI.java +++ b/uitest/src/main/java/com/vaadin/tests/components/checkbox/CheckBoxGroupTestUI.java @@ -1,12 +1,12 @@ /* * 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 @@ -15,6 +15,9 @@ */ package com.vaadin.tests.components.checkbox; +import java.util.stream.IntStream; + +import com.vaadin.shared.data.selection.SelectionModel.Multi; import com.vaadin.tests.components.abstractlisting.AbstractListingTestUI; import com.vaadin.ui.CheckBoxGroup; @@ -25,8 +28,50 @@ import com.vaadin.ui.CheckBoxGroup; */ public class CheckBoxGroupTestUI extends AbstractListingTestUI<CheckBoxGroup<Object>> { + + private final String selectionCategory = "Selection"; + + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override protected Class<CheckBoxGroup<Object>> getTestClass() { return (Class) CheckBoxGroup.class; } + + @Override + protected void createActions() { + super.createActions(); + createListenerMenu(); + createSelectionMenu(); + } + + protected void createSelectionMenu() { + createClickAction( + "Clear selection", selectionCategory, (component, item, + data) -> component.getSelectionModel().deselectAll(), + ""); + + Command<CheckBoxGroup<Object>, String> toggleSelection = (component, + item, data) -> toggleSelection(item); + + IntStream.of(0, 1, 5, 10, 25).mapToObj(i -> "Item " + i) + .forEach(item -> { + createClickAction("Toggle " + item, selectionCategory, + toggleSelection, item); + }); + } + + private void toggleSelection(String item) { + Multi<Object> selectionModel = getComponent().getSelectionModel(); + if (selectionModel.isSelected(item)) { + selectionModel.deselect(item); + } else { + selectionModel.select(item); + } + } + + protected void createListenerMenu() { + createListenerAction("Selection listener", "Listeners", + c -> c.addSelectionListener( + e -> log("Selected: " + e.getNewSelection()))); + } } diff --git a/uitest/src/test/java/com/vaadin/tests/components/checkboxgroup/CheckBoxGroupTest.java b/uitest/src/test/java/com/vaadin/tests/components/checkboxgroup/CheckBoxGroupTest.java index 9a3c2a656c..9a8f631594 100644 --- a/uitest/src/test/java/com/vaadin/tests/components/checkboxgroup/CheckBoxGroupTest.java +++ b/uitest/src/test/java/com/vaadin/tests/components/checkboxgroup/CheckBoxGroupTest.java @@ -1,6 +1,6 @@ /* * 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 @@ -15,13 +15,17 @@ */ package com.vaadin.tests.components.checkboxgroup; -import com.vaadin.testbench.customelements.CheckBoxGroupElement; -import com.vaadin.tests.components.checkbox.CheckBoxGroupTestUI; -import com.vaadin.tests.tb3.MultiBrowserTest; +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.assertEquals; +import com.vaadin.testbench.customelements.CheckBoxGroupElement; +import com.vaadin.tests.components.checkbox.CheckBoxGroupTestUI; +import com.vaadin.tests.tb3.MultiBrowserTest; /** * Test for CheckBoxGroup @@ -53,6 +57,45 @@ public class CheckBoxGroupTest extends MultiBrowserTest { assertItems(100); } + @Test + public void clickToSelect() { + selectMenuPath("Component", "Listeners", "Selection listener"); + + getSelect().selectByText("Item 4"); + Assert.assertEquals("1. Selected: [Item 4]", getLogRow(0)); + + getSelect().selectByText("Item 2"); + // Selection order (most recently selected is last) + Assert.assertEquals("2. Selected: [Item 4, Item 2]", getLogRow(0)); + + getSelect().selectByText("Item 4"); + Assert.assertEquals("3. Selected: [Item 2]", getLogRow(0)); + } + + @Test + public void selectProgramatically() { + selectMenuPath("Component", "Listeners", "Selection listener"); + + selectMenuPath("Component", "Selection", "Toggle Item 5"); + Assert.assertEquals("2. Selected: [Item 5]", getLogRow(0)); + assertSelected("Item 5"); + + selectMenuPath("Component", "Selection", "Toggle Item 1"); + // Selection order (most recently selected is last) + Assert.assertEquals("4. Selected: [Item 5, Item 1]", getLogRow(0)); + // DOM order + assertSelected("Item 1", "Item 5"); + + selectMenuPath("Component", "Selection", "Toggle Item 5"); + Assert.assertEquals("6. Selected: [Item 1]", getLogRow(0)); + assertSelected("Item 1"); + } + + private void assertSelected(String... expectedSelection) { + Assert.assertEquals(Arrays.asList(expectedSelection), + getSelect().getSelection()); + } + @Override protected Class<?> getUIClass() { return CheckBoxGroupTestUI.class; |