* 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
[source, java] | [source, java] | ||||
---- | ---- | ||||
binder.addStatusChangeListener(event -> { | 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); | saveButton.setEnabled(hasChanges && isValid); | ||||
resetButton.setEnabled(hasChanges); | resetButton.setEnabled(hasChanges); |
fireStatusChangeEvent(validationStatus.hasErrors()); | fireStatusChangeEvent(validationStatus.hasErrors()); | ||||
return validationStatus; | 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 | * Validates the bindings and returns the result of the validation as a list |
* <p> | * <p> | ||||
* The {@link Binder} status is changed whenever any of the following happens: | * The {@link Binder} status is changed whenever any of the following happens: | ||||
* <ul> | * <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 | * <li>{@link Binder#writeBean(Object)} or | ||||
* {@link Binder#writeBeanIfValid(Object)} is called | * {@link Binder#writeBeanIfValid(Object)} is called | ||||
* <li>{@link Binder#readBean(Object)} is called | * <li>{@link Binder#readBean(Object)} is called | ||||
private final boolean hasValidationErrors; | 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 | * @param binder | ||||
* the event source binder | * the event source binder | ||||
* @param hasValidationErrors | * @param hasValidationErrors | ||||
* the binder validation status | |||||
* the validation status associated with this event | |||||
*/ | */ | ||||
public StatusChangeEvent(Binder<?> binder, boolean hasValidationErrors) { | public StatusChangeEvent(Binder<?> binder, boolean hasValidationErrors) { | ||||
super(binder); | super(binder); | ||||
} | } | ||||
/** | /** | ||||
* 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() { | public boolean hasValidationErrors() { | ||||
return hasValidationErrors; | return hasValidationErrors; |
AtomicBoolean eventIsFired = new AtomicBoolean(false); | AtomicBoolean eventIsFired = new AtomicBoolean(false); | ||||
binder.addStatusChangeListener(event -> { | binder.addStatusChangeListener(event -> { | ||||
boolean isValid = !event.hasValidationErrors(); | |||||
boolean isValid = event.getBinder().isValid(); | |||||
boolean hasChanges = event.getBinder().hasChanges(); | boolean hasChanges = event.getBinder().hasChanges(); | ||||
eventIsFired.set(true); | eventIsFired.set(true); | ||||
AtomicBoolean eventIsFired = new AtomicBoolean(false); | AtomicBoolean eventIsFired = new AtomicBoolean(false); | ||||
binder.addStatusChangeListener(event -> { | binder.addStatusChangeListener(event -> { | ||||
boolean isValid = !event.hasValidationErrors(); | |||||
boolean isValid = event.getBinder().isValid(); | |||||
boolean hasChanges = event.getBinder().hasChanges(); | boolean hasChanges = event.getBinder().hasChanges(); | ||||
eventIsFired.set(true); | eventIsFired.set(true); | ||||
verifyEventIsFired(eventIsFired); | 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) { | private void verifyEventIsFired(AtomicBoolean flag) { | ||||
Assert.assertTrue(flag.get()); | Assert.assertTrue(flag.get()); | ||||
flag.set(false); | flag.set(false); |
Assert.assertFalse(ageField.isReadOnly()); | 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(); | |||||
} | |||||
} | } |