Demonstration: Address Book
This demonstration brings together all the topics covered in the previous chapters. The focus of this chapter is on using inter-portlet communication between two Vaadin Portlets. Using this document along with the demonstration code, you will be able to understand the demonstration code and build similar applications yourself.
The learning goals are:
-
Why events are needed
-
How to send events from one Vaadin Portlet to another
-
How to listen for events from other Vaadin Portlets
-
How to update the portlet’s state based on the event
The source code with a complete project for this example is available in
Address Book Project Structure
The address book demonstration shows how to build a simple address book application using Vaadin Portlets. The address book consists of two portlets. One portlet, Contact List, displays all the available contacts the user has. The other portlet, Contact Form, shows information about the contact selected via the contact list. The Contact Form portlet also allows the user to edit an existing contact.
The project is structured as a multi-module project and has the following four modules:
-
addressbook-backend
Holds the shared data layer for both portlets. -
addressbook-bundle
Builds the frontend resource bundle for both portlets. -
addressbook-form
Contact information portlet. -
addressbook-grid
Contact list portlet. -
portal
Configuration for automatically setting up and running the Pluto portal using the Cargo plugin.
In this chapter, we focus on portlet implementations. To get more familiar with setting up and maintaining a multi-module Vaadin Portlet project, take a look at the next chapter, Creating a Multi-Module Portlet Project.
Structure
The address book consists of two portlets and their view components.
The classes ending in Portlet
extend VaadinPortlet
/VaadinLiferayPortlet
, and classes ending in View
are the view component classes for the portlets.
-
Contact List:
ContactListPortlet
andContactListView
(in theaddressbook-grid
module)Responsible for displaying the available contacts in the address book. Allows the user to select a contact to be displayed in the Contact Form.
-
Contact Form:
ContactFormPortlet
andContactFormView
(in theaddressbook-form
module)Responsible for displaying and modifying the contact information in the address book.
The other classes are there to provide business logic and mock implementations for data services
Functionality Requirements as Portlets
Contact List
The portlet operates in only one portlet mode – view
– and the user cannot change the mode.
When the user selects a contact from the list, that contact’s details are displayed on the Contact Form portlet.
When the contact’s information is changed in the Contact Form portlet, the relevant contact information is updated on the list.
The list shows a limited number of contact attributes, including first name, last name, and phone number.
When the portlet is maximized (its window state is changed to MAXIMIZED), more attributes are shown, including email and birthday.
In order to provide this functionality, the Contact List's view class needs to be able to
-
receive events
-
send events
-
react to window state changes
These requirements can be fulfilled by implementing the PortletView
interface.
Contact Form
The portlet supports two portlet modes: view
and edit
.
When the portlet is in view
mode, the contact information is only displayed and cannot be changed.
When user changes the portlet to edit
mode, the information can be updated.
After editing the information, the user can either save or discard the changes.
In order to provide this functionality, the Contact List view class needs to be able to
-
react to portlet mode changes
-
receive events
-
send events
These requirements can be fulfilled by implementing the PortletView
interface.
Note
|
We use PortletView in both view classes, as it provides a single entry point to the portlet state.
We can both register event listeners and mutate the portlet’s state through the PortletViewContext object.
However, if we were interested in only very particular types of updates and did not need to change the state, we could simply implement the PortletModeHandler , WindowStateHandler , or EventHandler interfaces
|
Implementation Details
In this section, we have a look at selected parts of the view classes ListPortletView
and FormPortletView
.
The classes contain code that is not directly related to the portlet implementation and we will skip those parts.
public class ContactListView extends VerticalLayout implements PortletView {
private ListDataProvider<Contact> dataProvider;
private Grid<Contact> grid = new Grid<>(Contact.class);
private PortletViewContext portletViewContext;
@Override
public void onPortletViewContextInit(PortletViewContext context) {
// save context for sending events
portletViewContext = context;
// add event listeners for both "contact-updated" custom event
// and window state change event
context.addEventChangeListener("contact-updated",
this::onContactUpdated);
context.addWindowStateChangeListener(
event -> handleWindowStateChanged(event.getWindowState()));
init();
}
private void onContactUpdated(PortletEvent event) {
int contactId = Integer
.parseInt(event.getParameters().get("contactId")[0]);
// retrieve the contact information from contact service
Optional<Contact> contact = getService()
.findById(contactId);
// update grid's data provider with the updated contact
contact.ifPresent(value -> dataProvider.refreshItem(value));
}
private ContactService getService() {
// returns ContactService instance
}
private void handleWindowStateChanged(WindowState windowState) {
if (WindowState.MAXIMIZED.equals(windowState)) {
grid.setColumns("firstName", "lastName", "phoneNumber", "email",
"birthDate");
grid.setMinWidth("700px");
// ... rest of the configuration
} else if (WindowState.NORMAL.equals(windowState)) {
grid.setColumns("firstName", "lastName", "phoneNumber");
grid.setMinWidth("450px");
// ... rest of the configuration
}
}
private void fireSelectionEvent(
ItemClickEvent<Contact> contactItemClickEvent) {
// get contact id
Integer contactId = contactItemClickEvent.getItem().getId();
// save the id into a string-to-string map
Map<String, String> param = Collections.singletonMap(
"contactId", contactId.toString());
// send the event with name "contact-selected"
portletViewContext.fireEvent("contact-selected", param);
}
private void init() {
// ... grid initialization
// add item click listener which fires our contact-selected event
grid.addItemClickListener(this::fireSelectionEvent);
// ... rest of the configuration
}
}
The ContactListView
view implements the PortletView
interface.
The onPortletViewContextInit(PortletViewContext)
method in the PortletView
interface gives the implementing class a reference to a PortletViewContext
object, which allows us to register listeners and change the portlet’s state.
Apart from onPortletViewContextInit()
, the ContactListView
has three important methods from the portlet perspective: fireSelectionEvent()
, handleWindowStateChanged()
, and contactUpdated()
.
Firing the selection event is triggered when user selects a contact in the list.
The method creates a parameter map that contains the id of the selected contact.
We then use our portletViewContext
instance to send the event under the name contact-selected
.
Other Vaadin Portlet views that have registered listeners for this event name will be notified about the event.
handleWindowStateChanged()
is registered as a listener for the WindowStateChange
event.
It is called when, for example, the portlet view is maximized or normalized.
In this method, when the window state is changed to maximized, the minimum width of the grid increased and more grid columns are shown.
The other method, contactUpdated()
, is registered as an event listener for the contact-updated
event via a PortletViewContext
instance.
The contact-updated
event has the same parameters as the contact-selected
event.
We use the contact id to update the correct contact information in the list.
public class ContactFormView extends VerticalLayout implements PortletView {
private static final String ACTION_EDIT = "Edit";
private static final String ACTION_CREATE = "Create new";
private static final String ACTION_SAVE = "Save";
private PortletViewContext portletViewContext;
private Binder<Contact> binder;
private Contact contact;
private Button action;
// ... other components
@Override
public void onPortletViewContextInit(PortletViewContext context) {
// save context for sending events
this.portletViewContext = context;
// add event listeners for both "contact-selected" custom event
// and portlet mode change event
context.addEventChangeListener("contact-selected",
this::onContactSelected);
context.addPortletModeChangeListener(this::handlePortletModeChange);
init();
}
// handles "contact-selected" event from PortletListView.
// we check that the contact ID parameter is correct and that the contact exists.
// then we display the contact information on the form.
private void onContactSelected(PortletEvent event) {
int contactId = Integer
.parseInt(event.getParameters().get("contactId")[0]);
Optional<Contact> contact = getService().findById(contactId);
if (contact.isPresent()) {
// ... set active contact
this.contact = contact.get();
// ... update the form
} else {
// ... empty the form
clear();
}
}
// called when the portlet mode changes
// FormPortlet supports two modes: 'view' and 'edit'
private void handlePortletModeChange(PortletModeEvent event) {
// set fields to read-only mode when portlet mode is 'view'
binder.setReadOnly(event.isViewMode());
// set the button's text based on the portlet mode
if (event.isViewMode()) {
action.setText(ACTION_EDIT);
} else {
action.setText(ACTION_SAVE);
}
}
private void fireUpdateEvent(Contact contact) {
Map<String, String> param = Collections
.singletonMap("contactId", contact.getId().toString());
portletViewContext.fireEvent("contact-updated", param);
}
private PortletMode getPortletMode() {
return portletViewContext.getPortletMode();
}
private void init() {
// ... create the form layout
setupButtons();
// ... add components to form
}
private ContactService getService() {
// returns ContactService instance
}
private void setupButtons() {
action = new Button("action", event -> {
if (PortletMode.EDIT.equals(getPortletMode())) {
save();
} else {
portletViewContext.setPortletMode(PortletMode.EDIT);
}
});
// ... setup rest of the buttons
}
private void clear() {
// ... reset contact and clear form
}
private void save() {
if (contact != null) {
// ... save contact
} else {
// ... create new contact
}
// send custom portlet event
fireUpdateEvent(contact);
// ... update form
// sent portlet mode back to view
portletViewContext.setPortletMode(PortletMode.VIEW);
}
}
ContactFormView
uses the PortletViewContext
received via the onPortletViewContextInit(PortletViewContext)
method to register an event listener and portlet mode listener.
The important methods for the portlet operation are handlePortletMode()
and onContactSelected()
.
The ContactFormView
supports two portlet modes – view
and edit
– which are declared in portlet.xml
.
In the handlePortletMode()
method, depending on the portlet mode, we either enable or disable editing on the form fields.
We also change the name of the action
button to correspond to the correct mode.
The onContactSelected()
method is called when the contact-selected
event is sent by the Contact List portlet.
When the event arrives, the contact id is used to display information for the selected Contact
.
E34EF2E1-1EDE-40F7-ABD4-4494C83FAC5F