Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't send model properties to the server if they are not updatable #3498

Merged
merged 14 commits into from
Feb 7, 2018
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.vaadin.client.flow.collection.JsArray;
import com.vaadin.client.flow.collection.JsMap;
import com.vaadin.client.flow.dom.DomApi;
import com.vaadin.client.flow.model.UpdatableModelProperties;
import com.vaadin.client.flow.nodefeature.NodeList;
import com.vaadin.client.flow.nodefeature.NodeMap;
import com.vaadin.client.flow.reactive.Reactive;
Expand Down Expand Up @@ -133,15 +134,47 @@ public static void populateModelProperties(StateNode node,
return;
}
for (int i = 0; i < properties.length(); i++) {
String property = properties.get(i);
if (!isPropertyDefined(node.getDomNode(), property)) {
if (!map.hasPropertyValue(property)) {
map.getProperty(property).setValue(null);
}
} else {
map.getProperty(property).syncToServer(
WidgetUtil.getJsProperty(node.getDomNode(), property));
populateModelProperty(node, map, properties.get(i));
}
}

private static void populateModelProperty(StateNode node, NodeMap map,
String property) {
if (!isPropertyDefined(node.getDomNode(), property)) {
if (!map.hasPropertyValue(property)) {
map.getProperty(property).setValue(null);
}
} else {
UpdatableModelProperties updatableProperties = node
.getNodeData(UpdatableModelProperties.class);
if (updatableProperties == null
|| !updatableProperties.isUpdatableProperty(property)) {
return;
}
map.getProperty(property).syncToServer(
WidgetUtil.getJsProperty(node.getDomNode(), property));
}
}

/**
* Register the updatable model properties of the {@code node}.
* <p>
* Only updates for the properties from the {@code properties} array will be
* sent to the server without explicit synchronization. The
* {@code properties} array includes all properties that are allowed to be
* updated (including sub properties).
*
* @param node
* the node whose updatable properties should be registered
* @param properties
* all updatable model properties
*/
public static void registerUpdatableModelProperties(StateNode node,
JsArray<String> properties) {
if (!properties.isEmpty()) {
UpdatableModelProperties data = new UpdatableModelProperties(
properties);
node.setNodeData(data);
}
}

Expand All @@ -163,7 +196,7 @@ private static Integer getExistingIdOrUpdate(StateNode parent,
private static native boolean isPropertyDefined(Node node, String property)
/*-{
return !!(node["constructor"] && node["constructor"]["properties"] &&
node["constructor"]["properties"][property] &&
node["constructor"]["properties"][property]["value"]);
node["constructor"]["properties"][property]) &&
(typeof(node["constructor"]["properties"][property]["value"]) != "undefined");
}-*/;
}
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ private native JsonObject getContextExecutionObject(
object.populateModelProperties = function(element, properties){
@com.vaadin.client.ExecuteJavaScriptElementUtils::populateModelProperties(*)(object.getNode(element), properties);
};
object.registerUpdatableModelProperties = function(element, properties){
@com.vaadin.client.ExecuteJavaScriptElementUtils::registerUpdatableModelProperties(*)(object.getNode(element), properties);
};
return object;
}-*/;
}
10 changes: 10 additions & 0 deletions flow-client/src/main/java/com/vaadin/client/flow/StateNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -317,4 +317,14 @@ public <T> void setNodeData(T object) {
public <T> T getNodeData(Class<T> clazz) {
return (T) nodeData.get(clazz);
}

/**
* Removes the {@code object} from the stored data.
*
* @param object
* the object to remove
*/
public <T> void clearNodeData(T object) {
nodeData.delete(object.getClass());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.function.Supplier;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.Scheduler;
import com.vaadin.client.Command;
import com.vaadin.client.Console;
import com.vaadin.client.ExistingElementMap;
Expand All @@ -34,6 +35,7 @@
import com.vaadin.client.flow.collection.JsWeakMap;
import com.vaadin.client.flow.dom.DomApi;
import com.vaadin.client.flow.dom.DomElement.DomTokenList;
import com.vaadin.client.flow.model.UpdatableModelProperties;
import com.vaadin.client.flow.nodefeature.ListSpliceEvent;
import com.vaadin.client.flow.nodefeature.MapProperty;
import com.vaadin.client.flow.nodefeature.NodeList;
Expand Down Expand Up @@ -64,6 +66,8 @@
*/
public class SimpleElementBindingStrategy implements BindingStrategy<Element> {

private static final String INITIAL_CHANGE = "isInitialChange";

private static final String HIDDEN_ATTRIBUTE = "hidden";

private static final String ELEMENT_ATTACH_ERROR_PREFIX = "Element addressed by the ";
Expand Down Expand Up @@ -136,6 +140,26 @@ private BindingContext(StateNode node, Node htmlNode,
}
}

private static class InitialPropertyUpdate {
private Runnable command;
private final StateNode node;

private InitialPropertyUpdate(StateNode node) {
this.node = node;
}

private void setCommand(Runnable command) {
this.command = command;
}

private void execute() {
if (command != null) {
command.run();
}
node.clearNodeData(this);
}
}

@Override
public Element create(StateNode node) {
String tag = getTag(node);
Expand Down Expand Up @@ -211,6 +235,21 @@ assert hasSameTag(stateNode, htmlNode) : "Element tag name is '"
}
listeners.push(bindVisibility(listeners, context,
computationsCollection, nodeFactory));

scheduleInitialExecution(stateNode);
}

private void scheduleInitialExecution(StateNode stateNode) {
InitialPropertyUpdate update = new InitialPropertyUpdate(stateNode);
stateNode.setNodeData(update);
/*
* Update command will be executed after all initial Reactive stuff.
* E.g. initial JS (if any) will be executed BEFORE initial update
* command execution
*/
Reactive.addPostFlushListener(
() -> Scheduler.get().scheduleDeferred(() -> stateNode
.getNodeData(InitialPropertyUpdate.class).execute()));
}

private native void bindPolymerModelProperties(StateNode node,
Expand All @@ -235,7 +274,7 @@ private native void hookUpPolymerElement(StateNode node, Element element)
/*-{
this.@SimpleElementBindingStrategy::bindInitialModelProperties(*)(node, element);
var self = this;

var originalPropertiesChanged = element._propertiesChanged;
if (originalPropertiesChanged) {
element._propertiesChanged = function (currentProps, changedProps, oldProps) {
Expand All @@ -245,7 +284,7 @@ private native void hookUpPolymerElement(StateNode node, Element element)
originalPropertiesChanged.apply(this, arguments);
};
}

var originalReady = element.ready;
element.ready = function (){
originalReady.apply(this, arguments);
Expand All @@ -256,15 +295,36 @@ private native void hookUpPolymerElement(StateNode node, Element element)
private void handlePropertiesChanged(
JavaScriptObject changedPropertyPathsToValues, StateNode node) {
String[] keys = WidgetUtil.getKeys(changedPropertyPathsToValues);
for (String propertyName : keys) {
handlePropertyChange(propertyName, () -> WidgetUtil
.getJsProperty(changedPropertyPathsToValues, propertyName),
node);

Runnable runnable = () -> {
for (String propertyName : keys) {
handlePropertyChange(propertyName,
() -> WidgetUtil.getJsProperty(
changedPropertyPathsToValues, propertyName),
node);
}
};

InitialPropertyUpdate initialUpdate = node
.getNodeData(InitialPropertyUpdate.class);
if (initialUpdate == null) {
runnable.run();
} else {
initialUpdate.setCommand(runnable);
}
}

private void handlePropertyChange(String fullPropertyName,
Supplier<Object> valueProvider, StateNode node) {
UpdatableModelProperties updatableProperties = node
.getNodeData(UpdatableModelProperties.class);
if (updatableProperties == null
|| !updatableProperties.isUpdatableProperty(fullPropertyName)) {
// don't do anything if the property/sub-property is not in the
// collection of updatable properties
return;
}

// This is not the property value itself, its a parent node of the
// property
String[] subProperties = fullPropertyName.split("\\.");
Expand All @@ -279,14 +339,6 @@ private void handlePropertyChange(String fullPropertyName,
+ "' which isn't defined from server");
return;
}
if (containsProperty(
model.getList(NodeFeatures.SYNCHRONIZED_PROPERTIES),
subProperty)) {
Console.debug("Ignoring property change for property '"
+ fullPropertyName
+ "' which is intended to be synchronized separately");
return;
}

mapProperty = elementProperties.getProperty(subProperty);
if (mapProperty.getValue() instanceof StateNode) {
Expand All @@ -302,21 +354,9 @@ private void handlePropertyChange(String fullPropertyName,
return;
}
}

mapProperty.syncToServer(valueProvider.get());
}

private boolean containsProperty(NodeList synchronizedProperties,
String property) {
for (int i = 0; i < synchronizedProperties.length(); i++) {
if (Objects.equals(synchronizedProperties.get(i).toString(),
property)) {
return true;
}
}
return false;
}

private EventRemover bindShadowRoot(BindingContext context) {
assert context.htmlNode instanceof Element : "Cannot bind shadow root to a Node";
NodeMap map = context.node.getMap(NodeFeatures.SHADOW_ROOT_DATA);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2000-2017 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.client.flow.model;

import com.vaadin.client.flow.StateNode;
import com.vaadin.client.flow.binding.SimpleElementBindingStrategy;
import com.vaadin.client.flow.collection.JsArray;
import com.vaadin.client.flow.collection.JsCollections;
import com.vaadin.client.flow.collection.JsSet;

/**
* The storage class for set of updatable model properties.
* <p>
* This class is stored inside a {@link StateNode} via
* {@link StateNode#setNodeData(Object)} if there is any data to store at all.
* Once it's stored in the {@link StateNode} the code which sends updates to the
* server side when a polymer property is updated uses this data to detect
* whether server expects the update to be sent(see
* {@link SimpleElementBindingStrategy}).
*
* @author Vaadin Ltd
*
*/
public class UpdatableModelProperties {

private final JsSet<String> properties = JsCollections.set();

/**
* Creates a new instance of storage class based on given
* {@code properties}.
*
* @param properties
* updatable properties array
*/
public UpdatableModelProperties(JsArray<String> properties) {
properties.forEach(this.properties::add);
}

/**
* Tests whether the {@code property} is updatable.
*
* @param property
* the property to test
* @return {@code true} if property is updatable
*/
public boolean isUpdatableProperty(String property) {
return properties.has(property);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2000-2017 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.client;

import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.core.client.impl.SchedulerImpl;

public class CustomScheduler extends SchedulerImpl {

@Override
public void scheduleDeferred(ScheduledCommand cmd) {
cmd.execute();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,6 @@ public void inlineStyleSheet(String styleSheetContents,
}
}

public static class CustomScheduler extends SchedulerImpl {

@Override
public void scheduleDeferred(ScheduledCommand cmd) {
cmd.execute();
}
}

private MockResourceLoader mockResourceLoader;

private Registry registry;
Expand Down
Loading