Documentation versions (currently viewingVaadin 24)

Passing Data & Events among Vaadin Components

This article explains how to communicate among components in Vaadin Flow.

On the previous part of this tutorial, you created a reusable form component to edit contacts. Now you’ll connect it to the rest of the view and manage the view state.

The form shows the selected contact in the grid. It’s hidden when no contact is selected. It also saves and deletes contacts in the database.

Show Selected Contact in Form

The first step is to show the selected grid row in the form. To do this, update ListView as follows:

package com.example.application.views.list;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;

@Route(value = "")
@PageTitle("Contacts | Vaadin CRM")
public class ListView extends VerticalLayout {
    Grid<Contact> grid = new Grid<>(Contact.class);
    TextField filterText = new TextField();
    ContactForm form;
    CrmService service;

    public ListView(CrmService service) {
        this.service = service;

        add(getToolbar(), getContent());
        closeEditor(); // (1)

    private HorizontalLayout getContent() {
        HorizontalLayout content = new HorizontalLayout(grid, form);
        content.setFlexGrow(2, grid);
        content.setFlexGrow(1, form);
        return content;

    private void configureForm() {
        form = new ContactForm(service.findAllCompanies(), service.findAllStatuses());

    private void configureGrid() {
        grid.setColumns("firstName", "lastName", "email");
        grid.addColumn(contact -> contact.getStatus().getName()).setHeader("Status");
        grid.addColumn(contact -> contact.getCompany().getName()).setHeader("Company");
        grid.getColumns().forEach(col -> col.setAutoWidth(true));

        grid.asSingleSelect().addValueChangeListener(event ->
            editContact(event.getValue())); // (2)

    private Component getToolbar() {
        filterText.setPlaceholder("Filter by name...");
        filterText.addValueChangeListener(e -> updateList());

        Button addContactButton = new Button("Add contact");
        addContactButton.addClickListener(click -> addContact()); // (3)

        var toolbar = new HorizontalLayout(filterText, addContactButton);
        return toolbar;

    public void editContact(Contact contact) { // (4)
        if (contact == null) {
        } else {

    private void closeEditor() {

    private void addContact() { // (5)
        editContact(new Contact());

    private void updateList() {
  1. The closeEditor() call at the end of the constructor:

    • sets the form contact to null, clearing out old values;

    • hides the form;

    • removes the "editing" CSS class from the view.

  2. addValueChangeListener() adds a listener to the grid. The Grid component supports multi- and single-selection modes. You only need to select a single Contact, so you can use the asSingleSelect() method. The getValue() method returns the Contact in the selected row, or null if there is no selection.

  3. Call addContact() when the user clicks on the "Add contact" button.

  4. editContact() sets the selected contact in the ContactForm and hides or shows the form, depending on the selection. It also sets the "editing" CSS class name when editing.

  5. addContact() clears the grid selection and creates a new Contact.

Next, you’ll build the application. You should be able to select contacts in the grid and see them in the form. However, none of the buttons work yet.

Form showing a contact

Form Events

The ContactForm API is designed to be reusable; it’s configurable through properties and it fires the necessary events. So far, you’ve passed a list of companies, the status, and the contact to the form. However, you need the application to listen for the events to complete the integration.

To handle event listeners, update configureForm() and add saveContact() and deleteContact() methods.

private void configureForm() {
    form = new ContactForm(service.findAllCompanies(), service.findAllStatuses());
    form.addSaveListener(this::saveContact); // (1)
    form.addDeleteListener(this::deleteContact); // (2)
    form.addCloseListener(e -> closeEditor()); // (3)

private void saveContact(ContactForm.SaveEvent event) {

private void deleteContact(ContactForm.DeleteEvent event) {
  1. The save event listener calls saveContact(). It does a few things:

    1. Uses contactService to save the contact in the event to the database;

    2. Updates the list; and

    3. Closes the editor.

  2. The delete event listener calls deleteContact(). In the process, it also does a few things:

    1. Uses contactService to delete the contact from the database;

    2. Updates the list; and

    3. Closes the editor.

  3. The close event listener closes the editor.

Build the application now and verify that you’re able to select, add, update, and delete contacts.

The selected contact has updated first and last names.

Making the Layout Responsive

Now if you try the UI with a mobile device or make your desktop browser really narrow, you see that the UI is not currently well optimized for small screens. It usually makes sense to hide certain UI elements for smaller screens. You can accomplish this using Java code or with CSS media queries.

This example uses CSS and utilize the class names previously assigned to the components. Add the following CSS to frontend/themes/flowcrmtutorial/styles.css:

@media all and (max-width: 1100px) {
  .list-view.editing .toolbar,
  .list-view.editing .contact-grid {
    display: none;

The CSS media query hides the grid and the toolbar when you are editing contacts on a narrow screen.

migration assistance

Download free e-book.
The complete guide is also available in an easy-to-follow PDF format.

Open in a
new tab
export class RenderBanner extends HTMLElement {
  connectedCallback() {

  renderBanner() {
    let bannerWrapper = document.getElementById('tocBanner');

    if (bannerWrapper) {

    let tocEl = document.getElementById('toc');

    // Add an empty ToC div in case page doesn't have one.
    if (!tocEl) {
      const pageTitle = document.querySelector(
        'main > article > header[class^=PageHeader-module--pageHeader]'
      tocEl = document.createElement('div');

      pageTitle?.insertAdjacentElement('afterend', tocEl);

    // Prepare banner container
    bannerWrapper = document.createElement('div'); = 'tocBanner';

    // Banner elements
    const text = document.querySelector('.toc-banner-source-text')?.innerHTML;
    const link = document.querySelector('.toc-banner-source-link')?.textContent;

    const bannerHtml = `<div class='toc-banner'>
          <a href='${link}'>
            <div class="toc-banner--img"></div>
            <div class='toc-banner--content'>${text}</div>

    bannerWrapper.innerHTML = bannerHtml;

    // Add banner image
    const imgSource = document.querySelector('.toc-banner-source .image');
    const imgTarget = bannerWrapper.querySelector('.toc-banner--img');

    if (imgSource && imgTarget) {