From: Teemu Suo-Anttila Date: Wed, 23 Aug 2017 11:02:20 +0000 (+0300) Subject: Add item description to RadioButtonGroup and CheckBoxGroup (#9841) X-Git-Tag: 8.2.0.alpha1~39 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=f09d8c74dfb44854b4982a0b54de8564ff324d00;p=vaadin-framework.git Add item description to RadioButtonGroup and CheckBoxGroup (#9841) --- diff --git a/client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java b/client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java index ee1d83ee2c..8d38331af1 100644 --- a/client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java +++ b/client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.function.BiConsumer; import com.google.gwt.aria.client.Roles; +import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.ui.FocusWidget; @@ -94,6 +95,21 @@ public class VCheckBoxGroup extends FocusableFlowPanelComposite } } + /** + * Returns the JsonObject used to populate the CheckBox widget that contains + * given Element. + * + * @since + * @param element + * the element to search for + * @return the related JsonObject; {@code null} if not found + */ + public JsonObject getItem(Element element) { + return optionsToItems.entrySet().stream() + .filter(e -> e.getKey().getElement().isOrHasChild(element)) + .map(e -> e.getValue()).findFirst().orElse(null); + } + private void remove(Widget widget) { getWidget().remove(widget); optionsToItems.remove(widget); diff --git a/client/src/main/java/com/vaadin/client/ui/VRadioButtonGroup.java b/client/src/main/java/com/vaadin/client/ui/VRadioButtonGroup.java index 1386005351..1561cb9b8a 100644 --- a/client/src/main/java/com/vaadin/client/ui/VRadioButtonGroup.java +++ b/client/src/main/java/com/vaadin/client/ui/VRadioButtonGroup.java @@ -20,9 +20,11 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import com.google.gwt.aria.client.Roles; +import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.DOM; @@ -101,6 +103,28 @@ public class VRadioButtonGroup extends FocusableFlowPanelComposite } } + /** + * Returns the JsonObject used to populate the RadioButton widget that + * contains given Element. + * + * @since + * @param element + * the element to search for + * @return the related JsonObject; {@code null} if not found + */ + public JsonObject getItem(Element element) { + // The HTML populated in updateItem does not match RadioButton directly, + // which is why tryGetItem is also attempted on the parent element + return tryGetItem(element) + .orElse(tryGetItem(element.getParentElement()).orElse(null)); + } + + private Optional tryGetItem(Element element) { + return optionsToItems.entrySet().stream() + .filter(e -> e.getKey().getElement().equals(element)) + .map(e -> e.getValue()).findFirst(); + } + private void remove(Widget widget) { getWidget().remove(widget); JsonObject item = optionsToItems.remove(widget); diff --git a/client/src/main/java/com/vaadin/client/ui/optiongroup/CheckBoxGroupConnector.java b/client/src/main/java/com/vaadin/client/ui/optiongroup/CheckBoxGroupConnector.java index 1936aac79d..18d4019611 100644 --- a/client/src/main/java/com/vaadin/client/ui/optiongroup/CheckBoxGroupConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/optiongroup/CheckBoxGroupConnector.java @@ -21,6 +21,8 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import com.google.gwt.dom.client.Element; +import com.vaadin.client.TooltipInfo; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.connectors.AbstractFocusableListingConnector; import com.vaadin.client.data.DataSource; @@ -28,6 +30,7 @@ import com.vaadin.client.ui.HasRequiredIndicator; import com.vaadin.client.ui.VCheckBoxGroup; import com.vaadin.shared.data.selection.MultiSelectServerRpc; import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.ListingJsonConstants; import com.vaadin.shared.ui.optiongroup.CheckBoxGroupState; import com.vaadin.ui.CheckBoxGroup; @@ -98,4 +101,20 @@ public class CheckBoxGroupConnector public boolean isRequiredIndicatorVisible() { return getState().required && !isReadOnly(); } + + @Override + public TooltipInfo getTooltipInfo(Element element) { + JsonObject item = getWidget().getItem(element); + if (item != null + && item.hasKey(ListingJsonConstants.JSONKEY_ITEM_DESCRIPTION)) { + return new TooltipInfo(item + .getString(ListingJsonConstants.JSONKEY_ITEM_DESCRIPTION)); + } + return super.getTooltipInfo(element); + } + + @Override + public boolean hasTooltip() { + return true; + } } diff --git a/client/src/main/java/com/vaadin/client/ui/optiongroup/RadioButtonGroupConnector.java b/client/src/main/java/com/vaadin/client/ui/optiongroup/RadioButtonGroupConnector.java index de92728712..882a91aa26 100644 --- a/client/src/main/java/com/vaadin/client/ui/optiongroup/RadioButtonGroupConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/optiongroup/RadioButtonGroupConnector.java @@ -19,6 +19,8 @@ package com.vaadin.client.ui.optiongroup; import java.util.ArrayList; import java.util.List; +import com.google.gwt.dom.client.Element; +import com.vaadin.client.TooltipInfo; import com.vaadin.client.annotations.OnStateChange; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.connectors.AbstractSingleSelectConnector; @@ -28,6 +30,7 @@ import com.vaadin.shared.Range; import com.vaadin.shared.Registration; import com.vaadin.shared.data.selection.SelectionServerRpc; import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.ListingJsonConstants; import com.vaadin.shared.ui.optiongroup.RadioButtonGroupState; import com.vaadin.ui.RadioButtonGroup; @@ -120,4 +123,20 @@ public class RadioButtonGroupConnector select.buildOptions(options); updateSelectedItem(); } + + @Override + public TooltipInfo getTooltipInfo(Element element) { + JsonObject item = getWidget().getItem(element); + if (item != null + && item.hasKey(ListingJsonConstants.JSONKEY_ITEM_DESCRIPTION)) { + return new TooltipInfo(item + .getString(ListingJsonConstants.JSONKEY_ITEM_DESCRIPTION)); + } + return super.getTooltipInfo(element); + } + + @Override + public boolean hasTooltip() { + return true; + } } diff --git a/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java b/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java index a0c8fd2225..93b0b35403 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java +++ b/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java @@ -89,7 +89,7 @@ public abstract class AbstractMultiSelect extends AbstractListing } - private class MultiSelectDataGenerator implements DataGenerator { + private final class MultiSelectDataGenerator implements DataGenerator { @Override public void generateData(T data, JsonObject jsonObject) { String caption = getItemCaptionGenerator().apply(data); diff --git a/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java b/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java index b6b6d69074..c257a1a0d0 100644 --- a/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java +++ b/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java @@ -17,6 +17,7 @@ package com.vaadin.ui; import java.util.Collection; +import java.util.Objects; import java.util.Set; import org.jsoup.nodes.Element; @@ -32,7 +33,9 @@ import com.vaadin.event.FieldEvents.FocusListener; import com.vaadin.event.FieldEvents.FocusNotifier; import com.vaadin.server.SerializablePredicate; import com.vaadin.shared.Registration; +import com.vaadin.shared.ui.ListingJsonConstants; import com.vaadin.shared.ui.optiongroup.CheckBoxGroupState; +import com.vaadin.ui.components.grid.DescriptionGenerator; import com.vaadin.ui.declarative.DesignContext; import com.vaadin.ui.declarative.DesignFormatter; @@ -48,6 +51,8 @@ import com.vaadin.ui.declarative.DesignFormatter; public class CheckBoxGroup extends AbstractMultiSelect implements FocusNotifier, BlurNotifier, HasDataProvider { + private DescriptionGenerator descriptionGenerator = item -> null; + /** * Constructs a new CheckBoxGroup with caption. * @@ -92,6 +97,13 @@ public class CheckBoxGroup extends AbstractMultiSelect */ public CheckBoxGroup() { registerRpc(new FocusAndBlurServerRpcDecorator(this, this::fireEvent)); + addDataGenerator((item, jsonObject) -> { + String description = getItemDescriptionGenerator().apply(item); + if (description != null) { + jsonObject.put(ListingJsonConstants.JSONKEY_ITEM_DESCRIPTION, + description); + } + }); } /** @@ -162,6 +174,38 @@ public class CheckBoxGroup extends AbstractMultiSelect BlurListener.blurMethod); } + /** + * Sets the description generator that is used for generating descriptions + * for items. Description is shown as a tooltip when hovering on + * corresponding element. If the generator returns {@code null}, no tooltip + * is shown. + * + * + * @param descriptionGenerator + * the item description generator to set, not {@code null} + * + * @since + */ + public void setItemDescriptionGenerator( + DescriptionGenerator descriptionGenerator) { + Objects.requireNonNull(descriptionGenerator); + if (this.descriptionGenerator != descriptionGenerator) { + this.descriptionGenerator = descriptionGenerator; + getDataProvider().refreshAll(); + } + } + + /** + * Gets the item description generator. + * + * @return the item description generator + * + * @since + */ + public DescriptionGenerator getItemDescriptionGenerator() { + return descriptionGenerator; + } + @Override protected void readItems(Element design, DesignContext context) { setItemEnabledProvider(new DeclarativeItemEnabledProvider<>()); diff --git a/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java b/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java index 35490c49d1..c6e4a66c65 100644 --- a/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java +++ b/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java @@ -38,6 +38,7 @@ import com.vaadin.server.SerializablePredicate; import com.vaadin.shared.Registration; import com.vaadin.shared.ui.ListingJsonConstants; import com.vaadin.shared.ui.optiongroup.RadioButtonGroupState; +import com.vaadin.ui.components.grid.DescriptionGenerator; import com.vaadin.ui.declarative.DesignContext; import com.vaadin.ui.declarative.DesignFormatter; @@ -56,6 +57,7 @@ public class RadioButtonGroup extends AbstractSingleSelect implements FocusNotifier, BlurNotifier, HasDataProvider { private SerializablePredicate itemEnabledProvider = item -> true; + private DescriptionGenerator descriptionGenerator = item -> null; /** * Constructs a new RadioButtonGroup with caption. @@ -112,6 +114,12 @@ public class RadioButtonGroup extends AbstractSingleSelect } else { jsonObject.put(ListingJsonConstants.JSONKEY_ITEM_VALUE, ""); } + String description = getItemDescriptionGenerator().apply(data); + if (description != null) { + jsonObject.put( + ListingJsonConstants.JSONKEY_ITEM_DESCRIPTION, + description); + } Resource icon = getItemIconGenerator().apply(data); if (icon != null) { String iconUrl = ResourceReference @@ -219,6 +227,37 @@ public class RadioButtonGroup extends AbstractSingleSelect this.itemEnabledProvider = itemEnabledProvider; } + /** + * Sets the description generator that is used for generating descriptions + * for items. Description is shown as a tooltip when hovering on + * corresponding element. If the generator returns {@code null}, no tooltip + * is shown. + * + * @param descriptionGenerator + * the item description generator to set, not {@code null} + * + * @since + */ + public void setItemDescriptionGenerator( + DescriptionGenerator descriptionGenerator) { + Objects.requireNonNull(descriptionGenerator); + if (this.descriptionGenerator != descriptionGenerator) { + this.descriptionGenerator = descriptionGenerator; + getDataProvider().refreshAll(); + } + } + + /** + * Gets the item description generator. + * + * @return the item description generator + * + * @since + */ + public DescriptionGenerator getItemDescriptionGenerator() { + return descriptionGenerator; + } + @Override public Registration addFocusListener(FocusListener listener) { return addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener, diff --git a/shared/src/main/java/com/vaadin/shared/ui/ListingJsonConstants.java b/shared/src/main/java/com/vaadin/shared/ui/ListingJsonConstants.java index 2ffd08ad7d..acdbcd097f 100644 --- a/shared/src/main/java/com/vaadin/shared/ui/ListingJsonConstants.java +++ b/shared/src/main/java/com/vaadin/shared/ui/ListingJsonConstants.java @@ -32,4 +32,9 @@ public class ListingJsonConstants implements Serializable { public static final String JSONKEY_ITEM_VALUE = "v"; public static final String JSONKEY_ITEM_SELECTED = "s"; + + /** + * @since + */ + public static final String JSONKEY_ITEM_DESCRIPTION = "dsc"; } diff --git a/uitest/src/main/java/com/vaadin/tests/components/checkbox/CheckBoxGroupTestUI.java b/uitest/src/main/java/com/vaadin/tests/components/checkbox/CheckBoxGroupTestUI.java index 88006fd68a..71c3a6147f 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/checkbox/CheckBoxGroupTestUI.java +++ b/uitest/src/main/java/com/vaadin/tests/components/checkbox/CheckBoxGroupTestUI.java @@ -15,10 +15,13 @@ */ package com.vaadin.tests.components.checkbox; +import java.util.LinkedHashMap; + import com.vaadin.icons.VaadinIcons; import com.vaadin.tests.components.abstractlisting.AbstractMultiSelectTestUI; import com.vaadin.ui.CheckBoxGroup; import com.vaadin.ui.IconGenerator; +import com.vaadin.ui.components.grid.DescriptionGenerator; /** * Test UI for CheckBoxGroup component @@ -49,6 +52,7 @@ public class CheckBoxGroupTestUI protected void createActions() { super.createActions(); createItemIconGenerator(); + createItemDescriptionGeneratorMenu(); } private void createItemIconGenerator() { @@ -56,6 +60,21 @@ public class CheckBoxGroupTestUI this::useItemIconProvider); } + private void createItemDescriptionGeneratorMenu() { + LinkedHashMap> options = new LinkedHashMap<>(); + options.put("Null Description Generator", item -> null); + options.put("Default Description Generator", item -> item.toString()); + options.put("Custom Description Generator", + item -> item.toString() + " Description"); + + createSelectAction("Item Description Generator", + "Item Description Generator", options, "None", + (checkBoxGroup, generator, data) -> { + checkBoxGroup.setItemDescriptionGenerator(generator); + checkBoxGroup.getDataProvider().refreshAll(); + }, true); + } + private void useItemIconProvider(CheckBoxGroup group, boolean activate, Object data) { if (activate) { 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 20314fd347..d9161a6a46 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 @@ -22,6 +22,7 @@ import com.vaadin.icons.VaadinIcons; import com.vaadin.tests.components.abstractlisting.AbstractListingTestUI; import com.vaadin.ui.ItemCaptionGenerator; import com.vaadin.ui.RadioButtonGroup; +import com.vaadin.ui.components.grid.DescriptionGenerator; /** * Test UI for RadioButtonGroup component @@ -46,6 +47,7 @@ public class RadioButtonGroupTestUI createSelectionMenu(); createItemIconGeneratorMenu(); createItemCaptionGeneratorMenu(); + createItemDescriptionGeneratorMenu(); } protected void createSelectionMenu() { @@ -92,6 +94,21 @@ public class RadioButtonGroupTestUI }, true); } + private void createItemDescriptionGeneratorMenu() { + LinkedHashMap> options = new LinkedHashMap<>(); + options.put("Null Description Generator", item -> null); + options.put("Default Description Generator", item -> item.toString()); + options.put("Custom Description Generator", + item -> item.toString() + " Description"); + + createSelectAction("Item Description Generator", + "Item Description Generator", options, "None", + (radioButtonGroup, generator, data) -> { + radioButtonGroup.setItemDescriptionGenerator(generator); + radioButtonGroup.getDataProvider().refreshAll(); + }, true); + } + private void toggleSelection(String item) { if (getComponent().isSelected(item)) { getComponent().setValue(null); diff --git a/uitest/src/test/java/com/vaadin/tests/components/checkboxgroup/CheckBoxGroupTest.java b/uitest/src/test/java/com/vaadin/tests/components/checkboxgroup/CheckBoxGroupTest.java index 5d7c37c754..98d9cc03e5 100644 --- a/uitest/src/test/java/com/vaadin/tests/components/checkboxgroup/CheckBoxGroupTest.java +++ b/uitest/src/test/java/com/vaadin/tests/components/checkboxgroup/CheckBoxGroupTest.java @@ -33,6 +33,7 @@ import org.openqa.selenium.By; import org.openqa.selenium.WebElement; import com.vaadin.icons.VaadinIcons; +import com.vaadin.testbench.TestBenchElement; import com.vaadin.testbench.elements.CheckBoxGroupElement; import com.vaadin.tests.components.checkbox.CheckBoxGroupTestUI; import com.vaadin.tests.tb3.MultiBrowserTest; @@ -195,6 +196,28 @@ public class CheckBoxGroupTest extends MultiBrowserTest { assertSelected("Item 1"); } + @Test + public void testItemDescriptionGenerators() { + TestBenchElement label; + + selectMenuPath("Component", "Item Description Generator", + "Item Description Generator", "Default Description Generator"); + + label = (TestBenchElement) findElements(By.tagName("label")).get(5); + label.showTooltip(); + Assert.assertEquals("Tooltip should contain the same text as caption", + label.getText(), getTooltipElement().getText()); + + selectMenuPath("Component", "Item Description Generator", + "Item Description Generator", "Custom Description Generator"); + + label = (TestBenchElement) findElements(By.tagName("label")).get(5); + label.showTooltip(); + Assert.assertEquals("Tooltip should contain caption + ' Description'", + label.getText() + " Description", + getTooltipElement().getText()); + } + private void assertSelected(String... expectedSelection) { Assert.assertEquals(Arrays.asList(expectedSelection), getSelect().getValue()); diff --git a/uitest/src/test/java/com/vaadin/tests/components/radiobuttongroup/RadioButtonGroupTest.java b/uitest/src/test/java/com/vaadin/tests/components/radiobuttongroup/RadioButtonGroupTest.java index 2c53903d29..6c0b60be78 100644 --- a/uitest/src/test/java/com/vaadin/tests/components/radiobuttongroup/RadioButtonGroupTest.java +++ b/uitest/src/test/java/com/vaadin/tests/components/radiobuttongroup/RadioButtonGroupTest.java @@ -27,6 +27,7 @@ import org.openqa.selenium.WebElement; import com.vaadin.icons.VaadinIcons; import com.vaadin.testbench.By; +import com.vaadin.testbench.TestBenchElement; import com.vaadin.testbench.elements.RadioButtonGroupElement; import com.vaadin.tests.components.radiobutton.RadioButtonGroupTestUI; import com.vaadin.tests.tb3.MultiBrowserTest; @@ -189,6 +190,28 @@ public class RadioButtonGroupTest extends MultiBrowserTest { assertSelected("Item 5"); } + @Test + public void testItemDescriptionGenerators() { + TestBenchElement label; + + selectMenuPath("Component", "Item Description Generator", + "Item Description Generator", "Default Description Generator"); + + label = (TestBenchElement) findElements(By.tagName("label")).get(5); + label.showTooltip(); + Assert.assertEquals("Tooltip should contain the same text as caption", + label.getText(), getTooltipElement().getText()); + + selectMenuPath("Component", "Item Description Generator", + "Item Description Generator", "Custom Description Generator"); + + label = (TestBenchElement) findElements(By.tagName("label")).get(5); + label.showTooltip(); + Assert.assertEquals("Tooltip should contain caption + ' Description'", + label.getText() + " Description", + getTooltipElement().getText()); + } + private void assertSelected(String expectedSelection) { Assert.assertEquals(expectedSelection, getSelect().getValue()); }