Skip to content

Commit

Permalink
Add item level validator support to Binder
Browse files Browse the repository at this point in the history
An item level validator is run on the item (bean) after field validators
have passed. A failed item level validator will block save operations,
just like field level validators.

Change-Id: I3b918b33371ceef07cdfbd0a8b6d477d4ac26b85
  • Loading branch information
Denis Anisimov authored and Artur- committed Sep 2, 2016
1 parent 876b638 commit ccaabe6
Show file tree
Hide file tree
Showing 7 changed files with 582 additions and 98 deletions.
39 changes: 25 additions & 14 deletions documentation/datamodel/datamodel-forms.asciidoc
Expand Up @@ -584,30 +584,41 @@ binder.setStatusHandler((List<BinderResult> results) -> {
});
----

[classname]#BeanBinder# will automatically run bean-level validation based on the used bean instance if it has been bound using the [methodname]#bind# method.
We can add custom form validators to [classname]#Binder#. These will be run on the updated item instance (bean) after field validators have succeeded and the item has been updated. If item level validators fail, the values updated in the item instance will be reverted, i.e. the bean will temporarily contain new values but after a call to [methodname]#save# or [methodname]#saveIfValid#, the bean will only contain the new values if all validators passed.

If we are using the [methodname]#load# and [methodname]#save# methods, then the binder will not have any bean instance to use for bean-level validation.
We must use a copy of the bean for running bean-level validation if we want to make sure no changes are done to the original bean before we know that validation passes.
[classname]#BeanBinder# will automatically add bean-level validation based on the used bean instance and its annotations.

[source, java]
----
Button saveButton = new Button("Save", event -> {
// Create non-shared copy to use for validation
Person copy = new Person(person);
BeanBinder<Person> binder = new BeanBinder<Person>(
Person.class);
List<ValidationError<?>> errors = binder.validateWithBean(copy);
if (errors.isEmpty()) {
// Write new values to the actual bean
// Phone or email has to be specified for the bean
Validator<Person> phoneOrEmail = Validator.from(
personBean -> !"".equals(personBean.getPhone())
|| !"".equals(personBean.getEmail()),
"A person must have either a phone number or an email address");
binder.withValidator(phoneOrEmail);
// Using saveIfValid to avoid the try-catch block that is
// needed if using the regular save method
binder.saveIfValid(person);
binder.forField(emailField).bind("email");
binder.forField(phoneField).bind("phone");
// TODO: Do something with the updated person instance
Person person = // e.g. JPA entity or bean from Grid
// Load person data to a form
binder.load(person);
Button saveButton = new Button("Save", event -> {
// Using saveIfValid to avoid the try-catch block that is
// needed if using the regular save method
if (binder.saveIfValid(person)) {
// Person is valid and updated
// TODO Store in the database
}
})
});
----

If we want to ensure that the [classname]#Person# instance is not even temporarily updated, we should make a clone and use that with [methodname]#saveIfValid#.

== Using Binder with Vaadin Designer
We can use [classname]#Binder# to connect data to a form that is designed using Vaadin Designer.

Expand Down
6 changes: 6 additions & 0 deletions server/src/main/java/com/vaadin/data/BeanBinder.java
Expand Up @@ -310,6 +310,11 @@ public <FIELDVALUE> void bind(HasValue<FIELDVALUE> field,
forField(field).bind(propertyName);
}

@Override
public BeanBinder<BEAN> withValidator(Validator<? super BEAN> validator) {
return (BeanBinder<BEAN>) super.withValidator(validator);
}

@Override
protected <FIELDVALUE, TARGET> BeanBindingImpl<BEAN, FIELDVALUE, TARGET> createBinding(
HasValue<FIELDVALUE> field, Converter<FIELDVALUE, TARGET> converter,
Expand All @@ -318,4 +323,5 @@ protected <FIELDVALUE, TARGET> BeanBindingImpl<BEAN, FIELDVALUE, TARGET> createB
Objects.requireNonNull(converter, "converter cannot be null");
return new BeanBindingImpl<>(this, field, converter, handler);
}

}

0 comments on commit ccaabe6

Please sign in to comment.