diff options
5 files changed, 118 insertions, 11 deletions
diff --git a/documentation/datamodel/datamodel-forms.asciidoc b/documentation/datamodel/datamodel-forms.asciidoc index 1fd79dd9b9..0218140425 100644 --- a/documentation/datamodel/datamodel-forms.asciidoc +++ b/documentation/datamodel/datamodel-forms.asciidoc @@ -375,8 +375,8 @@ We can use that event to make the save and reset buttons of our forms become ena [source, java] ---- binder.addStatusChangeListener(event -> { - boolean isValid = !event.hasValidationErrors(); - boolean hasChanges = binder.hasChanges(); + boolean isValid = event.getBinder().isValid(); + boolean hasChanges = event.getBinder().hasChanges(); saveButton.setEnabled(hasChanges && isValid); resetButton.setEnabled(hasChanges); diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java index d36c9996fd..5fa486319a 100644 --- a/server/src/main/java/com/vaadin/data/Binder.java +++ b/server/src/main/java/com/vaadin/data/Binder.java @@ -1579,6 +1579,35 @@ public class Binder<BEAN> implements Serializable { fireStatusChangeEvent(validationStatus.hasErrors()); return validationStatus; } + + /** + * Runs all currently configured field level validators, as well as all bean + * level validators if a bean is currently set with + * {@link #setBean(Object)}, and returns whether any of the validators + * failed. + * + * @return whether this binder is in a valid state + * @throws IllegalStateException + * if bean level validators have been configured and no bean is + * currently set + */ + public boolean isValid() { + if (getBean() == null && !validators.isEmpty()) { + throw new IllegalStateException( + "Cannot validate binder: " + + "bean level validators have been configured " + + "but no bean is currently set"); + } + if (validateBindings().stream().filter(BindingValidationStatus::isError) + .findAny().isPresent()) { + return false; + } + if (getBean() != null && validateBean(getBean()).stream() + .filter(ValidationResult::isError).findAny().isPresent()) { + return false; + } + return true; + } /** * Validates the bindings and returns the result of the validation as a list diff --git a/server/src/main/java/com/vaadin/data/StatusChangeEvent.java b/server/src/main/java/com/vaadin/data/StatusChangeEvent.java index e4db2b8868..acef1bac75 100644 --- a/server/src/main/java/com/vaadin/data/StatusChangeEvent.java +++ b/server/src/main/java/com/vaadin/data/StatusChangeEvent.java @@ -26,7 +26,7 @@ import com.vaadin.server.Setter; * <p> * The {@link Binder} status is changed whenever any of the following happens: * <ul> - * <li>if it's bound and any of its bound field or select has been changed + * <li>if any of its bound fields or selects have been changed * <li>{@link Binder#writeBean(Object)} or * {@link Binder#writeBeanIfValid(Object)} is called * <li>{@link Binder#readBean(Object)} is called @@ -47,13 +47,14 @@ public class StatusChangeEvent extends EventObject { private final boolean hasValidationErrors; /** - * Create a new status change event for given {@code binder} using its - * current validation status. + * Create a new status change event for given {@code binder}, storing + * information of whether the change that triggered this event caused + * validation errors. * * @param binder * the event source binder * @param hasValidationErrors - * the binder validation status + * the validation status associated with this event */ public StatusChangeEvent(Binder<?> binder, boolean hasValidationErrors) { super(binder); @@ -61,10 +62,10 @@ public class StatusChangeEvent extends EventObject { } /** - * Gets the binder validation status. + * Gets the associated validation status. * - * @return {@code true} if the binder has validation errors, {@code false} - * otherwise + * @return {@code true} if the change that triggered this event caused + * validation errors, {@code false} otherwise */ public boolean hasValidationErrors() { return hasValidationErrors; diff --git a/server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java b/server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java index 59ca890740..f36a357617 100644 --- a/server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java +++ b/server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java @@ -724,7 +724,7 @@ public class BinderBookOfVaadinTest { AtomicBoolean eventIsFired = new AtomicBoolean(false); binder.addStatusChangeListener(event -> { - boolean isValid = !event.hasValidationErrors(); + boolean isValid = event.getBinder().isValid(); boolean hasChanges = event.getBinder().hasChanges(); eventIsFired.set(true); @@ -782,7 +782,7 @@ public class BinderBookOfVaadinTest { AtomicBoolean eventIsFired = new AtomicBoolean(false); binder.addStatusChangeListener(event -> { - boolean isValid = !event.hasValidationErrors(); + boolean isValid = event.getBinder().isValid(); boolean hasChanges = event.getBinder().hasChanges(); eventIsFired.set(true); @@ -824,6 +824,31 @@ public class BinderBookOfVaadinTest { verifyEventIsFired(eventIsFired); } + @Test + public void statusChangeListener_multipleRequiredFields() { + Button saveButton = new Button(); + + binder.addStatusChangeListener(event -> { + boolean isValid = event.getBinder().isValid(); + boolean hasChanges = event.getBinder().hasChanges(); + + saveButton.setEnabled(hasChanges && isValid); + }); + + binder.forField(field).asRequired("").bind(BookPerson::getLastName, + BookPerson::setLastName); + binder.forField(emailField).asRequired("").bind(BookPerson::getEmail, + BookPerson::setEmail); + + Assert.assertFalse(saveButton.isEnabled()); + field.setValue("not empty"); + Assert.assertFalse(saveButton.isEnabled()); + emailField.setValue("not empty"); + Assert.assertTrue(saveButton.isEnabled()); + field.clear(); + Assert.assertFalse(saveButton.isEnabled()); + } + private void verifyEventIsFired(AtomicBoolean flag) { Assert.assertTrue(flag.get()); flag.set(false); diff --git a/server/src/test/java/com/vaadin/data/BinderTest.java b/server/src/test/java/com/vaadin/data/BinderTest.java index e07aee9632..8dc4c76c3b 100644 --- a/server/src/test/java/com/vaadin/data/BinderTest.java +++ b/server/src/test/java/com/vaadin/data/BinderTest.java @@ -494,4 +494,56 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> { Assert.assertFalse(ageField.isReadOnly()); } + @Test + public void isValidTest_bound_binder() { + binder.forField(nameField) + .withValidator( + Validator.from( + name -> !name.equals("fail field validation"), + "")) + .bind(Person::getFirstName, Person::setFirstName); + + binder.withValidator( + Validator.from(person -> !person.getFirstName() + .equals("fail bean validation"), "")); + + binder.setBean(item); + + Assert.assertTrue(binder.isValid()); + + nameField.setValue("fail field validation"); + Assert.assertFalse(binder.isValid()); + + nameField.setValue(""); + Assert.assertTrue(binder.isValid()); + + nameField.setValue("fail bean validation"); + Assert.assertFalse(binder.isValid()); + } + + @Test + public void isValidTest_unbound_binder() { + binder.forField(nameField) + .withValidator(Validator.from( + name -> !name.equals("fail field validation"), "")) + .bind(Person::getFirstName, Person::setFirstName); + + Assert.assertTrue(binder.isValid()); + + nameField.setValue("fail field validation"); + Assert.assertFalse(binder.isValid()); + + nameField.setValue(""); + Assert.assertTrue(binder.isValid()); + } + + @Test(expected = IllegalStateException.class) + public void isValidTest_unbound_binder_throws_with_bean_level_validation() { + binder.forField(nameField).bind(Person::getFirstName, + Person::setFirstName); + binder.withValidator(Validator.from( + person -> !person.getFirstName().equals("fail bean validation"), + "")); + binder.isValid(); + } }
\ No newline at end of file |