Connecting Contact Form to Java
Now that the work with the grid is complete, you 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. You 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 you connect the fields from contact-form
, you need to use these exact names.
To do all of this, in Designer, open contact-form
. Select the first name field, give it the firstName
identifier attribute, and then connect it by clicking the Java icon in the outline. This connects the first name field with the firstName
id.
Repeat these steps for the other fields and buttons in the form:
-
Last name field:
lastName
identifier attribute. -
Email field:
email
identifier attribute. -
Company field:
company
identifier attribute. -
Status field:
status
identifier attribute. -
Save button:
save
identifier attribute. -
Delete button:
delete
identifier attribute. -
Close button:
close
identifier 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. You can get the list of companies and statuses from the ContactRepository
, and set the combo boxes using those lists. Second, you need to add getters for the save, delete, and close buttons so that you 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 & Form
Now you need to ensure that when a contact is selected in the grid, the form is populated with the details from this contact. You 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, you 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 you rerun the application, you’re 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 page and complete the tutorial: Wrap Up.
E42C1CDB-4F42-46C9-8388-4423B2E045D5