From 5cc6b0e4e265783808ac258c830964ed7e888c34 Mon Sep 17 00:00:00 2001 From: Pekka Hyvönen Date: Mon, 12 Sep 2016 12:37:12 +0300 Subject: Clear binder errors after load/bind Adds an unresolved validation status to make it possible to clear visible field validation errors, even if status is not yet valid. Change-Id: I227a8802b6a71be1533dc903bad1a8e2faef5ed2 --- server/src/main/java/com/vaadin/data/Binder.java | 9 +++ .../com/vaadin/data/BinderValidationStatus.java | 22 +++++++ .../java/com/vaadin/data/ValidationStatus.java | 33 +++++++++- .../src/test/java/com/vaadin/data/BinderTest.java | 74 ++++++++++++++++++++++ 4 files changed, 135 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java index c282a89e77..f58c69bb7b 100644 --- a/server/src/main/java/com/vaadin/data/Binder.java +++ b/server/src/main/java/com/vaadin/data/Binder.java @@ -677,6 +677,10 @@ public class Binder implements Serializable { public Binding forField( HasValue field) { Objects.requireNonNull(field, "field cannot be null"); + // clear previous errors for this field and any bean level validation + clearError(field); + getStatusLabel().ifPresent(label -> label.setValue("")); + return createBinding(field, Converter.identity(), this::handleValidationStatusChange); } @@ -767,6 +771,8 @@ public class Binder implements Serializable { bean = null; bindings.forEach(BindingImpl::unbind); } + getStatusHandler() + .accept(BinderValidationStatus.createUnresolvedStatus(this)); } /** @@ -788,6 +794,9 @@ public class Binder implements Serializable { Objects.requireNonNull(bean, "bean cannot be null"); setHasChanges(false); bindings.forEach(binding -> binding.setFieldValue(bean)); + + getStatusHandler() + .accept(BinderValidationStatus.createUnresolvedStatus(this)); } /** diff --git a/server/src/main/java/com/vaadin/data/BinderValidationStatus.java b/server/src/main/java/com/vaadin/data/BinderValidationStatus.java index f23d17e5b1..8b589f574c 100644 --- a/server/src/main/java/com/vaadin/data/BinderValidationStatus.java +++ b/server/src/main/java/com/vaadin/data/BinderValidationStatus.java @@ -54,6 +54,28 @@ public class BinderValidationStatus implements Serializable { private final List> bindingStatuses; private final List> binderStatuses; + /** + * Convenience method for creating a unresolved validation status for the + * given binder. + *

+ * In practice this status means that the values might not be valid, but + * validation errors should be hidden. + * + * @param source + * the source binder + * @return a unresolved validation status + * @param + * the bean type of the binder + */ + public static BinderValidationStatus createUnresolvedStatus( + Binder source) { + return new BinderValidationStatus<>(source, + source.getBindings().stream() + .map(b -> ValidationStatus.createUnresolvedStatus(b)) + .collect(Collectors.toList()), + Collections.emptyList()); + } + /** * Creates a new binder validation status for the given binder and * validation results. diff --git a/server/src/main/java/com/vaadin/data/ValidationStatus.java b/server/src/main/java/com/vaadin/data/ValidationStatus.java index 7203655e7b..f4b74c9d2c 100644 --- a/server/src/main/java/com/vaadin/data/ValidationStatus.java +++ b/server/src/main/java/com/vaadin/data/ValidationStatus.java @@ -46,19 +46,44 @@ public class ValidationStatus implements Serializable { * Status of the validation. *

* The status is the part of {@link ValidationStatus} which indicates - * whether the validation failed or not. + * whether the validation failed or not, or whether it is in unresolved + * state (e.g. after clear or reset). */ public enum Status { /** Validation passed. */ OK, /** Validation failed. */ - ERROR + ERROR, + /** + * Unresolved status, e.g field has not yet been validated because value + * was cleared. + *

+ * In practice this status means that the value might be invalid, but + * validation errors should be hidden. + */ + UNRESOLVED; } private final Status status; private final Result result; private final Binding binding; + /** + * Convenience method for creating a {@link Status#UNRESOLVED} validation + * status for the given binding. + * + * @param source + * the source binding + * @return unresolved validation status + * @param + * the target data type of the binding for which the validation + * status was reset + */ + public static ValidationStatus createUnresolvedStatus( + Binding source) { + return new ValidationStatus<>(source, Status.UNRESOLVED, null); + } + /** * Creates a new validation status for the given binding and validation * result. @@ -91,7 +116,9 @@ public class ValidationStatus implements Serializable { Objects.requireNonNull(source, "Event source may not be null"); Objects.requireNonNull(status, "Status may not be null"); if (Objects.equals(status, Status.OK) && result.isError() - || Objects.equals(status, Status.ERROR) && !result.isError()) { + || Objects.equals(status, Status.ERROR) && !result.isError() + || Objects.equals(status, Status.UNRESOLVED) + && result != null) { throw new IllegalStateException( "Invalid validation status " + status + " for given result " + (result == null ? "null" : result.toString())); diff --git a/server/src/test/java/com/vaadin/data/BinderTest.java b/server/src/test/java/com/vaadin/data/BinderTest.java index 0a82b527bb..81b4d965b4 100644 --- a/server/src/test/java/com/vaadin/data/BinderTest.java +++ b/server/src/test/java/com/vaadin/data/BinderTest.java @@ -1196,4 +1196,78 @@ public class BinderTest { binder.saveIfValid(new Person()); Assert.assertTrue(binder.hasChanges()); } + + @Test + public void binderLoad_clearsErrors() { + Binding binding = binder.forField(nameField) + .withValidator(notEmpty); + binding.bind(Person::getFirstName, Person::setFirstName); + binder.withValidator(bean -> bean.getFirstName().contains("error") + ? Result.error("error") : Result.ok(bean)); + Person person = new Person(); + person.setFirstName(""); + binder.bind(person); + + // initial value is invalid but no error + Assert.assertNull(nameField.getComponentError()); + + // make error show + nameField.setValue("foo"); + nameField.setValue(""); + Assert.assertNotNull(nameField.getComponentError()); + + // bind to another person to see that error is cleared + person = new Person(); + person.setFirstName(""); + binder.bind(person); + // error has been cleared + Assert.assertNull(nameField.getComponentError()); + + // make show error + nameField.setValue("foo"); + nameField.setValue(""); + Assert.assertNotNull(nameField.getComponentError()); + + // load should also clear error + binder.load(person); + Assert.assertNull(nameField.getComponentError()); + + // bind a new field that has invalid value in bean + TextField lastNameField = new TextField(); + person.setLastName(""); + Binding binding2 = binder + .forField(lastNameField).withValidator(notEmpty); + binding2.bind(Person::getLastName, Person::setLastName); + + // should not have error shown + Assert.assertNull(lastNameField.getComponentError()); + + // add status label to show bean level error + Label statusLabel = new Label(); + binder.setStatusLabel(statusLabel); + nameField.setValue("error"); + + // no error shown yet because second field validation doesn't pass + Assert.assertEquals("", statusLabel.getValue()); + + // make second field validation pass to get bean validation error + lastNameField.setValue("foo"); + Assert.assertEquals("error", statusLabel.getValue()); + + // reload bean to clear error + binder.load(person); + Assert.assertEquals("", statusLabel.getValue()); + + // unbind() should clear all errors and status label + nameField.setValue(""); + lastNameField.setValue(""); + Assert.assertNotNull(nameField.getComponentError()); + Assert.assertNotNull(lastNameField.getComponentError()); + statusLabel.setComponentError(new UserError("ERROR")); + + binder.unbind(); + Assert.assertNull(nameField.getComponentError()); + Assert.assertNull(lastNameField.getComponentError()); + Assert.assertEquals("", statusLabel.getValue()); + } } -- cgit v1.2.3