Binding Beans to Forms
Business objects are typically implemented as JavaBeans in an application. Binder
supports binding the properties of a business object to UI components in your forms.
Manual Data Binding
You can use reflection based on bean property names to bind values. This reduces the amount of code needed when binding to fields in the bean.
Examples: Binding using reflection based on bean property names.
Java
Binder<Person> binder = new Binder<>(Person.class);
// Bind based on property name
binder.bind(nameField, "name");
// Bind based on sub property path
binder.bind(streetAddressField, "address.street");
// Bind using forField for additional configuration
binder.forField(yearOfBirthField)
.withConverter(
new StringToIntegerConverter(
"Please enter a number"))
.bind("yearOfBirth");
Note | Be cautious when using strings to identify properties. A typo in the string, or a subsequent changes to the setter and getter method names, will result in a runtime exception. |
Automatic Data Binding
The bindInstanceFields
method facilitates automatic data binding.
UI fields are typically defined as members of a UI Java class. This allows you to access the fields easily using the different methods made available by the class. In this scenario, binding the fields is also simple, because when you pass the object to the UI class, the bindInstanceFields
method matches the fields of the object to the properties of the related business object, based on their names.
Example: Using the bindInstanceFields
method to bind all fields in a UI class.
Java
public class MyForm extends VerticalLayout {
private TextField firstName =
new TextField("First name");
private TextField lastName =
new TextField("Last name");
private ComboBox<Gender> gender =
new ComboBox<>("Gender");
public MyForm() {
Binder<Person> binder =
new Binder<>(Person.class);
binder.bindInstanceFields(this);
}
}
- This binds the
firstName
text field to the “firstName” property in the item,lastName
text field to the “lastName” property, and thegender
combo box to the “gender” property.
Without this method, it would be necessary to bind each field separately.
Example: Binding each field separately.
Java
binder.forField(firstName)
.bind(Person::getFirstName, Person::setFirstName);
binder.forField(lastName)
.bind(Person::getLastName, Person::setLastName);
binder.forField(gender)
.bind(Person::getGender, Person::setGender);
Specifying Property Names
The bindInstanceFields
method processes all Java member fields with a type that extends HasValue
(such as TextField
) that can be mapped to a property name.
If the field name does not match the corresponding property name in the business object, you can use the @PropertyId
annotation to specify the property name.
Example: Using the @PropertyId
annotation to specify the “sex” property for the gender
field.
Java
@PropertyId("sex")
private ComboBox<Gender> gender =
new ComboBox<>("Gender");
Configuring Converters and Validators
When using the automatic bindInstanceFields
method to bind fields, all converters and validators must be configured beforehand using a special forMemberField
configurator. It works similar to the forField
method, but it requires no explicit call to a bind method. If the bindInstanceFields
method finds incompatible property-field pairs, it throws an IllegalStateException
.
Alternatively, you can bind properties that need validators manually and then bind all remaining fields using the bindInstanceFields
method. This method skips the properties that have already been bound manually.
Example: Manually specifying StringToIntegerConverter
before calling the bindInstanceFields
method.
Java
TextField yearOfBirthField =
new TextField("Year of birth");
binder.forField(yearOfBirthField)
.withConverter(
new StringToIntegerConverter("Must enter a number"))
.bind(Person::getYearOfBirth, Person::setYearOfBirth);
binder.bindInstanceFields(this);
If you use JSR-303 validators, you should use BeanValidationBinder
that picks validators automatically when using bindInstanceFields
.
Using JSR 303 Bean Validation
You can use BeanValidationBinder
if you prefer to use JSR 303 Bean Validation annotations such as Max
, Min
, Size
, etc.
BeanValidationBinder
extends Binder
(and therefore has the same API), but its implementation automatically adds validators based on JSR 303 constraints.
To use Bean Validation annotations, you need a JSR 303 implementation like Hibernate Validator available in your classpath. If your environment, such as Java EE container, does not provide the implementation, you can use the following dependency in Maven:
XML
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.1.Final</version>
</dependency>
Defining Constraints for Properties
Example: Using JSR 303 Bean Validation annotations with BeanValidationBinder
Java
public class Person {
@Max(2000)
private int yearOfBirth;
// Non-standard constraint provided by
// Hibernate Validator
@NotEmpty
private String name;
// + other fields, constructors, setters and getters
}
BeanValidationBinder<Person> binder =
new BeanValidationBinder<>(Person.class);
binder.bind(nameField, "name");
binder.forField(yearOfBirthField)
.withConverter(
new StringToIntegerConverter("Enter a number"))
.bind("yearOfBirth");
Constraints defined for properties in the bean, work in the same way as if configured programmatically when the binding is created. For example, the following code snippets have the same result:
Example: Declarative Bean Validation annotation.
Java
public class Person {
@Max(value = 2000, message =
"Year of Birth must be less than or equal to 2000")
private int yearOfBirth;
Example: Programmatic validation using Binder specific API.
Java
binder.forField(yearOfBirthField)
.withValidator(
yearOfBirth -> yearOfBirth <= 2000,
"Year of Birth must be less than or equal to 2000")
.bind(Person::getYearOfBirth, Person::setYearOfBirth);
Note | As an alternative to defining constraint annotations for specific properties, you can define constraints on the bean level, but Vaadin’s BeanValidationBinder does not currently support them. It simply ignores all JSR 303 validations that are not assigned directly to properties. |
Automatically Marking Form Fields as Required
Some built-in validators in the bean validation API suggest that a value is required in input field. BeanValidationBinder
automatically enables the visual “required” indicator using the HasValue.setRequiredIndicatorVisible(true)
method for properties annotated with such validators. By default, @NotNull
, @NotEmpty
and @Size
(if min()
value is greater than 0) configures the field as required. You can change this behavior using the BeanValidationBinder.setRequiredConfigurator
method.
Example: Overriding the default @Size
behavior.
Java
binder.setRequiredConfigurator(
RequiredFieldConfigurator.NOT_EMPTY
.chain(RequiredFieldConfigurator.NOT_NULL));