Skip to main content

Validating and Binding Data

Open in ChatGPT

Your app from Observers and Route Parameters can use FormView to edit existing customer data. This step uses Data binding, which connects UI components directly to the data model for automatic value synchronization. This reduces boilerplate in your app and lets you add validation checks to the Spring entity Customer, making your users provide complete and accurate information when filling out forms. This step covers the following concepts:

Completing this step creates a version of 5-validating-and-binding-data.

Running the app

As you develop your app, you can use 5-validating-and-binding-data as a comparison. To see the app in action:

  1. Navigate to the top-level directory containing the pom.xml file, this is 5-validating-and-binding-data if you're following along with the version on GitHub.

  2. Use the following Maven command to run the Spring Boot app locally:

    mvn

Running the app automatically opens a new browser at http://localhost:8080.

Defining validation rules

Developing an app with editable data should include validation. Validation checks help maintain meaningful and accurate user-submitted data. If left unchecked, it could lead to issues, so it’s important to catch the kinds of errors users can make when filling out a form in real time.

Since what’s considered valid can differ between properties, you'll need to define what makes each property valid and inform the user if there's something that's invalid. Fortunately, you can easily do this with Jakarta Validation. Jakarta validation allows you to add constraints to properties as annotations.

This tutorial uses two Jakarta annotations, @NotEmpty and @Pattern. @NotEmpty checks for null and empty strings, while @Pattern checks if the property matches a regular expression that you set. Both annotations allow you to add a message to display when the property becomes invalid.

To require that both first and last names are mandatory and contain only letters, while making the company name optional and allowing letters, numbers, and spaces, apply the following annotations to the Customer entity:

Customer.java

@NotEmpty(message = "Customer first name is required")
@Pattern(regexp = "[a-zA-Z]*", message = "Invalid characters")
private String firstName = "";

@NotEmpty(message = "Customer last name is required")
@Pattern(regexp = "[a-zA-Z]*", message = "Invalid characters")
private String lastName = "";

@Pattern(regexp = "[a-zA-Z0-9 ]*", message = "Invalid characters")
private String company = "";

private Country country = Country.UNKNOWN;

public enum Country {
UNKNOWN,

See the Jakarta Bean Validation constraints reference for a full list of validations, or learn more from the webforJ Jakarta Validation article.

Binding the fields

To use the validation checks in Customer for the UI in FormView, you’ll make a BindingContext for data binding. Before data binding, each field in FormView required an event listener to sync with a Spring entity Customer manually. Creating a BindingContext in FormView binds and automatically syncs the Customer data model to the UI components.

Creating a BindingContext

An instance of BindingContext needs the Spring bean that the bindings are synchronized with. In FormView, declare a BindingContext using the Customer entity:

FormView.java
public class FormView extends Composite<Div> implements WillEnterObserver {
private final CustomerService customerService;

private BindingContext<Customer> context;

Customer customer = new Customer();

Then, to automatically bind UI components to bean properties based on their names, use BindingContext.of() with the following parameters:

  • this : Earlier, you declared context as the BindingContext. The first parameter sets what object contains the bindable components.
  • Customer.class : The second parameter is the class of the bean to use for binding.
  • true : The third parameter enables Jakarta validation, allowing the context to use the validations you set for Customer. Doing this will change the style of invalid components and display the set messages.

All together, it'll look like the following line of code:

context = BindingContext.of(this, Customer.class, true);

Making the form responsive

With data binding, your app now automatically performs validation checks. By adding an event listener to the checks, you can prevent users from submitting an invalid form. Add the following to make the submit button active only when the form's valid:

context = BindingContext.of(this, Customer.class, true);
context.onValidate(e -> submit.setEnabled(e.isValid()));

Removing event listeners for components

Every UI change is now automatically synced with the BindingContext. This means you can now remove the event listeners to each field:

Before

FormView.java
// Without data binding
TextField firstName = new TextField("First Name", e -> customer.setFirstName(e.getValue()));
TextField lastName = new TextField("Last Name", e -> customer.setLastName(e.getValue()));
TextField company = new TextField("Company", e -> customer.setCompany(e.getValue()));
ChoiceBox country = new ChoiceBox("Country",
e -> customer.setCountry(Country.valueOf(e.getSelectedItem().getText())));

After

FormView.java
// With data binding
TextField firstName = new TextField("First Name");
TextField lastName = new TextField("Last Name");
TextField company = new TextField("Company");
ChoiceBox country = new ChoiceBox("Country");

Binding by property names

Since each component's name matched to the data model, webforJ applied Automatic Binding. If the names didn't match, you could use the @UseProperty annotation to map them.

@UseProperty("firstName")
TextField firstNameField = new TextField("First Name");

Reading data in the fillForm() method

Previously, in the fillForm() method, you initialized each component's value by manually retrieving the data from the Customer copy. But now, since you’re using a BindingContext, you can use the read() method. This method fills each bound component with the associated property from the data in the Customer copy.

In the fillForm() method, replace the setValue() methods with read():

FormView.java
public void fillForm(Long customerId) {
customer = customerService.getCustomerByKey(customerId);

// Removed each setValue() method for the UI components

context.read(customer);
}

Adding validation to submitCustomer()

The last change to FormView for this step will be adding a safeguard to the submitCustomer() method. Before committing changes to the H2 database, the app will perform a final validation on the results of the bound context using the write() method.

The write() method updates a bean's properties using the bound UI components in the BindingContext and returns a ValidationResult.

Use the write() method to write to the Customer copy using the bound components in FormView. Then, if the returned ValidationResult is valid, update the H2 database using the written data.

FormView.java
private void submitCustomer() {
ValidationResult results = context.write(customer);
if (results.isValid()) {
if (customerService.doesCustomerExist(customerId)) {
customerService.updateCustomer(customer);
} else {
customerService.createCustomer(customer);
}
navigateToMain();
}
}

Completed FormView

With these changes, here's what FormView looks like. The app now supports data binding and validation using Spring Boot and webforJ. Form inputs are automatically synchronized with the model and checked against validation rules.

FormView.java
@Route("customer/:id?<[0-9]+>")
@FrameTitle("Customer Form")
public class FormView extends Composite<Div> implements WillEnterObserver {
private final CustomerService customerService;
private BindingContext<Customer> context;
private Customer customer = new Customer();
private Long customerId = 0L;
private Div self = getBoundComponent();
private TextField firstName = new TextField("First Name");
private TextField lastName = new TextField("Last Name");
private TextField company = new TextField("Company");
private ChoiceBox country = new ChoiceBox("Country");
private Button submit = new Button("Submit", ButtonTheme.PRIMARY, e -> submitCustomer());
private Button cancel = new Button("Cancel", ButtonTheme.OUTLINED_PRIMARY, e -> navigateToMain());
private ColumnsLayout layout = new ColumnsLayout(
Next steps

Looking for more ways to improve your app from this tutorial? You can try using the AppLayout component as a wrapper to add your customer table and add more features.