Documentation

Documentation versions (currently viewingVaadin 23)
New Acceleration Kits: Observability Kit, SSO Kit, and Swing Kit. Read the blog post.

Connecting Contact Form to Java

Now that the work with the grid is complete, we still need to configure the contact form, and bind it to the values displayed in the grid.

Configure the Contact Form

To make it possible to bind the fields of a contact bean to the form, the form’s fields must be present as members in the ContactForm class. We can add the fields to the class using Designer, but need to be careful with naming them because Vaadin binder works by matching the bean and field names. The Contact bean has fields named: firstName, lastName, email, company, and status. When we connect the fields from contact-form, we need to use these exact names.

  1. In Designer, open contact-form.

  2. Select the first name field, give it the firstName id attribute, and then connect it by clicking the Java icon in the outline. This connects the first name field with the firstName id.

  3. Repeat the procedure from step 2 above for the other fields and buttons in the form:

    1. Last name field = lastName id attribute.

    2. Email field = email id attribute.

    3. Company field = company id attribute.

    4. Status field = status id attribute.

    5. Save button = save id attribute.

    6. Delete button = delete id attribute.

    7. Close button = close id attribute.

Connect the form fields in Designer.

After this is done in Designer, you should have the following fields in the ContactForm class:

    @Id("firstName")
    private TextField firstName;
    @Id("lastName")
    private TextField lastName;
    @Id("email")
    private EmailField email;
    @Id("company")
    private ComboBox<String> company;
    @Id("status")
    private ComboBox<String> status;
    @Id("save")
    private Button save;
    @Id("delete")
    private Button delete;
    @Id("close")
    private Button close;

Two things must still be added in the ContactForm class. First, the items in the combo boxes are still not set. We can get the list of companies and statuses from the ContactRepository, and set the combo boxes using those lists. Second, we need to add getters for the save, delete, and close buttons so that we can use those from inside the MainView class to listen to events inside the form.

Here’s the full ContactForm class that implements the above changes:

@Tag("contact-form")
@JsModule("./src/views/contact-form.ts")
public class ContactForm extends LitTemplate {

    @Id("firstName")
    private TextField firstName;
    @Id("lastName")
    private TextField lastName;
    @Id("email")
    private EmailField email;
    @Id("company")
    private ComboBox<String> company;
    @Id("status")
    private ComboBox<String> status;
    @Id("save")
    private Button save;
    @Id("delete")
    private Button delete;
    @Id("close")
    private Button close;

    public ContactForm(ContactRepository contactRepository) { // (1)
        company.setItems(contactRepository.findDistinctCompanies()); // (2)
        status.setItems(contactRepository.findDistinctStatuses()); // (3)
    }

    public Button getSave() { // (4)
        return save;
    }

    public Button getDelete() {
        return delete;
    }

    public Button getClose() {
        return close;
    }
}
  1. Adds ContactRepository as a parameter. The Spring framework injects it here.

  2. Sets the company combo box items by fetching them from ContactRepository.

  3. Sets the status combo box items by fetching them from ContactRepository.

  4. Add getters for the save, delete, and close buttons.

Connect Grid and Form

Now we need to ensure that when a contact is selected in the grid, the form is populated with the details from this contact. We also need to ensure that any modifications made to the contact object inside the form are persisted in the backend and that the grid is updated accordingly.

To do this, we change the MainView class one last time, so that it looks as follows:

@Tag("main-view")
@JsModule("./src/views/main-view.ts")
@Route("")
public class MainView extends LitTemplate {

    @Id("filterText")
    private TextField filterText;
    @Id("addContactButton")
    private Button addContactButton;
    @Id("grid")
    private Grid<Contact> grid;
    @Id("contactForm")
    private ContactForm contactForm;

    private ContactRepository contactRepository;

    private Contact currentContact; // (1)

    private BeanValidationBinder<Contact> binder; // (2)

    public MainView(ContactRepository contactRepository) {
        this.contactRepository = contactRepository;

        grid.addColumn(Contact::getFirstName).setHeader("First name");
        grid.addColumn(Contact::getLastName).setHeader("Last name");
        grid.addColumn(Contact::getEmail).setHeader("Email");
        grid.addColumn(Contact::getCompany).setHeader("Company");
        grid.addColumn(Contact::getStatus).setHeader("Status");
        grid.getColumns().forEach(col -> col.setAutoWidth(true));
        updateList();

        filterText.setValueChangeMode(ValueChangeMode.LAZY);
        filterText.addValueChangeListener(e -> updateList());

        configureBinding(); // (3)
    }

    public void updateList() {
        String filterValue = filterText.getValue();
        if (filterValue == null || filterValue.isBlank()) {
            grid.setItems(contactRepository.findAll());
        } else {
            grid.setItems(contactRepository.findByFirstNameOrLastNameContainsIgnoreCase(filterValue, filterValue));
        }
    }

    private void configureBinding() {
        grid.asSingleSelect().addValueChangeListener(event -> {  // (4)
            Contact contact = event.getValue();
            if (contact != null) {
                populateForm(contact);
            } else {
                clearForm();
            }
        });

        binder = new BeanValidationBinder<>(Contact.class); // (5)
        binder.bindInstanceFields(contactForm); // (6)

        contactForm.getDelete().addClickListener(e -> { // (7)
            if (this.currentContact != null) {
                contactRepository.delete(this.currentContact); // (8)
                updateList();
                clearForm();
                Notification.show("Contact deleted.");
            }
        });

        contactForm.getClose().addClickListener(e -> { // (9)
            clearForm();
        });

        contactForm.getSave().addClickListener(e -> { // (10)
            try {
                if (this.currentContact == null) {
                    this.currentContact = new Contact();
                }
                binder.writeBean(this.currentContact); // (11)
                contactRepository.save(this.currentContact); // (12)
                updateList();
                clearForm();
                Notification.show("Contact details stored.");
            } catch (ValidationException validationException) {
                Notification.show("Please enter a valid contact details."); // (13)
            }
        });
    }

    void clearForm() {  // (14)
        populateForm(null);
    }

    void populateForm(Contact contact) {  // (15)
        this.currentContact = contact;
        binder.readBean(this.currentContact);
    }
}
  1. An object to hold the currently selected contact.

  2. A Vaadin Binder that uses reflection based on the provided Contact type to resolve bean properties. The Binder automatically adds BeanValidator which validates beans using Java Specification Requests (JSR) 303 specification.

  3. Initiate binding configuration.

  4. When a row is selected or deselected, populate or clear the form.

  5. Instantiate the binder.

  6. Bind the member fields found in the ContactForm object. This process is done automatically because the ContactForm object has member fields that are named identically to the fields found in the Contact bean.

  7. Add a click listener to the delete button of the contact form in which the delete operations are performed.

  8. Delete the currently selected contact from the backend, and refresh the grid afterwards.

  9. Add a click listener to the close button of the form in which the form is cleared without making any modifications to the contact object.

  10. Add a click listener to the save button of the contact form in which the save operations are performed.

  11. Writes changes from the bound form fields to the currentContact object if all validators pass. If any field binding validator fails, no values are written and a ValidationException is thrown.

  12. Save the currentContact object to the backend, after which update the grid and clear the form.

  13. Show a notification a ValidationException is thrown. This can occur, for example, if the user tries to save a contact with a blank email field.

  14. Clears the form

  15. Populates the form with the provided contact.

That’s all. Now if we rerun the application, we are able to see the form populated with the contact that was selected from the grid. Changes made to the form are now also updated in the backend and reflected in the grid.

Proceed to the last chapter and complete the tutorial: Wrap up.

E42C1CDB-4F42-46C9-8388-4423B2E045D5