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.
-
In Designer, open
contact-form
. -
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 thefirstName
id. -
Repeat the procedure from step 2 above for the other fields and buttons in the form:
-
Last name field =
lastName
id attribute. -
Email field =
email
id attribute. -
Company field =
company
id attribute. -
Status field =
status
id attribute. -
Save button =
save
id attribute. -
Delete button =
delete
id attribute. -
Close button =
close
id attribute.
-
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;
}
}
-
Adds
ContactRepository
as a parameter. The Spring framework injects it here. -
Sets the
company
combo box items by fetching them fromContactRepository
. -
Sets the
status
combo box items by fetching them fromContactRepository
. -
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);
}
}
-
An object to hold the currently selected contact.
-
A Vaadin
Binder
that uses reflection based on the providedContact
type to resolve bean properties. The Binder automatically addsBeanValidator
which validates beans using Java Specification Requests (JSR) 303 specification. -
Initiate binding configuration.
-
When a row is selected or deselected, populate or clear the form.
-
Instantiate the binder.
-
Bind the member fields found in the
ContactForm
object. This process is done automatically because theContactForm
object has member fields that are named identically to the fields found in theContact
bean. -
Add a click listener to the delete button of the contact form in which the delete operations are performed.
-
Delete the currently selected contact from the backend, and refresh the grid afterwards.
-
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.
-
Add a click listener to the save button of the contact form in which the save operations are performed.
-
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 aValidationException
is thrown. -
Save the
currentContact
object to the backend, after which update the grid and clear the form. -
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. -
Clears the form
-
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