Docs

Documentation versions (currently viewingVaadin 24)

Binding Data to Forms

How to bind data to forms, which are composites with multiple fields that each bind to sub-structures in data.

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:

  1. Create a Binder and bind the input fields.

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