Loading from and Saving to Business Objects

Once all bindings have been set up, you are ready to actually fill the bound UI components with data from your business object. Changes can be written to the business object automatically or manually.

Writing the changes automatically when the user makes any change through the UI is often the most convenient option, but it might have undesirable side effects – the user may see unsaved changes if some other part of the application uses the same business object instance. To prevent that, you either need to use a copy of the edited object or use manual writing to only update the object when the user wants to save.

Manual Reading and Writing

The readBean method reads values from a business object instance into the UI components.

Person person = new Person("John Doe", 1957);

binder.readBean(person);

Assuming binder has already been configured as in previous examples with a TextField bound to the name property, this example would show the value "John Doe" in that field.

To avoid showing lots of errors to the user, validation errors are not shown until the user edits each field after the form has been bound or loaded.

Even if the user has not edited a field, all validation errors will be shown if we explicitly validate the form or try to save the values to a business object.

// This will make all current validation errors visible
BinderValidationStatus<Person> status = binder.validate();

if (status.hasErrors()) {
  notifyValidationErrors(status.getValidationErrors());
}

Trying to write the field values to a business object will fail if any of the bound fields has an invalid value. There are different methods that let us choose how to structure the code for dealing with invalid values.

Handling a checked exception
try {
  binder.writeBean(person);
  MyBackend.updatePersonInDatabase(person);
} catch (ValidationException e) {
  notifyValidationErrors(e.getValidationErrors());
}
Checking a return value
boolean saved = binder.writeBeanIfValid(person);
if (saved) {
  MyBackend.updatePersonInDatabase(person);
} else {
  notifyValidationErrors(binder.validate().getValidationErrors());
}

Binder keeps track of which bindings have been updated by the user and which bindings are in an invalid state. It also fires an event when this status changes. We can use that event to make the save and reset buttons of our forms become enabled or disabled depending on the current status of the form.

binder.addStatusChangeListener(event -> {
  boolean isValid = event.getBinder().isValid();
  boolean hasChanges = event.getBinder().hasChanges();

  saveButton.setEnabled(!hasChanges || !isValid);
  resetButton.setEnabled(!hasChanges);
});

Automatic Saving

Instead of manually saving field values to a business object instance, we can also bind the values directly to an instance. In this way, Binder takes care of automatically saving values from the fields.

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

// Field binding configuration omitted, it should be done here

Person person = new Person("John Doe", 1957);

// Loads the values from the person instance
// Sets person to be updated when any bound field is updated
binder.setBean(person);

Button saveButton = new Button("Save", event -> {
  if (binder.validate().isOk()) {
    // person is always up-to-date as long as there are no
    // validation errors

    MyBackend.updatePersonInDatabase(person);
  }
});
Warning
When using the setBean method, the business object instance will be updated whenever the user changes the value in any bound field. If some other part of the application is also using the same instance, then that part might show changes before the user has clicked the save button.