diff options
Diffstat (limited to 'server/src/main')
9 files changed, 105 insertions, 38 deletions
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<BEAN> extends Binder<BEAN> { @Override public <FIELDVALUE> BeanBinding<BEAN, FIELDVALUE, FIELDVALUE> forField( HasValue<FIELDVALUE> field) { - return createBinding(field, Converter.identity(), - this::handleValidationStatus); + return (BeanBinding<BEAN, FIELDVALUE, FIELDVALUE>) 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 @@ -280,6 +280,23 @@ public class Binder<BEAN> implements Serializable { } /** + * 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<BEAN, FIELDVALUE, TARGET> withNullRepresentation( + TARGET nullRepresentation) { + return withConverter( + fieldValue -> Objects.equals(fieldValue, nullRepresentation) + ? null : fieldValue, + modelValue -> Objects.isNull(modelValue) + ? nullRepresentation : modelValue); + } + + /** * Gets the field the binding uses. * * @return the field for the binding @@ -615,10 +632,6 @@ public class Binder<BEAN> 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<BEAN> implements Serializable { * {@link Binding#bind(Function, BiConsumer) Binding.bind} which completes * the binding. Until {@code Binding.bind} is called, the binding has no * effect. + * <p> + * <strong>Note:</strong> 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 <FIELDVALUE> * the value type of the field @@ -710,7 +730,10 @@ public class Binder<BEAN> 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<BEAN> implements Serializable { * Use the {@link #forField(HasValue)} overload instead if you want to * further configure the new binding. * <p> + * <strong>Note:</strong> 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))}. + * <p> * 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<BEAN> implements Serializable { // Save old bean values so we can restore them if validators fail Map<Binding<BEAN, ?, ?>, 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<BEAN> 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<V> extends Serializable { */ public Registration addValueChangeListener( ValueChangeListener<? super V> listener); + + /** + * Returns the value that represents an empty value. + * <p> + * 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. + * <p> + * 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()); + } } diff --git a/server/src/main/java/com/vaadin/ui/AbstractColorPicker.java b/server/src/main/java/com/vaadin/ui/AbstractColorPicker.java index dd482a5bd0..f256636845 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractColorPicker.java +++ b/server/src/main/java/com/vaadin/ui/AbstractColorPicker.java @@ -534,4 +534,9 @@ public abstract class AbstractColorPicker extends AbstractField<Color> { this.color = color; getState().color = color.getCSS(); } + + @Override + public Color getEmptyValue() { + return Color.WHITE; + } } diff --git a/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java b/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java index 6c1fbde990..7332941f91 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java +++ b/server/src/main/java/com/vaadin/ui/AbstractMultiSelect.java @@ -342,7 +342,7 @@ public abstract class AbstractMultiSelect<T> * The call is delegated to {@link #getSelectedItems()} * * @return the current selection - * + * * @see #getSelectedItems() * @see SelectionModel#getSelectedItems */ @@ -378,11 +378,16 @@ public abstract class AbstractMultiSelect<T> new LinkedHashSet<>(getSelectionModel().getSelectedItems())); } + @Override + public Set<T> getEmptyValue() { + return Collections.emptySet(); + } + /** * Adds a value change listener. The listener is called when the selection * set of this multi select is changed either by the user or * programmatically. - * + * * @see #addSelectionListener(MultiSelectionListener) * * @param listener diff --git a/server/src/main/java/com/vaadin/ui/AbstractTextField.java b/server/src/main/java/com/vaadin/ui/AbstractTextField.java index b5119ec92c..9c2720efd8 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractTextField.java +++ b/server/src/main/java/com/vaadin/ui/AbstractTextField.java @@ -17,6 +17,7 @@ package com.vaadin.ui; import java.util.Collection; +import java.util.Objects; import org.jsoup.nodes.Attributes; import org.jsoup.nodes.Element; @@ -80,11 +81,8 @@ public abstract class AbstractTextField extends AbstractField<String> @Override public void setValue(String value) { - if (value == null) { - setValue("", false); - } else { - setValue(value, false); - } + Objects.requireNonNull(value, "Null value not supported"); + setValue(value, false); } /** @@ -270,16 +268,6 @@ public abstract class AbstractTextField extends AbstractField<String> setValue(""); } - /** - * Checks if the field is empty. - * - * @return <code>true</code> if the field value is an empty string, - * <code>false</code> otherwise - */ - public boolean isEmpty() { - return "".equals(getValue()); - } - @Override public void writeDesign(Element design, DesignContext designContext) { super.writeDesign(design, designContext); @@ -299,4 +287,9 @@ public abstract class AbstractTextField extends AbstractField<String> customAttributes.add("cursor-position"); return customAttributes; } + + @Override + public String getEmptyValue() { + return ""; + } } diff --git a/server/src/main/java/com/vaadin/ui/CheckBox.java b/server/src/main/java/com/vaadin/ui/CheckBox.java index 4fa08a82b5..c4bedbea0a 100644 --- a/server/src/main/java/com/vaadin/ui/CheckBox.java +++ b/server/src/main/java/com/vaadin/ui/CheckBox.java @@ -134,6 +134,11 @@ public class CheckBox extends AbstractField<Boolean> } @Override + public Boolean getEmptyValue() { + return false; + } + + @Override protected CheckBoxState getState() { return (CheckBoxState) super.getState(); } diff --git a/server/src/main/java/com/vaadin/ui/RichTextArea.java b/server/src/main/java/com/vaadin/ui/RichTextArea.java index 49346928be..545166d33a 100644 --- a/server/src/main/java/com/vaadin/ui/RichTextArea.java +++ b/server/src/main/java/com/vaadin/ui/RichTextArea.java @@ -115,6 +115,11 @@ public class RichTextArea extends AbstractField<String> } @Override + public String getEmptyValue() { + return ""; + } + + @Override protected void doSetValue(String value) { getState().value = value; } @@ -152,16 +157,6 @@ public class RichTextArea extends AbstractField<String> } /** - * Checks if the field is empty. - * - * @return <code>true</code> if the field value is an empty string, - * <code>false</code> otherwise - */ - public boolean isEmpty() { - return getValue().length() == 0; - } - - /** * Clears the value of this field. */ public void clear() { diff --git a/server/src/main/java/com/vaadin/ui/Slider.java b/server/src/main/java/com/vaadin/ui/Slider.java index 7a59b86fca..99ab476e2a 100644 --- a/server/src/main/java/com/vaadin/ui/Slider.java +++ b/server/src/main/java/com/vaadin/ui/Slider.java @@ -294,6 +294,11 @@ public class Slider extends AbstractField<Double> { return getState().value; } + @Override + public Double getEmptyValue() { + return getMin(); + } + /** * Thrown when the value of the slider is about to be set to a value that is * outside the valid range of the slider. |