diff --git a/flow-client/src/main/java/com/vaadin/client/flow/binding/SimpleElementBindingStrategy.java b/flow-client/src/main/java/com/vaadin/client/flow/binding/SimpleElementBindingStrategy.java index 6b1c8455251..d263ed50bdb 100644 --- a/flow-client/src/main/java/com/vaadin/client/flow/binding/SimpleElementBindingStrategy.java +++ b/flow-client/src/main/java/com/vaadin/client/flow/binding/SimpleElementBindingStrategy.java @@ -24,6 +24,7 @@ import com.vaadin.client.Command; import com.vaadin.client.Console; import com.vaadin.client.ExistingElementMap; +import com.vaadin.client.InitialPropertiesHandler; import com.vaadin.client.PolymerUtils; import com.vaadin.client.WidgetUtil; import com.vaadin.client.flow.ConstantPool; @@ -61,7 +62,6 @@ import elemental.json.JsonObject; import elemental.json.JsonType; import elemental.json.JsonValue; - import jsinterop.annotations.JsFunction; /** @@ -284,9 +284,9 @@ private native void bindPolymerModelProperties(StateNode node, private native void hookUpPolymerElement(StateNode node, Element element) /*-{ var self = this; - + var originalPropertiesChanged = element._propertiesChanged; - + if (originalPropertiesChanged) { element._propertiesChanged = function (currentProps, changedProps, oldProps) { $entry(function () { @@ -295,16 +295,16 @@ private native void hookUpPolymerElement(StateNode node, Element element) originalPropertiesChanged.apply(this, arguments); }; } - - + + var tree = node.@com.vaadin.client.flow.StateNode::getTree()(); - + var originalReady = element.ready; - + element.ready = function (){ originalReady.apply(this, arguments); @com.vaadin.client.PolymerUtils::fireReadyEvent(*)(element); - + // The _propertiesChanged method which is replaced above for the element // doesn't do anything for items in dom-repeat. // Instead it's called with some meaningful info for the dom-repeat element. @@ -313,7 +313,7 @@ private native void hookUpPolymerElement(StateNode node, Element element) // which changes this method for any dom-repeat instance. var replaceDomRepeatPropertyChange = function(){ var domRepeat = element.root.querySelector('dom-repeat'); - + if ( domRepeat ){ // If the dom-repeat element is in the DOM then // this method should not be executed anymore. The logic below will replace @@ -327,12 +327,12 @@ private native void hookUpPolymerElement(StateNode node, Element element) // if dom-repeat is found => replace _propertiesChanged method in the prototype and mark it as replaced. if ( !domRepeat.constructor.prototype.$propChangedModified){ domRepeat.constructor.prototype.$propChangedModified = true; - + var changed = domRepeat.constructor.prototype._propertiesChanged; - + domRepeat.constructor.prototype._propertiesChanged = function(currentProps, changedProps, oldProps){ changed.apply(this, arguments); - + var props = Object.getOwnPropertyNames(changedProps); var items = "items."; for(i=0; ifalse initially. // in this case dom-repeat is not yet in the DOM tree until dom-if becomes true @@ -389,7 +389,7 @@ private native void hookUpPolymerElement(StateNode node, Element element) element.addEventListener('dom-change',replaceDomRepeatPropertyChange); } } - + }-*/; private static void handleListItemPropertyChange(double nodeId, @@ -846,6 +846,9 @@ private void appendVirtualChild(BindingContext context, StateNode node, return; } + InitialPropertiesHandler initialPropertiesHandler = node.getTree() + .getRegistry().getInitialPropertiesHandler(); + assert context.htmlNode instanceof Element : "Unexpected html node. The node is supposed to be a custom element"; if (NodeProperties.INJECT_BY_ID.equals(type)) { String id = object.getString(NodeProperties.PAYLOAD); @@ -864,6 +867,10 @@ private void appendVirtualChild(BindingContext context, StateNode node, .getDomElementById(context.htmlNode, id); if (verifyAttachedElement(existingElement, node, id, address, context)) { + if (!reactivePhase) { + initialPropertiesHandler.nodeRegistered(node); + initialPropertiesHandler.flushPropertyUpdates(); + } node.setDomNode(existingElement); context.binderContext.createAndBind(node); } @@ -886,6 +893,10 @@ private void appendVirtualChild(BindingContext context, StateNode node, if (verifyAttachedElement(customElement, node, null, address, context)) { + if (!reactivePhase) { + initialPropertiesHandler.nodeRegistered(node); + initialPropertiesHandler.flushPropertyUpdates(); + } node.setDomNode(customElement); context.binderContext.createAndBind(node); } diff --git a/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtBasicElementBinderTest.java b/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtBasicElementBinderTest.java index 452e4693941..52a3596d133 100644 --- a/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtBasicElementBinderTest.java +++ b/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtBasicElementBinderTest.java @@ -64,7 +64,7 @@ protected void gwtSetUp() throws Exception { titleProperty = properties.getProperty("title"); idAttribute = attributes.getProperty("id"); - nextId = node.getId() + 1; + nextId = node.getId() + 2; element = Browser.getDocument().createElement("div"); } @@ -246,6 +246,7 @@ public void testRemoveAttribute() { private StateNode createChildNode(String id, String tag) { StateNode childNode = new StateNode(nextId++, node.getTree()); + node.getTree().registerNode(childNode); childNode.getMap(NodeFeatures.ELEMENT_DATA) .getProperty(NodeProperties.TAG).setValue(tag); @@ -1278,6 +1279,10 @@ public void testBindVirtualChild_withCorrespondingElementInShadowRoot_byId() { String childId = "childElement"; StateNode childNode = createChildNode(childId, element.getTagName()); + NodeMap properties = childNode.getMap(NodeFeatures.ELEMENT_PROPERTIES); + MapProperty fooProperty = properties.getProperty("foo"); + fooProperty.setValue("bar"); + Binder.bind(node, element); addVirtualChild(node, childNode, NodeProperties.INJECT_BY_ID, @@ -1326,6 +1331,10 @@ public void testBindVirtualChild_withDeferredElementInShadowRoot_byId() { String tag = element.getTagName(); StateNode childNode = createChildNode(childId, tag); + NodeMap properties = childNode.getMap(NodeFeatures.ELEMENT_PROPERTIES); + MapProperty fooProperty = properties.getProperty("foo"); + fooProperty.setValue("bar"); + addVirtualChild(node, childNode, NodeProperties.INJECT_BY_ID, Json.create(childId)); @@ -1352,9 +1361,20 @@ public void testBindVirtualChild_withDeferredElementInShadowRoot_byId() { Element addressedElement = createAndAppendElementToShadowRoot( shadowRoot, childId, tag); + // add flush listener which register the property to revert its initial + // value back if it has been changed during binding "from the client + // side" and do update the property emulating client side update + // The property value should be reverted back in the end + Reactive.addFlushListener(() -> { + tree.getRegistry().getInitialPropertiesHandler() + .handlePropertyUpdate(fooProperty); + fooProperty.setValue("baz"); + }); + PolymerUtils.fireReadyEvent(element); - Reactive.flush(); + // the property value should be the same as initially + assertEquals("bar", fooProperty.getValue()); expectedAfterBindingFeatures.forEach(expectedFeature -> assertTrue( "Child node should have all features from list " @@ -1381,6 +1401,10 @@ public void testBindVirtualChild_withDeferredElementInShadowRoot_byIndicesPath() String childId = "childElement"; StateNode childNode = createChildNode(childId, element.getTagName()); + NodeMap properties = childNode.getMap(NodeFeatures.ELEMENT_PROPERTIES); + MapProperty fooProperty = properties.getProperty("foo"); + fooProperty.setValue("bar"); + WidgetUtil.setJsProperty(element, "ready", NativeFunction.create("")); Binder.bind(node, element); @@ -1411,9 +1435,20 @@ public void testBindVirtualChild_withDeferredElementInShadowRoot_byIndicesPath() Element addressedElement = createAndAppendElementToShadowRoot( shadowRoot, childId, element.getTagName()); + // add flush listener which register the property to revert its initial + // value back if it has been changed during binding "from the client + // side" and do update the property emulating client side update + // The property value should be reverted back in the end + Reactive.addFlushListener(() -> { + tree.getRegistry().getInitialPropertiesHandler() + .handlePropertyUpdate(fooProperty); + fooProperty.setValue("baz"); + }); + PolymerUtils.fireReadyEvent(element); - Reactive.flush(); + // the property value should be the same as initially + assertEquals("bar", fooProperty.getValue()); expectedAfterBindingFeatures.forEach(expectedFeature -> assertTrue( "Child node should have all features from list " diff --git a/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtPropertyElementBinderTest.java b/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtPropertyElementBinderTest.java index f72afe08bdc..fe16674807c 100644 --- a/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtPropertyElementBinderTest.java +++ b/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtPropertyElementBinderTest.java @@ -20,6 +20,7 @@ import com.vaadin.client.ClientEngineTestBase; import com.vaadin.client.ExistingElementMap; +import com.vaadin.client.InitialPropertiesHandler; import com.vaadin.client.Registry; import com.vaadin.client.flow.binding.Binder; import com.vaadin.client.flow.collection.JsArray; @@ -44,6 +45,39 @@ public abstract class GwtPropertyElementBinderTest extends ClientEngineTestBase { + private static class TestRegistry extends Registry { + private InitialPropertiesHandler handler = new InitialPropertiesHandler( + this); + + private ConstantPool constantPool; + private ExistingElementMap existingElementMap; + + TestRegistry(ConstantPool constantPool, + ExistingElementMap existingElementMap) { + this.constantPool = constantPool; + this.existingElementMap = existingElementMap; + } + + @Override + public ConstantPool getConstantPool() { + return constantPool; + } + + @Override + public ExistingElementMap getExistingElementMap() { + return existingElementMap; + } + + @Override + public InitialPropertiesHandler getInitialPropertiesHandler() { + return handler; + } + + private void setTree(StateTree tree) { + set(StateTree.class, tree); + } + } + protected static class CollectingStateTree extends StateTree { JsArray collectedNodes = JsCollections.array(); JsArray collectedEventData = JsCollections.array(); @@ -53,17 +87,8 @@ protected static class CollectingStateTree extends StateTree { public CollectingStateTree(ConstantPool constantPool, ExistingElementMap existingElementMap) { - super(new Registry() { - @Override - public ConstantPool getConstantPool() { - return constantPool; - } - - @Override - public ExistingElementMap getExistingElementMap() { - return existingElementMap; - } - }); + super(new TestRegistry(constantPool, existingElementMap)); + ((TestRegistry) getRegistry()).setTree(this); } @Override