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

feat:Improve Binder change detection #19488

Merged
merged 13 commits into from
Aug 23, 2024
Merged
64 changes: 63 additions & 1 deletion flow-data/src/main/java/com/vaadin/flow/data/binder/Binder.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import com.vaadin.flow.data.converter.StringToIntegerConverter;
import com.vaadin.flow.data.validator.BeanValidator;
import com.vaadin.flow.dom.Style;
import com.vaadin.flow.function.SerializableBiPredicate;
import com.vaadin.flow.function.SerializableConsumer;
import com.vaadin.flow.function.SerializableFunction;
import com.vaadin.flow.function.SerializablePredicate;
Expand Down Expand Up @@ -320,6 +321,8 @@ default BindingValidationStatus<TARGET> validate() {
* changes, otherwise {@literal false}.
*/
boolean hasChanges();

SerializableBiPredicate<Object, Object> getEqualityPredicate();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This misses Javadoc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the type be instead TARGET (data type of the binding), not Object ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

/**
Expand Down Expand Up @@ -949,6 +952,18 @@ BindingBuilder<BEAN, TARGET> asRequired(
*/
public BindingBuilder<BEAN, TARGET> asRequired(
Validator<TARGET> customRequiredValidator);

/**
* Sets the {@code equalityPredicate} used to compare the current value of a field with its initial value.
* <p>
* The default implementation uses {@link Objects#equals(Object, Object)}, but a custom equality predicate
* can be provided for cases where the value type does not support the standard {@code equals} method.
* </p>
*
* @param equalityPredicate the predicate to use for equality comparison
* @return this {@code BindingBuilder}, for method chaining
*/
public BindingBuilder<BEAN, TARGET> withEqualityPredicate(SerializableBiPredicate<Object, Object> equalityPredicate);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vote 4 default implementation; otherwise this is a breaking change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

/**
Expand Down Expand Up @@ -983,6 +998,13 @@ protected static class BindingBuilderImpl<BEAN, FIELDVALUE, TARGET>
private boolean asRequiredSet;

private Boolean defaultValidatorEnabled;

/**
* A predicate used to compare the current value of a field with its initial value.
* The default implementation is {@link Objects#equals(Object, Object)}
* but can be customized if the value type does not support equals
*/
private SerializableBiPredicate<Object, Object> equalityPredicate = (val1, val2) -> Objects.equals(val1, val2);

/**
* Creates a new binding builder associated with the given field.
Expand Down Expand Up @@ -1201,6 +1223,13 @@ public BindingBuilder<BEAN, TARGET> asRequired(
}
});
}

@Override
public BindingBuilder<BEAN, TARGET> withEqualityPredicate(SerializableBiPredicate<Object, Object> equalityPredicate) {
Objects.requireNonNull(equalityPredicate, "equality predicate cannot be null");
this.equalityPredicate = equalityPredicate;
return this;
}

/**
* Implements {@link #withConverter(Converter)} method with additional
Expand Down Expand Up @@ -1315,6 +1344,8 @@ protected static class BindingImpl<BEAN, FIELDVALUE, TARGET>
private Registration onValidationStatusChange;

private Boolean defaultValidatorEnabled;

private SerializableBiPredicate<Object, Object> equalityPredicate;

public BindingImpl(BindingBuilderImpl<BEAN, FIELDVALUE, TARGET> builder,
ValueProvider<BEAN, TARGET> getter,
Expand All @@ -1326,6 +1357,8 @@ public BindingImpl(BindingBuilderImpl<BEAN, FIELDVALUE, TARGET> builder,
converterValidatorChain = ((Converter<FIELDVALUE, TARGET>) builder.converterValidatorChain);

defaultValidatorEnabled = builder.defaultValidatorEnabled;

equalityPredicate = builder.equalityPredicate;

onValueChange = getField().addValueChangeListener(
event -> handleFieldValueChange(event));
Expand Down Expand Up @@ -1702,6 +1735,11 @@ public boolean hasChanges() throws IllegalStateException {

return this.binder.hasChanges(this);
}

@Override
public SerializableBiPredicate<Object, Object> getEqualityPredicate() {
return equalityPredicate;
}
}

/**
Expand Down Expand Up @@ -1917,6 +1955,30 @@ public static <BEAN> Binder<BEAN> withPropertySet(
return new Binder<>(propertySet);
}

/**
* Informs the Binder that a value in Binding was changed.
*
* If {@link #readBean(Object)} was used, this method will only validate the
* changed binding and ignore state of other bindings.
*
* If {@link #setBean(Object)} was used, all pending changed bindings will
* be validated and non-changed ones will be ignored. The changed value will
* be written to the bean immediately, assuming that Binder-level validators
* also pass.
*
* @param binding
* the binding whose value has been changed
*
* @Deprecated(since = "24.5", forRemoval = true)
*/
protected void handleFieldValueChange(Binding<BEAN, ?> binding) {
if (getBean() == null) {
binding.validate();
} else {
doWriteIfValid(getBean(), changedBindings);
}
}

/**
* Informs the Binder that a value in Binding was changed.
*
Expand Down Expand Up @@ -1962,7 +2024,7 @@ protected void handleFieldValueChange(Binding<BEAN, ?> binding, ValueChangeEvent
* @return true if the field value is reverted
*/
protected boolean isRevertedToInitialValue(Binding<BEAN, ?> binding, Object newValue) {
return Objects.equals(bindingInitialValuesMap.get(binding), newValue);
return binding.getEqualityPredicate().test(bindingInitialValuesMap.get(binding), newValue);
}

/**
Expand Down
Loading