Jens Jansson

Vaadin Designer Tutorial

Note
Vaadin Designer is a commercial tool included in Vaadin Pro, Prime, and Enterprise subscriptions. We offer a free, 14-day trial for our commercial products and you do not need to provide any credit card information to get started.

In this tutorial, you learn how to build an application with Vaadin Designer, including how to create new views, create layout components, adjust CSS, and integrate views with the backend. This is a hands-on tutorial, so you can read the explanations and follow the steps to build the same application.

When you open Vaadin Designer the first time, you need to log in to vaadin.com to validate your license:

  • If you do not have an active subsciption or trial: Click Start free trial and log in with your credentials. This will give you access to Vaadin Designer, as well as other useful development tools, such as TestBench, Charts, CRUD, Grid Pro, and more. See the pricing page for more details.

  • If you have an active subscription or trial: Click Have a subscription? and log in with your credentials.

Setting up a Java development environment

Before you can start developing your Java app, you need to install the necessary tools and set up your development environment.

If you need to set up your Java development environment from scratch, follow the steps in the Building Modern Web Apps with Spring Boot and Vaadin - Setting up a Java development environment tutorial.

If you already have a working Java development environment, including a JDK, Maven, Git, Node.js, and an IDE, you can skip this step and proceed to set up a Vaadin project. The tutorial uses IntelliJ IDEA, but everything demonstrated is also available in Eclipse.

Installing Vaadin Designer

Vaadin Designer is a plug-in for your integrated development environment (IDE). It is available for both IntelliJ IDEA and Eclipse.

Follow the instructions below to install the plug-in in your IDE:

Tip
If you followed the tutorial mentioned in the previous section to set up your environment, you will have installed IntelliJ IDEA as your IDE.

Installing Google Chrome

When you open a design file with Designer, the source code shows in your IDE, while the visual editor opens up in the Chrome browser. If you do not have Chrome, follow these instructions to install it on your computer.

Setting up a Vaadin project

The first step in any development project is to set up a project and get it running. When this is done, we will continue using the project with Vaadin Designer.

Follow chapters 2-4 of the Building Modern Web Apps with Spring Boot and Vaadin guide to set up a Vaadin project:

As an alternative, you can skip the steps above and download the end result of Chapter 5 directly: download the project as a zip from GitHub.

Building the application

Now that you have IntelliJ IDEA running and a project available, we can take start building our first view with Designer.

To do this:

  1. Delete MainView.java in src/main/java/com/vaadin/tutorial/crm/ui.

  2. In the Project tree, expand the frontend/src folder.

  3. Right click the frontend/src folder and select New > Directory. Name the new folder views.

  4. Right click the newly-created views folder and select New > Vaadin 10+ Design.

  5. In the Name field, type main-view.

  6. Make sure the Create Java companion file checkbox is checked. This allows us to bind data and add listeners to our view in Java.

  7. In the Java package field, click the folder selection button and navigate to com.vaadin.tutorial.crm.ui.

The New Vaadin 10+ design dialog should now look like this.

Creating the main view.

Click OK to generate the files.

Vaadin Designer will open in Google Chrome.

Getting familiar with the UI

Designer consists of four main parts:

  • Paper is where the UI you are building renders. It shows you what your view will look like. Before you add components to your view, the paper proposes a set of starting points for your newly-created view.

  • Palette is a list of components that are available for use. The list is divided into multiple sections:

  • HTML elements are the built-in elements of the HTML language, like <p>, <h1> and <div>.

  • Parts is a list of all available web components in your project. These are scanned automatically from the node_modules folder in your project. When you add new 3rd-party web components to your project, they show up in the list and are available for use.

  • Components are snippets for Vaadin’s components that give you handy, commonly-used configurations, like having a button with an icon and caption.

  • Project components are the other designs in your project. You can include them in your current view.

  • Outline shows you what components are in use in the view and their hierarchy.

  • Properties is where you modify how a single component looks and behaves.

Creating the main layout

This is the layout that we will build.

A web application with a listing of contacts and an editor open.

From the image, you can see we need:

  • A vertical layout at the root of the view.

  • A horizontal layout with a text field for filtering, as well as a button to add new entries, at the top.

  • A horizontal layout for a grid of data entries and a form below.

We’ll disregard the form for now, as we build it separately after adding the other components to the view.

Adding the components

  1. On the paper, click Vertical to get a vertical layout as your starting point. Your view now constists of an empty layout.

  2. Find Horizontal Layout Spacing in the palette and drag it onto the vaadin-vertical-layout on the paper twice. You can use the search field at the top to find components easily.

  3. Find Vaadin Text Field in the palette and drag it onto the first vaadin-horizontal-layout.

  4. Find Button in the palette and drag it onto the first vaadin-horizontal-layout.

  5. Find vaadin-grid in the palette and drag it onto the second vaadin-horizontal-layout.

Components added to the view.

Our view does not look as planned yet, but it does have the components we need. We’ll now continue to configure the components to get it looking like we want.

Configuring the main layout

In the main layout, we need to add a bit of space between the components, as well as around the layout, to make it look better:

  1. Select the top-level layout, by clicking anywhere in the empty space of the vertical layout.

  2. In the properties view, in the Size and space panel, open the Padding selector and select M. This adds some space around the main layout, giving the design room to "breath".

  3. In the same panel, open the Spacing selector and select M. This adds space between the toolbar and the grid.

Spacing and padding added to the main layout.

Configuring the toolbar

For the toolbar, we need to configure a text field:

  1. Select the text field.

  2. In the properties view, under attributes, find the label attribute and remove the value from it. We do not need a separate label as we have the description as the placeholder value for the field.

  3. In the properties view, under attributes, find the placeholder attribute and replace "Placeholder" with "Filter by name…​".

  4. In the properties view, under attributes, find the clear-button-visible attribute and enable the checkbox. This gives the user an easy way to clear the filter.

  5. Select the button.

  6. In the properties view, find the text panel at the top and replace the default "Button" text with "Add contact".

Toolbar has the correct texts.

Making the Grid fill the available space

To show as much data as possible, we want to give the Grid the rest of the screen space:

  1. Select the lower horizontal layout, vaadin-horizontal-layout.

  2. In the properties view, in the Size and space panel find the width and height fields and choose "100%" in both. This expands the layout to take up all the available space.

  3. Select the Grid, vaadin-grid.

  4. In the properties view, in the Size and space panel set the width and height fields to "100%". This gives all the space in the layout to the grid.

Toolbar has the correct texts.

Our main layout is now looking great, but it is still missing the form.

Creating the form

It is good to keep your designs focused on one thing. When there are too many focus areas in one view, splitting them into smaller parts helps to keep things clear. We create the form in its own design with a Form Layout for editing the information of a single contact in it.

Creating a new design for the form

  1. Switch back to IntelliJ IDEA.

  2. In the Project tree, right click the frontend/src/views folder and select New > Vaadin 10+ Design.

  3. In the Name field, type contact-form.

  4. Make sure that

    1. the Create Java companion file checkbox is checked.

    2. the Java package is com.vaadin.tutorial.crm.ui.

  5. Click OK to generate the files.

  6. The design opens in the browser.

  7. In the palette, search for "vaadin-form" and drag vaadin-form-layout onto the drop section in the paper.

New design for the form.

Our form component will have:

  • Two text fields for the first and last name.

  • An email field.

  • Two select fields: one to select the company and the other to select the contact status.

Adding two text fields for the name

  1. Find Vaadin Text Field in the palette and drag it twice onto vaadin-form-layout.

  2. Select the first vaadin-text-field in the design. In the properties view, go to the attributes panel. Find the label attribute and replace the text with "First name". Find the placeholder attribute and remove the value.

  3. Select the second vaadin-text-field in the design. In the properties view, go to the attributes panel. Find the label attribute and replace the text with "Last name". Find the placeholder attribute and remove the value.

Note
If your paper is wide, the text fields will line up horizontally. You can drag the left and right paper edges to make it narrower. This will make the form fields stack vertically. This behavior comes from vaadin-form-layout that, by default, arranges its fields into one or two columns, depending on the width.

Adding the email field

  1. In the palette, find vaadin-email-field and drag it onto the vaadin-form-layout.

  2. In the properties view, go to the attributes panel. Find the label attribute and type in "Email".

Adding company and status fields

  1. In the palette, find vaadin-combo-box and drag it onto the vaadin-form-layout twice.

  2. Select the first vaadin-combo-box in the design. In the properties view, go to the attributes panel. Find the label attribute and add the value "Company".

  3. Select the second vaadin-combo-box in the design. In the properties view, go to the attributes panel. Find the label attribute and add the value "Status".

Adding buttons

We need to add save, delete and close buttons to the form. Save is the primary action and should be highlighted. Delete can be a dangerous action and should be marked as an error. Close is not an important action and can be downplayed with a tertiary styling.

  1. In the palette, find Horizontal Layout Spacing and drag it onto the vaadin-form-layout.

  2. In the Vaadin button section of the palette, drag the following three buttons onto the horizontal layout: Primary Button, then Primary Error Button next to it, and finally Tertiary Button next to that.

  3. Select the Primary button and change the button text to "Save".

  4. Select the Primary Error button and change the button text to "Delete". In the attributes panel, change the theme attribute from "primary error" to "error".

  5. Select the Tertiary button and change the button text to "Close".

Now, our form is done.

Finished form.

Adding the form to the main view

We can now go back to our main view and add the contact form next to the Grid. The main-view design should be open in a second Chrome tab. If it is not, reopen the main-view.js file again in IntelliJ IDEA.

In the palette, you should now see a section called Project Components that lists all designs you have created. By dragging items from this list, you can embed other designs into the one that is currently open.

  1. In the main-view Designer, in the palette locate contact-form in Project Components.

  2. Drag contact-form onto the second vaadin-horizontal-layout.

Form embedded in main view.

Sizing the grid and form

We want to give most of the space to the grid. For this, we use flex sizing to set a 2:1 ratio between the grid and the form.

  1. Select the vaadin-grid. In the properties view, find the style attribute in the attributes panel and add the value "flex: 2;" at the end.

  2. Select contact-form. In the properties view, find the style attribute in the attributes panel and set the value to "flex: 1;".

Now, our layout is done.

Form embedded in main view.

Adding the route to the main view

We previously replaced the original MainView with our own. The new one does not have an @Route annotation that we need set our view as the the root route.

  1. Switch back to IntelliJ IDEA.

  2. Expand the src/main/java/com.vaadin.tutorial.crm.ui package and open MainView.java.

  3. Add the @Route("") annotation at the beginning of the MainView class.

Your MainView class should now look like this:

MainView.java
@Tag("main-view")
@JsModule("./src/views/main-view.js")
@Route("") (1)
public class MainView extends PolymerTemplate<MainView.MainViewModel> {
    // The rest of the file is omitted from the code snippet
}
  1. The @Route annotation maps http://localhost:8080/ to MainView.

Running the project

Next, we run the project to see what the new layout looks like.

The easiest way to run the project for the first time is to:

  1. Open the Application Java class in src/main/java/com/vaadin/tutorial/crm/Application.java

  2. Click the green play button next to the line that starts with "public class Application".

    Running the project from the Application class.

    This starts the application and automatically adds a run configuration for it in IntelliJ IDEA. Later, when you want to run or restart the application, you can build, run/restart, stop and debug the application the toolbar:

    Running the project from the toolbar.

    When the build is finished and the application is running open http://localhost:8080/ in your browser to see the result.

The application running in the browser.

Connecting your view to Java

When you build a view with Designer, there are two main files for each view:

  • main-view.js: This is the template file where you lay out the components and define everything that is static.

  • MainView.java: This is the Java class that uses the template, adds UI logic, and configures all parts that are dynamic. We refer to this file as the companion file for the template.

When we created the views earlier in the tutorial, we checked the Create Java Companion file option in the New Vaadin 10+ Design dialog. This is why the MainView.java and ContactForm.java files were generated automatically. Adding Java code to your views is not visible in the template in Designer, but it is used when you run your application.

For the main view, in the companion file, we need to

  1. Load data to the grid.

  2. Filter data based on the text field.

  3. Populate the contact form when the user selects a row in the grid.

  4. Enable adding new contacts using the Add contact button.

In the contact form, we need to

  1. Bind fields with a contact object given by the main view.

  2. Implement the save, delete and close buttons.

We start with loading data into the grid.

Exporting components to Java

First, we export the components that need data or to interact to Java. To do this, we need to tell Designer which components should be available from Java code. For the main view, we need the grid to display data, the text field to filter data and the button to add new contacts.

  1. In Designer, open main-view.js.

  2. Select the "Filter by name…​" text field.

  3. In the properties view, go to the attributes panel, find the id attribute and give the text field the "filterText" id. This is what the component will be called in the Java companion file.

  4. In the outline, hover over the selected vaadin-text-field and click the icon that appears in the row on the right. This allows you to connect the component to Java and makes the field available from your Java code.

    Connect the text field to java using the outline.
  5. Repeat this process (steps 2 - 4) for the:

    1. vaadin-button: id attribute = "addContactButton".

    2. vaadin-grid: id attribute = "grid".

All the fields should now be available from your Java code. To check this:

  1. Go back to IntelliJ IDEA.

  2. Open the src/main/java/com.vaadin.tutorial.crm.ui.MainView class.

MainView.java
@Tag("main-view")
@JsModule("./src/views/main-view.js")
@Route("")
public class MainView extends PolymerTemplate<MainView.MainViewModel> {

    @Id("filterText") (1)
    private TextField filterText; (2)
    @Id("addContactButton")
    private Button addContactButton; (3)
    @Id("grid")
    private Grid grid; (4)

    /**
     * Creates a new MainView.
     */
    public MainView() {
        // You can initialise any data required for the connected UI components here.
    }

    /**
     * This model binds properties between MainView and main-view
     */
    public interface MainViewModel extends TemplateModel {
        // Add setters and getters for template properties here.
    }
}
  1. The fields are now connected to the template in the @Id("identifier") annotations.

  2. The text field uses the filterText id.

  3. The button uses the addContactButton id.

  4. The grid uses the grid id.

Adding data to the grid

Next, we want to load data from the backend and show it in the grid. The first task is to configure the columns. For each contact we want to show their first name, last name, email and status.

We take advantage of Spring’s dependency injection to get hold of the backend service, by adding it as a parameter in the constructor. Spring passes it in when MainView is created.

Modify MainView as follows:

MainView.java

public class MainView extends PolymerTemplate<MainView.MainViewModel> {

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

    private ContactService contactService; (2)

    /**
     * Creates a new MainView.
     */

    public MainView(ContactService contactService) { (3)
        this.contactService = contactService; (4)
        // You can initialise any data required for the connected UI components here.
        grid.addColumn(Contact::getFirstName).setHeader("First name"); (5)
        grid.addColumn(Contact::getLastName).setHeader("Last name");
        grid.addColumn(Contact::getEmail).setHeader("Email");
        grid.addColumn(Contact::getStatus).setHeader("Status");
        grid.addColumn(contact -> {  (6)
            Company company = contact.getCompany();
            return company == null ? "-" : company.getName();
        }).setHeader("Company");
        grid.getColumns().forEach(col -> col.setAutoWidth(true)); (7)
        updateList();
    }

    private void updateList() {
        grid.setItems(contactService.findAll()); (8)
    }

    /**
     * This model binds properties between MainView and main-view
     */
    public interface MainViewModel extends TemplateModel {
        // Add setters and getters for template properties here.
    }
}
  1. Adds the bean type as a type parameter to the grid.

  2. Creates a field for storing the service for future access.

  3. Spring passes in the service when the view is created.

  4. The passed reference to the service is stored in the field.

  5. Adds and configures columns in the grid.

  6. Creates a custom column for fetching the name of the company the contact works for.

  7. Configures column sizing: all columns are sized based on their content.

  8. Fetches items from the service and passes them to the grid.

Next, run the application, or restart it if it is already running.

Refresh the http://localhost:8080/ browser tab. The grid now shows the contacts from the service.

Grid configured and populated with items from the service.

Enabling filtering

We would like to filter the grid based on the first name and last name when the user types a value in the filter text field. For this, we need a value-change listener on the text field, and a way to pass the value to the contact service. We also need to add filtering support to the contact service and repository

We start be changing MainView as follows:

MainView.java
public class MainView extends PolymerTemplate<MainView.MainViewModel> {

    // Fields omitted
    ...

    public MainView(ContactService contactService) {
        this.contactService = contactService;
        // You can initialise any data required for the connected UI components here.
        grid.addColumn(Contact::getFirstName).setHeader("First name");
        grid.addColumn(Contact::getLastName).setHeader("Last name");
        grid.addColumn(Contact::getEmail).setHeader("Email");
        grid.addColumn(Contact::getStatus).setHeader("Status");
        grid.addColumn(contact -> {
            Company company = contact.getCompany();
            return company == null ? "-" : company.getName();
        }).setHeader("Company");
        grid.getColumns().forEach(col -> col.setAutoWidth(true));

        updateList();

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

    private void updateList() {
        grid.setItems(contactService.findAll(filterText.getValue())); (3)
    }

    // Template model omitted
    ...
}
  1. Puts text field value changes in lazy mode, so that the database is not queried on each keystroke.

  2. Adds a value-change listener to the text field that tells the grid to update items.

  3. Passes the value of the text field to the service using the findAll method. This will not compile yet because the method does not exist.

Next, we add a findAll method that takes a filter String as a parameter to the contact service.

ContactService.java
public List<Contact> findAll() { (1)
    return contactRepository.findAll();
}

public List<Contact> findAll(String stringFilter) { (2)
    if (stringFilter == null || stringFilter.isEmpty()) {
        return contactRepository.findAll();
    } else {
        return contactRepository.search(stringFilter);
    }
}
  1. This is the existing findAll method that we do not need to touch.

  2. Adds a new method for filtering based on a String.

Finally, we add a method to search contacts to ContactRepository:

ContactRepository.java
public interface ContactRepository extends JpaRepository<Contact, Long> {
    @Query("select c from Contact c " +
            "where lower(c.firstName) like lower(concat('%', :searchTerm, '%')) " +
            "or lower(c.lastName) like lower(concat('%', :searchTerm, '%'))")
    List<Contact> search(@Param("searchTerm") String searchTerm);
}

Run the application. The grid is now searchable based on the name entered in the text field.

Grid filtered based on text field contents.

Implementing the contact form

Now that the work in the grid is complete, we turn our attention to the contact form.

We want the following behavior:

  1. The form is hidden when it is not needed.

  2. The form can be used to edit or delete an existing contact.

  3. The form can be used to add a new contact.

We start by hiding the form.

Hiding the form

The MainView class knows when the form is needed and we use this to hide and show the form.

First, we connect the form to the MainView class, and then call setVisible on the form to hide it.

  1. Open main-view in Designer.

  2. Select the contact-form.

  3. Give the contact form an id attribute with the value "form".

  4. In the outline, click the Java connection icon to make the component available in the Java code.

The contact form is now accessible from the MainView class.

Next, open the MainView class and change it as follows to hide the form initially:

MainView.java
public class MainView extends PolymerTemplate<MainView.MainViewModel> {

    // Previous fields omitted

    @Id("form")
    private ContactForm form; (1)

    public MainView(ContactService contactService) {
      // Previous lines omitted

      closeEditor(); (2)
    }

    private void closeEditor() { (3)
      form.setVisible(false);
    }

    // Rest of the class omitted
}
  1. This is the field created by Designer.

  2. Calls closeEditor on the last line of the constructor to initially hide the form.

  3. Adds a new method that calls setVisible to close the form.

Warning
Unfortunately, there is currently a bug that prevents setVisible from working correctly in our case. For now, we can use the workaround detailed below. Without this the form won’t be hidden.

To work around the bug:

  1. Open contact-form.js in IntelliJ IDEA.

  2. Edit the source by adding the following CSS rule into the style element.

contact-form.js
    static get template() {
        return html`
<style include="shared-styles">
                :host {
                    display: block;
                    height: 100%;
                }
                /* Workaround for https://github.com/vaadin/flow/issues/8256 */
                :host([hidden]) { (1)
                  display: none !important;
                }
            </style>
`;
    }
  1. This CSS is applied when the hidden attribute is present on the contact-form element.

Now, the contact form will be hidden when the application starts.

Form hidden when initially starting the application.

Next, we open the form when a contact is selected in the grid, and pass that contact to the form.

Opening the form when a contact is selected

When the user clicks a contact in the grid, the contact is selected. At this point, we want to open the form and fill it with the contact’s data.

We start by listening for a selection event in the grid, and when this occurs we pass the contact to the form:

MainView.java
public class MainView extends PolymerTemplate<MainView.MainViewModel> {
    // Omitted

    public MainView(ContactService contactService) {

        // Omitted

        grid.getColumns().forEach(col -> col.setAutoWidth(true));
        grid.asSingleSelect().addValueChangeListener(event ->
                editContact(event.getValue())); (1)

        // Omitted
    }

    private void editContact(Contact contact) { (2)
        if (contact == null) {
            closeEditor();
        } else {
            form.setContact(contact); (3)
            form.setVisible(true);
        }
    }

    // Omitted
}
  1. Adds a listener for selection changes in grid.

  2. Adds a new method to show or hide the form depending on whether there’s a selection or not.

  3. Passes the contact to the form. This is a new method that needs to be added to ContactForm.

Next, we add the setContact method to ContactForm. For now, it is sufficient that the project compiles, so we leave the method empty. It will be implemented in the next section.

ContactForm.java
public class ContactForm extends PolymerTemplate<ContactForm.ContactFormModel> {
    // Omitted

    public void setContact(Contact contact) { (1)
        // to be implemented
    }

    // Omitted
}
  1. Adds a method to set the contact. This will be implemented shortly.

If you run the application now, you’ll see that when you select a contact the form is opened. And, if you click the selected contact, it becomes deselected and the form closes. The form remains empty though, because we have not yet bound its fields to the given contact.

Next, we populate the form with the selected contact’s details.

Adding a binder

To make the contact’s details visible and editable in the form, we need to bind the contact bean to the form. This can be done by using a binder object. We use a validating binder that gives us simple validation based on the member fields of the contact bean.

First, we add the binder to the ContactForm class and use it to bind the given contact’s fields to the form:

ContactForm.java
// Other fields omitted
Binder<Contact> binder = new BeanValidationBinder<>(Contact.class); (1)

public ContactForm() {
    binder.bindInstanceFields(this); (2)
}

public void setContact(Contact contact) {
    binder.setBean(contact); (3)
}
  1. BeanValidationBinder is a Binder that is aware of bean validation annotations. By passing it to Contact.class, we define the type of object we are binding to.

  2. bindInstanceFields matches fields in Contact and ContactForm based on their names.

  3. Sets the given contact to the binder.

The binder will look for fields in the Contact class and in ContactForm and bind those with matching names.

Warning
If you run the application now, the binder will throw an exception because it won’t find a single field to bind to. We’ll add the fields shortly in Designer.

Next, we add the fields that the binder can bind to.

Connecting the fields from contact-form

To make it possible for the binder to bind the fields of a contact bean to the form fields, the 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 the binder works by matching the bean and field names. The bean contains 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 in 2 above for the other fields 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.

Connect the form fields in Designer.

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

ContactForm.java
    @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;

If you run the application now, it will once again raise an exception, because the types of the combo boxes do not match the types in the Contact bean.

We fix this by editing them directly in the Java file:

ContactForm.java
    @Id("company")
    private ComboBox<Company> company;
    @Id("status")
    private ComboBox<Contact.Status> status;

There are still a few more things to fix. One is the text displayed in the company combo box. Currently, the contact object is printed as the value of the combo box. Instead of the object, we want to see the name of the company. The other issue is that the items in the combo boxes are still not set. We can get the companies from the CompanyService, and the statuses from the Status enumeration.

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

ContactForm.java
public class ContactForm extends PolymerTemplate<ContactForm.ContactFormModel> {

    Binder<Contact> binder = new BeanValidationBinder<>(Contact.class);
    @Id("firstName")
    private TextField firstName;
    @Id("lastName")
    private TextField lastName;
    @Id("email")
    private EmailField email;
    @Id("company")
    private ComboBox<Company> company;
    @Id("status")
    private ComboBox<Contact.Status> status;

    public ContactForm(CompanyService companyService) { (1)
        binder.bindInstanceFields(this);

        company.setItems(companyService.findAll()); (2)
        company.setItemLabelGenerator(Company::getName); (3)
        status.setItems(Contact.Status.values()); (4)
    }

    public void setContact(Contact contact) {
        binder.setBean(contact);
    }

    // TemplateModel omitted
}
  1. Adds companyService as a parameter. The Spring framework will inject it here.

  2. Sets the company combo box items by getting them from the service.

  3. Sets the item label generator so that we see company names in the combo box instead of company objects.

  4. Sets the items of the status combo box.

When you run the app now, there are no exceptions, all form fields are filled correctly, and items in the combo boxes are populated.

Form populated with the selected contact's details.

Next, we make sure that changes made in the form persist.

Adding, saving and deleting contacts

So far, we’ve displayed existing contact data in the application, but still don’t have the ability to add or modify data.

In this section, we make adding and modifying contacts work.

First, we make the Save, Delete and Close buttons work in the contact form. To add functionality to the buttons, we first need to make them available in the ContactForm class using Designer.

  1. In Designer, open contact-form.

  2. Select the save button, give it the "save" id attribute and connect it using the outline.

  3. Select the delete button, give it the "delete" id attribute and connect it using the outline.

  4. Select the close button, give it the "close" id attribute and connect using the outline.

Now, you’ve added the following fields to ContactForm.

ContactForm.java
    @Id("save")
    private Button save;
    @Id("delete")
    private Button delete;
    @Id("close")
    private Button close;

When any of the above buttons is clicked, we want to execute a corresponding action. To avoid a circular dependency between MainView and ContactForm, and to keep ContactForm reusable, we make ContactForm send an event on a button click. MainView captures the events and performs the actual actions.

Vaadin comes with an event-handling system for components. We’ve already used it to listen to value-change events from the filter text field. We want the form component to have a similar way of letting MainView know what is happening in the form.

To do this, add the following event definitions at the end of the ContactForm class:

ContactForm.java
// Events
public static abstract class ContactFormEvent extends ComponentEvent<ContactForm> {
  private Contact contact;

  protected ContactFormEvent(ContactForm source, Contact contact) { (1)
    super(source, false);
    this.contact = contact;
  }

  public Contact getContact() {
    return contact;
  }
}

public static class SaveEvent extends ContactFormEvent {
  SaveEvent(ContactForm source, Contact contact) {
    super(source, contact);
  }
}

public static class DeleteEvent extends ContactFormEvent {
  DeleteEvent(ContactForm source, Contact contact) {
    super(source, contact);
  }

}

public static class CloseEvent extends ContactFormEvent {
  CloseEvent(ContactForm source) {
    super(source, null);
  }
}

public <T extends ComponentEvent<?>> Registration addListener(Class<T> eventType,
    ComponentEventListener<T> listener) { (2)
  return getEventBus().addListener(eventType, listener);
}
  1. ContactFormEvent is a common superclass for all the events. It contains the contact that was edited or deleted.

  2. The addListener method uses Vaadin’s event bus to register the custom event types. Select the com.vaadin import for Registration if IntelliJ IDEA asks.

With the above events, we can now implement the click listeners. Add the following to the ContactForm class:

ContactForm.java
    public ContactForm(CompanyService companyService) {
        // Omitted

        save.addClickListener(e -> validateAndSave()); (1)
        delete.addClickListener(e -> fireEvent(new DeleteEvent(this, binder.getBean()))); (2)
        close.addClickListener(e -> fireEvent(new CloseEvent(this))); (3)

        binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid())); (4)
    }

    private void validateAndSave() {
        if (binder.isValid()) { (5)
            fireEvent(new SaveEvent(this, binder.getBean()));
        }
    }
  1. The save button calls the validateAndSave method.

  2. The delete button fires a delete event and passes the currently-edited contact.

  3. The cancel button fires a close event.

  4. Validates the form every time it changes. If it is invalid, it disables the save button to avoid invalid submissions.

  5. Only fires a save event if the form is valid.

Now, the events are sent. Next, we need to listen to and handle them in MainView.

Add the following changes to MainView to handle the contact form events, and the "add contact" button click that is still missing:

MainView.java
    public MainView(ContactService contactService) {
        // omitted

        form.addListener(ContactForm.SaveEvent.class, this::saveContact); (1)
        form.addListener(ContactForm.DeleteEvent.class, this::deleteContact); (2)
        form.addListener(ContactForm.CloseEvent.class, e -> closeEditor()); (3)

        addContactButton.addClickListener(e -> editContact(new Contact())); (4)
    }

    private void saveContact(ContactForm.SaveEvent event) { (5)
        contactService.save(event.getContact());
        updateList();
        closeEditor();
    }

    private void deleteContact(ContactForm.DeleteEvent event) { (6)
        contactService.delete(event.getContact());
        updateList();
        closeEditor();
    }

    private void closeEditor() {
        form.setVisible(false);
        grid.asSingleSelect().clear(); (7)
    }
  1. Calls saveContact when a save event is received from the contact form.

  2. Calls deleteContact when a delete event is received from the contact form.

  3. Closes the form when a close event is received from the contact form.

  4. Handles add button clicks by opening the form with a new Contact object.

  5. This new method saves the contact to the service, refreshes the grid, and closes the form.

  6. This new method deletes the contact in the service, refreshes the grid, and closes the form.

  7. Clears selection when closing from the form to keep the behavior consistent.

Congratulations, you have now completed the tutorial!

You can find the complete source code of this tutorial on GitHub.

You can find more information about Vaadin Designer, Vaadin, and Spring Boot here:

Please let us know what you think of this Vaadin Designer tutorial and if you have any ideas for other Designer-related topics you would like us to cover. You can reach us on Twitter, through the Vaadin Designer Help menu, or by posting a comment below.

Vaadin is an open-source framework offering the fastest way to build web apps on Java backends
GET STARTED

Comments (15)

Jens Jansson
1 year ago May 31, 2020 4:09pm
Karan Kumar
1 year ago Jun 01, 2020 9:15am
Stepan Zolotarev
1 year ago Jun 01, 2020 10:51am
Алексей Домнин
1 year ago Jun 17, 2020 8:38pm
Jens Jansson
1 year ago Jun 18, 2020 10:23am
Karan Kumar
1 year ago Jun 19, 2020 8:15am
Алексей Домнин
1 year ago Jun 22, 2020 12:43pm
Jens Jansson
2 years ago May 05, 2020 2:03pm
Jens Jansson
2 years ago May 06, 2020 7:04am
Jens Jansson
2 years ago May 06, 2020 9:25am
Jens Jansson
2 years ago Apr 30, 2020 12:36pm