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, and vice versa.

Binder can only bind components that implement the HasValue interface, for example TextField and ComboBox.

It is 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. Java 8 method references are used.

To bind data to a form:

  1. Create a Binder and bind the input fields.

    Note
    There can only be one Binder instance for each form. You should use it for all 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);
  2. Use the Binder to:

    1. Load values from a person into the field.

    2. Allow the user to edit the values.

    3. 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 that includes all errors in the data, is thrown. This is the reason writeBean is in a try/catch block.

It is 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 do not 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);