[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);
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
* <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
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);
}
/**
- * 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;
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);
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);
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);
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