diff options
author | Pekka Hyvönen <pekka@vaadin.com> | 2016-11-07 09:16:37 +0200 |
---|---|---|
committer | Ilia Motornyi <elmot@vaadin.com> | 2016-11-10 07:49:08 +0000 |
commit | 39c15034076015a16cd056adf9dc422335985543 (patch) | |
tree | 8bb235b285e69cb48b83de067152f06d7c076e8d | |
parent | 0f42869ce1f81270141e94f169f1447febaff446 (diff) | |
download | vaadin-framework-39c15034076015a16cd056adf9dc422335985543.tar.gz vaadin-framework-39c15034076015a16cd056adf9dc422335985543.zip |
Remove HasValue from Grid
Extracts grid single selection into separate class, which
is an extension like in V7. Using an extension makes it
possible to easily add multiselect and no-select modes back,
and support custom selection models.
Adds Grid:asSingleSelect() SingleSelect so that grid can be
used as a Select in a binder.
Removes all remaining references to SelectionModels in Listings.
Renames SingleSelectionChangeEvent to SingleSelectionEvent, because
then it is unified with selection listener and MultiSelectionEvent.
Fixes vaadin/framework8-issues#424
Fixes vaadin/framework8-issues#425
Change-Id: Ie22bef29cfd4336c3f65d4e63531c578b8dd76a3
26 files changed, 976 insertions, 522 deletions
diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java index ef715acfad..f5d95dfa80 100644 --- a/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java +++ b/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java @@ -21,7 +21,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.stream.Collectors; import com.google.gwt.dom.client.Element; @@ -40,7 +39,6 @@ import com.vaadin.client.annotations.OnStateChange; import com.vaadin.client.connectors.AbstractListingConnector; import com.vaadin.client.connectors.grid.ColumnConnector.CustomColumn; import com.vaadin.client.data.DataSource; -import com.vaadin.client.data.SelectionModel; import com.vaadin.client.ui.SimpleManagedLayout; import com.vaadin.client.widget.grid.CellReference; import com.vaadin.client.widget.grid.EventCellReference; @@ -58,8 +56,6 @@ import com.vaadin.client.widgets.Grid.FooterRow; import com.vaadin.client.widgets.Grid.HeaderCell; import com.vaadin.client.widgets.Grid.HeaderRow; import com.vaadin.shared.MouseEventDetails; -import com.vaadin.shared.data.DataCommunicatorConstants; -import com.vaadin.shared.data.selection.SelectionServerRpc; import com.vaadin.shared.data.sort.SortDirection; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.grid.GridConstants; @@ -204,33 +200,6 @@ public class GridConnector extends AbstractListingConnector /* Item click events */ getWidget().addBodyClickHandler(itemClickHandler); getWidget().addBodyDoubleClickHandler(itemClickHandler); - getWidget().setSelectionModel(new SelectionModel<JsonObject>() { - - @Override - public void select(JsonObject item) { - getRpcProxy(SelectionServerRpc.class) - .select(item.getString(DataCommunicatorConstants.KEY)); - } - - @Override - public void deselect(JsonObject item) { - getRpcProxy(SelectionServerRpc.class).deselect( - item.getString(DataCommunicatorConstants.KEY)); - } - - @Override - public Set<JsonObject> getSelectedItems() { - throw new UnsupportedOperationException( - "Selected item not known on the client side"); - } - - @Override - public boolean isSelected(JsonObject item) { - return item.hasKey(DataCommunicatorConstants.SELECTED) - && item.getBoolean(DataCommunicatorConstants.SELECTED); - } - - }); layout(); } diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/SingleSelectionModelConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/SingleSelectionModelConnector.java new file mode 100644 index 0000000000..0a30dfa070 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/connectors/grid/SingleSelectionModelConnector.java @@ -0,0 +1,75 @@ +/* + * 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.client.connectors.grid; + +import java.util.Set; + +import com.vaadin.client.ServerConnector; +import com.vaadin.client.data.SelectionModel; +import com.vaadin.client.extensions.AbstractExtensionConnector; +import com.vaadin.shared.data.DataCommunicatorConstants; +import com.vaadin.shared.data.selection.SelectionServerRpc; +import com.vaadin.shared.ui.Connect; + +import elemental.json.JsonObject; + +/** + * Client side connector for grid single selection model. + * + * @author Vaadin Ltd. + * + * @since 8.0 + */ +@Connect(com.vaadin.ui.components.grid.SingleSelectionModel.class) +public class SingleSelectionModelConnector extends AbstractExtensionConnector { + + @Override + protected void extend(ServerConnector target) { + getParent().getWidget() + .setSelectionModel(new SelectionModel<JsonObject>() { + + @Override + public void select(JsonObject item) { + getRpcProxy(SelectionServerRpc.class).select( + item.getString(DataCommunicatorConstants.KEY)); + } + + @Override + public void deselect(JsonObject item) { + getRpcProxy(SelectionServerRpc.class).deselect( + item.getString(DataCommunicatorConstants.KEY)); + } + + @Override + public Set<JsonObject> getSelectedItems() { + throw new UnsupportedOperationException( + "Selected item not known on the client side"); + } + + @Override + public boolean isSelected(JsonObject item) { + return SelectionModel.isItemSelected(item); + } + + }); + } + + @Override + public GridConnector getParent() { + return (GridConnector) super.getParent(); + } + +} diff --git a/client/src/main/java/com/vaadin/client/connectors/selection/AbstractSelectionConnector.java b/client/src/main/java/com/vaadin/client/connectors/selection/AbstractSelectionConnector.java deleted file mode 100644 index 55ce2dee7b..0000000000 --- a/client/src/main/java/com/vaadin/client/connectors/selection/AbstractSelectionConnector.java +++ /dev/null @@ -1,81 +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.client.connectors.selection; - -import com.vaadin.client.ServerConnector; -import com.vaadin.client.connectors.AbstractListingConnector; -import com.vaadin.client.extensions.AbstractExtensionConnector; -import com.vaadin.shared.data.DataCommunicatorConstants; - -import elemental.json.JsonObject; - -/** - * The client-side connector for selection extensions. - * - * @author Vaadin Ltd. - * - * @since 8.0 - */ -public abstract class AbstractSelectionConnector - extends AbstractExtensionConnector { - - @Override - @SuppressWarnings("unchecked") - protected void extend(ServerConnector target) { - if (!(target instanceof AbstractListingConnector)) { - throw new IllegalArgumentException( - "Cannot extend a connector that is not an " - + AbstractListingConnector.class.getSimpleName()); - } - } - - @Override - @SuppressWarnings("unchecked") - public AbstractListingConnector getParent() { - return (AbstractListingConnector) super.getParent(); - } - - /** - * Gets the selected state from a given json object. This is a helper method - * for selection model connectors. - * - * @param item - * a json object - * @return {@code true} if the json object is marked as selected; - * {@code false} if not - */ - public static boolean isItemSelected(JsonObject item) { - return item.hasKey(DataCommunicatorConstants.SELECTED) - && item.getBoolean(DataCommunicatorConstants.SELECTED); - } - - /** - * Gets the item key from given json object. This is a helper method for - * selection model connectors. - * - * @param item - * a json object - * @return item key; {@code null} if there is no key - */ - public static String getKey(JsonObject item) { - if (item.hasKey(DataCommunicatorConstants.KEY)) { - return item.getString(DataCommunicatorConstants.KEY); - } else { - return null; - } - } - -} diff --git a/client/src/main/java/com/vaadin/client/data/SelectionModel.java b/client/src/main/java/com/vaadin/client/data/SelectionModel.java index 80eb20fef1..4079140128 100644 --- a/client/src/main/java/com/vaadin/client/data/SelectionModel.java +++ b/client/src/main/java/com/vaadin/client/data/SelectionModel.java @@ -17,6 +17,10 @@ package com.vaadin.client.data; import java.util.Set; +import com.vaadin.shared.data.DataCommunicatorConstants; + +import elemental.json.JsonObject; + /** * Models the selection logic of a {@code Grid} component. Determines how items * can be selected and deselected. @@ -32,7 +36,7 @@ public interface SelectionModel<T> { /** * Selects the given item. If another item was already selected, that item * is deselected. - * + * * @param item * the item to select, not null */ @@ -50,7 +54,7 @@ public interface SelectionModel<T> { /** * Returns a set of the currently selected items. It is safe to invoke other * {@code SelectionModel} methods while iterating over the set. - * + * * @return the items in the current selection, not null */ Set<T> getSelectedItems(); @@ -70,4 +74,19 @@ public interface SelectionModel<T> { default void deselectAll() { getSelectedItems().forEach(this::deselect); } + + /** + * Gets the selected state from a given grid row json object. This is a + * helper method for grid selection models. + * + * @param item + * a json object + * @return {@code true} if the json object is marked as selected; + * {@code false} if not + */ + public static boolean isItemSelected(JsonObject item) { + return item.hasKey(DataCommunicatorConstants.SELECTED) + && item.getBoolean(DataCommunicatorConstants.SELECTED); + } + } diff --git a/server/src/main/java/com/vaadin/data/SelectionModel.java b/server/src/main/java/com/vaadin/data/SelectionModel.java index eca43e6c8d..98a068993a 100644 --- a/server/src/main/java/com/vaadin/data/SelectionModel.java +++ b/server/src/main/java/com/vaadin/data/SelectionModel.java @@ -90,6 +90,11 @@ public interface SelectionModel<T> extends Serializable { return getSelectedItem().map(Collections::singleton) .orElse(Collections.emptySet()); } + + @Override + default Optional<T> getFirstSelectedItem() { + return getSelectedItem(); + } } /** @@ -173,6 +178,11 @@ public interface SelectionModel<T> extends Serializable { * the items to remove, not {@code null} */ public void updateSelection(Set<T> addedItems, Set<T> removedItems); + + @Override + default Optional<T> getFirstSelectedItem() { + return getSelectedItems().stream().findFirst(); + } } /** @@ -188,6 +198,17 @@ public interface SelectionModel<T> extends Serializable { public Set<T> getSelectedItems(); /** + * Get first selected data item. + * <p> + * This is the same as {@link Single#getSelectedItem()} in case of single + * selection and the first selected item from + * {@link Multi#getSelectedItems()} in case of multiselection. + * + * @return the first selected item. + */ + Optional<T> getFirstSelectedItem(); + + /** * Selects the given item. Depending on the implementation, may cause other * items to be deselected. If the item is already selected, does nothing. * diff --git a/server/src/main/java/com/vaadin/data/selection/AbstractSelectionModel.java b/server/src/main/java/com/vaadin/data/selection/AbstractSelectionModel.java deleted file mode 100644 index 7e60847685..0000000000 --- a/server/src/main/java/com/vaadin/data/selection/AbstractSelectionModel.java +++ /dev/null @@ -1,47 +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.data.selection; - -import com.vaadin.data.SelectionModel; -import com.vaadin.shared.data.DataCommunicatorConstants; -import com.vaadin.ui.AbstractListing.AbstractListingExtension; - -import elemental.json.JsonObject; - -/** - * An astract base class for {@code SelectionModel}s. - * - * @author Vaadin Ltd. - * - * @param <T> - * type of selected data - * - * @since 8.0 - */ -public abstract class AbstractSelectionModel<T> extends - AbstractListingExtension<T> implements SelectionModel<T> { - - @Override - public void generateData(T data, JsonObject jsonObject) { - if (isSelected(data)) { - jsonObject.put(DataCommunicatorConstants.SELECTED, true); - } - } - - @Override - public void destroyData(T data) { - } -} diff --git a/server/src/main/java/com/vaadin/event/selection/SingleSelectionChangeEvent.java b/server/src/main/java/com/vaadin/event/selection/SingleSelectionEvent.java index 7f1acff82b..c6c9ce6b6c 100644 --- a/server/src/main/java/com/vaadin/event/selection/SingleSelectionChangeEvent.java +++ b/server/src/main/java/com/vaadin/event/selection/SingleSelectionEvent.java @@ -1,12 +1,12 @@ /* * 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 @@ -18,45 +18,62 @@ package com.vaadin.event.selection; import java.util.Optional; import com.vaadin.data.HasValue.ValueChangeEvent; -import com.vaadin.ui.AbstractListing; import com.vaadin.ui.AbstractSingleSelect; +import com.vaadin.ui.Component; +import com.vaadin.ui.SingleSelect; /** * Fired when the selection changes in a listing component. - * + * * @author Vaadin Ltd. * * @param <T> * the type of the selected item * @since 8.0 */ -public class SingleSelectionChangeEvent<T> extends ValueChangeEvent<T> +public class SingleSelectionEvent<T> extends ValueChangeEvent<T> implements SelectionEvent<T> { /** * Creates a new selection change event. - * + * * @param source * the listing that fired the event * @param userOriginated * {@code true} if this event originates from the client, * {@code false} otherwise. */ - public SingleSelectionChangeEvent(AbstractSingleSelect<T> source, + public SingleSelectionEvent(AbstractSingleSelect<T> source, boolean userOriginated) { super(source, userOriginated); } /** + * Creates a new selection change event in a component. + * + * @param component + * the component where the event originated + * @param source + * the single select source + * @param userOriginated + * {@code true} if this event originates from the client, + * {@code false} otherwise. + */ + public SingleSelectionEvent(Component component, SingleSelect<T> source, + boolean userOriginated) { + super(component, source, userOriginated); + } + + /** * Returns an optional of the item that was selected, or an empty optional * if a previously selected item was deselected. * <p> * The result is the current selection of the source * {@link AbstractSingleSelect} object. So it's always exactly the same as * optional describing {@link AbstractSingleSelect#getValue()}. - * + * * @see #getValue() - * + * * @return the selected item or an empty optional if deselected * * @see SelectionModel.Single#getSelectedItem() @@ -65,10 +82,14 @@ public class SingleSelectionChangeEvent<T> extends ValueChangeEvent<T> return Optional.ofNullable(getValue()); } + /** + * The single select on which the Event initially occurred. + * + * @return The single select on which the Event initially occurred. + */ @Override - @SuppressWarnings("unchecked") - public AbstractListing<T> getComponent() { - return (AbstractListing<T>) super.getComponent(); + public SingleSelect<T> getSource() { + return (SingleSelect<T>) super.getSource(); } @Override diff --git a/server/src/main/java/com/vaadin/event/selection/SingleSelectionListener.java b/server/src/main/java/com/vaadin/event/selection/SingleSelectionListener.java index d36c3ceaf1..eb054e250b 100644 --- a/server/src/main/java/com/vaadin/event/selection/SingleSelectionListener.java +++ b/server/src/main/java/com/vaadin/event/selection/SingleSelectionListener.java @@ -26,14 +26,14 @@ import java.util.function.Consumer; * @param <T> * the type of the selected item * - * @see SingleSelectionChangeEvent + * @see SingleSelectionEvent * * @since 8.0 */ @FunctionalInterface public interface SingleSelectionListener<T> - extends Consumer<SingleSelectionChangeEvent<T>>, Serializable { + extends Consumer<SingleSelectionEvent<T>>, Serializable { @Override - public void accept(SingleSelectionChangeEvent<T> event); + public void accept(SingleSelectionEvent<T> event); } diff --git a/server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java b/server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java index 7125139d8a..2ffcedde15 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java +++ b/server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java @@ -22,7 +22,7 @@ import java.util.Optional; import com.vaadin.data.HasValue; import com.vaadin.data.SelectionModel; import com.vaadin.data.SelectionModel.Single; -import com.vaadin.event.selection.SingleSelectionChangeEvent; +import com.vaadin.event.selection.SingleSelectionEvent; import com.vaadin.event.selection.SingleSelectionListener; import com.vaadin.server.data.DataCommunicator; import com.vaadin.shared.Registration; @@ -49,7 +49,7 @@ public abstract class AbstractSingleSelect<T> extends AbstractListing<T> @Deprecated private static final Method SELECTION_CHANGE_METHOD = ReflectTools .findMethod(SingleSelectionListener.class, "accept", - SingleSelectionChangeEvent.class); + SingleSelectionEvent.class); /** * Creates a new {@code AbstractListing} with a default data communicator. @@ -92,11 +92,11 @@ public abstract class AbstractSingleSelect<T> extends AbstractListing<T> * the value change listener, not null * @return a registration for the listener */ - public Registration addSelectionListener( + public Registration addSelectionChangeListener( SingleSelectionListener<T> listener) { - addListener(SingleSelectionChangeEvent.class, listener, + addListener(SingleSelectionEvent.class, listener, SELECTION_CHANGE_METHOD); - return () -> removeListener(SingleSelectionChangeEvent.class, listener); + return () -> removeListener(SingleSelectionEvent.class, listener); } /** @@ -158,7 +158,7 @@ public abstract class AbstractSingleSelect<T> extends AbstractListing<T> @Override public Registration addValueChangeListener( HasValue.ValueChangeListener<T> listener) { - return addSelectionListener(event -> listener.accept( + return addSelectionChangeListener(event -> listener.accept( new ValueChangeEvent<>(this, event.isUserOriginated()))); } @@ -192,18 +192,6 @@ public abstract class AbstractSingleSelect<T> extends AbstractListing<T> return super.isReadOnly(); } - public void deselect(T item) { - Objects.requireNonNull(item, "deselected item cannot be null"); - if (isSelected(item)) { - setSelectedFromServer(null); - } - } - - public void select(T item) { - Objects.requireNonNull(item, "selected item cannot be null"); - setSelectedFromServer(item); - } - /** * Returns the communication key of the selected item or {@code null} if no * item is selected. @@ -245,7 +233,7 @@ public abstract class AbstractSingleSelect<T> extends AbstractListing<T> } doSetSelectedKey(key); - fireEvent(new SingleSelectionChangeEvent<>(AbstractSingleSelect.this, + fireEvent(new SingleSelectionEvent<>(AbstractSingleSelect.this, true)); } @@ -266,7 +254,7 @@ public abstract class AbstractSingleSelect<T> extends AbstractListing<T> } doSetSelectedKey(key); - fireEvent(new SingleSelectionChangeEvent<>(AbstractSingleSelect.this, + fireEvent(new SingleSelectionEvent<>(AbstractSingleSelect.this, false)); } diff --git a/server/src/main/java/com/vaadin/ui/ComboBox.java b/server/src/main/java/com/vaadin/ui/ComboBox.java index 097ae30bc7..d4df2236f7 100644 --- a/server/src/main/java/com/vaadin/ui/ComboBox.java +++ b/server/src/main/java/com/vaadin/ui/ComboBox.java @@ -524,7 +524,7 @@ public class ComboBox<T> extends AbstractSingleSelect<T> implements HasValue<T>, @Override public Registration addValueChangeListener( HasValue.ValueChangeListener<T> listener) { - return addSelectionListener(event -> { + return addSelectionChangeListener(event -> { listener.accept(new ValueChangeEvent<>(event.getComponent(), this, event.isUserOriginated())); }); diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java index 63c8fe3e6f..42aab6a7e4 100644 --- a/server/src/main/java/com/vaadin/ui/Grid.java +++ b/server/src/main/java/com/vaadin/ui/Grid.java @@ -30,19 +30,19 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import com.vaadin.data.SelectionModel.Single; +import com.vaadin.data.Binder; +import com.vaadin.data.SelectionModel; import com.vaadin.event.ConnectorEvent; import com.vaadin.event.ContextClickEvent; import com.vaadin.event.EventListener; -import com.vaadin.event.selection.SingleSelectionChangeEvent; import com.vaadin.server.EncodeResult; +import com.vaadin.server.Extension; import com.vaadin.server.JsonCodec; import com.vaadin.server.SerializableComparator; import com.vaadin.server.SerializableFunction; @@ -50,7 +50,6 @@ import com.vaadin.server.data.SortOrder; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.Registration; import com.vaadin.shared.data.DataCommunicatorConstants; -import com.vaadin.shared.data.selection.SelectionServerRpc; import com.vaadin.shared.data.sort.SortDirection; import com.vaadin.shared.ui.grid.ColumnState; import com.vaadin.shared.ui.grid.GridConstants; @@ -65,6 +64,7 @@ import com.vaadin.ui.Grid.FooterRow; import com.vaadin.ui.components.grid.Footer; import com.vaadin.ui.components.grid.Header; import com.vaadin.ui.components.grid.Header.Row; +import com.vaadin.ui.components.grid.SingleSelectionModel; import com.vaadin.ui.renderers.AbstractRenderer; import com.vaadin.ui.renderers.Renderer; import com.vaadin.ui.renderers.TextRenderer; @@ -83,7 +83,7 @@ import elemental.json.JsonValue; * @param <T> * the grid bean type */ -public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { +public class Grid<T> extends AbstractListing<T> implements HasComponents { @Deprecated private static final Method COLUMN_REORDER_METHOD = ReflectTools.findMethod( @@ -121,6 +121,27 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { } /** + * The server-side interface that controls Grid's selection state. + * SelectionModel should extend {@link AbstractGridExtension}. + * + * @param <T> + * the grid bean type + */ + public interface GridSelectionModel<T> + extends SelectionModel<T>, Extension { + + /** + * Removes this selection model from the grid. + * <p> + * Must call super {@link Extension#remove()} to detach the extension, + * and fire an selection change event for the selection model (with an + * empty selection). + */ + @Override + void remove(); + } + + /** * An event listener for column resize events in the Grid. */ @FunctionalInterface @@ -602,8 +623,8 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { .hasKey(diffStateKey) : "Field name has changed"; Type type = null; try { - type = (getState(false).getClass() - .getDeclaredField(diffStateKey).getGenericType()); + type = getState(false).getClass() + .getDeclaredField(diffStateKey).getGenericType(); } catch (NoSuchFieldException | SecurityException e) { e.printStackTrace(); } @@ -1488,163 +1509,6 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { } } - private final class SingleSelection implements Single<T> { - private T selectedItem = null; - - SingleSelection() { - addDataGenerator((item, json) -> { - if (isSelected(item)) { - json.put(DataCommunicatorConstants.SELECTED, true); - } - }); - registerRpc(new SelectionServerRpc() { - - @Override - public void select(String key) { - setSelectedFromClient(key); - } - - @Override - public void deselect(String key) { - if (isKeySelected(key)) { - setSelectedFromClient(null); - } - } - }); - } - - @Override - public Optional<T> getSelectedItem() { - return Optional.ofNullable(selectedItem); - } - - @Override - public void deselect(T item) { - Objects.requireNonNull(item, "deselected item cannot be null"); - if (isSelected(item)) { - setSelectedFromServer(null); - } - } - - @Override - public void select(T item) { - Objects.requireNonNull(item, "selected item cannot be null"); - setSelectedFromServer(item); - } - - /** - * Returns whether the given key maps to the currently selected item. - * - * @param key - * the key to test or {@code null} to test whether nothing is - * selected - * @return {@code true} if the key equals the key of the currently - * selected item (or {@code null} if no selection), - * {@code false} otherwise. - */ - protected boolean isKeySelected(String key) { - return Objects.equals(key, getSelectedKey()); - } - - /** - * Returns the communication key of the selected item or {@code null} if - * no item is selected. - * - * @return the key of the selected item if any, {@code null} otherwise. - */ - protected String getSelectedKey() { - return itemToKey(selectedItem); - } - - /** - * Sets the selected item based on the given communication key. If the - * key is {@code null}, clears the current selection if any. - * - * @param key - * the key of the selected item or {@code null} to clear - * selection - */ - protected void doSetSelectedKey(String key) { - if (selectedItem != null) { - getDataCommunicator().refresh(selectedItem); - } - selectedItem = keyToItem(key); - if (selectedItem != null) { - getDataCommunicator().refresh(selectedItem); - } - } - - /** - * Sets the selection based on a client request. Does nothing if the - * select component is {@linkplain Component#isReadOnly()} or if the - * selection would not change. Otherwise updates the selection and fires - * a selection change event with {@code isUserOriginated == true}. - * - * @param key - * the key of the item to select or {@code null} to clear - * selection - */ - protected void setSelectedFromClient(String key) { - if (isReadOnly()) { - return; - } - if (isKeySelected(key)) { - return; - } - - doSetSelectedKey(key); - fireEvent(new SingleSelectionChangeEvent<>(Grid.this, true)); - } - - /** - * Sets the selection based on server API call. Does nothing if the - * selection would not change; otherwise updates the selection and fires - * a selection change event with {@code isUserOriginated == false}. - * - * @param item - * the item to select or {@code null} to clear selection - */ - protected void setSelectedFromServer(T item) { - // TODO creates a key if item not in data source - String key = itemToKey(item); - - if (isKeySelected(key) || isSelected(item)) { - return; - } - - doSetSelectedKey(key); - fireEvent(new SingleSelectionChangeEvent<>(Grid.this, false)); - } - - /** - * Returns the communication key assigned to the given item. - * - * @param item - * the item whose key to return - * @return the assigned key - */ - protected String itemToKey(T item) { - if (item == null) { - return null; - } else { - // TODO creates a key if item not in data source - return getDataCommunicator().getKeyMapper().key(item); - } - } - - /** - * Returns the item that the given key is assigned to, or {@code null} - * if there is no such item. - * - * @param key - * the key whose item to return - * @return the associated item if any, {@code null} otherwise. - */ - protected T keyToItem(String key) { - return getDataCommunicator().getKeyMapper().get(key); - } - } - /** * A header row in a Grid. */ @@ -1835,17 +1699,18 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { private int counter = 0; - private Single<T> selectionModel; + private GridSelectionModel<T> selectionModel; /** * Constructor for the {@link Grid} component. */ public Grid() { - setSelectionModel(new SingleSelection()); registerRpc(new GridServerRpcImpl()); setDefaultHeaderRow(appendHeaderRow()); + selectionModel = new SingleSelectionModel<>(this); + detailsManager = new DetailsManager<>(); addExtension(detailsManager); addDataGenerator(detailsManager); @@ -2690,30 +2555,40 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { * * @return the selection model, not null */ - public Single<T> getSelectionModel() { + public GridSelectionModel<T> getSelectionModel() { assert selectionModel != null : "No selection model set by " + getClass().getName() + " constructor"; return selectionModel; } - @Override - public Optional<T> getSelectedItem() { - return getSelectionModel().getSelectedItem(); + /** + * Use this grid as a single select in {@link Binder}. + * <p> + * Sets the grid to single select mode, if not yet so. + * + * @return the single select wrapper that can be used in binder + */ + public SingleSelect<T> asSingleSelect() { + GridSelectionModel<T> model = getSelectionModel(); + if (!(model instanceof SingleSelectionModel)) { + model = new SingleSelectionModel<>(this); + setSelectionModel(model); + } + + return ((SingleSelectionModel<T>) model).asSingleSelect(); } /** * Sets the selection model for this listing. + * <p> + * The default selection model is {@link SingleSelectionModel}. * * @param model - * the selection model to use, not null + * the selection model to use, not {@code null} */ - protected void setSelectionModel(Single<T> model) { - if (selectionModel != null) { - throw new IllegalStateException( - "A selection model can't be changed."); - } - + protected void setSelectionModel(GridSelectionModel<T> model) { Objects.requireNonNull(model, "selection model cannot be null"); + selectionModel.remove(); selectionModel = model; } diff --git a/server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerSelect.java b/server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerSelect.java index ec200082ab..f762aae827 100644 --- a/server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerSelect.java +++ b/server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerSelect.java @@ -62,7 +62,7 @@ public class ColorPickerSelect extends CustomField<Color> { range.setWidth("100%"); range.addValueChangeListener(this::valueChange); - range.select(ColorRange.ALL); + range.setValue(ColorRange.ALL); layout.addComponent(range); @@ -90,20 +90,20 @@ public class ColorPickerSelect extends CustomField<Color> { for (int row = 0; row < rows; row++) { for (int col = 0; col < columns; col++) { - if (row < (rows - 1)) { + if (row < rows - 1) { // Create the color grid by varying the saturation and value // Calculate new hue value - float hue = ((float) col / (float) columns); + float hue = (float) col / (float) columns; float saturation = 1f; float value = 1f; // For the upper half use value=1 and variable // saturation - if (row < (rows / 2)) { - saturation = ((row + 1f) / (rows / 2f)); + if (row < rows / 2) { + saturation = (row + 1f) / (rows / 2f); } else { - value = 1f - ((row - (rows / 2f)) / (rows / 2f)); + value = 1f - (row - rows / 2f) / (rows / 2f); } colors[row][col] = new Color( @@ -112,7 +112,7 @@ public class ColorPickerSelect extends CustomField<Color> { // The last row should have the black&white gradient float hue = 0f; float saturation = 0f; - float value = 1f - ((float) col / (float) columns); + float value = 1f - (float) col / (float) columns; colors[row][col] = new Color( Color.HSVtoRGB(hue, saturation, value)); @@ -151,13 +151,11 @@ public class ColorPickerSelect extends CustomField<Color> { saturation = 1f; value = 1f; - if (index <= ((rows * columns) / 2)) { - saturation = index - / (((float) rows * (float) columns) / 2f); + if (index <= rows * columns / 2) { + saturation = index / ((float) rows * (float) columns / 2f); } else { - index -= ((rows * columns) / 2); - value = 1f - - index / (((float) rows * (float) columns) / 2f); + index -= rows * columns / 2; + value = 1f - index / ((float) rows * (float) columns / 2f); } colors[row][col] = new Color( diff --git a/server/src/main/java/com/vaadin/ui/components/grid/SingleSelectionModel.java b/server/src/main/java/com/vaadin/ui/components/grid/SingleSelectionModel.java new file mode 100644 index 0000000000..bf2f55648e --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/components/grid/SingleSelectionModel.java @@ -0,0 +1,299 @@ +/* + * 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.components.grid; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import com.vaadin.data.SelectionModel; +import com.vaadin.event.selection.SingleSelectionEvent; +import com.vaadin.event.selection.SingleSelectionListener; +import com.vaadin.shared.Registration; +import com.vaadin.shared.data.DataCommunicatorConstants; +import com.vaadin.shared.data.selection.SelectionServerRpc; +import com.vaadin.ui.Component; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.AbstractGridExtension; +import com.vaadin.ui.Grid.GridSelectionModel; +import com.vaadin.ui.SingleSelect; +import com.vaadin.util.ReflectTools; + +import elemental.json.JsonObject; + +/** + * Single selection model for grid. + * + * @author Vaadin Ltd. + * @since 8.0 + * + * @param <T> + * the type of the selected item in grid. + */ +public class SingleSelectionModel<T> extends AbstractGridExtension<T> + implements GridSelectionModel<T>, SelectionModel.Single<T> { + + private static final Method SELECTION_CHANGE_METHOD = ReflectTools + .findMethod(SingleSelectionListener.class, "accept", + SingleSelectionEvent.class); + + private final Grid<T> grid; + private T selectedItem = null; + + /** + * Constructs a new single selection model for the given grid. + * + * @param grid + * the grid to bind the selection model into + */ + public SingleSelectionModel(Grid<T> grid) { + this.grid = grid; + extend(grid); + registerRpc(new SelectionServerRpc() { + + @Override + public void select(String key) { + setSelectedFromClient(key); + } + + @Override + public void deselect(String key) { + if (isKeySelected(key)) { + setSelectedFromClient(null); + } + } + }); + } + + /** + * Adds a selection change listener to this select. The listener is called + * when the value of this select is changed either by the user or + * programmatically. + * + * @param listener + * the value change listener, not null + * @return a registration for the listener + */ + public Registration addSelectionChangeListener( + SingleSelectionListener<T> listener) { + addListener(SingleSelectionEvent.class, listener, + SELECTION_CHANGE_METHOD); + return () -> removeListener(SingleSelectionEvent.class, listener); + } + + @Override + public Optional<T> getSelectedItem() { + return Optional.ofNullable(selectedItem); + } + + @Override + public void deselect(T item) { + Objects.requireNonNull(item, "deselected item cannot be null"); + if (isSelected(item)) { + setSelectedFromServer(null); + } + } + + @Override + public void select(T item) { + Objects.requireNonNull(item, "selected item cannot be null"); + setSelectedFromServer(item); + } + + /** + * Returns whether the given key maps to the currently selected item. + * + * @param key + * the key to test or {@code null} to test whether nothing is + * selected + * @return {@code true} if the key equals the key of the currently selected + * item (or {@code null} if no selection), {@code false} otherwise. + */ + protected boolean isKeySelected(String key) { + return Objects.equals(key, getSelectedKey()); + } + + /** + * Returns the communication key of the selected item or {@code null} if no + * item is selected. + * + * @return the key of the selected item if any, {@code null} otherwise. + */ + protected String getSelectedKey() { + return itemToKey(selectedItem); + } + + /** + * Sets the selected item based on the given communication key. If the key + * is {@code null}, clears the current selection if any. + * + * @param key + * the key of the selected item or {@code null} to clear + * selection + */ + protected void doSetSelectedKey(String key) { + if (selectedItem != null) { + grid.getDataCommunicator().refresh(selectedItem); + } + selectedItem = getData(key); + if (selectedItem != null) { + grid.getDataCommunicator().refresh(selectedItem); + } + } + + /** + * Sets the selection based on a client request. Does nothing if the select + * component is {@linkplain Component#isReadOnly()} or if the selection + * would not change. Otherwise updates the selection and fires a selection + * change event with {@code isUserOriginated == true}. + * + * @param key + * the key of the item to select or {@code null} to clear + * selection + */ + protected void setSelectedFromClient(String key) { + if (isKeySelected(key)) { + return; + } + + doSetSelectedKey(key); + fireEvent( + new SingleSelectionEvent<>(grid, asSingleSelect(), true)); + } + + /** + * Sets the selection based on server API call. Does nothing if the + * selection would not change; otherwise updates the selection and fires a + * selection change event with {@code isUserOriginated == false}. + * + * @param item + * the item to select or {@code null} to clear selection + */ + protected void setSelectedFromServer(T item) { + // TODO creates a key if item not in data source + String key = itemToKey(item); + + if (isSelected(item) || isKeySelected(key)) { + return; + } + + doSetSelectedKey(key); + fireEvent(new SingleSelectionEvent<>(grid, asSingleSelect(), + false)); + } + + /** + * Returns the communication key assigned to the given item. + * + * @param item + * the item whose key to return + * @return the assigned key + */ + protected String itemToKey(T item) { + if (item == null) { + return null; + } else { + // TODO creates a key if item not in data source + return grid.getDataCommunicator().getKeyMapper().key(item); + } + } + + @Override + public Set<T> getSelectedItems() { + if (selectedItem != null) { + return new HashSet<>(Arrays.asList(selectedItem)); + } else { + return Collections.emptySet(); + } + } + + @Override + public void generateData(T item, JsonObject jsonObject) { + if (isSelected(item)) { + jsonObject.put(DataCommunicatorConstants.SELECTED, true); + } + } + + @Override + public void remove() { + // when selection model changes, firing an event for selection change + // event fired before removing so that parent is still intact (in case + // needed) + selectedItem = null; + fireEvent(new SingleSelectionEvent<>(grid, asSingleSelect(), + false)); + + super.remove(); + } + + /** + * Gets a wrapper for using this grid as a single select in a binder. + * + * @return a single select wrapper for grid + */ + public SingleSelect<T> asSingleSelect() { + return new SingleSelect<T>() { + + @Override + public void setValue(T value) { + SingleSelectionModel.this.setSelectedFromServer(value); + } + + @Override + public T getValue() { + return SingleSelectionModel.this.getSelectedItem().orElse(null); + } + + @Override + public Registration addValueChangeListener( + com.vaadin.data.HasValue.ValueChangeListener<T> listener) { + return SingleSelectionModel.this.addSelectionChangeListener( + event -> listener.accept(event)); + } + + @Override + public void setRequiredIndicatorVisible( + boolean requiredIndicatorVisible) { + // TODO support required indicator when grid is used in binder ? + throw new UnsupportedOperationException( + "Required indicator is not supported for Grid."); + } + + @Override + public boolean isRequiredIndicatorVisible() { + throw new UnsupportedOperationException( + "Required indicator is not supported for Grid."); + } + + @Override + public void setReadOnly(boolean readOnly) { + // TODO support read only when grid is used in binder ? + throw new UnsupportedOperationException( + "Read only is not supported for Grid."); + } + + @Override + public boolean isReadOnly() { + throw new UnsupportedOperationException( + "Read only is not supported for Grid."); + } + }; + } +}
\ No newline at end of file diff --git a/server/src/test/java/com/vaadin/data/BinderSingleSelectTest.java b/server/src/test/java/com/vaadin/data/BinderSingleSelectTest.java index 00a946247d..a0ebe374fb 100644 --- a/server/src/test/java/com/vaadin/data/BinderSingleSelectTest.java +++ b/server/src/test/java/com/vaadin/data/BinderSingleSelectTest.java @@ -1,12 +1,12 @@ /* * 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 @@ -76,7 +76,7 @@ public class BinderSingleSelectTest public void bound_setSelection_beanValueUpdated() { bindSex(); - select.select(Sex.MALE); + select.setValue(Sex.MALE); assertSame(Sex.MALE, item.getSex()); } @@ -86,7 +86,7 @@ public class BinderSingleSelectTest item.setSex(Sex.MALE); bindSex(); - select.deselect(Sex.MALE); + select.setValue(null); assertNull(item.getSex()); } @@ -97,7 +97,7 @@ public class BinderSingleSelectTest bindSex(); binder.removeBean(); - select.select(Sex.FEMALE); + select.setValue(Sex.FEMALE); assertSame(Sex.UNKNOWN, item.getSex()); } diff --git a/server/src/test/java/com/vaadin/data/GridAsSingleSelectInBinder.java b/server/src/test/java/com/vaadin/data/GridAsSingleSelectInBinder.java new file mode 100644 index 0000000000..7b34c78f25 --- /dev/null +++ b/server/src/test/java/com/vaadin/data/GridAsSingleSelectInBinder.java @@ -0,0 +1,157 @@ +package com.vaadin.data; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.tests.data.bean.Person; +import com.vaadin.tests.data.bean.Sex; +import com.vaadin.ui.Grid; +import com.vaadin.ui.SingleSelect; +import com.vaadin.ui.components.grid.SingleSelectionModel; + +public class GridAsSingleSelectInBinder + extends BinderTestBase<Binder<Person>, Person> { + + private class GridWithCustomSingleSelectionModel extends Grid<Sex> { + @Override + public void setSelectionModel( + com.vaadin.ui.Grid.GridSelectionModel<Sex> model) { + super.setSelectionModel(model); + } + } + + private class CustomSingleSelectModel extends SingleSelectionModel<Sex> { + + public CustomSingleSelectModel(Grid<Sex> grid) { + super(grid); + } + + public void setSelectedFromClient(Sex item) { + setSelectedFromClient(itemToKey(item)); + } + } + + private Grid<Sex> grid; + private SingleSelect<Sex> select; + + @Before + public void setup() { + binder = new Binder<>(); + item = new Person(); + grid = new Grid<>(); + grid.setItems(Sex.values()); + select = grid.asSingleSelect(); + } + + @Test + public void personBound_bindSelectByShortcut_selectionUpdated() { + item.setSex(Sex.FEMALE); + binder.setBean(item); + binder.bind(select, Person::getSex, Person::setSex); + + assertSame(Sex.FEMALE, select.getValue()); + } + + @Test + public void personBound_bindSelect_selectionUpdated() { + item.setSex(Sex.MALE); + binder.setBean(item); + binder.forField(select).bind(Person::getSex, Person::setSex); + + assertSame(Sex.MALE, select.getValue()); + } + + @Test + public void selectBound_bindPersonWithNullSex_selectedItemNotPresent() { + bindSex(); + + assertFalse(select.getValue() != null); + } + + @Test + public void selectBound_bindPerson_selectionUpdated() { + item.setSex(Sex.FEMALE); + bindSex(); + + assertSame(Sex.FEMALE, select.getValue()); + } + + @Test + public void bound_setSelection_beanValueUpdated() { + bindSex(); + + select.setValue(Sex.MALE); + + assertSame(Sex.MALE, item.getSex()); + } + + @Test + public void bound_deselect_beanValueUpdatedToNull() { + item.setSex(Sex.MALE); + bindSex(); + + select.setValue(null); + + assertNull(item.getSex()); + } + + @Test + public void unbound_changeSelection_beanValueNotUpdated() { + item.setSex(Sex.UNKNOWN); + bindSex(); + binder.removeBean(); + + select.setValue(Sex.FEMALE); + + assertSame(Sex.UNKNOWN, item.getSex()); + } + + @Test + public void addValueChangeListener_selectionUpdated_eventTriggeredForSelect() { + binder = new Binder<>(); + item = new Person(); + GridWithCustomSingleSelectionModel grid = new GridWithCustomSingleSelectionModel(); + CustomSingleSelectModel model = new CustomSingleSelectModel(grid); + grid.setSelectionModel(model); + grid.setItems(Sex.values()); + select = grid.asSingleSelect(); + + List<Sex> selected = new ArrayList<>(); + List<Boolean> userOriginated = new ArrayList<>(); + select.addValueChangeListener(event -> { + selected.add(event.getValue()); + userOriginated.add(event.isUserOriginated()); + assertSame(grid, event.getComponent()); + // cannot compare that the event source is the select since a new + // SingleSelect wrapper object has been created for the event + assertSame(select.getValue(), event.getValue()); + }); + + grid.getSelectionModel().select(Sex.UNKNOWN); + model.setSelectedFromClient(Sex.MALE); // simulates client side + // selection + grid.getSelectionModel().select(Sex.MALE); // NOOP + grid.getSelectionModel().deselect(Sex.UNKNOWN); // NOOP + model.setSelectedFromClient(null); // simulates deselect from client + // side + grid.getSelectionModel().select(Sex.FEMALE); + + assertEquals(Arrays.asList(Sex.UNKNOWN, Sex.MALE, null, Sex.FEMALE), + selected); + assertEquals(Arrays.asList(false, true, true, false), userOriginated); + } + + protected void bindSex() { + binder.forField(select).bind(Person::getSex, Person::setSex); + binder.setBean(item); + } +} diff --git a/server/src/test/java/com/vaadin/event/selection/SelectionEventTest.java b/server/src/test/java/com/vaadin/event/selection/SelectionEventTest.java index 006025c197..97a6ed03d1 100644 --- a/server/src/test/java/com/vaadin/event/selection/SelectionEventTest.java +++ b/server/src/test/java/com/vaadin/event/selection/SelectionEventTest.java @@ -51,8 +51,8 @@ public class SelectionEventTest { @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void getFirstSelected_singleSelectEvent() { - SingleSelectionChangeEvent event = Mockito - .mock(SingleSelectionChangeEvent.class); + SingleSelectionEvent event = Mockito + .mock(SingleSelectionEvent.class); Mockito.doCallRealMethod().when(event).getFirstSelected(); Mockito.when(event.getSelectedItem()).thenReturn(Optional.of("foo")); diff --git a/server/src/test/java/com/vaadin/tests/components/grid/GridSelectionTest.java b/server/src/test/java/com/vaadin/tests/components/grid/GridSelectionTest.java deleted file mode 100644 index 572413bb99..0000000000 --- a/server/src/test/java/com/vaadin/tests/components/grid/GridSelectionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.vaadin.tests.components.grid; - -import java.util.Optional; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import com.vaadin.ui.Grid; - -public class GridSelectionTest { - - Grid<String> grid; - - @Before - public void setUp() { - grid = new Grid<>(); - grid.setItems("Foo", "Bar"); - } - - @Test - public void testGridWithSingleSelection() { - Assert.assertFalse(grid.getSelectionModel().isSelected("Foo")); - grid.getSelectionModel().select("Foo"); - Assert.assertTrue(grid.getSelectionModel().isSelected("Foo")); - Assert.assertEquals(Optional.of("Foo"), grid.getSelectedItem()); - grid.getSelectionModel().select("Bar"); - Assert.assertFalse(grid.getSelectionModel().isSelected("Foo")); - Assert.assertTrue(grid.getSelectionModel().isSelected("Bar")); - grid.getSelectionModel().deselect("Bar"); - Assert.assertFalse(grid.getSelectionModel().isSelected("Bar")); - Assert.assertFalse( - grid.getSelectionModel().getSelectedItem().isPresent()); - } - -} diff --git a/server/src/test/java/com/vaadin/tests/components/grid/GridSingleSelectionModelTest.java b/server/src/test/java/com/vaadin/tests/components/grid/GridSingleSelectionModelTest.java new file mode 100644 index 0000000000..9746e2eef1 --- /dev/null +++ b/server/src/test/java/com/vaadin/tests/components/grid/GridSingleSelectionModelTest.java @@ -0,0 +1,244 @@ +package com.vaadin.tests.components.grid; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import com.vaadin.data.HasValue.ValueChangeEvent; +import com.vaadin.event.selection.SingleSelectionEvent; +import com.vaadin.event.selection.SingleSelectionListener; +import com.vaadin.server.data.datasource.bov.Person; +import com.vaadin.shared.Registration; +import com.vaadin.shared.data.DataCommunicatorClientRpc; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.GridSelectionModel; +import com.vaadin.ui.components.grid.SingleSelectionModel; + +public class GridSingleSelectionModelTest { + + public static final Person PERSON_C = new Person("c", 3); + public static final Person PERSON_B = new Person("b", 2); + public static final Person PERSON_A = new Person("a", 1); + public static final String RPC_INTERFACE = DataCommunicatorClientRpc.class + .getName(); + + private class CustomSelectionModelGrid extends Grid<String> { + public void switchSelectionModel() { + // just switch selection model to cause event + setSelectionModel(new SingleSelectionModel(this)); + } + } + + private List<Person> selectionChanges; + private Grid<Person> grid; + private SingleSelectionModel<Person> selectionModel; + + @Before + public void setUp() { + grid = new Grid<>(); + grid.setItems(PERSON_A, PERSON_B, PERSON_C); + selectionModel = (SingleSelectionModel<Person>) grid + .getSelectionModel(); + + selectionChanges = new ArrayList<>(); + selectionModel.addSelectionChangeListener( + e -> selectionChanges.add(e.getValue())); + } + + @Test + public void testGridChangingSelectionModel_firesSelectionChangeEvent() { + CustomSelectionModelGrid customGrid = new CustomSelectionModelGrid(); + customGrid.setItems("Foo", "Bar", "Baz"); + + List<String> selectionChanges = new ArrayList<>(); + ((SingleSelectionModel<String>) customGrid.getSelectionModel()) + .addSelectionChangeListener( + e -> selectionChanges.add(e.getValue())); + + customGrid.getSelectionModel().select("Foo"); + assertEquals("Foo", + customGrid.getSelectionModel().getFirstSelectedItem().get()); + assertEquals(Arrays.asList("Foo"), selectionChanges); + + customGrid.switchSelectionModel(); + assertEquals(Arrays.asList("Foo", null), selectionChanges); + } + + @Test + public void testGridWithSingleSelection() { + Grid<String> gridWithStrings = new Grid<>(); + gridWithStrings.setItems("Foo", "Bar", "Baz"); + + GridSelectionModel<String> model = gridWithStrings.getSelectionModel(); + Assert.assertFalse(model.isSelected("Foo")); + + model.select("Foo"); + Assert.assertTrue(model.isSelected("Foo")); + Assert.assertEquals(Optional.of("Foo"), model.getFirstSelectedItem()); + + model.select("Bar"); + Assert.assertFalse(model.isSelected("Foo")); + Assert.assertTrue(model.isSelected("Bar")); + + model.deselect("Bar"); + Assert.assertFalse(model.isSelected("Bar")); + Assert.assertFalse(model.getFirstSelectedItem().isPresent()); + } + + @Test + public void select_isSelected() { + selectionModel.select(PERSON_B); + + assertTrue(selectionModel.getSelectedItem().isPresent()); + + assertEquals(PERSON_B, selectionModel.getSelectedItem().orElse(null)); + + assertFalse(selectionModel.isSelected(PERSON_A)); + assertTrue(selectionModel.isSelected(PERSON_B)); + assertFalse(selectionModel.isSelected(PERSON_C)); + + assertEquals(Optional.of(PERSON_B), selectionModel.getSelectedItem()); + + assertEquals(Arrays.asList(PERSON_B), selectionChanges); + } + + @Test + public void selectDeselect() { + + selectionModel.select(PERSON_B); + selectionModel.deselect(PERSON_B); + + assertFalse(selectionModel.getSelectedItem().isPresent()); + + assertFalse(selectionModel.isSelected(PERSON_A)); + assertFalse(selectionModel.isSelected(PERSON_B)); + assertFalse(selectionModel.isSelected(PERSON_C)); + + assertFalse(selectionModel.getSelectedItem().isPresent()); + + assertEquals(Arrays.asList(PERSON_B, null), selectionChanges); + } + + @Test + public void reselect() { + selectionModel.select(PERSON_B); + selectionModel.select(PERSON_C); + + assertEquals(PERSON_C, selectionModel.getSelectedItem().orElse(null)); + + assertFalse(selectionModel.isSelected(PERSON_A)); + assertFalse(selectionModel.isSelected(PERSON_B)); + assertTrue(selectionModel.isSelected(PERSON_C)); + + assertEquals(Optional.of(PERSON_C), selectionModel.getSelectedItem()); + + assertEquals(Arrays.asList(PERSON_B, PERSON_C), selectionChanges); + } + + @Test + public void selectTwice() { + + selectionModel.select(PERSON_C); + selectionModel.select(PERSON_C); + + assertEquals(PERSON_C, selectionModel.getSelectedItem().orElse(null)); + + assertFalse(selectionModel.isSelected(PERSON_A)); + assertFalse(selectionModel.isSelected(PERSON_B)); + assertTrue(selectionModel.isSelected(PERSON_C)); + + assertEquals(Optional.of(PERSON_C), selectionModel.getSelectedItem()); + + assertEquals(Arrays.asList(PERSON_C), selectionChanges); + } + + @Test + public void deselectTwice() { + + selectionModel.select(PERSON_C); + selectionModel.deselect(PERSON_C); + selectionModel.deselect(PERSON_C); + + assertFalse(selectionModel.getSelectedItem().isPresent()); + + assertFalse(selectionModel.isSelected(PERSON_A)); + assertFalse(selectionModel.isSelected(PERSON_B)); + assertFalse(selectionModel.isSelected(PERSON_C)); + + assertFalse(selectionModel.getSelectedItem().isPresent()); + + assertEquals(Arrays.asList(PERSON_C, null), selectionChanges); + } + + @Test + public void getSelectedItem() { + selectionModel.setSelectedItem(PERSON_B); + + Assert.assertEquals(PERSON_B, selectionModel.getSelectedItem().get()); + + selectionModel.deselect(PERSON_B); + Assert.assertFalse(selectionModel.getSelectedItem().isPresent()); + } + + @Test + public void select_deselect_getSelectedItem() { + selectionModel.select(PERSON_C); + + Assert.assertEquals(PERSON_C, selectionModel.getSelectedItem().get()); + + selectionModel.deselect(PERSON_C); + + Assert.assertFalse(selectionModel.getSelectedItem().isPresent()); + } + + @SuppressWarnings({ "serial" }) + @Test + public void addValueChangeListener() { + AtomicReference<SingleSelectionListener<String>> selectionListener = new AtomicReference<>(); + Registration registration = Mockito.mock(Registration.class); + Grid<String> grid = new Grid<>(); + grid.setItems("foo", "bar"); + String value = "foo"; + SingleSelectionModel<String> select = new SingleSelectionModel<String>( + grid) { + @Override + public Registration addSelectionChangeListener( + SingleSelectionListener<String> listener) { + selectionListener.set(listener); + return registration; + } + + @Override + public Optional<String> getSelectedItem() { + return Optional.of(value); + } + }; + + AtomicReference<ValueChangeEvent<?>> event = new AtomicReference<>(); + Registration actualRegistration = select + .addSelectionChangeListener(evt -> { + Assert.assertNull(event.get()); + event.set(evt); + }); + Assert.assertSame(registration, actualRegistration); + + selectionListener.get().accept(new SingleSelectionEvent<>(grid, + select.asSingleSelect(), true)); + + Assert.assertEquals(grid, event.get().getComponent()); + Assert.assertEquals(value, event.get().getValue()); + Assert.assertTrue(event.get().isUserOriginated()); + } + +} diff --git a/server/src/test/java/com/vaadin/ui/AbstractSingleSelectTest.java b/server/src/test/java/com/vaadin/ui/AbstractSingleSelectTest.java index 47f5fbc81d..b429afb600 100644 --- a/server/src/test/java/com/vaadin/ui/AbstractSingleSelectTest.java +++ b/server/src/test/java/com/vaadin/ui/AbstractSingleSelectTest.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 @@ -21,10 +21,8 @@ import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Arrays; -import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import org.junit.Assert; @@ -33,7 +31,7 @@ import org.junit.Test; import org.mockito.Mockito; import com.vaadin.data.HasValue.ValueChangeEvent; -import com.vaadin.event.selection.SingleSelectionChangeEvent; +import com.vaadin.event.selection.SingleSelectionEvent; import com.vaadin.event.selection.SingleSelectionListener; import com.vaadin.server.data.datasource.bov.Person; import com.vaadin.shared.Registration; @@ -56,7 +54,8 @@ public class AbstractSingleSelectTest { listing = new PersonListing(); listing.setItems(PERSON_A, PERSON_B, PERSON_C); selectionChanges = new ArrayList<>(); - listing.addSelectionListener(e -> selectionChanges.add(e.getValue())); + listing.addSelectionChangeListener( + e -> selectionChanges.add(e.getValue())); } public static final Person PERSON_C = new Person("c", 3); @@ -69,7 +68,7 @@ public class AbstractSingleSelectTest { @Test public void select() { - listing.select(PERSON_B); + listing.setValue(PERSON_B); assertTrue(listing.getSelectedItem().isPresent()); @@ -87,8 +86,8 @@ public class AbstractSingleSelectTest { @Test public void selectDeselect() { - listing.select(PERSON_B); - listing.deselect(PERSON_B); + listing.setValue(PERSON_B); + listing.setValue(null); assertFalse(listing.getSelectedItem().isPresent()); @@ -104,8 +103,8 @@ public class AbstractSingleSelectTest { @Test public void reselect() { - listing.select(PERSON_B); - listing.select(PERSON_C); + listing.setValue(PERSON_B); + listing.setValue(PERSON_C); assertEquals(PERSON_C, listing.getSelectedItem().orElse(null)); @@ -119,27 +118,10 @@ public class AbstractSingleSelectTest { } @Test - public void deselectNoOp() { - - listing.select(PERSON_C); - listing.deselect(PERSON_B); - - assertEquals(PERSON_C, listing.getSelectedItem().orElse(null)); - - assertFalse(listing.isSelected(PERSON_A)); - assertFalse(listing.isSelected(PERSON_B)); - assertTrue(listing.isSelected(PERSON_C)); - - assertEquals(Optional.of(PERSON_C), listing.getSelectedItem()); - - assertEquals(Arrays.asList(PERSON_C), selectionChanges); - } - - @Test public void selectTwice() { - listing.select(PERSON_C); - listing.select(PERSON_C); + listing.setValue(PERSON_C); + listing.setValue(PERSON_C); assertEquals(PERSON_C, listing.getSelectedItem().orElse(null)); @@ -155,9 +137,9 @@ public class AbstractSingleSelectTest { @Test public void deselectTwice() { - listing.select(PERSON_C); - listing.deselect(PERSON_C); - listing.deselect(PERSON_C); + listing.setValue(PERSON_C); + listing.setValue(null); + listing.setValue(null); assertFalse(listing.getSelectedItem().isPresent()); @@ -176,7 +158,7 @@ public class AbstractSingleSelectTest { Assert.assertEquals(PERSON_B, listing.getValue()); - listing.deselect(PERSON_B); + listing.setValue(null); Assert.assertNull(listing.getValue()); } @@ -220,7 +202,7 @@ public class AbstractSingleSelectTest { Mockito.verify(select).setSelectedItem(null); } - @SuppressWarnings({ "unchecked", "serial" }) + @SuppressWarnings("serial") @Test public void addValueChangeListener() { AtomicReference<SingleSelectionListener<String>> selectionListener = new AtomicReference<>(); @@ -228,7 +210,7 @@ public class AbstractSingleSelectTest { String value = "foo"; AbstractSingleSelect<String> select = new AbstractSingleSelect<String>() { @Override - public Registration addSelectionListener( + public Registration addSelectionChangeListener( SingleSelectionListener<String> listener) { selectionListener.set(listener); return registration; @@ -248,32 +230,11 @@ public class AbstractSingleSelectTest { Assert.assertSame(registration, actualRegistration); selectionListener.get() - .accept(new SingleSelectionChangeEvent<>(select, true)); + .accept(new SingleSelectionEvent<>(select, true)); Assert.assertEquals(select, event.get().getComponent()); Assert.assertEquals(value, event.get().getValue()); Assert.assertTrue(event.get().isUserOriginated()); } - @Test - @SuppressWarnings({ "unchecked", "rawtypes" }) - public void setValue_isDelegatedToDeselectAndUpdateSelection() { - AbstractMultiSelect<String> select = Mockito - .mock(AbstractMultiSelect.class); - - Set set = new LinkedHashSet<>(); - set.add("foo1"); - set.add("foo"); - Set selected = new LinkedHashSet<>(); - selected.add("bar1"); - selected.add("bar"); - selected.add("bar2"); - Mockito.when(select.getSelectedItems()).thenReturn(selected); - Mockito.doCallRealMethod().when(select).setValue(Mockito.anySet()); - - select.setValue(set); - - Mockito.verify(select).updateSelection(set, selected); - } - } diff --git a/server/src/test/java/com/vaadin/ui/RadioButtonGroupTest.java b/server/src/test/java/com/vaadin/ui/RadioButtonGroupTest.java index ea1cf39351..ec48a74799 100644 --- a/server/src/test/java/com/vaadin/ui/RadioButtonGroupTest.java +++ b/server/src/test/java/com/vaadin/ui/RadioButtonGroupTest.java @@ -42,16 +42,16 @@ public class RadioButtonGroupTest { public void apiSelectionChange_notUserOriginated() { AtomicInteger listenerCount = new AtomicInteger(0); - radioButtonGroup.addSelectionListener(event -> { + radioButtonGroup.addSelectionChangeListener(event -> { listenerCount.incrementAndGet(); Assert.assertFalse(event.isUserOriginated()); }); - radioButtonGroup.select("First"); - radioButtonGroup.select("Second"); + radioButtonGroup.setValue("First"); + radioButtonGroup.setValue("Second"); - radioButtonGroup.deselect("Second"); - radioButtonGroup.deselect("Second"); + radioButtonGroup.setValue(null); + radioButtonGroup.setValue(null); Assert.assertEquals(3, listenerCount.get()); } @@ -60,7 +60,7 @@ public class RadioButtonGroupTest { public void rpcSelectionChange_userOriginated() { AtomicInteger listenerCount = new AtomicInteger(0); - radioButtonGroup.addSelectionListener(event -> { + radioButtonGroup.addSelectionChangeListener(event -> { listenerCount.incrementAndGet(); Assert.assertTrue(event.isUserOriginated()); }); diff --git a/uitest/src/main/java/com/vaadin/tests/components/abstractlisting/AbstractSingleSelectTestUI.java b/uitest/src/main/java/com/vaadin/tests/components/abstractlisting/AbstractSingleSelectTestUI.java index a3c43adc1f..2fa6abced8 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/abstractlisting/AbstractSingleSelectTestUI.java +++ b/uitest/src/main/java/com/vaadin/tests/components/abstractlisting/AbstractSingleSelectTestUI.java @@ -32,7 +32,7 @@ public abstract class AbstractSingleSelectTestUI<T extends AbstractSingleSelect< protected void createListenerMenu() { createListenerAction("Selection listener", "Listeners", c -> c - .addSelectionListener(e -> log("Selected: " + e.getValue()))); + .addSelectionChangeListener(e -> log("Selected: " + e.getValue()))); } protected void createSelectionMenu() { @@ -46,12 +46,7 @@ public abstract class AbstractSingleSelectTestUI<T extends AbstractSingleSelect< createSelectAction("Select", "Selection", options, "None", (component, selected, data) -> { - if (selected != null) { - component.select(selected); - } else { - component.getSelectedItem() - .ifPresent(component::deselect); - } + component.setValue(selected); }); } diff --git a/uitest/src/main/java/com/vaadin/tests/components/combobox/ComboBoxSelectingWithNewItemsAllowed.java b/uitest/src/main/java/com/vaadin/tests/components/combobox/ComboBoxSelectingWithNewItemsAllowed.java index cbd3c4ebcf..dc87ee59e5 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/combobox/ComboBoxSelectingWithNewItemsAllowed.java +++ b/uitest/src/main/java/com/vaadin/tests/components/combobox/ComboBoxSelectingWithNewItemsAllowed.java @@ -31,7 +31,7 @@ public class ComboBoxSelectingWithNewItemsAllowed extends ComboBoxSelecting { comboBox.setNewItemHandler(text -> { items.add(text); comboBox.setItems(items); - comboBox.select(text); + comboBox.setValue(text); label.setValue(String.valueOf(items.size())); }); diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java b/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java index 96a2f313b9..59368b198b 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java +++ b/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java @@ -30,6 +30,7 @@ import com.vaadin.ui.Notification; import com.vaadin.ui.Panel; import com.vaadin.ui.StyleGenerator; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.components.grid.SingleSelectionModel; import com.vaadin.ui.renderers.DateRenderer; import com.vaadin.ui.renderers.HtmlRenderer; import com.vaadin.ui.renderers.NumberRenderer; @@ -150,7 +151,7 @@ public class GridBasics extends AbstractTestUIWithLog { .put("\"Watching\"", dataObj -> new Label("You are watching item id " + dataObj.getRowNumber() + " (" - + (watchingCount++) + ")")); + + watchingCount++ + ")")); persistingDetails = new PersistingDetailsGenerator(); generators.put("Persisting", persistingDetails); } @@ -185,7 +186,8 @@ public class GridBasics extends AbstractTestUIWithLog { grid.addColumn(data -> data.getSmallRandom() / 5d, new ProgressBarRenderer()).setCaption(COLUMN_CAPTIONS[7]); - grid.addSelectionListener(e -> log("Selected: " + e.getValue())); + ((SingleSelectionModel<DataObject>) grid.getSelectionModel()) + .addSelectionChangeListener(e -> log("Selected: " + e.getValue())); layout.addComponent(createMenu()); layout.addComponent(grid); diff --git a/uitest/src/main/java/com/vaadin/tests/components/nativeselect/NativeSelectInit.java b/uitest/src/main/java/com/vaadin/tests/components/nativeselect/NativeSelectInit.java index 28c2e2035d..af5ecbf3a0 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/nativeselect/NativeSelectInit.java +++ b/uitest/src/main/java/com/vaadin/tests/components/nativeselect/NativeSelectInit.java @@ -29,7 +29,7 @@ public class NativeSelectInit extends AbstractReindeerTestUI { protected void setup(VaadinRequest request) { NativeSelect<String> select = new NativeSelect<>(); select.setItems("Foo", "Bar"); - select.select("Bar"); + select.setValue("Bar"); addComponent(select); } diff --git a/uitest/src/main/java/com/vaadin/tests/components/radiobutton/RadioButtonGroupTestUI.java b/uitest/src/main/java/com/vaadin/tests/components/radiobutton/RadioButtonGroupTestUI.java index 7230eb9017..246441598b 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/radiobutton/RadioButtonGroupTestUI.java +++ b/uitest/src/main/java/com/vaadin/tests/components/radiobutton/RadioButtonGroupTestUI.java @@ -51,7 +51,7 @@ public class RadioButtonGroupTestUI protected void createSelectionMenu() { createClickAction("Clear selection", selectionCategory, (component, item, data) -> component.getSelectedItem() - .ifPresent(component::deselect), + .ifPresent(value -> component.setValue(null)), ""); Command<RadioButtonGroup<Object>, String> toggleSelection = (component, @@ -94,15 +94,15 @@ public class RadioButtonGroupTestUI private void toggleSelection(String item) { if (getComponent().isSelected(item)) { - getComponent().deselect(item); + getComponent().setValue(null); } else { - getComponent().select(item); + getComponent().setValue(item); } } protected void createListenerMenu() { createListenerAction("Selection listener", "Listeners", - c -> c.addSelectionListener( + c -> c.addSelectionChangeListener( e -> log("Selected: " + e.getSelectedItem()))); } diff --git a/uitest/src/main/java/com/vaadin/tests/data/DummyData.java b/uitest/src/main/java/com/vaadin/tests/data/DummyData.java index 7157a205f4..cc1e60e7b0 100644 --- a/uitest/src/main/java/com/vaadin/tests/data/DummyData.java +++ b/uitest/src/main/java/com/vaadin/tests/data/DummyData.java @@ -33,7 +33,7 @@ public class DummyData extends AbstractTestUIWithLog { @Override public Stream<String> fetch(Query query) { - log("Backend request #" + (count++)); + log("Backend request #" + count++); return super.fetch(query); } } @@ -61,7 +61,7 @@ public class DummyData extends AbstractTestUIWithLog { } @Override - public void select(String item) { + public void setValue(String item) { if (selected != null) { getDataCommunicator().refresh(selected); } @@ -71,12 +71,6 @@ public class DummyData extends AbstractTestUIWithLog { } } - @Override - public void deselect(String item) { - if (item == selected) { - select(null); - } - } } @Override @@ -87,12 +81,12 @@ public class DummyData extends AbstractTestUIWithLog { items.add("Foo " + i); } dummy.setDataSource(new LoggingDataSource(items)); - dummy.select("Foo 200"); + dummy.setValue("Foo 200"); HorizontalLayout controls = new HorizontalLayout(); addComponent(controls); controls.addComponent(new Button("Select Foo 20", e -> { - dummy.select("Foo " + 20); + dummy.setValue("Foo " + 20); })); controls.addComponent(new Button("Reset data source", e -> { dummy.setDataSource(new LoggingDataSource(items)); |