Binding Data to Forms
In many applications, users provide structured data by completing fields in forms. This data is typically represented in code as an instance of a business object (JavaBean), for example a Person in an HR application.
The Binder
class allows you to define how the values in a business object are bound to fields in the UI.
Binder
reads the values in the business object and converts them from the format expected by the business object to the format expected by the field.
It also handles the reverse process, converting values from UI fields to the format expected by the business object.
Binder
can only bind components that implement the HasValue
interface, for example TextField
and ComboBox
.
It’s also possible to validate user input and present the validation status to the user in different ways.
How to Bind Form Data
The following steps include everything needed to load, edit, and save values for a form.
To bind data to a form:
-
Create a
Binder
and bind the input fields.NoteThere can be only one Binder
instance for each form. You should use this instance for all the fields in the form.Binder<Person> binder = new Binder<>(Person.class); TextField titleField = new TextField(); // Start by defining the Field instance to use binder.forField(titleField) // Finalize by doing the actual binding // to the Person class .bind( // Callback that loads the title // from a person instance Person::getTitle, // Callback that saves the title // in a person instance Person::setTitle); TextField nameField = new TextField(); // Shorthand for cases without extra configuration binder.bind(nameField, Person::getName, Person::setName);
-
Use the
Binder
to:-
load values from a
person
into the field; -
allow the user to edit the values;
-
save the values back into a
person
instance.// The person to edit // would be loaded from the backend // in a real application Person person = new Person("John Doe", 1957); // Updates the value in each bound field component binder.readBean(person); Button saveButton = new Button("Save", event -> { try { binder.writeBean(person); // A real application would also save // the updated person // using the application's backend } catch (ValidationException e) { notifyValidationException(e); } }); // Updates the fields again with the // previously saved values Button resetButton = new Button("Reset", event -> binder.readBean(person));
-
Every time
writeBean()
is called, the data is validated and then copied from the UI to the business object. -
If the data is invalid, a
ValidationException
is thrown, listing all the errors in the data. Hence,writeBean()
is enclosed in a try/catch block.
-
-
It’s also possible to use a lambda expression, instead of a method reference.
// With lambda expressions
binder.bind(titleField,
person -> person.getTitle(),
(person, title) -> {
person.setTitle(title);
logger.info("setTitle: {}", title);
});
Binding Read-Only Data
To bind a component to read-only data, you can use a null
value for the setter
.
Example: Using a null
value setter.
TextField fullName = new TextField();
binder.forField(fullName)
.bind(Person::getFullName, null);
To bind components that don’t implement the HasValue
interface to read-only data, you can use the ReadOnlyHasValue
helper class.
Example: Using the ReadOnlyHasValue
helper class.
Label fullNameLabel = new Label();
ReadOnlyHasValue<String> fullName =
new ReadOnlyHasValue<>(
text -> fullNameLabel.setText(text));
binder.forField(fullName)
.bind(Person::getFullName, null);
1BC1EEA8-75BE-4A79-AECA-4243E5FFDA30