Binding Data to Forms

A typical application lets the user fill out structured data and maybe also browse previously entered data. The data that is being entered is typically represented in code as an instance of a business object (bean), for instance aPerson# in an HR application.

Flow provides a Binder class that the developer can use to define how the values in a business object should be bound to the fields shown in the user interface. Binder takes care of reading values from the business object and converting the user’s data between the format expected by the business object and the format expected by the field. The input entered by the user can also be validated, and the current validation status can be presented to the user in different ways.

The first step to binding fields for a form is to create a Binder and bind some input fields. There is only one Binder instance for each form and it is used for all fields in that form.

Binder<Person> binder = new Binder<>();

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);

When we have bound field components using our binder, we can use the binder to load values from a person into the field, let the user edit the values and finally 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));

With these basic steps, we have defined everything that is needed for loading, editing and saving values for a form.

The above example uses Java 8 method references for defining how field values are loaded and saved. It is also possible to use a lambda expression or an explicit instance of the callback interface instead of a method reference.

// With lambda expressions
binder.bind(titleField,
  person -> person.getTitle(),
  (person, title) -> person.setTitle(title));

// With explicit callback interface instances
binder.bind(nameField,
  new ValueProvider<Person, String>() {
    @Override
    public String apply(Person person) {
      return person.getName();
    }
  },
  new Setter<Person, String>() {
    @Override
    public void accept(Person person, String name) {
      person.setName(name);
    }
  });

Binding Non-Modifiable Data

Non-modifiable data can be also bound to any component or component property with the ReadOnlyHasValue helper class. For example, we can bind a Label to display a person’s full name:

Label nameLabel = new Label();
ReadOnlyHasValue<Person> fullName = new ReadOnlyHasValue<>(
        person -> nameLabel.setText(
                person.getLastName() + ", " + person.getName()));
binder.forField(fullName).bind(person -> person, null);