diff --git a/flow-data/src/main/java/com/vaadin/flow/data/binder/Binder.java b/flow-data/src/main/java/com/vaadin/flow/data/binder/Binder.java index 30d245fe1ed..8ed78a6606c 100644 --- a/flow-data/src/main/java/com/vaadin/flow/data/binder/Binder.java +++ b/flow-data/src/main/java/com/vaadin/flow/data/binder/Binder.java @@ -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; @@ -320,6 +321,8 @@ default BindingValidationStatus validate() { * changes, otherwise {@literal false}. */ boolean hasChanges(); + + SerializableBiPredicate getEqualityPredicate(); } /** @@ -949,6 +952,18 @@ BindingBuilder asRequired( */ public BindingBuilder asRequired( Validator customRequiredValidator); + + /** + * Sets the {@code equalityPredicate} used to compare the current value of a field with its initial value. + *

+ * 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. + *

+ * + * @param equalityPredicate the predicate to use for equality comparison + * @return this {@code BindingBuilder}, for method chaining + */ + public BindingBuilder withEqualityPredicate(SerializableBiPredicate equalityPredicate); } /** @@ -983,6 +998,13 @@ protected static class BindingBuilderImpl 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 equalityPredicate = (val1, val2) -> Objects.equals(val1, val2); /** * Creates a new binding builder associated with the given field. @@ -1201,6 +1223,13 @@ public BindingBuilder asRequired( } }); } + + @Override + public BindingBuilder withEqualityPredicate(SerializableBiPredicate equalityPredicate) { + Objects.requireNonNull(equalityPredicate, "equality predicate cannot be null"); + this.equalityPredicate = equalityPredicate; + return this; + } /** * Implements {@link #withConverter(Converter)} method with additional @@ -1315,6 +1344,8 @@ protected static class BindingImpl private Registration onValidationStatusChange; private Boolean defaultValidatorEnabled; + + private SerializableBiPredicate equalityPredicate; public BindingImpl(BindingBuilderImpl builder, ValueProvider getter, @@ -1326,6 +1357,8 @@ public BindingImpl(BindingBuilderImpl builder, converterValidatorChain = ((Converter) builder.converterValidatorChain); defaultValidatorEnabled = builder.defaultValidatorEnabled; + + equalityPredicate = builder.equalityPredicate; onValueChange = getField().addValueChangeListener( event -> handleFieldValueChange(event)); @@ -1702,6 +1735,11 @@ public boolean hasChanges() throws IllegalStateException { return this.binder.hasChanges(this); } + + @Override + public SerializableBiPredicate getEqualityPredicate() { + return equalityPredicate; + } } /** @@ -1917,6 +1955,30 @@ public static Binder 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 binding) { + if (getBean() == null) { + binding.validate(); + } else { + doWriteIfValid(getBean(), changedBindings); + } + } + /** * Informs the Binder that a value in Binding was changed. * @@ -1962,7 +2024,7 @@ protected void handleFieldValueChange(Binding binding, ValueChangeEvent * @return true if the field value is reverted */ protected boolean isRevertedToInitialValue(Binding binding, Object newValue) { - return Objects.equals(bindingInitialValuesMap.get(binding), newValue); + return binding.getEqualityPredicate().test(bindingInitialValuesMap.get(binding), newValue); } /**