diff options
author | Denis Anisimov <denis@vaadin.com> | 2016-09-19 16:26:32 +0300 |
---|---|---|
committer | Denis Anisimov <denis@vaadin.com> | 2016-11-24 11:03:23 +0300 |
commit | 159d413602380497b189e5cabbdd9ecf6431c725 (patch) | |
tree | 85de070c7dae0a8792eeafa70e7c17d38ad5fe7c /server/src/main/java | |
parent | fdc80972f55adc3bc2b987d40d3b4e628635f42f (diff) | |
download | vaadin-framework-159d413602380497b189e5cabbdd9ecf6431c725.tar.gz vaadin-framework-159d413602380497b189e5cabbdd9ecf6431c725.zip |
Provide declarative support for listing components.
Fixes vaadin/framework8-issues#388
Change-Id: I4f8045bba51d308f4343a1f6d01b3f1ddca63e69
Diffstat (limited to 'server/src/main/java')
10 files changed, 776 insertions, 159 deletions
diff --git a/server/src/main/java/com/vaadin/ui/AbstractListing.java b/server/src/main/java/com/vaadin/ui/AbstractListing.java index f49d8715de..ce44bb8352 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractListing.java +++ b/server/src/main/java/com/vaadin/ui/AbstractListing.java @@ -15,14 +15,25 @@ */ package com.vaadin.ui; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; +import org.jsoup.nodes.Attributes; +import org.jsoup.nodes.Element; + import com.vaadin.data.Listing; import com.vaadin.data.SelectionModel; import com.vaadin.server.AbstractExtension; +import com.vaadin.server.Resource; import com.vaadin.server.data.DataCommunicator; import com.vaadin.server.data.DataGenerator; import com.vaadin.server.data.DataProvider; +import com.vaadin.server.data.Query; +import com.vaadin.ui.declarative.DesignAttributeHandler; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.DesignException; +import com.vaadin.ui.declarative.DesignFormatter; /** * A base class for listing components. Provides common handling for fetching @@ -37,6 +48,16 @@ import com.vaadin.server.data.DataProvider; */ public abstract class AbstractListing<T> extends AbstractComponent implements Listing<T> { + /** + * The item icon caption provider. + */ + private ItemCaptionGenerator<T> itemCaptionGenerator = String::valueOf; + + /** + * The item icon provider. It is up to the implementing class to support + * this or not. + */ + private IconGenerator<T> itemIconGenerator = item -> null; /** * A helper base class for creating extensions for Listing components. This @@ -143,6 +164,64 @@ public abstract class AbstractListing<T> extends AbstractComponent } /** + * Gets the item caption generator that is used to produce the strings shown + * in the combo box for each item. + * + * @return the item caption generator used, not null + */ + protected ItemCaptionGenerator<T> getItemCaptionGenerator() { + return itemCaptionGenerator; + } + + /** + * Sets the item caption generator that is used to produce the strings shown + * in the combo box for each item. By default, + * {@link String#valueOf(Object)} is used. + * + * @param itemCaptionGenerator + * the item caption provider to use, not null + */ + protected void setItemCaptionGenerator( + ItemCaptionGenerator<T> itemCaptionGenerator) { + Objects.requireNonNull(itemCaptionGenerator, + "Item caption generators must not be null"); + this.itemCaptionGenerator = itemCaptionGenerator; + getDataCommunicator().reset(); + } + + /** + * Sets the item icon generator that is used to produce custom icons for + * showing items in the popup. The generator can return null for items with + * no icon. + * + * @see IconGenerator + * + * @param itemIconGenerator + * the item icon generator to set, not null + * @throws NullPointerException + * if {@code itemIconGenerator} is {@code null} + */ + protected void setItemIconGenerator(IconGenerator<T> itemIconGenerator) { + Objects.requireNonNull(itemIconGenerator, + "Item icon generator must not be null"); + this.itemIconGenerator = itemIconGenerator; + getDataCommunicator().reset(); + } + + /** + * Gets the currently used item icon generator. The default item icon + * provider returns null for all items, resulting in no icons being used. + * + * @see IconGenerator + * @see #setItemIconGenerator(IconGenerator) + * + * @return the currently used item icon generator, not null + */ + protected IconGenerator<T> getItemIconGenerator() { + return itemIconGenerator; + } + + /** * Adds the given data generator to this listing. If the generator was * already added, does nothing. * @@ -172,4 +251,183 @@ public abstract class AbstractListing<T> extends AbstractComponent public DataCommunicator<T> getDataCommunicator() { return dataCommunicator; } + + @Override + public void writeDesign(Element design, DesignContext designContext) { + super.writeDesign(design, designContext); + + // Write options if warranted + if (designContext.shouldWriteData(this)) { + writeItems(design, designContext); + } + + AbstractListing<T> select = designContext.getDefaultInstance(this); + Attributes attr = design.attributes(); + DesignAttributeHandler.writeAttribute("readonly", attr, isReadOnly(), + select.isReadOnly(), Boolean.class, designContext); + } + + /** + * Writes the data source items to a design. Hierarchical select components + * should override this method to only write the root items. + * + * @param design + * the element into which to insert the items + * @param context + * the DesignContext instance used in writing + */ + protected void writeItems(Element design, DesignContext context) { + getDataProvider().fetch(new Query<>()) + .forEach(item -> writeItem(design, item, context)); + } + + /** + * Writes a data source Item to a design. Hierarchical select components + * should override this method to recursively write any child items as well. + * + * @param design + * the element into which to insert the item + * @param item + * the item to write + * @param context + * the DesignContext instance used in writing + * @return a JSOUP element representing the {@code item} + */ + protected Element writeItem(Element design, T item, DesignContext context) { + Element element = design.appendElement("option"); + + String caption = getItemCaptionGenerator().apply(item); + if (caption != null) { + element.html(DesignFormatter.encodeForTextNode(caption)); + } else { + element.html(DesignFormatter.encodeForTextNode(item.toString())); + } + element.attr("item", serializeDeclarativeRepresentation(item)); + + Resource icon = getItemIconGenerator().apply(item); + if (icon != null) { + DesignAttributeHandler.writeAttribute("icon", element.attributes(), + icon, null, Resource.class, context); + } + + return element; + } + + @Override + public void readDesign(Element design, DesignContext context) { + super.readDesign(design, context); + Attributes attr = design.attributes(); + if (attr.hasKey("readonly")) { + setReadOnly(DesignAttributeHandler.readAttribute("readonly", attr, + Boolean.class)); + } + readItems(design, context); + } + + /** + * Reads the data source items from the {@code design}. + * + * @param design + * The element to obtain the state from + * @param context + * The DesignContext instance used for parsing the design + */ + protected void readItems(Element design, DesignContext context) { + setItemCaptionGenerator(new DeclarativeCaptionGenerator<>()); + setItemIconGenerator(new DeclarativeIconGenerator<>()); + } + + /** + * Reads an Item from a design and inserts it into the data source. + * <p> + * Doesn't care about selection/value (if any). + * + * @param child + * a child element representing the item + * @param context + * the DesignContext instance used in parsing + * @return the item id of the new item + * + * @throws DesignException + * if the tag name of the {@code child} element is not + * {@code option}. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected T readItem(Element child, DesignContext context) { + if (!"option".equals(child.tagName())) { + throw new DesignException("Unrecognized child element in " + + getClass().getSimpleName() + ": " + child.tagName()); + } + + String serializedItem = ""; + String caption = DesignFormatter.decodeFromTextNode(child.html()); + List<T> items = new ArrayList<>(); + if (child.hasAttr("item")) { + serializedItem = child.attr("item"); + } + + T item = deserializeDeclarativeRepresentation(serializedItem); + items.add(item); + + ItemCaptionGenerator<T> captionGenerator = getItemCaptionGenerator(); + if (captionGenerator instanceof DeclarativeCaptionGenerator) { + ((DeclarativeCaptionGenerator) captionGenerator).setCaption(item, + caption); + } else { + throw new IllegalStateException(String.format( + "Don't know how " + + "to set caption using current caption generator '%s'", + captionGenerator.getClass().getName())); + } + + IconGenerator<T> iconGenerator = getItemIconGenerator(); + if (child.hasAttr("icon")) { + if (iconGenerator instanceof DeclarativeIconGenerator) { + ((DeclarativeIconGenerator) iconGenerator).setIcon(item, + DesignAttributeHandler.readAttribute("icon", + child.attributes(), Resource.class)); + } else { + throw new IllegalStateException(String.format( + "Don't know how " + + "to set icon using current caption generator '%s'", + iconGenerator.getClass().getName())); + } + } + + return item; + } + + /** + * Deserializes a string to a data item. + * <p> + * Default implementation is able to handle only {@link String} as an item + * type. There will be a {@link ClassCastException} if {@code T } is not a + * {@link String}. + * + * @see #serializeDeclarativeRepresentation(Object) + * + * @param item + * string to deserialize + * @throws ClassCastException + * if type {@code T} is not a {@link String} + * @return deserialized item + */ + protected T deserializeDeclarativeRepresentation(String item) { + return (T) item; + } + + /** + * Serializes an {@code item} to a string for saving declarative format. + * <p> + * Default implementation delegates a call to {@code item.toString()}. + * + * @see #serializeDeclarativeRepresentation(Object) + * + * @param item + * a data item + * @return string representation of the {@code item}. + */ + protected String serializeDeclarativeRepresentation(T item) { + return item.toString(); + } } diff --git a/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java b/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java index 3c85496e41..5ae0ee4ebd 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java +++ b/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java @@ -16,7 +16,9 @@ package com.vaadin.ui; import java.lang.reflect.Method; +import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Objects; import java.util.Optional; @@ -25,6 +27,8 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; +import org.jsoup.nodes.Element; + import com.vaadin.data.HasValue; import com.vaadin.data.SelectionModel; import com.vaadin.data.SelectionModel.Multi; @@ -38,6 +42,8 @@ import com.vaadin.shared.AbstractFieldState; import com.vaadin.shared.Registration; import com.vaadin.shared.data.selection.MultiSelectServerRpc; import com.vaadin.shared.ui.ListingJsonConstants; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.DesignException; import com.vaadin.util.ReflectTools; import elemental.json.JsonObject; @@ -121,17 +127,6 @@ public abstract class AbstractMultiSelect<T> extends AbstractListing<T> MultiSelectionEvent.class); /** - * The item icon caption provider. - */ - private ItemCaptionGenerator<T> itemCaptionGenerator = String::valueOf; - - /** - * The item icon provider. It is up to the implementing class to support - * this or not. - */ - private IconGenerator<T> itemIconGenerator = item -> null; - - /** * The item enabled status provider. It is up to the implementing class to * support this or not. */ @@ -163,30 +158,15 @@ public abstract class AbstractMultiSelect<T> extends AbstractListing<T> SELECTION_CHANGE_METHOD); } - /** - * Gets the item caption generator that is used to produce the strings shown - * in the select for each item. - * - * @return the item caption generator used, not {@code null} - * @see #setItemCaptionGenerator(ItemCaptionGenerator) - */ + @Override public ItemCaptionGenerator<T> getItemCaptionGenerator() { - return itemCaptionGenerator; + return super.getItemCaptionGenerator(); } - /** - * Sets the item caption generator that is used to produce the strings shown - * in the select for each item. By default, {@link String#valueOf(Object)} - * is used. - * - * @param itemCaptionGenerator - * the item caption generator to use, not {@code null} - */ + @Override public void setItemCaptionGenerator( ItemCaptionGenerator<T> itemCaptionGenerator) { - Objects.requireNonNull(itemCaptionGenerator); - this.itemCaptionGenerator = itemCaptionGenerator; - getDataCommunicator().reset(); + super.setItemCaptionGenerator(itemCaptionGenerator); } /** @@ -255,40 +235,6 @@ public abstract class AbstractMultiSelect<T> extends AbstractListing<T> } /** - * Returns the item icon generator for this multiselect. - * <p> - * <em>Implementation note:</em> Override this method and - * {@link #setItemIconGenerator(IconGenerator)} as {@code public} and invoke - * {@code super} methods to support this feature in the multiselect - * component. - * - * @return the item icon generator, not {@code null} - * @see #setItemIconGenerator(IconGenerator) - */ - protected IconGenerator<T> getItemIconGenerator() { - return itemIconGenerator; - } - - /** - * Sets the item icon generator for this multiselect. The icon generator is - * queried for each item to optionally display an icon next to the item - * caption. If the generator returns null for an item, no icon is displayed. - * The default provider always returns null (no icons). - * <p> - * <em>Implementation note:</em> Override this method and - * {@link #getItemIconGenerator()} as {@code public} and invoke - * {@code super} methods to support this feature in the multiselect - * component. - * - * @param itemIconGenerator - * the item icon generator to set, not {@code null} - */ - protected void setItemIconGenerator(IconGenerator<T> itemIconGenerator) { - Objects.requireNonNull(itemIconGenerator); - this.itemIconGenerator = itemIconGenerator; - } - - /** * Returns the item enabled provider for this multiselect. * <p> * <em>Implementation note:</em> Override this method and @@ -461,6 +407,66 @@ public abstract class AbstractMultiSelect<T> extends AbstractListing<T> updateSelection(set -> set.add(item), userOriginated); } + @Override + protected Collection<String> getCustomAttributes() { + Collection<String> attributes = super.getCustomAttributes(); + // "value" is not an attribute for the component. "selected" attribute + // is used in "option"'s tag to mark selection which implies value for + // multiselect component + attributes.add("value"); + return attributes; + } + + @Override + protected Element writeItem(Element design, T item, DesignContext context) { + Element element = super.writeItem(design, item, context); + + if (isSelected(item)) { + element.attr("selected", ""); + } + + return element; + } + + @Override + protected void readItems(Element design, DesignContext context) { + super.readItems(design, context); + Set<T> selected = new HashSet<>(); + design.children().stream() + .forEach(child -> readItem(child, selected, context)); + deselectAll(); + selected.forEach(this::select); + } + + /** + * Reads an Item from a design and inserts it into the data source. + * Hierarchical select components should override this method to recursively + * recursively read any child items as well. + * + * @param child + * a child element representing the item + * @param selected + * A set accumulating selected items. If the item that is read is + * marked as selected, its item id should be added to this set. + * @param context + * the DesignContext instance used in parsing + * @return the item id of the new item + * + * @throws DesignException + * if the tag name of the {@code child} element is not + * {@code option}. + */ + protected T readItem(Element child, Set<T> selected, + DesignContext context) { + T item = readItem(child, context); + + if (child.hasAttr("selected")) { + selected.add(item); + } + + return item; + } + private void updateSelection(Consumer<Set<T>> handler, boolean userOriginated) { LinkedHashSet<T> oldSelection = new LinkedHashSet<>(selection); diff --git a/server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java b/server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java index cdd91424e0..d2602746fe 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java +++ b/server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java @@ -16,8 +16,13 @@ package com.vaadin.ui; import java.lang.reflect.Method; +import java.util.Collection; +import java.util.HashSet; import java.util.Objects; import java.util.Optional; +import java.util.Set; + +import org.jsoup.nodes.Element; import com.vaadin.data.HasValue; import com.vaadin.data.SelectionModel; @@ -28,6 +33,8 @@ import com.vaadin.server.data.DataCommunicator; import com.vaadin.shared.Registration; import com.vaadin.shared.data.selection.SelectionServerRpc; import com.vaadin.shared.ui.AbstractSingleSelectState; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.DesignException; import com.vaadin.util.ReflectTools; /** @@ -307,6 +314,65 @@ public abstract class AbstractSingleSelect<T> extends AbstractListing<T> return Objects.equals(getValue(), item); } + @Override + protected Element writeItem(Element design, T item, DesignContext context) { + Element element = super.writeItem(design, item, context); + + if (isSelected(item)) { + element.attr("selected", ""); + } + + return element; + } + + @Override + protected void readItems(Element design, DesignContext context) { + super.readItems(design, context); + Set<T> selected = new HashSet<>(); + design.children().stream() + .forEach(child -> readItem(child, selected, context)); + selected.forEach(this::setValue); + } + + /** + * Reads an Item from a design and inserts it into the data source. + * Hierarchical select components should override this method to recursively + * recursively read any child items as well. + * + * @param child + * a child element representing the item + * @param selected + * A set accumulating selected items. If the item that is read is + * marked as selected, its item id should be added to this set. + * @param context + * the DesignContext instance used in parsing + * @return the item id of the new item + * + * @throws DesignException + * if the tag name of the {@code child} element is not + * {@code option}. + */ + protected T readItem(Element child, Set<T> selected, + DesignContext context) { + T item = readItem(child, context); + + if (child.hasAttr("selected")) { + selected.add(item); + } + + return item; + } + + @Override + protected Collection<String> getCustomAttributes() { + Collection<String> attributes = super.getCustomAttributes(); + // "value" is not an attribute for the component. "selected" attribute + // is used in "option"'s tag to mark selection which implies value for + // single select component + attributes.add("value"); + return attributes; + } + private void init() { registerRpc(new SelectionServerRpc() { diff --git a/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java b/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java index d5df147f57..5098cca628 100644 --- a/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java +++ b/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java @@ -17,6 +17,9 @@ package com.vaadin.ui; import java.util.Collection; +import java.util.Set; + +import org.jsoup.nodes.Element; import com.vaadin.data.Listing; import com.vaadin.event.FieldEvents.BlurEvent; @@ -30,6 +33,8 @@ import com.vaadin.server.SerializablePredicate; import com.vaadin.server.data.DataProvider; import com.vaadin.shared.Registration; import com.vaadin.shared.ui.optiongroup.CheckBoxGroupState; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.DesignFormatter; /** * A group of Checkboxes. Individual checkboxes are made from items supplied by @@ -171,4 +176,50 @@ public class CheckBoxGroup<T> extends AbstractMultiSelect<T> public void removeBlurListener(BlurListener listener) { removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener); } + + @Override + protected void readItems(Element design, DesignContext context) { + setItemEnabledProvider(new DeclarativeItemEnabledProvider<>()); + super.readItems(design, context); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + protected T readItem(Element child, Set<T> selected, + DesignContext context) { + T item = super.readItem(child, selected, context); + + SerializablePredicate<T> provider = getItemEnabledProvider(); + if (provider instanceof DeclarativeItemEnabledProvider) { + if (child.hasAttr("disabled")) { + ((DeclarativeItemEnabledProvider) provider).addDisabled(item); + } + } else { + throw new IllegalStateException(String.format( + "Don't know how " + + "to disable item using current item enabled provider '%s'", + provider.getClass().getName())); + } + return item; + } + + @Override + protected Element writeItem(Element design, T item, DesignContext context) { + Element elem = super.writeItem(design, item, context); + + if (!getItemEnabledProvider().test(item)) { + elem.attr("disabled", ""); + } + + if (isHtmlContentAllowed()) { + // need to unencode HTML entities. AbstractMultiSelect.writeDesign + // can't check if HTML content is allowed, so it always encodes + // entities like '>', '<' and '&'; in case HTML content is allowed + // this is undesirable so we need to unencode entities. Entities + // other than '<' and '>' will be taken care by Jsoup. + elem.html(DesignFormatter.decodeFromTextNode(elem.html())); + } + + return elem; + } } diff --git a/server/src/main/java/com/vaadin/ui/ComboBox.java b/server/src/main/java/com/vaadin/ui/ComboBox.java index 905d180a7f..a932d60423 100644 --- a/server/src/main/java/com/vaadin/ui/ComboBox.java +++ b/server/src/main/java/com/vaadin/ui/ComboBox.java @@ -18,10 +18,15 @@ package com.vaadin.ui; import java.io.Serializable; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.function.BiFunction; import java.util.function.Consumer; +import org.jsoup.nodes.Element; + import com.vaadin.data.HasValue; import com.vaadin.event.FieldEvents; import com.vaadin.event.FieldEvents.BlurEvent; @@ -40,6 +45,9 @@ import com.vaadin.shared.data.DataCommunicatorConstants; import com.vaadin.shared.ui.combobox.ComboBoxConstants; import com.vaadin.shared.ui.combobox.ComboBoxServerRpc; import com.vaadin.shared.ui.combobox.ComboBoxState; +import com.vaadin.ui.declarative.DesignAttributeHandler; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.DesignFormatter; import elemental.json.JsonObject; @@ -64,6 +72,36 @@ public class ComboBox<T> extends AbstractSingleSelect<T> implements HasValue<T>, } /** + * Item style generator class for declarative support. + * <p> + * Provides a straightforward mapping between an item and its style. + * + * @param <T> + * item type + */ + protected static class DeclarativeStyleGenerator<T> + implements StyleGenerator<T> { + private Map<T, String> styles = new HashMap<>(); + + @Override + public String apply(T item) { + return styles.get(item); + } + + /** + * Sets a {@code style} for the {@code item}. + * + * @param item + * a data item + * @param style + * a style for the {@code item} + */ + protected void setStyle(T item, String style) { + styles.put(item, style); + } + } + + /** * Filter can be used to customize the filtering of items based on user * input. * @@ -107,10 +145,7 @@ public class ComboBox<T> extends AbstractSingleSelect<T> implements HasValue<T>, */ private NewItemHandler newItemHandler; - private ItemCaptionGenerator<T> itemCaptionGenerator = String::valueOf; - private StyleGenerator<T> itemStyleGenerator = item -> null; - private IconGenerator<T> itemIconGenerator = item -> null; private ItemFilter<T> filter = (filterText, item) -> { if (filterText == null) { @@ -200,7 +235,7 @@ public class ComboBox<T> extends AbstractSingleSelect<T> implements HasValue<T>, if (style != null) { jsonObject.put(ComboBoxConstants.STYLE, style); } - Resource icon = itemIconGenerator.apply(data); + Resource icon = getItemIconGenerator().apply(data); if (icon != null) { String iconUrl = ResourceReference .create(icon, ComboBox.this, null).getURL(); @@ -377,30 +412,15 @@ public class ComboBox<T> extends AbstractSingleSelect<T> implements HasValue<T>, return getState(false).scrollToSelectedItem; } - /** - * Gets the item caption generator that is used to produce the strings shown - * in the combo box for each item. - * - * @return the item caption generator used, not null - */ + @Override public ItemCaptionGenerator<T> getItemCaptionGenerator() { - return itemCaptionGenerator; + return super.getItemCaptionGenerator(); } - /** - * Sets the item caption generator that is used to produce the strings shown - * in the combo box for each item. By default, - * {@link String#valueOf(Object)} is used. - * - * @param itemCaptionGenerator - * the item caption provider to use, not null - */ + @Override public void setItemCaptionGenerator( ItemCaptionGenerator<T> itemCaptionGenerator) { - Objects.requireNonNull(itemCaptionGenerator, - "Item caption generators must not be null"); - this.itemCaptionGenerator = itemCaptionGenerator; - getDataCommunicator().reset(); + super.setItemCaptionGenerator(itemCaptionGenerator); } /** @@ -437,36 +457,14 @@ public class ComboBox<T> extends AbstractSingleSelect<T> implements HasValue<T>, return itemStyleGenerator; } - /** - * Sets the item icon generator that is used to produce custom icons for - * showing items in the popup. The generator can return null for items with - * no icon. - * - * @see IconGenerator - * - * @param itemIconGenerator - * the item icon generator to set, not null - * @throws NullPointerException - * if {@code itemIconGenerator} is {@code null} - */ + @Override public void setItemIconGenerator(IconGenerator<T> itemIconGenerator) { - Objects.requireNonNull(itemIconGenerator, - "Item icon generator must not be null"); - this.itemIconGenerator = itemIconGenerator; - getDataCommunicator().reset(); + super.setItemIconGenerator(itemIconGenerator); } - /** - * Gets the currently used item icon generator. The default item icon - * provider returns null for all items, resulting in no icons being used. - * - * @see IconGenerator - * @see #setItemIconGenerator(IconGenerator) - * - * @return the currently used item icon generator, not null - */ + @Override public IconGenerator<T> getItemIconGenerator() { - return itemIconGenerator; + return super.getItemIconGenerator(); } /** @@ -548,4 +546,61 @@ public class ComboBox<T> extends AbstractSingleSelect<T> implements HasValue<T>, getState().selectedItemCaption = selectedCaption; } + @Override + protected Element writeItem(Element design, T item, DesignContext context) { + Element element = design.appendElement("option"); + + String caption = getItemCaptionGenerator().apply(item); + if (caption != null) { + element.html(DesignFormatter.encodeForTextNode(caption)); + } else { + element.html(DesignFormatter.encodeForTextNode(item.toString())); + } + element.attr("item", item.toString()); + + Resource icon = getItemIconGenerator().apply(item); + if (icon != null) { + DesignAttributeHandler.writeAttribute("icon", element.attributes(), + icon, null, Resource.class, context); + } + + String style = getStyleGenerator().apply(item); + if (style != null) { + element.attr("style", style); + } + + if (isSelected(item)) { + element.attr("selected", ""); + } + + return element; + } + + @Override + protected void readItems(Element design, DesignContext context) { + setStyleGenerator(new DeclarativeStyleGenerator<>()); + super.readItems(design, context); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + protected T readItem(Element child, Set<T> selected, + DesignContext context) { + T item = super.readItem(child, selected, context); + + if (child.hasAttr("style")) { + StyleGenerator<T> styleGenerator = getStyleGenerator(); + if (styleGenerator instanceof DeclarativeStyleGenerator) { + ((DeclarativeStyleGenerator) styleGenerator).setStyle(item, + child.attr("style")); + } else { + throw new IllegalStateException(String.format( + "Don't know how " + + "to set style using current style generator '%s'", + styleGenerator.getClass().getName())); + } + } + return item; + } + } diff --git a/server/src/main/java/com/vaadin/ui/DeclarativeCaptionGenerator.java b/server/src/main/java/com/vaadin/ui/DeclarativeCaptionGenerator.java new file mode 100644 index 0000000000..9aa6035d9d --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/DeclarativeCaptionGenerator.java @@ -0,0 +1,50 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.ui; + +import java.util.HashMap; +import java.util.Map; + +/** + * Item caption generator class for declarative support. + * <p> + * Provides a straightforward mapping between an item and its caption. + * + * @param <T> + * item type + */ +class DeclarativeCaptionGenerator<T> implements ItemCaptionGenerator<T> { + + private Map<T, String> captions = new HashMap<>(); + + @Override + public String apply(T item) { + return captions.get(item); + } + + /** + * Sets a {@code caption} for the {@code item}. + * + * @param item + * a data item + * @param caption + * a caption for the {@code item} + */ + protected void setCaption(T item, String caption) { + captions.put(item, caption); + } + +} diff --git a/server/src/main/java/com/vaadin/ui/DeclarativeIconGenerator.java b/server/src/main/java/com/vaadin/ui/DeclarativeIconGenerator.java new file mode 100644 index 0000000000..984ac4cf47 --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/DeclarativeIconGenerator.java @@ -0,0 +1,52 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.ui; + +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.server.Resource; + +/** + * Icon generator class for declarative support. + * <p> + * Provides a straightforward mapping between an item and its icon. + * + * @param <T> + * item type + */ +class DeclarativeIconGenerator<T> implements IconGenerator<T> { + + private Map<T, Resource> captions = new HashMap<>(); + + @Override + public Resource apply(T item) { + return captions.get(item); + } + + /** + * Sets an {@code icon} for the {@code item}. + * + * @param item + * a data item + * @param icon + * an icon for the {@code item} + */ + protected void setIcon(T item, Resource icon) { + captions.put(item, icon); + } + +} diff --git a/server/src/main/java/com/vaadin/ui/DeclarativeItemEnabledProvider.java b/server/src/main/java/com/vaadin/ui/DeclarativeItemEnabledProvider.java new file mode 100644 index 0000000000..f16e54f1b2 --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/DeclarativeItemEnabledProvider.java @@ -0,0 +1,52 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.ui; + +import java.util.HashSet; +import java.util.Set; + +import com.vaadin.server.SerializablePredicate; + +/** + * Item enabled provider class for declarative support. + * <p> + * Provides a straightforward mapping between an item and its enable state. + * + * @param <T> + * item type + */ +class DeclarativeItemEnabledProvider<T> implements SerializablePredicate<T> { + + private Set<T> disabled = new HashSet<>(); + + @Override + public boolean test(T item) { + return !disabled.contains(item); + } + + /** + * Adds the {@code item} to disabled items list. + * + * @param item + * a data item + * @param caption + * a caption for the {@code item} + */ + protected void addDisabled(T item) { + disabled.add(item); + } + +} diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java index a019476aeb..b9f5f5afbb 100644 --- a/server/src/main/java/com/vaadin/ui/Grid.java +++ b/server/src/main/java/com/vaadin/ui/Grid.java @@ -36,6 +36,8 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.jsoup.nodes.Element; + import com.vaadin.data.Binder; import com.vaadin.data.SelectionModel; import com.vaadin.event.ConnectorEvent; @@ -65,6 +67,7 @@ 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.declarative.DesignContext; import com.vaadin.ui.renderers.AbstractRenderer; import com.vaadin.ui.renderers.Renderer; import com.vaadin.ui.renderers.TextRenderer; @@ -2668,4 +2671,15 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents { fireEvent(new ColumnResizeEvent(this, column, userOriginated)); } + @Override + protected Element writeItem(Element design, T item, DesignContext context) { + // TODO see vaadin/framework8-issues#390 + return null; + } + + @Override + protected void readItems(Element design, DesignContext context) { + // TODO see vaadin/framework8-issues#390 + } + } diff --git a/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java b/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java index 2a6ab48402..5ee3a7b807 100644 --- a/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java +++ b/server/src/main/java/com/vaadin/ui/RadioButtonGroup.java @@ -18,6 +18,9 @@ package com.vaadin.ui; import java.util.Collection; import java.util.Objects; +import java.util.Set; + +import org.jsoup.nodes.Element; import com.vaadin.data.Listing; import com.vaadin.event.FieldEvents.BlurEvent; @@ -35,6 +38,8 @@ import com.vaadin.server.data.DataProvider; import com.vaadin.shared.Registration; import com.vaadin.shared.ui.ListingJsonConstants; import com.vaadin.shared.ui.optiongroup.RadioButtonGroupState; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.DesignFormatter; import elemental.json.JsonObject; @@ -50,10 +55,6 @@ import elemental.json.JsonObject; public class RadioButtonGroup<T> extends AbstractSingleSelect<T> implements FocusNotifier, BlurNotifier { - private IconGenerator<T> itemIconGenerator = item -> null; - - private ItemCaptionGenerator<T> itemCaptionGenerator = String::valueOf; - private SerializablePredicate<T> itemEnabledProvider = item -> true; /** @@ -174,59 +175,25 @@ public class RadioButtonGroup<T> extends AbstractSingleSelect<T> return (RadioButtonGroupState) super.getState(markAsDirty); } - /** - * Returns the item icon generator. - * - * @return the currently set icon generator - * @see #setItemIconGenerator - * @see IconGenerator - */ + @Override public IconGenerator<T> getItemIconGenerator() { - return itemIconGenerator; + return super.getItemIconGenerator(); } - /** - * Sets the item icon generator for this radiobutton group. The icon - * generator is queried for each item to optionally display an icon next to - * the item caption. If the generator returns null for an item, no icon is - * displayed. The default generator always returns null (no icons). - * - * @param itemIconGenerator - * the icon generator, not null - * @see IconGenerator - */ + @Override public void setItemIconGenerator(IconGenerator<T> itemIconGenerator) { - Objects.requireNonNull(itemIconGenerator, - "Item icon generator cannot be null."); - this.itemIconGenerator = itemIconGenerator; + super.setItemIconGenerator(itemIconGenerator); } - /** - * Returns the currently set item caption generator. - * - * @return the currently set caption generator - * @see #setItemCaptionGenerator - * @see ItemCaptionGenerator - */ + @Override public ItemCaptionGenerator<T> getItemCaptionGenerator() { - return itemCaptionGenerator; + return super.getItemCaptionGenerator(); } - /** - * Sets the item caption generator for this radiobutton group. The caption - * generator is queried for each item to optionally display an item textual - * representation. The default generator returns - * {@code String.valueOf(item)}. - * - * @param itemCaptionGenerator - * the item caption generator, not null - * @see ItemCaptionGenerator - */ + @Override public void setItemCaptionGenerator( ItemCaptionGenerator<T> itemCaptionGenerator) { - Objects.requireNonNull(itemCaptionGenerator, - "Item caption generator cannot be null."); - this.itemCaptionGenerator = itemCaptionGenerator; + super.setItemCaptionGenerator(itemCaptionGenerator); } /** @@ -278,4 +245,50 @@ public class RadioButtonGroup<T> extends AbstractSingleSelect<T> public void removeBlurListener(BlurListener listener) { removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener); } + + @Override + protected void readItems(Element design, DesignContext context) { + setItemEnabledProvider(new DeclarativeItemEnabledProvider<>()); + super.readItems(design, context); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + protected T readItem(Element child, Set<T> selected, + DesignContext context) { + T item = super.readItem(child, selected, context); + + SerializablePredicate<T> provider = getItemEnabledProvider(); + if (provider instanceof DeclarativeItemEnabledProvider) { + if (child.hasAttr("disabled")) { + ((DeclarativeItemEnabledProvider) provider).addDisabled(item); + } + } else { + throw new IllegalStateException(String.format( + "Don't know how " + + "to disable item using current item enabled provider '%s'", + provider.getClass().getName())); + } + return item; + } + + @Override + protected Element writeItem(Element design, T item, DesignContext context) { + Element elem = super.writeItem(design, item, context); + + if (!getItemEnabledProvider().test(item)) { + elem.attr("disabled", ""); + } + + if (isHtmlContentAllowed()) { + // need to unencode HTML entities. AbstractMultiSelect.writeDesign + // can't check if HTML content is allowed, so it always encodes + // entities like '>', '<' and '&'; in case HTML content is allowed + // this is undesirable so we need to unencode entities. Entities + // other than '<' and '>' will be taken care by Jsoup. + elem.html(DesignFormatter.decodeFromTextNode(elem.html())); + } + + return elem; + } } |