Skip to content

Commit

Permalink
Merge branch 'main' into issues/19260-binder-change-detection
Browse files Browse the repository at this point in the history
  • Loading branch information
tepi authored Jun 4, 2024
2 parents d201945 + 135bcfe commit c6f5f92
Show file tree
Hide file tree
Showing 129 changed files with 4,016 additions and 661 deletions.
2 changes: 1 addition & 1 deletion flow-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.2.0</version>
<version>3.3.0</version>
<executions>
<execution>
<id>npm-install</id>
Expand Down
3 changes: 3 additions & 0 deletions flow-client/src/main/java/com/vaadin/client/ElementUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,7 @@ public static native Element getElementById(Node context, String id)
}
}-*/;

public static native Element getElementByName(Node context, String name) /*-{
return Array.from(context.querySelectorAll('[name]')).find(function(e) {return e.getAttribute('name') == name});
}-*/;
}
63 changes: 63 additions & 0 deletions flow-client/src/main/java/com/vaadin/client/ReactUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2000-2024 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 java.util.function.Supplier;

import elemental.dom.Element;

/**
* Utils class, intended to ease working with React component related code on
* the client side.
*
* @author Vaadin Ltd
* @since 24.5.
*/
public final class ReactUtils {

/**
* Add a callback to the react component that is called when the component
* initialization is ready for binding flow.
*
* @param element
* react component element
* @param name
* name of container to bind to
* @param runnable
* callback function runnable
*/
public static native void addReadyCallback(Element element, String name,
Runnable runnable)
/*-{
if(element.addReadyCallback){
element.addReadyCallback(name,
$entry([email protected]::run(*).bind(runnable))
);
}
}-*/;

/**
* Check if the react element is initialized and functional.
*
* @param elementLookup
* react element lookup supplier
* @return {@code true} if Flow binding can already be done
*/
public static boolean isInitialized(Supplier<Element> elementLookup) {
return elementLookup.get() != null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import com.vaadin.flow.shared.JsonConstants;
import com.vaadin.flow.shared.ui.LoadMode;

import elemental.client.Browser;
import elemental.dom.Node;
import elemental.json.JsonArray;
import elemental.json.JsonObject;
Expand Down Expand Up @@ -222,6 +223,23 @@ protected void handleJSON(final ValueMap valueMap) {

if (!hasResynchronize && registry.getMessageSender()
.getResynchronizationState() == ResynchronizationState.WAITING_FOR_RESPONSE) {

JsonObject json = valueMap.cast();
if (json.hasKey(JsonConstants.UIDL_KEY_EXECUTE)) {
JsonArray commands = json
.getArray(JsonConstants.UIDL_KEY_EXECUTE);
for (int i = 0; i < commands.length(); i++) {
JsonArray command = commands.getArray(i);
if (command.length() > 0 && "window.location.reload();"
.equals(command.getString(0))) {
Console.warn(
"Executing forced page reload while a resync request is ongoing.");
Browser.getWindow().getLocation().reload();
return;
}
}
}

Console.warn(
"Ignoring message from the server as a resync request is ongoing.");
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.vaadin.client.InitialPropertiesHandler;
import com.vaadin.client.LitUtils;
import com.vaadin.client.PolymerUtils;
import com.vaadin.client.ReactUtils;
import com.vaadin.client.WidgetUtil;
import com.vaadin.client.flow.ConstantPool;
import com.vaadin.client.flow.StateNode;
Expand Down Expand Up @@ -878,6 +879,23 @@ private void appendVirtualChild(BindingContext context, StateNode node,
return;
}
handleTemplateInTemplate(context, node, object, reactivePhase);
} else if (NodeProperties.INJECT_BY_NAME.equals(type)) {
String name = object.getString(NodeProperties.PAYLOAD);
String address = "name='" + name + "'";

Supplier<Element> elementLookup = () -> ElementUtil
.getElementByName(context.htmlNode, name);

if (!ReactUtils.isInitialized(elementLookup)) {
ReactUtils.addReadyCallback((Element) context.htmlNode, name,
() -> {
doAppendVirtualChild(context, node, false,
elementLookup, name, address);
});
return;
}
doAppendVirtualChild(context, node, reactivePhase, elementLookup,
name, address);
} else {
assert false : "Unexpected payload type " + type;
}
Expand Down
11 changes: 6 additions & 5 deletions flow-data/src/main/java/com/vaadin/flow/data/binder/Binder.java
Original file line number Diff line number Diff line change
Expand Up @@ -1444,14 +1444,15 @@ private BindingValidationStatus<TARGET> doValidation() {
* @return the value context
*/
protected ValueContext createValueContext() {
return createValueContext(field);
return createValueContext(binder, field);
}

static ValueContext createValueContext(HasValue<?, ?> field) {
static ValueContext createValueContext(Binder binder,
HasValue<?, ?> field) {
if (field instanceof Component) {
return new ValueContext((Component) field, field);
return new ValueContext(binder, (Component) field, field);
}
return new ValueContext(null, field, findLocale());
return new ValueContext(binder, null, field, findLocale());
}

/**
Expand Down Expand Up @@ -2891,7 +2892,7 @@ private List<BindingValidationStatus<?>> validateBindings() {
private List<ValidationResult> validateBean(BEAN bean) {
Objects.requireNonNull(bean, "bean cannot be null");
return validators.stream()
.map(validator -> validator.apply(bean, new ValueContext()))
.map(validator -> validator.apply(bean, new ValueContext(this)))
.collect(Collectors.collectingAndThen(Collectors.toList(),
Collections::unmodifiableList));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,10 @@ public static boolean testConvertedDefaultValue(
Converter converter = ((BindingBuilderImpl<?, ?, ?>) binding)
.getConverterValidatorChain();

Binder<?> binder = ((BindingBuilderImpl<?, ?, ?>) binding)
.getBinder();
Result<?> result = converter.convertToModel(field.getEmptyValue(),
BindingImpl.createValueContext(field));
BindingImpl.createValueContext(binder, field));

if (!result.isError()) {
Object convertedEmptyValue = result
Expand Down
119 changes: 119 additions & 0 deletions flow-data/src/main/java/com/vaadin/flow/data/binder/ValueContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,111 @@
*/
public class ValueContext implements Serializable {

private final Binder<?> binder;
private final Component component;
private final HasValue<?, ?> hasValue;
private final Locale locale;

/**
* Constructor for {@code ValueContext} without a {@code Locale}.
*
* @param binder
* the Binder using the value context
*/
public ValueContext(Binder<?> binder) {
this.binder = binder;
component = null;
hasValue = null;
locale = findLocale(component);
}

/**
* Constructor for {@code ValueContext} without a {@code Component}.
*
* @param binder
* the Binder using the value context
* @param locale
* The locale used with conversion. Can be null.
*/
public ValueContext(Binder binder, Locale locale) {
this.binder = binder;
component = null;
this.locale = locale;
hasValue = null;
}

/**
* Constructor for {@code ValueContext}.
*
* @param binder
* the Binder using the value context
* @param component
* The component related to current value. Can be null. If the
* component implements {@link HasValue}, it will be returned by
* {@link #getHasValue()} as well.
*/
public ValueContext(Binder binder, Component component) {
this.binder = binder;
Objects.requireNonNull(component,
"Component can't be null in ValueContext construction");
this.component = component;
if (component instanceof HasValue) {
hasValue = (HasValue<?, ?>) component;
} else {
hasValue = null;
}
locale = findLocale(component);
}

/**
* Constructor for {@code ValueContext}.
*
* @param binder
* the Binder using the value context
* @param component
* The component related to current value. Can be null.
* @param hasValue
* The value source related to current value. Can be null.
*/
public ValueContext(Binder binder, Component component,
HasValue<?, ?> hasValue) {
this.binder = binder;
Objects.requireNonNull(component,
"Component can't be null in ValueContext construction");
this.component = component;
this.hasValue = hasValue;
locale = findLocale(component);
}

/**
* Constructor for {@code ValueContext}.
*
* @param binder
* the Binder using the value context
* @param component
* The component can be {@code null}.
* @param locale
* The locale used with conversion. Can be {@code null}.
* @param hasValue
* The value source related to current value. Can be
* {@code null}.
*/
public ValueContext(Binder binder, Component component,
HasValue<?, ?> hasValue, Locale locale) {
this.binder = binder;
this.component = component;
this.hasValue = hasValue;
this.locale = locale;
}

/**
* Constructor for {@code ValueContext} without a {@code Locale}.
*
* @deprecated Use the version with binder reference instead
*/
@Deprecated
public ValueContext() {
this.binder = null;
component = null;
hasValue = null;
locale = findLocale(component);
Expand All @@ -51,8 +148,11 @@ public ValueContext() {
*
* @param locale
* The locale used with conversion. Can be null.
* @deprecated Use the version with binder reference instead
*/
@Deprecated
public ValueContext(Locale locale) {
this.binder = null;
component = null;
this.locale = locale;
hasValue = null;
Expand All @@ -65,8 +165,11 @@ public ValueContext(Locale locale) {
* The component related to current value. Can be null. If the
* component implements {@link HasValue}, it will be returned by
* {@link #getHasValue()} as well.
* @deprecated Use the version with binder reference instead
*/
@Deprecated
public ValueContext(Component component) {
this.binder = null;
Objects.requireNonNull(component,
"Component can't be null in ValueContext construction");
this.component = component;
Expand All @@ -85,8 +188,11 @@ public ValueContext(Component component) {
* The component related to current value. Can be null.
* @param hasValue
* The value source related to current value. Can be null.
* @deprecated Use the version with binder reference instead
*/
@Deprecated
public ValueContext(Component component, HasValue<?, ?> hasValue) {
this.binder = null;
Objects.requireNonNull(component,
"Component can't be null in ValueContext construction");
this.component = component;
Expand All @@ -104,9 +210,12 @@ public ValueContext(Component component, HasValue<?, ?> hasValue) {
* @param hasValue
* The value source related to current value. Can be
* {@code null}.
* @deprecated Use the version with binder reference instead
*/
@Deprecated
public ValueContext(Component component, HasValue<?, ?> hasValue,
Locale locale) {
this.binder = null;
this.component = component;
this.hasValue = hasValue;
this.locale = locale;
Expand Down Expand Up @@ -157,4 +266,14 @@ public Optional<Locale> getLocale() {
public Optional<HasValue<?, ?>> getHasValue() {
return Optional.ofNullable(hasValue);
}

/**
* Returns an {@code Optional} for the {@code Binder} owning this value
* context.
*
* @return the optional of {@code Binder}
*/
public Optional<Binder<?>> getBinder() {
return Optional.ofNullable(binder);
}
}
Loading

0 comments on commit c6f5f92

Please sign in to comment.