From 09485d529d085a380b347a118b54df9ae30fefd0 Mon Sep 17 00:00:00 2001 From: Teemu Suo-Anttila Date: Wed, 19 Oct 2016 14:54:33 +0300 Subject: Initial support for null representations in Binder Change-Id: If40bfa28764d1399b5ed4d5928988560e9989dce --- .../src/main/java/com/vaadin/data/BeanBinder.java | 4 +- server/src/main/java/com/vaadin/data/Binder.java | 49 ++++++++++++++++++---- server/src/main/java/com/vaadin/data/HasValue.java | 28 +++++++++++++ 3 files changed, 70 insertions(+), 11 deletions(-) (limited to 'server/src/main/java/com/vaadin/data') diff --git a/server/src/main/java/com/vaadin/data/BeanBinder.java b/server/src/main/java/com/vaadin/data/BeanBinder.java index d8e8c0450d..96654e37a0 100644 --- a/server/src/main/java/com/vaadin/data/BeanBinder.java +++ b/server/src/main/java/com/vaadin/data/BeanBinder.java @@ -266,8 +266,8 @@ public class BeanBinder extends Binder { @Override public BeanBinding forField( HasValue field) { - return createBinding(field, Converter.identity(), - this::handleValidationStatus); + return (BeanBinding) super.forField( + field); } /** diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java index 1dfed366ba..8356a03dda 100644 --- a/server/src/main/java/com/vaadin/data/Binder.java +++ b/server/src/main/java/com/vaadin/data/Binder.java @@ -279,6 +279,23 @@ public class Binder implements Serializable { exception -> errorMessage)); } + /** + * Maps binding value {@code null} to given null representation and back + * to {@code null} when converting back to model value. + * + * @param nullRepresentation + * the value to use instead of {@code null} + * @return a new binding with null representation handling. + */ + public default Binding withNullRepresentation( + TARGET nullRepresentation) { + return withConverter( + fieldValue -> Objects.equals(fieldValue, nullRepresentation) + ? null : fieldValue, + modelValue -> Objects.isNull(modelValue) + ? nullRepresentation : modelValue); + } + /** * Gets the field the binding uses. * @@ -615,10 +632,6 @@ public class Binder implements Serializable { return validationStatus; } - private void setBeanValue(BEAN bean, TARGET value) { - setter.accept(bean, value); - } - private void notifyStatusHandler(ValidationStatus status) { statusHandler.accept(status); } @@ -694,6 +707,13 @@ public class Binder implements Serializable { * {@link Binding#bind(Function, BiConsumer) Binding.bind} which completes * the binding. Until {@code Binding.bind} is called, the binding has no * effect. + *

+ * Note: Not all {@link HasValue} implementations support + * passing {@code null} as the value. For these the Binder will + * automatically change {@code null} to a null representation provided by + * {@link HasValue#getEmptyValue()}. This conversion is one-way only, if you + * want to have a two-way mapping back to {@code null}, use + * {@link Binding#withNullRepresentation(Object))}. * * @param * the value type of the field @@ -710,7 +730,10 @@ public class Binder implements Serializable { clearError(field); getStatusLabel().ifPresent(label -> label.setValue("")); - return createBinding(field, Converter.identity(), + return createBinding(field, Converter.from(fieldValue -> fieldValue, + modelValue -> Objects.isNull(modelValue) ? field.getEmptyValue() + : modelValue, + exception -> exception.getMessage()), this::handleValidationStatus); } @@ -722,6 +745,14 @@ public class Binder implements Serializable { * Use the {@link #forField(HasValue)} overload instead if you want to * further configure the new binding. *

+ * Note: Not all {@link HasValue} implementations support + * passing {@code null} as the value. For these the Binder will + * automatically change {@code null} to a null representation provided by + * {@link HasValue#getEmptyValue()}. This conversion is one-way only, if you + * want to have a two-way mapping back to {@code null}, use + * {@link #forField(HasValue)} and + * {@link Binding#withNullRepresentation(Object))}. + *

* When a bean is bound with {@link Binder#bind(BEAN)}, the field value is * set to the return value of the given getter. The property value is then * updated via the given setter whenever the field value changes. The setter @@ -905,8 +936,8 @@ public class Binder implements Serializable { // Save old bean values so we can restore them if validators fail Map, Object> oldValues = new HashMap<>(); - bindings.forEach(binding -> oldValues.put(binding, - binding.convertDataToFieldType(bean))); + bindings.forEach( + binding -> oldValues.put(binding, binding.getter.apply(bean))); bindings.forEach(binding -> binding.storeFieldValue(bean)); // Now run bean level validation against the updated bean @@ -915,8 +946,8 @@ public class Binder implements Serializable { .findAny().isPresent(); if (hasErrors) { // Bean validator failed, revert values - bindings.forEach((BindingImpl binding) -> binding.setBeanValue(bean, - oldValues.get(binding))); + bindings.forEach((BindingImpl binding) -> binding.setter + .accept(bean, oldValues.get(binding))); } else { // Save successful, reset hasChanges to false setHasChanges(false); diff --git a/server/src/main/java/com/vaadin/data/HasValue.java b/server/src/main/java/com/vaadin/data/HasValue.java index 89d8d69e66..0ad3203b78 100644 --- a/server/src/main/java/com/vaadin/data/HasValue.java +++ b/server/src/main/java/com/vaadin/data/HasValue.java @@ -17,6 +17,9 @@ package com.vaadin.data; import java.io.Serializable; import java.lang.reflect.Method; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Function; import com.vaadin.event.ConnectorEvent; import com.vaadin.event.EventListener; @@ -170,4 +173,29 @@ public interface HasValue extends Serializable { */ public Registration addValueChangeListener( ValueChangeListener listener); + + /** + * Returns the value that represents an empty value. + *

+ * By default {@link HasValue} is expected to support {@code null} as empty + * values. Specific implementations might not support this. + * + * @return empty value + * @see Binder#bind(HasValue, Function, BiConsumer) + */ + public default V getEmptyValue() { + return null; + } + + /** + * Returns whether this {@code HasValue} is considered to be empty. + *

+ * By default this is an equality check between current value and empty + * value. + * + * @return {@code true} if considered empty; {@code false} if not + */ + public default boolean isEmpty() { + return Objects.equals(getValue(), getEmptyValue()); + } } -- cgit v1.2.3