aboutsummaryrefslogtreecommitdiffstats
path: root/server/src/main/java
diff options
context:
space:
mode:
authorDenis Anisimov <denis@vaadin.com>2016-09-19 16:26:32 +0300
committerDenis Anisimov <denis@vaadin.com>2016-11-24 11:03:23 +0300
commit159d413602380497b189e5cabbdd9ecf6431c725 (patch)
tree85de070c7dae0a8792eeafa70e7c17d38ad5fe7c /server/src/main/java
parentfdc80972f55adc3bc2b987d40d3b4e628635f42f (diff)
downloadvaadin-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')
-rw-r--r--server/src/main/java/com/vaadin/ui/AbstractListing.java258
-rw-r--r--server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java134
-rw-r--r--server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java66
-rw-r--r--server/src/main/java/com/vaadin/ui/CheckBoxGroup.java51
-rw-r--r--server/src/main/java/com/vaadin/ui/ComboBox.java153
-rw-r--r--server/src/main/java/com/vaadin/ui/DeclarativeCaptionGenerator.java50
-rw-r--r--server/src/main/java/com/vaadin/ui/DeclarativeIconGenerator.java52
-rw-r--r--server/src/main/java/com/vaadin/ui/DeclarativeItemEnabledProvider.java52
-rw-r--r--server/src/main/java/com/vaadin/ui/Grid.java14
-rw-r--r--server/src/main/java/com/vaadin/ui/RadioButtonGroup.java105
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;
+ }
}