From c692a2db966daa3760ce209457f7dd6589c0491b Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Thu, 18 Jun 2020 10:30:14 +0300 Subject: [PATCH] Support Element.setProperty for lists and maps Fixes #7308 --- .../java/com/vaadin/flow/dom/Element.java | 56 ++++++++++++++++++- .../com/vaadin/flow/internal/JsonUtils.java | 37 +++++++++++- .../java/com/vaadin/flow/dom/ElementTest.java | 27 +++++++++ .../vaadin/flow/internal/JsonUtilsTest.java | 31 ++++++++++ 4 files changed, 148 insertions(+), 3 deletions(-) diff --git a/flow-server/src/main/java/com/vaadin/flow/dom/Element.java b/flow-server/src/main/java/com/vaadin/flow/dom/Element.java index 6775f3c7bc8..bf85421274f 100644 --- a/flow-server/src/main/java/com/vaadin/flow/dom/Element.java +++ b/flow-server/src/main/java/com/vaadin/flow/dom/Element.java @@ -17,6 +17,7 @@ import java.io.Serializable; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; @@ -67,6 +68,8 @@ public class Element extends Node { static final String ATTRIBUTE_NAME_CANNOT_BE_NULL = "The attribute name cannot be null"; + private static final String USE_SET_PROPERTY_WITH_JSON_NULL = "setProperty(name, Json.createNull()) must be used to set a property to null"; + // Can't set $name as a property, use $replacement instead. private static final Map illegalPropertyReplacements = new HashMap<>(); @@ -684,12 +687,61 @@ public Element setPropertyJson(String name, JsonValue value) { // Distinct name so setProperty("foo", null) is not ambiguous public Element setPropertyBean(String name, Object value) { if (value == null) { - throw new IllegalArgumentException( - "setProperty(name, Json.createNull()) must be used to set a property to null"); + throw new IllegalArgumentException(USE_SET_PROPERTY_WITH_JSON_NULL); } return setPropertyJson(name, JsonUtils.beanToJson(value)); } + /** + * Sets the given property to the given list of beans or primitive values, + * converted to a JSON array. + *

+ * Note that properties changed on the server are updated on the client but + * changes made on the client side are not reflected back to the server + * unless configured using + * {@link #addPropertyChangeListener(String, String, PropertyChangeListener)} + * or {@link DomListenerRegistration#synchronizeProperty(String)}. + * + * @param + * the type of items in the list + * @param name + * the property name, not null + * @param value + * the property value, not null + * @return this element + */ + public Element setPropertyList(String name, List value) { + if (value == null) { + throw new IllegalArgumentException(USE_SET_PROPERTY_WITH_JSON_NULL); + } + + return setPropertyJson(name, JsonUtils.listToJson(value)); + } + + /** + * Sets the given property to the given map of beans or primitive values, + * converted to a JSON object. + *

+ * Note that properties changed on the server are updated on the client but + * changes made on the client side are not reflected back to the server + * unless configured using + * {@link #addPropertyChangeListener(String, String, PropertyChangeListener)} + * or {@link DomListenerRegistration#synchronizeProperty(String)}. + * + * @param name + * the property name, not null + * @param value + * the property value, not null + * @return this element + */ + public Element setPropertyMap(String name, Map value) { + if (value == null) { + throw new IllegalArgumentException(USE_SET_PROPERTY_WITH_JSON_NULL); + } + + return setPropertyJson(name, JsonUtils.mapToJson(value)); + } + /** * Adds a property change listener which is triggered when the property's * value is updated on the server side. diff --git a/flow-server/src/main/java/com/vaadin/flow/internal/JsonUtils.java b/flow-server/src/main/java/com/vaadin/flow/internal/JsonUtils.java index 9253ee7a8f9..29d4c354672 100644 --- a/flow-server/src/main/java/com/vaadin/flow/internal/JsonUtils.java +++ b/flow-server/src/main/java/com/vaadin/flow/internal/JsonUtils.java @@ -19,6 +19,7 @@ import java.util.AbstractList; import java.util.Collections; import java.util.EnumSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -48,6 +49,8 @@ */ public final class JsonUtils { + private static final String CANNOT_CONVERT_NULL_TO_A_JSON_OBJECT = "Cannot convert null to JSON"; + private static final ObjectMapper objectMapper = new ObjectMapper(); /** @@ -290,7 +293,7 @@ public static JsonObject createObject(Map map, * @return a JSON representation of the bean */ public static JsonObject beanToJson(Object bean) { - Objects.requireNonNull(bean, "Cannot convert null to a JSON object"); + Objects.requireNonNull(bean, CANNOT_CONVERT_NULL_TO_A_JSON_OBJECT); try { return Json.parse(objectMapper.writeValueAsString(bean)); @@ -298,4 +301,36 @@ public static JsonObject beanToJson(Object bean) { throw new RuntimeException("Error converting bean to JSON", e); } } + + /** + * Converts the given list to JSON. + * + * @param list + * the list to convert, not {@code null} + * @return a JSON representation of the bean + */ + public static JsonArray listToJson(List list) { + Objects.requireNonNull(list, CANNOT_CONVERT_NULL_TO_A_JSON_OBJECT); + try { + return Json.instance().parse(objectMapper.writeValueAsString(list)); + } catch (JsonProcessingException e) { + throw new RuntimeException("Error converting list to JSON", e); + } + } + + /** + * Converts the given map to JSON. + * + * @param map + * the map to convert, not {@code null} + * @return a JSON representation of the bean + */ + public static JsonObject mapToJson(Map map) { + Objects.requireNonNull(map, CANNOT_CONVERT_NULL_TO_A_JSON_OBJECT); + try { + return Json.instance().parse(objectMapper.writeValueAsString(map)); + } catch (JsonProcessingException e) { + throw new RuntimeException("Error converting map to JSON", e); + } + } } diff --git a/flow-server/src/test/java/com/vaadin/flow/dom/ElementTest.java b/flow-server/src/test/java/com/vaadin/flow/dom/ElementTest.java index fb82104d9b4..d5fce8335ad 100644 --- a/flow-server/src/test/java/com/vaadin/flow/dom/ElementTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/dom/ElementTest.java @@ -10,8 +10,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -50,6 +52,7 @@ import com.vaadin.tests.util.TestUtil; import elemental.json.Json; +import elemental.json.JsonArray; import elemental.json.JsonObject; import elemental.json.JsonValue; import elemental.json.impl.JreJsonObject; @@ -573,6 +576,30 @@ public void propertyRawValues() { Assert.assertEquals(1.0, json.getNumber("number"), 0.0); Assert.assertEquals(2.3, json.getNumber("flt"), 0.0); Assert.assertEquals(4.56, json.getNumber("dbl"), 0.0); + + List list = new ArrayList<>(); + SimpleBean bean1 = new SimpleBean(); + bean1.string = "bean1"; + SimpleBean bean2 = new SimpleBean(); + bean2.string = "bean2"; + list.add(bean1); + list.add(bean2); + element.setPropertyList("p", list); + JsonArray jsonArray = (JsonArray) element.getPropertyRaw("p"); + Assert.assertEquals("bean1", + jsonArray.getObject(0).getString("string")); + Assert.assertEquals("bean2", + jsonArray.getObject(1).getString("string")); + + Map map = new HashMap<>(); + map.put("one", bean1); + map.put("two", bean2); + element.setPropertyMap("p", map); + JsonObject jsonObject = (JsonObject) element.getPropertyRaw("p"); + Assert.assertEquals("bean1", + jsonObject.getObject("one").getString("string")); + Assert.assertEquals("bean2", + jsonObject.getObject("two").getString("string")); } @Test diff --git a/flow-server/src/test/java/com/vaadin/flow/internal/JsonUtilsTest.java b/flow-server/src/test/java/com/vaadin/flow/internal/JsonUtilsTest.java index e364d1fe5a5..ff55d46b557 100644 --- a/flow-server/src/test/java/com/vaadin/flow/internal/JsonUtilsTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/internal/JsonUtilsTest.java @@ -376,4 +376,35 @@ public void beanWithListAndMap() { ((JsonObject) childBeanList.get(1)).getString("childValue")); } + @Test + public void simpleBeanListToJson() { + ArrayList list = new ArrayList<>(); + SimpleBean bean1 = new SimpleBean(); + bean1.string = "bean1"; + SimpleBean bean2 = new SimpleBean(); + bean2.string = "bean2"; + list.add(bean1); + list.add(bean2); + JsonArray json = JsonUtils.listToJson(list); + + Assert.assertEquals("bean1", json.getObject(0).getString("string")); + Assert.assertEquals("bean2", json.getObject(1).getString("string")); + } + + @Test + public void simpleMapToJson() { + Map map = new HashMap<>(); + SimpleBean bean1 = new SimpleBean(); + bean1.string = "bean1"; + SimpleBean bean2 = new SimpleBean(); + bean2.string = "bean2"; + + map.put("one", bean1); + map.put("two", bean2); + JsonObject json = JsonUtils.mapToJson(map); + + Assert.assertEquals("bean1", json.getObject("one").getString("string")); + Assert.assertEquals("bean2", json.getObject("two").getString("string")); + } + }