From 5a24c4865519be892b33a412e26326bc58f9cf0a Mon Sep 17 00:00:00 2001 From: Teemu Suo-Anttila Date: Tue, 4 Sep 2018 15:45:08 +0300 Subject: [PATCH] Add API to access features of SelectionModels (#10939) --- server/src/main/java/com/vaadin/ui/Grid.java | 21 +- .../ui/components/grid/GridMultiSelect.java | 282 ++++++++++++++++++ .../ui/components/grid/GridSingleSelect.java | 260 ++++++++++++++++ .../grid/GridSelectionModeTest.java | 62 ++++ 4 files changed, 610 insertions(+), 15 deletions(-) create mode 100644 server/src/main/java/com/vaadin/ui/components/grid/GridMultiSelect.java create mode 100644 server/src/main/java/com/vaadin/ui/components/grid/GridSingleSelect.java diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java index 40618074d8..6e7d7b1cd4 100644 --- a/server/src/main/java/com/vaadin/ui/Grid.java +++ b/server/src/main/java/com/vaadin/ui/Grid.java @@ -103,7 +103,9 @@ import com.vaadin.ui.components.grid.Editor; import com.vaadin.ui.components.grid.EditorImpl; import com.vaadin.ui.components.grid.Footer; import com.vaadin.ui.components.grid.FooterRow; +import com.vaadin.ui.components.grid.GridMultiSelect; import com.vaadin.ui.components.grid.GridSelectionModel; +import com.vaadin.ui.components.grid.GridSingleSelect; import com.vaadin.ui.components.grid.Header; import com.vaadin.ui.components.grid.Header.Row; import com.vaadin.ui.components.grid.HeaderCell; @@ -3904,14 +3906,8 @@ public class Grid extends AbstractListing implements HasComponents, * @throws IllegalStateException * if not using a single selection model */ - public SingleSelect asSingleSelect() { - GridSelectionModel model = getSelectionModel(); - if (!(model instanceof SingleSelectionModel)) { - throw new IllegalStateException( - "Grid is not in single select mode, it needs to be explicitly set to such with setSelectionModel(SingleSelectionModel) before being able to use single selection features."); - } - - return ((SingleSelectionModel) model).asSingleSelect(); + public GridSingleSelect asSingleSelect() { + return new GridSingleSelect<>(this); } public Editor getEditor() { @@ -3928,13 +3924,8 @@ public class Grid extends AbstractListing implements HasComponents, * @throws IllegalStateException * if not using a multiselection model */ - public MultiSelect asMultiSelect() { - GridSelectionModel model = getSelectionModel(); - if (!(model instanceof MultiSelectionModel)) { - throw new IllegalStateException( - "Grid is not in multiselect mode, it needs to be explicitly set to such with setSelectionModel(MultiSelectionModel) before being able to use multiselection features."); - } - return ((MultiSelectionModel) model).asMultiSelect(); + public GridMultiSelect asMultiSelect() { + return new GridMultiSelect<>(this); } /** diff --git a/server/src/main/java/com/vaadin/ui/components/grid/GridMultiSelect.java b/server/src/main/java/com/vaadin/ui/components/grid/GridMultiSelect.java new file mode 100644 index 0000000000..bf3c9d02d3 --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/components/grid/GridMultiSelect.java @@ -0,0 +1,282 @@ +/* + * Copyright 2000-2018 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.util.Optional; +import java.util.Set; + +import com.vaadin.event.selection.MultiSelectionListener; +import com.vaadin.event.selection.SelectionListener; +import com.vaadin.shared.Registration; +import com.vaadin.ui.Grid; +import com.vaadin.ui.MultiSelect; +import com.vaadin.ui.components.grid.MultiSelectionModel.SelectAllCheckBoxVisibility; + +/** + * Wrapper class to wrap Grid into a MultiSelect. This class also provides + * useful access to API of MultiSelectionModel. + * + * @param + * the bean type of grid + * @since + */ +public class GridMultiSelect implements MultiSelect { + + private MultiSelectionModel model; + + /** + * Constructs a MultiSelect wrapper for given Grid. + * + * @param grid + * the grid to wrap + */ + public GridMultiSelect(Grid grid) { + GridSelectionModel selectionModel = grid.getSelectionModel(); + if (!(selectionModel instanceof MultiSelectionModel)) { + throw new IllegalStateException( + "Grid is not in multiselect mode, it needs to be explicitly set to such with setSelectionModel(MultiSelectionModel) before being able to use multiselection features."); + } + model = (MultiSelectionModel) selectionModel; + } + + /* API for MultiSelectionModelImpl */ + + /** + * Get first selected data item. + * + * @return the first selected item. + */ + public Optional getFirstSelectedItem() { + return model.getFirstSelectedItem(); + } + + /** + * Selects all available the items. + */ + public void selectAll() { + model.selectAll(); + } + + /** + * Selects the given item. Depending on the implementation, may cause other + * items to be deselected. If the item is already selected, does nothing. + * + * @param item + * the item to select, not null + */ + public void deselect(T item) { + model.deselect(item); + } + + /** + * Selects the given item. If another item was already selected, that item + * is deselected. + * + * @param item + * the item to select + */ + public void select(T item) { + model.select(item); + } + + /** + * Deselects all currently selected items, if any. + */ + public void deselectAll() { + model.deselectAll(); + } + + /** + * Adds the given items to the set of currently selected items. + *

+ * By default this does not clear any previous selection. To do that, use + * {@link #deselectAll()}. + *

+ * If the all the items were already selected, this is a NO-OP. + *

+ * This is a short-hand for {@link #updateSelection(Set, Set)} with nothing + * to deselect. + * + * @param items + * to add to selection, not {@code null} + */ + public void selectItems(T... items) { + model.selectItems(items); + } + + /** + * Removes the given items from the set of currently selected items. + *

+ * If the none of the items were selected, this is a NO-OP. + *

+ * This is a short-hand for {@link #updateSelection(Set, Set)} with nothing + * to select. + * + * @param items + * to remove from selection, not {@code null} + */ + public void deselectItems(T... items) { + model.deselectItems(items); + } + + /** + * Sets the select all checkbox visibility mode. + *

+ * The default value is {@link SelectAllCheckBoxVisibility#DEFAULT}, which + * means that the checkbox is only visible if the grid's data provider is + * in- memory. + * + * @param visibility + * the visiblity mode to use + * @see SelectAllCheckBoxVisibility + */ + public void setSelectAllCheckBoxVisibility( + SelectAllCheckBoxVisibility visibility) { + model.setSelectAllCheckBoxVisibility(visibility); + } + + /** + * Gets the current mode for the select all checkbox visibility. + * + * @return the select all checkbox visibility mode + * @see SelectAllCheckBoxVisibility + * @see #isSelectAllCheckBoxVisible() + */ + public SelectAllCheckBoxVisibility getSelectAllCheckBoxVisibility() { + return model.getSelectAllCheckBoxVisibility(); + } + + /** + * Returns whether the select all checkbox will be visible with the current + * setting of + * {@link #setSelectAllCheckBoxVisibility(SelectAllCheckBoxVisibility)}. + * + * @return {@code true} if the checkbox will be visible with the current + * settings + * @see SelectAllCheckBoxVisibility + * @see #setSelectAllCheckBoxVisibility(SelectAllCheckBoxVisibility) + */ + public boolean isSelectAllCheckBoxVisible() { + return model.isSelectAllCheckBoxVisible(); + } + + /** + * Sets whether the user is allowed to change the selection. + *

+ * The check is done only for the client side actions. It doesn't affect + * selection requests sent from the server side. + * + * @param allowed + * true if the user is allowed to change the + * selection, false otherwise + */ + public void setUserSelectionAllowed(boolean allowed) { + model.setUserSelectionAllowed(allowed); + } + + /** + * Checks if the user is allowed to change the selection. + *

+ * The check is done only for the client side actions. It doesn't affect + * selection requests sent from the server side. + * + * @return true if the user is allowed to change the selection, + * false otherwise + */ + public boolean isUserSelectionAllowed() { + return model.isUserSelectionAllowed(); + } + + /** + * Adds a generic listener to this selection model, accepting both single + * and multiselection events. + * + * @param listener + * the listener to add + * @return a registration handle for removing the listener + */ + public Registration addSelectionListener(SelectionListener listener) { + return model.addSelectionListener(listener); + } + + /** + * 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} + * @return a registration for the listener + */ + public Registration addMultiSelectionListener( + MultiSelectionListener listener) { + return model.addMultiSelectionListener(listener); + } + + /* MultiSelect implementation */ + + @Override + public void setValue(Set value) { + model.asMultiSelect().setValue(value); + } + + @Override + public Set getValue() { + return model.asMultiSelect().getValue(); + } + + @Override + public Registration addValueChangeListener( + ValueChangeListener> listener) { + return model.asMultiSelect().addValueChangeListener(listener); + } + + @Override + public void setRequiredIndicatorVisible(boolean requiredIndicatorVisible) { + model.asMultiSelect() + .setRequiredIndicatorVisible(requiredIndicatorVisible); + } + + @Override + public boolean isRequiredIndicatorVisible() { + return model.asMultiSelect().isRequiredIndicatorVisible(); + } + + @Override + public void setReadOnly(boolean readOnly) { + model.asMultiSelect().setReadOnly(readOnly); + } + + @Override + public boolean isReadOnly() { + return model.asMultiSelect().isReadOnly(); + } + + @Override + public void updateSelection(Set addedItems, Set removedItems) { + model.asMultiSelect().updateSelection(addedItems, removedItems); + } + + @Override + public Set getSelectedItems() { + return model.asMultiSelect().getSelectedItems(); + } + + @Override + public Registration addSelectionListener( + MultiSelectionListener listener) { + return model.asMultiSelect().addSelectionListener(listener); + } +} diff --git a/server/src/main/java/com/vaadin/ui/components/grid/GridSingleSelect.java b/server/src/main/java/com/vaadin/ui/components/grid/GridSingleSelect.java new file mode 100644 index 0000000000..41804ad617 --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/components/grid/GridSingleSelect.java @@ -0,0 +1,260 @@ +/* + * Copyright 2000-2018 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.util.Optional; +import java.util.Set; + +import com.vaadin.event.selection.SelectionListener; +import com.vaadin.event.selection.SingleSelectionListener; +import com.vaadin.shared.Registration; +import com.vaadin.ui.Grid; +import com.vaadin.ui.SingleSelect; + +/** + * Wrapper class to wrap Grid into a SingleSelect. This class also provides + * useful access to API of SingleSelectionModel. + * + * @param + * the bean type of grid + * @since + */ +public class GridSingleSelect implements SingleSelect { + + private SingleSelectionModel model; + + /** + * Constructs a SingleSelect wrapper for given Grid. + * + * @param grid + * the grid to wrap + */ + public GridSingleSelect(Grid grid) { + GridSelectionModel selectionModel = grid.getSelectionModel(); + if (!(selectionModel instanceof SingleSelectionModel)) { + throw new IllegalStateException( + "Grid is not in singleiselect mode, it needs to be explicitly set to such with setSelectionModel(SingleSelectionModel) before being able to use singleselection features."); + } + model = (SingleSelectionModel) selectionModel; + } + + /* API for SingleSelectionModel */ + + /** + * Sets whether it's allowed to deselect the selected row through the UI. + * Deselection is allowed by default. + * + * @param deselectAllowed + * true if the selected row can be deselected + * without selecting another row instead; otherwise + * false. + */ + public void setDeselectAllowed(boolean deselectAllowed) { + model.setDeselectAllowed(deselectAllowed); + } + + /** + * Gets whether it's allowed to deselect the selected row through the UI. + * + * @return true if deselection is allowed; otherwise + * false + */ + public boolean isDeselectAllowed() { + return model.isDeselectAllowed(); + } + + /** + * Adds a single selection listener that is called when the value of this + * select is changed either by the user or programmatically. + * + * @param listener + * the value change listener, not {@code null} + * @return a registration for the listener + */ + public Registration addSingleSelectionListener( + SingleSelectionListener listener) { + return model.addSingleSelectionListener(listener); + } + + /** + * Returns the currently selected item, or an empty optional if no item is + * selected. + * + * @return an optional of the selected item if any, an empty optional + * otherwise + */ + public Optional getSelectedItem() { + return model.getSelectedItem(); + } + + /** + * Sets the current selection to the given item, or clears selection if + * given {@code null}. + * + * @param item + * the item to select or {@code null} to clear selection + */ + public void setSelectedItem(T item) { + model.setSelectedItem(item); + } + + /* Generic SelectionModel API */ + + /** + * Sets whether the user is allowed to change the selection. + *

+ * The check is done only for the client side actions. It doesn't affect + * selection requests sent from the server side. + * + * @param allowed + * true if the user is allowed to change the + * selection, false otherwise + */ + public void setUserSelectionAllowed(boolean allowed) { + model.setUserSelectionAllowed(allowed); + } + + /** + * Checks if the user is allowed to change the selection. + *

+ * The check is done only for the client side actions. It doesn't affect + * selection requests sent from the server side. + * + * @return true if the user is allowed to change the selection, + * false otherwise + */ + public boolean isUserSelectionAllowed() { + return model.isUserSelectionAllowed(); + } + + /** + * Returns a singleton set of the currently selected item or an empty set if + * no item is selected. + * + * @return a singleton set of the selected item if any, an empty set + * otherwise + * + * @see #getSelectedItem() + */ + public Set getSelectedItems() { + return model.getSelectedItems(); + } + + /** + * Get first selected data item. + * + * @return the first selected item. + */ + public Optional getFirstSelectedItem() { + return model.getFirstSelectedItem(); + } + + /** + * Selects the given item. Depending on the implementation, may cause other + * items to be deselected. If the item is already selected, does nothing. + * + * @param item + * the item to select, not null + */ + public void deselect(T item) { + model.deselect(item); + } + + /** + * Deselects all currently selected items, if any. + */ + public void deselectAll() { + model.deselectAll(); + } + + /** + * Adds a generic listener to this selection model, accepting both single + * and multiselection events. + *

+ * Use {@link #addSingleSelectionListener(SingleSelectionListener)} for more + * specific single selection event. + * + * @see #addSingleSelectionListener(SingleSelectionListener) + * + * @param listener + * the listener to add + * @return a registration handle for removing the listener + */ + public Registration addSelectionListener(SelectionListener listener) { + return model.addSelectionListener(listener); + } + + /** + * Returns whether the given item is currently selected. + * + * @param item + * the item to check, not null + * @return {@code true} if the item is selected, {@code false} otherwise + */ + public boolean isSelected(T item) { + return model.isSelected(item); + } + + /** + * Selects the given item. If another item was already selected, that item + * is deselected. + * + * @param item + * the item to select + */ + public void select(T item) { + model.select(item); + } + + /* SingleSelect implementation */ + + @Override + public void setValue(T value) { + model.asSingleSelect().setValue(value); + } + + @Override + public T getValue() { + return model.asSingleSelect().getValue(); + } + + @Override + public Registration addValueChangeListener( + ValueChangeListener listener) { + return model.asSingleSelect().addValueChangeListener(listener); + } + + @Override + public void setRequiredIndicatorVisible(boolean requiredIndicatorVisible) { + model.asSingleSelect() + .setRequiredIndicatorVisible(requiredIndicatorVisible); + } + + @Override + public boolean isRequiredIndicatorVisible() { + return model.asSingleSelect().isRequiredIndicatorVisible(); + } + + @Override + public void setReadOnly(boolean readOnly) { + model.asSingleSelect().setReadOnly(readOnly); + } + + @Override + public boolean isReadOnly() { + return model.asSingleSelect().isReadOnly(); + } +} diff --git a/server/src/test/java/com/vaadin/tests/components/grid/GridSelectionModeTest.java b/server/src/test/java/com/vaadin/tests/components/grid/GridSelectionModeTest.java index c8ca4cf791..15413eb2d7 100644 --- a/server/src/test/java/com/vaadin/tests/components/grid/GridSelectionModeTest.java +++ b/server/src/test/java/com/vaadin/tests/components/grid/GridSelectionModeTest.java @@ -2,13 +2,26 @@ package com.vaadin.tests.components.grid; import static org.junit.Assert.assertEquals; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import com.vaadin.data.SelectionModel; +import com.vaadin.server.Extension; import com.vaadin.ui.Grid; import com.vaadin.ui.Grid.SelectionMode; +import com.vaadin.ui.components.grid.GridMultiSelect; +import com.vaadin.ui.components.grid.GridSingleSelect; +import com.vaadin.ui.components.grid.MultiSelectionModel; import com.vaadin.ui.components.grid.MultiSelectionModelImpl; import com.vaadin.ui.components.grid.NoSelectionModel; +import com.vaadin.ui.components.grid.SingleSelectionModel; import com.vaadin.ui.components.grid.SingleSelectionModelImpl; public class GridSelectionModeTest { @@ -47,4 +60,53 @@ public class GridSelectionModeTest { grid.setSelectionMode(null); } + @Test + public void testGridAsMultiSelectHasAllAPI() { + assertAllAPIAvailable(GridMultiSelect.class, MultiSelectionModel.class, + "asMultiSelect"); + } + + @Test + public void testGridAsSingleSelectHasAllAPI() { + assertAllAPIAvailable(GridSingleSelect.class, + SingleSelectionModel.class, "asSingleSelect"); + } + + @SuppressWarnings("rawtypes") + protected void assertAllAPIAvailable(Class testedClass, + Class selectionModelClass, + String... ignoredMethods) { + List ignored = Arrays.asList(ignoredMethods); + List missing = new ArrayList<>(); + Arrays.stream(selectionModelClass.getMethods()).filter(method -> { + if (ignored.contains(method.getName())) { + // Explicitly ignored method. + return false; + } + + try { + // Skip methods from Extension interface + Extension.class.getMethod(method.getName(), + method.getParameterTypes()); + return false; + } catch (Exception e) { + return true; + } + }).forEach(method -> + + { + try { + testedClass.getMethod(method.getName(), + method.getParameterTypes()); + } catch (Exception e) { + missing.add(method); + } + }); + if (!missing.isEmpty()) { + Assert.fail("Methods " + + missing.stream().map(Method::getName) + .collect(Collectors.joining(", ")) + + " not found in " + testedClass.getSimpleName()); + } + } } -- 2.39.5