Validating and Binding Data
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:
- Jakarta validation
- Using the
BindingContextclass
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:
-
Navigate to the top-level directory containing the
pom.xmlfile, this is5-validating-and-binding-dataif you're following along with the version on GitHub. -
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:
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:
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 declaredcontextas theBindingContext. 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 forCustomer. 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
// 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
// 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():
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.
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.
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.