* Add isValid to Binder isValid enables you to check the current validation status of a given Binder, without firing events or invoking handlers. This patch also clarifies the javadocs of StatusChangeEvents' hasValidationErrors. * Throw in Binder.isValid if no bean is set and bean validators exist * Add test cases to BinderTesttags/8.0.0.rc1
@@ -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); |
@@ -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 |
@@ -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; |
@@ -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); |
@@ -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(); | |||
} | |||
} |