Copyright © 2016-2019

Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.

1. Introduction

The Vaadin Flow module provides integration between the Holon Platform architecture and the Vaadin Flow web applications platform.

The module main features are:

  • A complete set of UI components fluent builders, to make the UI development fast and easy.

  • A full integration with the Holon Platform Property model and Datastore API for data bound UI components.

  • A complete support for UI components and messages localization, using the Holon Platform internationalization support.

  • A convenient navigation API to manage the UI views, with typed parameters support and components lifecycle management.

  • Integration with the Holon Platform authentication and authentication architecture.

  • A complete integration with Spring and Spring Boot, with application auto-configuration facilities.

1.1. Sources and contributions

The Holon Platform Vaadin module source code is available from the GitHub repository https://github.com/holon-platform/holon-vaadin-flow.

See the repository README file for information about:

  • The source code structure.

  • How to build the module artifacts from sources.

  • Where to find the code examples.

  • How to contribute to the module development.

2. Obtaining the artifacts

The Holon Platform uses Maven for projects build and configuration. All the platform artifacts are published in the Maven Central Repository, so there is no need to explicitly declare additional repositories in your project pom file.

At the top of each section of this documentation you will find the Maven coordinates (group id, artifact id and version) to obtain the artifact(s) as a dependency for your project.

A BOM (Bill Of Materials) pom is provided to import the available dependencies for a specific version in your projects. The Maven coordinates for the core BOM are the following:

Maven coordinates:

<groupId>com.holon-platform.vaadin</groupId>
<artifactId>holon-vaadin-flow-bom</artifactId>
<version>5.2.7</version>

The BOM can be imported in a Maven project in the following way:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.holon-platform.vaadin</groupId>
      <artifactId>holon-vaadin-flow-bom</artifactId>
      <version>5.2.7</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

2.1. Using the Platform BOM

The Holon Platform provides an overall Maven BOM (Bill of Materials) to easily obtain all the available platform artifacts.

3. Vaadin Flow UI components

The holon-vaadin-flow artifact is the main entry point to enable the Holon platform Vaadin Flow integration.

Maven coordinates:

<groupId>com.holon-platform.vaadin</groupId>
<artifactId>holon-vaadin-flow</artifactId>
<version>5.2.7</version>

It provides integration between the Vaadin Flow core architecture and the Holon Platform one, providing also a set of UI components specifically designed to ensure a seamless integration with the Holon Platform Property model, Datastore API, authentication and internationalization APIs.

3.1. Datastore API integration

The Holon Platform Datastore API can be used to bind a UI component to backend data in a simple and powerful way, relying on the Holon Platform Property model to represent the underlying data model.

The DatastoreDataProvider API is a standard Vaadin Flow com.vaadin.flow.data.provider.DataProvider which uses the Datastore API to fetch data from backend and makes available a set of configuration methods to control and setup the queries to perform.

Since it is a standard DataProvider, it can be seamlessy used in any Vaadin UI component which support data binding.

The DatastoreDataProvider provides a set of create(..) and builder(..) methods to build and configure a new Datastore based DataProvider. At least the following elements are required to create a DatastoreDataProvider:

  • The reference to the Datastore instance to use.

  • A DataTarget declaration, which represents the data model entity to use as data source (for example, a RDBMS table name).

3.1.1. Item types

By default, the Holon Platform Property model is used to represent the backend data model attributes, and the PropertyBox type to represent a data source item, intended as a collection of properties and their values.

So, besides the Datastore reference and the DataTarget definition, the property set to use has to be provided to the DatastoreDataProvider builder methods.

final NumericProperty<Long> ID = NumericProperty.longType("id");
final StringProperty NAME = StringProperty.create("name");

final PropertySet<?> SUBJECT = PropertySet.of(ID, NAME); (1)

final DataTarget<?> TARGET = DataTarget.named("subjects"); (2)

final Datastore datastore = obtainDatastore(); (3)

DataProvider<PropertyBox, QueryFilter> dataProvider = DatastoreDataProvider.create(datastore, TARGET, SUBJECT); (4)

dataProvider = DatastoreDataProvider.create(datastore, TARGET, ID, NAME); (5)
1 Property set declaration
2 Data target definition
3 Datastore reference
4 Create a new DatastoreDataProvider using the SUBJECT property set
5 Create a new DatastoreDataProvider providing the ID and NAME properties as property set

Furthermore, the DatastoreDataProvider API provides methods to use a bean class as item type. The item property set is obtained form the bean class attributes, using the bean property names as property set property names.

See the Java Beans and property model documentation section for information about the conventions and the configuration options which can be used when a Java Bean is used with the Holon Platform property model.
final DataTarget<?> TARGET = DataTarget.named("subjects"); (1)

final Datastore datastore = obtainDatastore(); (2)

DataProvider<MyBean, QueryFilter> dataProvider = DatastoreDataProvider.create(datastore, TARGET, MyBean.class); (3)
1 Data target definition
2 Datastore reference
3 Create a new DatastoreDataProvider using the MyBean class as item type

3.1.2. Filter types

By default, the QueryFilter type is used used as data provider filter type, since it is the default property model representation for the Datastore API query restrictions definition.

To use a different data provider filter type, a filter converter function can be configured for the DatastoreDataProvider, to provide the custom filter type to QueryFilter type conversion logic.

In the example below, a String type filter is used and the conversion function creates a QueryFilter which represents the condition "The NAME data model attribute value starts with the given filter value":

DataProvider<PropertyBox, String> dataProvider = DatastoreDataProvider.create(datastore, TARGET, SUBJECT,
    stringValue -> NAME.startsWith(stringValue));

Similarly when a bean item type is used:

DataProvider<MyBean, String> dataProvider = DatastoreDataProvider.create(datastore, TARGET, MyBean.class,
    stringValue -> NAME.startsWith(stringValue));

3.1.3. Custom item and filter type

The DatastoreDataProvider API makes also available methods to customize both the item type and the data provider filter type, providing:

  • The PropertyBox type to the custom item type conversion function.

  • The custom filter type to the QueryFilter type conversion function.

For example, using a MyItem item class and a String filter type:

DataProvider<MyItem, String> dataProvider = DatastoreDataProvider.create(datastore, TARGET, //
    SUBJECT, (1)
    propertyBox -> new MyItem(propertyBox.getValue(ID), propertyBox.getValue(NAME)), (2)
    stringValue -> NAME.startsWith(stringValue)); (3)
1 The SUBJECT property set is used as query projection
2 Item conversion function: from the PropertyBox type object to the MyItem item type
3 Filter conversion function: from a String type filter to a QueryFilter type

3.1.4. Datastore Query configuration

The queries performed by the Datastore to fetch the data from backend can be configured using the DatastoreDataProvider builder API.

Query filters and sorts

The query filters and sorts can be configured in two ways:

1. At construction time, using the appropriate builder methods:

The DatastoreDataProvider builder API provide methods to:

  • Add one or more QueryFilter to the query definition. These filters will always be included in the query execution.

  • Add one or more QuerySort to the query definition. These sorts will always be included in the query execution.

  • Set a default QuerySort, which will be used when no other sort is configured for the query.

DataProvider<?, ?> dataProvider = DatastoreDataProvider.builder(datastore, TARGET, SUBJECT) (1)
    .withQueryFilter(NAME.isNotNull()) (2)
    .withQuerySort(NAME.asc()) (3)
    .withDefaultQuerySort(ID.desc()) (4)
    .build();
1 Get a DatastoreDataProvider builder
2 Add a query filter
3 Add a query sort
4 Set the default query sort

2. Dynamically, using a QueryConfigurationProvider:

One or more QueryConfigurationProvider references can be configured for the query using the DatastoreDataProvider builder API.

At each query execution, the getQueryFilter() and getQuerySort() methods of each available QueryConfigurationProvider will be invoked to obtain the filters and sorts to add to the query, if any.

DataProvider<?, ?> dataProvider = DatastoreDataProvider.builder(datastore, TARGET, SUBJECT) (1)
    .withQueryConfigurationProvider(new QueryConfigurationProvider() { (2)

      @Override
      public QueryFilter getQueryFilter() { (3)
        return null;
      }

      @Override
      public QuerySort getQuerySort() { (4)
        return null;
      }

    }).build();
1 Get a DatastoreDataProvider builder
2 Add a QueryConfigurationProvider
3 At each query execution, this method will be invoked to obtain the query filters, if any
4 At each query execution, this method will be invoked to obtain the query sorts, if any
QuerySortOrder conversion

When the standard Vaadin Flow com.vaadin.flow.data.provider.QuerySortOrder type is used for the data provider query configuration, the DatastoreDataProvider can be configured to control how a QuerySortOrder declaration is converted into a QuerySort definition.

For this purpose, the DatastoreDataProvider builder API allows to configure a QuerySortOrder conversion function.

DataProvider<?, ?> dataProvider = DatastoreDataProvider.builder(datastore, TARGET, SUBJECT) (1)
    .querySortOrderConverter(querySortOrder -> { (2)
      // QuerySortOrder to QuerySort conversion logic omitted
      return null;
    }).build();
1 Get a DatastoreDataProvider builder
2 Set the QuerySortOrder conversion function
Item identifier

By default, the item itself is used as item identifier to discern each item from another within the query results. The item identification relies on the standard Java equals and hashCode object methods.

When a PropertyBox type item is used, the default item identification strategy relies on the property set identifier properties values, if declared.

See the Identifier properties documentation section for details.

If a custom item identifier should be used, an item identifier provider function can be configured using the DatastoreDataProvider builder API. This function must provide the item identifier for each item obtained form query execution.

Example using a PropertyBox type item
DataProvider<?, ?> dataProvider = DatastoreDataProvider.builder(datastore, TARGET, SUBJECT)
    .itemIdentifierProvider(propertyBox -> propertyBox.getValue(ID)) (1)
    .build();
1 Set an item identifier provider function which provides the ID property value as item identifier
Example using a bean item
DataProvider<?, ?> dataProvider = DatastoreDataProvider.builder(datastore, TARGET, MyBean.class)
    .itemIdentifierProvider(bean -> bean.getId()) (1)
    .build();
1 Set an item identifier provider function which provides the bean id value as item identifier using the getId() method

3.1.5. Setup a Datastore data provider using builders

The Holon Platform Vaadin Flow module builder APIs provides covenient methods to setup a Datastore based data provider for UI components which support data binding.

Example for a single select, implemented using a Vaadin ComboBox component:

SingleSelect<Long> select = Components.input.singleSelect(ID).dataSource(datastore, TARGET).build();

See Component builders and providers to learn about the available component builders.

3.2. Internationalization

The Holon Platform Vaadin Flow module provides a complete integration with the Holon Platform internationalization architecture, making UI components and messages localization simple and conistent with the full application stack.

Each localizable messages is represented by:

  • A required message code, i.e. the String key to be used to obtain the message localization for a given Locale.

  • An optional default message, to be used if a localized message is not available for a Locale.

  • A set of optional localization arguments, which can be used to replace a specific placeholder with the actual values.

The localizable message attributes described above can be declared and represented using the Localizable interface, which provides convenient methods to create Localizable instances providing the message localization attributes.

The UI component builders which allow to setup localizable messages and texts, always support the Localizable API to provide the message localization attributes, providing also convenient methods to directly set the message localization attributes.

Example using a button component builder:

Button button = Components.button().text("Not localizable").build(); (1)

button = Components.button().text("Default message", "message.localization.code").build(); (2)

button = Components.button().text("Default message", "message.localization.code", "arg1", "arg2").build(); (3)

Localizable message = Localizable.of("Default message", "message.localization.code");
button = Components.button().text(message).build(); (4)
1 Set the button text using a not localizable message
2 Set the button text using a localizable message, providing the default message and the localization code
3 Set the button text using a localizable message, also providing two message localization arguments
4 Set the button text using a Localizable reference

See Component builders and providers to learn about the available component builders.

3.2.1. LocalizationContext and LocalizationProvider

The Holon Platform internationalization support relies on the LocalizationContext API, which acts as message resolver, localized data formatter and holder for the current user Locale.

In order for the UI components localization to work, one of the following conditions must be met:

  • A LocalizationContext is available as context resource and a Locale is configured. See the Holon Platform Context architecture for information about the context resources management.

  • Otherwise, a Vaadin Flow I18NProvider is available from the current Vaadin Service.

See Vaadin session scope to learn about the context scope for resources bound to the current Vaadin application session.

The LocalizationProvider API is used by the UI components and builders to obtain the localization information and to localize the messages to display.

The LocalizationProvider API makes available a set of static methods to seamlessy integrate the messages localization in any part of the application, delegating the actual internationalization logic to the concrete localization provider, if available, which can be either a Holon Platform LocalizationContext or a Vaadin Flow I18NProvider.

The actual localization service to use is automatically detected with the following strategy:

  • If a Vaadin Flow I18NProvider is available from current VaadinService, it is used for messages localization.

  • Otherwise, a LocalizationContext is used if available as a context resource.

This way, the LocalizationProvider API getLocalization static methods can be used as follows:

final Localizable message = Localizable.of("Default message", "message.localization.code");

Optional<String> localized = LocalizationProvider.getLocalization(Locale.US, message); (1)
String localizedOrDefault = LocalizationProvider.getLocalization(Locale.US, "Default message",
    "message.localization.code"); (2)
1 Get the localization for the given Localizable message using the given Locale
2 Get the localization for the given message.localization.code message code using the given Locale and the default Default message message if the localization is not available

If the Locale is not explicitly provided, the LocalizationProvider API localize static methods can be used and the current Locale is obtained with the following strategy:

  • If a current UI is available and a Locale is configured for the UI, this one is used.

  • If a LocalizationContext is available as a context resource and it is localized, the LocalizationContext current Locale is used.

  • If a Vaadin Flow I18NProvider is available from current VaadinService, the first provided Locale is used if available.

final Localizable message = Localizable.of("Default message", "message.localization.code");

Optional<Locale> locale = LocalizationProvider.getCurrentLocale(); (1)

Optional<String> localized = LocalizationProvider.localize(message); (2)
String localizedOrDefault = LocalizationProvider.localize("Default message", "message.localization.code"); (3)
1 Get the current Locale, if available
2 Get the localization for the given Localizable message using the current Locale
3 Get the localization for the given message.localization.code message code using the current Locale: the default Default message message is returned if the message localization or the current Locale is not available

3.2.2. Use a LocalizationContext as I18NProvider

The Holon Platform LocalizationContext can be used as a standard Vaadin Flow I18NProvider through the LocalizationContextI18NProvider API.

See the LocalizationContext documentation for information about the LocalizationContext configuration and use.

The LocalizationContextI18NProvider provides static methods to build a I18NProvider instance which uses a LocalizationContext as message localizations provider.

LocalizationContext localizationContext = LocalizationContext.builder()
    .withMessageProvider(MessageProvider.fromProperties("messages").build()) (1)
    .withInitialLocale(Locale.US) (2)
    .build();

I18NProvider i18nProvider = LocalizationContextI18NProvider.create(localizationContext); (3)
1 Set the messages localization source
2 Set the initial Locale
3 Create a I18NProvider instance using the LocalizationContext as message localization provider

A LocalizationContextI18NProvider which uses the current LocalizationContext (if available as a context resource) can be obtained with the create() method:

LocalizationContext localizationContext = LocalizationContext.builder()
    .withMessageProvider(MessageProvider.fromProperties("messages").build()).withInitialLocale(Locale.US)
    .build(); (1)

VaadinSession.getCurrent().setAttribute(LocalizationContext.class, localizationContext); (2)

I18NProvider i18nProvider = LocalizationContextI18NProvider.create(); (3)
1 Create a LocalizationContext
2 Put the LocalizationContext instance as a Vaadin Session attribute
3 Create a I18NProvider instance which uses the current LocalizationContext

3.3. The HasComponent interface

Many of the additional UI components provided by the Holon Platform Vaadin Flow module extends the HasComponent interface to make available the actual Vaadin com.vaadin.flow.component.Component which can be used in the application UI.

The getComponent() method should be used to obtain the actual Component, for example to add it to a layout.

This because all the Holon UI components are represented using an interface, while the Vaadin Component object is a class. For this reason, the UI component interface cannot directly extend a class and the actual Vaadin Component is thus provided through the getComponent() method.

Example for an Input type component:

Input<String> input = Input.string().build(); (1)

VerticalLayout layout = new VerticalLayout();
layout.add(input.getComponent()); (2)
1 Create a String type Input
2 Use the getComponent() method to add the actual input component to a layout

All the Holon UI components interfaces are located in the com.holonplatform.vaadin.flow.components package.

Furthermore, the HasComponent API provides methods to check if the actual component supports a set of features and to obtain the appropriate interface which allow to manage a specific feature.

Some of the component features which can be checked and accessed are:

  • Enabled mode: using hasEnabled().

  • Size: using hasSize().

  • Style: using hasStyle().

  • Label: using hasLabel().

Input<String> input = Input.string().build(); (1)

input.hasEnabled().ifPresent(e -> e.setEnabled(false)); (2)
input.hasSize().ifPresent(s -> s.setSizeFull()); (3)
input.hasStyle().ifPresent(s -> s.addClassName("my-style")); (4)
input.hasLabel().ifPresent(l -> l.setLabel("My label")); (5)
1 Create a String type Input
2 Check if the component supports the enabled mode and if so set it to false
3 Check if the component supports size and if so set it to full
4 Check if the component supports style and if so add a CSS style class name
5 Check if the component supports a label and if so set it to My label

3.4. The ValueHolder interface

The ValueHolder API is the abstract representation for components which handle a typed value, allowing to get and set the value according to the value type with which they are declared.

A set of convenience methods are provided to check if the value holder is empty (i.e. has no value) and to clear the value.

See View components and forms and Input components and forms to check two ValueHolder implementations.

Input<String> input = Input.string().build(); (1)

input.setValue("String value"); (2)
String value = input.getValue(); (3)
Optional<String> optionalValue = input.getValueIfPresent(); (4)

boolean empty = input.isEmpty(); (5)
input.clear(); (6)
1 Create a String type Input, which extends ValueHolder
2 Set the value
3 Get the value
4 Get the value, if present
5 Check whether the input is empty
6 Clear the value

Furthermore, the ValueHolder type components allow to listen to value changes, using a ValueChangeListener. The value change event provides both the changed and the previous value, and the information about the value change context, including whether the value change was originated by a user action or programmatically.

Input<String> input = Input.string().build(); (1)

input.addValueChangeListener(event -> { (2)
  String oldValue = event.getOldValue(); (3)
  String newValue = event.getValue(); (4)
  boolean byUser = event.isUserOriginated(); (5)
});
1 Create a String type Input, which extends ValueHolder
2 Add a ValueChangeListener
3 Get the previous value
4 Get the changed value
5 Get whether the value change was originated by a user action or programmatically

3.5. Component builders and providers

The Components API is the main entry point to build and configure the UI components.

It provides a complete set of methods to obtain fluent builders to create and configure both standard Vaadin Flow components and the additional UI components provided by the Holon Platform Vaadin Flow module.

The Components API structure:

Sub-interface Provides

[NONE]

A set of configure(*) methods to setup existing standard component instances and a set of methods to obtain the fluent builders for base Vaadin components, such as Button and standard layout components. See Base components builders.

dialog

Message and question dialogs builders. See Dialogs.

view

Builders for view components and forms. See View components and forms.

input

Builders input components and forms. See Input components and forms.

listing

Builders for grid type components to list data. See List data in grids.

3.5.1. Component builders structure

Each component builder API is composed by a set of specific builders, each related to a specific feature of the component. The feature builders are independent from the specific UI component to build, thus providing a common API which makes component configuration simple and intuitive.

For example, the HasTextConfigurator represents a builder part for UI components which supports a text, and it is included in any builder for UI components with text support.

Similarly, the the HasSizeConfigurator API is included in component builders which supports the component size configuration.

This ensures a consistent and common API to build and configure any UI component.

In the example below, the HasTextConfigurator and the HasSizeConfigurator are used in the same way to configure a label and a button component text and size:

Div label = Components.label() (1)
    .text("Label text") (2)
    .width("200px") (3)
    .build();

Button button = Components.button() (4)
    .text("Button text") (5)
    .width("200px") (6)
    .build();
1 Get a label component builder
2 Set the label text (using the HasTextConfigurator API)
3 Set the label width (using the HasSizeConfigurator API)
4 Get a button component builder
5 Set the button text (using the HasTextConfigurator API)
6 Set the button width (using the HasSizeConfigurator API)

3.5.2. Base components builders

Button

The Components.button() method provides a standard Vaadin Button component builder, with localizable text support.

Button button = Components.button() (1)
    .text("Button text") (2)
    .text("Default text", "message.code") (3)
    .description("The description") (4)
    .fullWidth() (5)
    .styleName("my-style") (6)
    .withThemeVariants(ButtonVariant.LUMO_PRIMARY) (7)
    .icon(VaadinIcon.CHECK) (8)
    .disableOnClick() (9)
    .onClick(event -> { (10)
      // do something
    }).build();

button = Components.button("Button text", e -> {
  /* do something */ }); (11)
1 Get the Button builder
2 Set the button text
3 Set the button localizable text, providing a default text and a message localization code
4 Set the button description
5 Set the button width to 100%
6 Add a CSS style class name
7 Add a theme variant
8 Set the button icon
9 Automatically disables button when clicked
10 Add a click event listener
11 Shorter to directly create a Button providing the text and the click handler

The Components.nativeButton() builder can be used to build a NativeButton component.

Label

A label component can be used to display a text, which can be localizable, and can be implemented using a set of tags. The default tag used by the Components.label() method is DIV.

See Internationalization for information about the UI components text localization.
Div label = Components.label() (1)
    .text("Label text") (2)
    .text("Default text", "message.code") (3)
    .description("The description") (4)
    .fullWidth() (5)
    .styleName("my-style") (6)
    .build();
1 Get a label builder using a Div component
2 Set the label text
3 Set the label localizable text, providing a default text and a message localization code
4 Set the label description
5 Set the label width to 100%
6 Add a CSS style class name

Other builders are available for the tags: P, SPAN, H1, H2, H3, H4, H5, H6.

Paragraph p = Components.paragraph().text("text").build(); (1)
Span span = Components.span().text("text").build(); (2)
H1 h1 = Components.h1().text("text").build(); (3)
H2 h2 = Components.h2().text("text").build(); (4)
H3 h3 = Components.h3().text("text").build(); (5)
H4 h4 = Components.h4().text("text").build(); (6)
H5 h5 = Components.h5().text("text").build(); (7)
H6 h6 = Components.h6().text("text").build(); (8)
1 Get a label builder using a Paragraph component
2 Get a label builder using a Span component
3 Get a label builder using a H1 component
4 Get a label builder using a H2 component
5 Get a label builder using a H3 component
6 Get a label builder using a H4 component
7 Get a label builder using a H5 component
8 Get a label builder using a H6 component
Layouts

The Vaadin Flow base layouts can be created and configured using the following builders:

  • *Vertical layout: * Components.vl().

  • *Horizontal layout: * Components.hl().

  • *Form layout: * Components.formLayout().

VerticalLayout verticalLayout = Components.vl() (1)
    .spacing() (2)
    .margin() (3)
    .alignItems(Alignment.CENTER) (4)
    .justifyContentMode(JustifyContentMode.END) (5)
    .fullSize() (6)
    .styleName("my-style") (7)
    .add(Components.button().text("Button text").build()) (8)
    .add(Components.label().text("Label1").build(), Components.label().text("Label2").build()) (9)
    .addAndAlign(myComponent1, Alignment.START) (10)
    .build();

HorizontalLayout horizontalLayout = Components.hl() (11)
    .add(myComponent1) (12)
    .add(myComponent2) (13)
    .flexGrow(1, myComponent1) (14)
    .build();

FormLayout formLayout = Components.formLayout() (15)
    .add(myComponent1) (16)
    .withFormItem(myComponent1, "Label 1") (17)
    .responsiveSteps(new ResponsiveStep("100px", 2)) (18)
    .build();
1 Get a VerticalLayout builder
2 Enable spacing
3 Enable margins
4 Set the default component alignment
5 Set the justify content mode
6 Set width and height to 100%
7 Add a CSS style class name
8 Add a Button component
9 Add two label components
10 Add a component and set its alignment
11 Get a HorizontalLayout builder
12 Add a component
13 Add another component
14 Set the flex grow ratio for the first component
15 Get a FormLayout builder
16 Add a component
17 Add a FormItem slot with a component and its label
18 Set the responsive steps
Context menu

The Components.contextMenu() method can be used to configure a ContextMenu and bind it to a component.

Components.contextMenu() (1)
    .withItem("Item 1").onClick(e -> {
      // do something
    }).add() (2)
    .withItem("Item 2", "message.code").disabled().add() (3)
    .withItem(itemComponent).add() (4)
    .withOpenedChangeListener(e -> { (5)
      e.isOpened();
    }).build(myComponent); (6)
1 Get a ContextMenu builder
2 Add a menu item with a text and a click handler
3 Add disabled a menu item with a localizable text
4 Add a menu item using a component
5 Add a menu open change listener
6 Build the ContextMenu and bind it to the myComponent component

3.5.3. Base components configurators

Besides the builders, the Components API provides a set of configurator methods, which can be used to configure existing UI components. The available configurators acts on some base UI components: label, Button and layouts.

Button btn = new Button();

Components.configure(btn).text("Default text", "message.code"); (1)

VerticalLayout vl = new VerticalLayout();

Components.configure(vl).withoutSpacing().add(myComponent); (2)
1 Configure the Button, setting a localizable text
2 Configure the VerticalLayout, disabling spacing and adding a component

3.6. Dialogs

The Components.dialog sub-interface provides a set of method to build and configure a Vaadin Flow Dialog. Three types of dialogs are available, as described below.

3.6.1. Message Dialog

A message dialog is a Dialog which can be used to display a simple message to the user.

Dialog dialog = Components.dialog.message().text("Message").build(); (1)

Components.dialog.message().text("Default text", "message.code").open(); (2)

Components.dialog.showMessage("Default text", "message.code"); (3)
1 Build a message Dialog with given message text
2 Build a message Dialog with given localizable message text and open it
3 Shorter to build and show a message Dialog with a localizable message text

3.6.2. Confirm Dialog

A confirm dialog is a Dialog which can be used to display a simple message to the user and provides a OK button at the bottom right position.

The OK button can be configured using the dialog builder.

Components.dialog.confirm() (1)
    .text("Default text", "message.code") (2)
    .okButtonConfigurator(button -> { (3)
      button.text("My text").icon(VaadinIcon.CHECK);
    }).open(); (4)

Components.dialog.showConfirm("Default text", "message.code"); (5)
1 Get a confirm Dialog builder
2 Set the localizable dialog message
3 Configure the confirm dialog OK button
4 Build and open the dialog
5 Shorter to build and show a confirm Dialog with a localizable message text

3.6.3. Question Dialog

A question dialog is a Dialog which can be used to ask a question to the user and handle the user answer. A QuestionDialogCallback function must be provided to handle the user answer.

Two buttons are displayed at the bottom right position, which by default represents the yes or no answers. The buttons can be configured using the dialog builder.

Components.dialog.question(confirm -> { (1)
  // handle user response (true/false)
}).text("Default text", "message.code") (2)
    .confirmButtonConfigurator(button -> { (3)
      // confirm button configuration
    }).denialButtonConfigurator(button -> { (4)
      // deny button configuration
    }).open(); (5)

Components.dialog.showQuestion(confirm -> {
  /* handle user response */ }, "Default text", "message.code"); (6)
1 Get a question Dialog builder, providing the user answer callback
2 Set the localizable dialog message
3 Configure the confirm dialog button
4 Configure the deny dialog button
5 Build and open the dialog
6 Shorter to build and show a question Dialog with a localizable message text

3.6.4. Dialog configuration options

All the dialog builders provides a set of common Dialog configuration options, including:

  • Dialog size configuration

  • Dialog style configuration

  • Dialog closing mode configuration and open change listener

Components.dialog.message().text("Default text", "message.code") //
    .width("200px") (1)
    .height("200px") (2)
    .styleName("my-style") (3)
    .closeOnEsc(false) (4)
    .closeOnOutsideClick(true) (5)
    .withOpenedChangeListener(event -> { (6)
      // open changed
    }).build();
1 Set the dialog width
2 Set the dialog height
3 Add a CSS style class name
4 Disable closing when ESC pressed
5 Enable closing when user clicks outside the dialog
6 Add a open change listener

3.6.5. Adding components to the Dialog

The dialog builders allow to add custom components to the Dialog content. The components can be added in two positions:

  • In the main dialog content: will be placed after the dialog message label, if any.

  • In the dialog bottom toolbar: will be placed before the standard dialog buttons, if any.

Components.dialog.message().text("Default text", "message.code") //
    .withComponent(Components.label().text("My label").build()) (1)
    .withToolbarComponent(Components.button().text("My button").build()) (2)
    .open();
1 Add a label component in the dialog content area
2 Add a button component in the dialog toolbar

3.7. List data in grids

The ItemListing API represents a UI component which can be used to display data in a tabular way.

Extends HasComponent (see The HasComponent interface) and it is actually implemented using a Vaadin Flow Grid component.

A ItemListing is designed to display a set of data items of a specific type, each one rendered as a list row. An item is a collection of values, and each value is bound to a property definition of a specific type.

Each ItemListing column is bound to an item property by default, even if virtual or calculated columns can be added to the listing.

Two ItemListing implementations are available:

  • PropertyListing: to use the Holon Platform property model to represent the item properties and the PropertyBox type as item representation. See PropertyListing.

  • BeanListing: to use a Java Bean class as item type and the bean property names as item properties. See BeanListing.

Each item listing implementation provides one or more suitable builder methods to obtain the fluent builder to use to create and configure an item listing instance.

The same builders are also available from the Components API, through the Components.listing sub interface. See Component builders and providers.

3.7.1. PropertyListing

The PropertyListing API is the default ItemListing implementation which uses the Holon Platform Property model to represent and manage the data items.

  • A property set must be provided at PropertyListing costruction time. It represents the available item properties, each one bound by default to a listing column.

  • The PropertyBox type is used to represent an item instance, using the listing property set. So each listing row is bound to a PropertyBox instance, which collects the property values to be displayed for each listing column.

For example, given the following property model definition:

static final NumericProperty<Long> ID = NumericProperty.longType("id");
static final StringProperty NAME = StringProperty.create("name");
static final PropertySet<?> SUBJECT = PropertySet.of(ID, NAME);

A PropertyListing can be created in the following way:

PropertyListing listing = PropertyListing.builder(SUBJECT).build(); (1)

listing = Components.listing.properties(ID, NAME).build(); (2)

new VerticalLayout().add(listing.getComponent()); (3)
1 Build a PropertyListing using the SUBJECT property set. The listing will provide two columns, one bound to the ID item property and the other bound to the NAME item property
2 Build a PropertyListing using the Components API, directly providing the ID and NAME properties as listing property set
3 Just like any HasComponent component type, the getComponent() method should be used to obtain the actual listing Component

3.7.2. BeanListing

The BeanListing API is the default ItemListing implementation to use a Java Bean class as item type.

  • A bean class must be provided at BeanListing costruction time. The bean class property names will be used as listing property set and so rendered as listing columns. For this reason the item listing property definition type is a String.

  • Each listing row is bound to a bean class instance, and the bean class getters will be used to obtain the property value to be rendered for each listing column.

For example, given the following bean class definition:

class MyBean {

  private Long id;
  private String name;

  public MyBean() {
    super();
  }

  public MyBean(Long id, String name) {
    super();
    this.id = id;
    this.name = name;
  }

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

}

A BeanListing can be created in the following way:

BeanListing<MyBean> listing = BeanListing.builder(MyBean.class).build(); (1)

listing = Components.listing.items(MyBean.class).build(); (2)

new VerticalLayout().add(listing.getComponent()); (3)
1 Build a BeanListing using the MyBean class. The listing will provide two columns, one bound to the id bean property name and the other bound to the name bean property name
2 Build a BeanListing using the Components API
3 Just like any HasComponent component type, the getComponent() method should be used to obtain the actual listing Component

3.7.3. Item listing data source

To obtain the item set to display, an item listing needs a data source to be configured. This can be achieved either using the items(…​) or the datasource(…​) builder methods, to provide a static set of items or a backend data connection respectively.

1. Using a static set of items:

The item listing item set can be provided at listing costruction time, using the items(…​) or addItem(…​) builder methods. The provided item instances must be of the same type of the listing item type.

The provided item will be displayed in the same order they are provided at construction time.

Example using a PropertyListing:

PropertyListing listing = PropertyListing.builder(SUBJECT) (1)
    .items(PropertyBox.builder(SUBJECT).set(ID, 1L).set(NAME, "One").build()).build();

listing = PropertyListing.builder(SUBJECT) (2)
    .addItem(PropertyBox.builder(SUBJECT).set(ID, 1L).set(NAME, "One").build())
    .addItem(PropertyBox.builder(SUBJECT).set(ID, 2L).set(NAME, "Two").build()).build();
1 Set the listing items
2 Set the listing items using addItem(…​)

Example using a BeanListing:

BeanListing<MyBean> listing = BeanListing.builder(MyBean.class) (1)
    .items(new MyBean(1L, "One"), new MyBean(2L, "Two")).build();

listing = BeanListing.builder(MyBean.class) (2)
    .addItem(new MyBean(1L, "One")) //
    .addItem(new MyBean(2L, "Two")).build();
1 Set the listing items
2 Set the listing items using addItem(…​)

2. Using a DataProvider:

A Vaadin com.vaadin.flow.data.provider.DataProvider can be used as items data source. This allows to provide a dynamic set of items, for example using a backend service.

The DataProvider data type must be consistent with the listing item type.

Example using a PropertyListing:

DataProvider<PropertyBox, ?> dataProvider = getPropertyBoxDataProvider(); (1)

PropertyListing listing = PropertyListing.builder(SUBJECT).dataSource(dataProvider) (2)
    .build();
1 Obtain a DataProvider using the PropertyBox data type
2 Set the data provider as listing data source

Example using a BeanListing:

DataProvider<MyBean, ?> dataProvider = getBeanDataProvider(); (1)

BeanListing<MyBean> listing = BeanListing.builder(MyBean.class).dataSource(dataProvider) (2)
    .build();
1 Obtain a DataProvider using the MyBean data type
2 Set the data provider as listing data source

When a DataProvider is used as data source, the refresh() and refreshItem(T item) can be used to refresh the listing item set:

  • The refresh method can be used to refresh the whole item set. In most cases, the data provider configured as data source will execute again a query to obtain the data from the backend.

  • The refreshItem method can be used to refresh a single item, providing an updated instance to be included in the listing item set, replacing the previous one.

PropertyListing listing = PropertyListing.builder(SUBJECT).dataSource(getDataProvider()).build(); (1)

listing.refresh(); (2)
listing.refreshItem(itemToRefresh); (3)
1 Create a listing and use a data provider as data source
2 Refresh all the listing items
3 Refresh a specific item

3.7.4. Using a Datastore as item listing data source

The Holon Platform Datastore API can be used to implement a listing data source, and maybe it is the most straight and easy way to connect a property model based listing to a backend data source.

This first way to use a Datastore as listing data source is to use the DataProvider adapter, available through the DatastoreDataProvider API.

See the Datastore API integration section to learn how to create and configure a Datastore based DataProvider.

When a Datastore is used, a DataTarget definition is required to declare the data model entity to use as items data source (for example a RDBMS table name, or a collection name in a document based database).

Supposing to use the following DataTarget definition:

static final DataTarget<?> TARGET = DataTarget.named("subjects");

A PropertyListing data source can be declared as follows:

Datastore datastore = getDatastore();

PropertyListing listing = PropertyListing.builder(SUBJECT) (1)
    .dataSource(DatastoreDataProvider.create(datastore, TARGET, SUBJECT)) (2)
    .build();
1 Create a PropertyListing using the SUBJECT property set
2 Use the DatastoreDataProvider API to create a data provider which uses the provided Datastore and DataTarget to perform backend data queries

But the most straight and easy way to use a Datastore as listing data source is to rely on the listing builder methods available for this purpose. Furthermore, the builder methods allow to avoid to specify the query projection property set again, using the listing property set by default.

Datastore datastore = getDatastore();

PropertyListing listing = PropertyListing.builder(SUBJECT) (1)
    .dataSource(datastore, TARGET) (2)
    .build();
1 Create a PropertyListing using the SUBJECT property set
2 Directly use the dataSource builder method to create a data provider which uses the provided Datastore and DataTarget to perform backend data queries. In this case, there is no need to specify the SUBJECT property set, since the listing property set is used as query projection

The dataSource builder method turns the root listing builder in a Datastore-aware builder, making available a set of additional methods which can be used to configure the Datastore based data provider, for example to add query filters and sorts. The Datastore based data provider configuration methods corresponds to the ones provided by the DatastoreDataProvider API. See Datastore Query configuration for details.

PropertyListing listing = PropertyListing.builder(SUBJECT) //
    .dataSource(getDatastore(), TARGET) (1)
    .withQueryFilter(NAME.isNotNull()) (2)
    .withQuerySort(ID.asc()) (3)
    .build();
1 Set the listing data source using a Datastore and providing the data target to use
2 Configure the Datastore data provider adding a query filter
3 Configure the Datastore data provider adding a query sort

3.7.5. Item listing frozen data source modality

The item listing data source can be set in a frozen state using the item listing API:

  • When the item listing is in frozen state, it never shows any item and no fetch is performed from the data provider.

  • When the item listing refresh() method is invoked, the frozen state is automatically disabled, allowing the listing to fetch and display the items.

The frozen state can be useful when you don’t want to fetch the items just after the listing is displayed in UI, maybe because it involves an onerous backend query. The backend query is only performed when the refresh() method is called, typically by a user explicit action.

The item listing frozen state can be configured either from the buider API or from the ItemListing API:

PropertyListing listing = PropertyListing.builder(SUBJECT) //
    .frozen(true) (1)
    .build();

listing.setFrozen(true); (2)

boolean frozen = listing.isFrozen(); (3)

listing.refresh(); (4)
1 Use the builder API to set the listing in frozen state
2 Use the listing API to set the listing in frozen state
3 Check whether the listing is in frozen state
4 Calling refresh() will disable the frozen state, allowing the listing to fetch and display the items

3.7.6. Item listing data source additional items

The ItemListing API supports additional data source items. Additional items can be added using the ItemListing API and they will appear as conventional listing items, even they are not part of the items returned by the concrete data source.

Additional items can be used, for example, to allow the user to add an item to the listing, edit its data and then saving the item in the concrete datastore. At this point, the additional item can be removed, since the saved item will now be returned by the data source query.

The ItemListing API provides method to manage additional items: add an additional item, remove an additional item, remove all additional items and obtain the current additional items.

PropertyListing listing = PropertyListing.builder(SUBJECT).build();

listing.addAdditionalItem(myItem); (1)

List<PropertyBox> additionalItems = listing.getAdditionalItems(); (2)

listing.removeAdditionalItem(myItem); (3)

listing.removeAdditionalItems(); (4)
1 Add an additional item
2 Get the current additional items
3 Remove the additional item
4 Remove all the additional items

3.7.7. Item listing configuration

The item listing builders provides a set of methods to configure the item listing component, most of them coming from the Vaadin Grid component configuration options.

This includes for example setting the page size, setting whether the listing column are resizable and can be reordered, setting the frozen columns and so on.

PropertyListing listing = PropertyListing.builder(SUBJECT) //
    .pageSize(50) (1)
    .columnReorderingAllowed(true) (2)
    .resizable(true) (3)
    .frozenColumns(1) (4)
    .heightByRows(true) (5)
    .verticalScrollingEnabled(true) (6)
    .multiSort(true) (7)
    .build();
1 Sets the page size (the number of items fetched at a time from the data source)
2 Set whether the columns can by reordered by the user
3 Set whether the columns can by resized by the user
4 Set the number of frozen columns, i.e. the fixed columns, not involved in horizontal scrolling
5 Set whether the listing’s height is defined by the number of its rows
6 Set whether the vertical scrolling is enabled
7 Set whether multiple column sorting is enabled on the client-side

3.7.8. Listen to listing row clicks

One ore more item click listener can be added to the listing to handle user clicks on the listing rows. The click event provides the clicked item instance and a reference to the listing on which the click occurred.

Furthermore, a set of additional click information are provided, such as the click count, the modifier keys, the screen coordinates and so on.

PropertyListing listing = PropertyListing.builder(SUBJECT) //
    .withItemClickListener(event -> { (1)
      PropertyBox clickedItem = event.getItem(); (2)
      PropertyListing source = event.getSource(); (3)
      event.getClickCount(); (4)
      event.getButton(); (5)
      event.isCtrlKey(); (6)
      /* other getters omitted */
    }).build();
1 Add an item click listener
2 Get the item instance on which the click occurred
3 Get the item listing reference
4 Get the consecutive clicks count
5 Get the mouse button, if available
6 Check whether the CTRL key was down when the click occurred

3.7.9. Configure the item listing columns

The item listing columns are auto-generated by default using the provided listing property set.

  • PropertyListing: each property declared in the property set will be rendered as a column. You can refer to a column using the corresponding property definition.

  • BeanListing: each available bean property will be rendered as a column. You can refer to a column using the corresponding bean property name.

Change the column order and visibility

A set of builder methods are available to change the listing column display order. The builder methods allow to:

  • Explicitly provide the column to show and their order.

  • Declare the column position relative to another column (before or after)

  • Display a column as first or last in the columns set

  • Set a column as hidden

PropertyListing listing = PropertyListing.builder(SUBJECT) //
    .visibleColumns(NAME, ID) (1)
    .displayAsFirst(NAME) (2)
    .displayAsLast(ID) (3)
    .displayBefore(NAME, ID) (4)
    .displayAfter(ID, NAME) (5)
    .hidden(ID) (6)
    .build();

BeanListing<MyBean> listing2 = BeanListing.builder(MyBean.class) (7)
    .visibleColumns("name", "id").displayAsFirst("name").hidden("id").build();
1 Declare the visibile columns and the display order
2 Set the NAME column to be displayed as first
3 Set the ID column to be displayed as last
4 Set the NAME column to be displayed before the ID column
5 Set the ID column to be displayed after the NAME column
6 Set the ID column as hidden
7 Example using a BeanListing
Column configuration

The item listing builder provides a set of methods to configure a listing column, including:

  • Se the column header, with localizable messages support. See Internationalization.

  • Se the column footer, with localizable messages support. See Internationalization.

  • Set the column width and alignment

  • Set whether the column is resizable by the user

  • Set whether the column is sortable and optionally provide the sort logic

  • Set the column value provider and renderer

  • Set whether the column is frozen, i.e. fixed and not involved in horizontal scrolling

PropertyListing listing = PropertyListing.builder(SUBJECT) //
    .header(NAME, "The name") (1)
    .header(NAME, "The name", "name.message.code") (2)
    .headerComponent(NAME, new Button("name")) (3)
    .width(NAME, "100px") (4)
    .flexGrow(NAME, 1) (5)
    .alignment(NAME, ColumnAlignment.CENTER) (6)
    .footer(NAME, "Footer text") (7)
    .resizable(NAME, true) (8)
    .sortable(NAME, true) (9)
    .sortUsing(NAME, ID) (10)
    .sortProvider(NAME, direction ->
    /* sort logic omitted */
    null) (11)
    .valueProvider(NAME, item -> item.getValue(NAME)) (12)
    .renderer(NAME, new TextRenderer<>()) (13)
    .frozen(NAME, true) (14)
    .build();
1 Set the NAME column header
2 Set the NAME column header using a localizable message
3 Set the NAME column header using a Component
4 Set the NAME column width
5 Set the NAME column flex grow ratio
6 Set the NAME column alignment
7 Set the NAME column footer
8 Set whether the NAME column is resizable
9 Set whether the NAME column is sortable
10 Set that the ID property must be used to sort the NAME column
11 Set the NAME column sort logic
12 Set the NAME column value provider
13 Set the NAME column renderer
14 Set the NAME column as frozen
Adding columns

New virtual columns can be added to an item listing, i.e. columns which are not directly bound to an item property.

A virtual column can be used for example to display a calculated result, using the item instance property values, or to display a UI component.

1. Add a virtual column:

A new column can be added to the listing using the withColumn builder method and providing a function to obtain the column value, given the current row item instance (a PropertyBox for a PropertyListing and a bean class instance for a BeanListing.

The added column can be configured, with the same configuration options described in the section above. Then, the column is added to the listing using the add() method.

Example using a PropertyListing:

PropertyListing listing = PropertyListing.builder(SUBJECT) //
    .withColumn(item -> "Virtual: " + item.getValue(ID)) (1)
    .header("Virtual") (2)
    .displayBefore(NAME) (3)
    .add() (4)
    .build();
1 Add a new virtual column, which uses the row item to provide its value
2 Configure the added column setting the header text
3 Set to display the added column before the NAME column
4 Add the column to the listing

Example using a BeanListing:

BeanListing<MyBean> listing2 = BeanListing.builder(MyBean.class) //
    .withColumn(item -> "Virtual: " + item.getId()) (1)
    .header("Virtual") (2)
    .displayAsFirst() (3)
    .add() (4)
    .build();
1 Add a new virtual column, which uses the row item to provide its value
2 Configure the added column setting the header text
3 Set to display the added column as first
4 Add the column to the listing

2. Add a Component type column:

A convenience withComponentColumn method is available to add a Vaadin Component type column, automatically configuring a suitable Component renderer for the column itself.

Just like a virtual column, the column can be configured and then the add() method should be used to add it to the listing.

PropertyListing listing = PropertyListing.builder(SUBJECT) //
    .withComponentColumn(item -> new Button(item.getValue(NAME))) (1)
    .header("Component") (2)
    .sortUsing(NAME) (3)
    .add() (4)
    .build();
1 Add a new Component type column, which provides a Button and uses the row item to set the button text
2 Configure the added column setting the header text
3 Set that the NAME property has to be used to sort the added column
4 Add the column to the listing

3.7.10. Default column value rendering strategy

If an explicit column renderer is not configured for a column, the default column value rendering strategy uses the Holon Platform StringValuePresenter API to render a column value.

This ensures a consistent behaviour across the application on how the values are displayed and the current LocalizationContext is used, if available, to apply the proper internationalization conventions to format, for example, a numeric value.

See the StringValuePresenter API documentation for details.

Furthermore, for a PropertyListing implementation, the PropertyValuePresenter architecture is used to render the column value, allowing to extend and tune the presentation strategy at higher level, ensuring consistency across the application UI and backend.

See the Property value presentation documentation for details.

For example, suppose we want to add a # prefix to each value of the ID property. We can declare a PropertyValuePresenter and bind it to the ID property in the property presenters registry:

PropertyValuePresenterRegistry.getDefault() (1)
    .forProperty(ID, (property, value) -> "#" + value); (2)
1 Get the default PropertyValuePresenterRegistry
2 Register a PropertyValuePresenter, bound to the ID property, which adds a # prefix to the property value

From now on, any PropertyListing instance will use the new presenter to render the ID property column value.

PropertyListing listing = PropertyListing.builder(SUBJECT).build(); (1)

String value = ID.present(1L); (2)
1 The PropertyListing instance will use the new presenter to render the ID property column value (so the ID column cells will show #1, #2 and so on)
2 Any other property presentation service will use the new presenter for the ID property value. In this example, the ID property is directly used to present the 1 value (declared as a Long) and the result will be the String #1.

3.7.11. Render columns using Components

The PropertyListing builder makes available helper methods to render a column using a Component, providing the function to use to obtain the Component instance for each cell, given the current row PropertyBox item.

PropertyListing listing = PropertyListing.builder(SUBJECT) //
    .componentRenderer(NAME, item -> { (1)
      return new Button(item.getValue(NAME));
    }).build();
1 Render the column identified by the NAME property as a Component. In this example, a Button is returned, with button text setted as the current item NAME property value

3.7.12. Render columns using a ViewComponent

A PropertyListing column can be rendered using a ViewComponent, leveraging the ViewComponent rendering architecture and thus providing a consistent data rendering strategy across all the application UI components.

See View components and forms for information about ViewComponent purpose and configuration.

PropertyListing listing = PropertyListing.builder(SUBJECT) //
    .renderAsViewComponent(NAME) (1)
    .build();
1 The column identified by the NAME property will be rendered using a ViewComponent

3.7.13. Add a context menu

The item listing supports a context menu, which can be added to the listing and will open by default when a right click or a long tap is performed by the user on a listing row.

The item listing context menu builder provides a set of methods to:

  • Add context menu item using a text, with message localization support.

  • Add context menu item using a Component.

  • Set the menu item click handler, providing an event form which to obtain the listing item instance of the row on which to context menu was opened and a reference to the item listing itself.

  • Configure the context menu items.

PropertyListing listing = PropertyListing.builder(SUBJECT) //
    .contextMenu() (1)
    .withItem("Context menu item 1") (2)
    .withClickListener(e -> { (3)
      PropertyBox rowItem = e.getItem(); (4)
      e.getItemListing(); (5)
      Notification.show("Context menu item clicked on row id " + rowItem.getValue(ID));
    }).add() (6)
    .withItem("Context menu item 2", "item.message.code", e -> {
      /* do something */}) (7)
    .withItem(new Button("Context menu item 3"), e -> {
      /* do something */}) (8)
    .add() (9)
    .build();
1 Create a context menu for the listing: a context menu builder is returned to configure the menu items
2 Add a menu item with given text
3 Configure the menu item adding the click handler
4 The click event provides the listing item instance of the row on which to context menu was opened
5 The parent item listing can be obtained from the click event
6 Add the context menu item to the context menu
7 Add another context menu item, directly providing a localizable text and the item click handler
8 Add a Component type context menu item, directly providing a Button and the item click handler
9 Add the configured context menu to the item listing

3.7.14. Manage row details components

The item listing supports row details components, which can be opened and closed under each listing row.

To enable the item row details, a function which provides the details content for each row has to be provided. For each row, the listing row item instance is provided as function argument.

The item details contents can be rendered in two ways:

  • As a text: using the itemDetailsText builder method.

  • As a Component: using the itemDetailsComponent builder method.

PropertyListing listing = PropertyListing.builder(SUBJECT) //
    .itemDetailsText(item -> "Detail of: " + item.getValue(ID)) (1)
    .itemDetailsComponent(item -> new Button(item.getValue(NAME))) (2)
    .build();
1 Set the item details renderer using text
2 Set the item details renderer using a Component

By default, the item details are shown/hidden for each listing row when the user clicks on a row. This behaviour can be disabled using the itemDetailsVisibleOnClick builder method.

In this case, the item details content should be opened or closed programmatically. For this purpose, the setItemDetailsVisible method of the item listing API can be use to show or hide the item details for a specific item.

PropertyListing listing = PropertyListing.builder(SUBJECT) //
    .itemDetailsText(item -> "Detail of: " + item.getValue(ID)) (1)
    .itemDetailsVisibleOnClick(false) (2)
    .withItemClickListener(e -> { (3)
      e.getSource().setItemDetailsVisible(e.getItem(), true);
    }).build();
1 Set the item details renderer using text
2 Disable the item details display at user click
3 Add an item click listener to handle the item details display, using the setItemDetailsVisible method

3.7.15. Header and footer configuration

The item listing header and footer sections can be completely customized, setting the cells contents and joining two or more cells together.

The header and footer sections handlers can be obtainer either using:

  • The builder API, using the header and footer methods.

  • The item listing API, using the getHeader() and getFooter() methods.

PropertyListing listing = PropertyListing.builder(SUBJECT) //
    .header(header -> { (1)
      header.prependRow().join(ID, NAME).setText("A text");
    }).footer(footer -> { (2)
      footer.appendRow().getCell(ID).ifPresent(cell -> cell.setText("ID footer"));
    }).build();

listing.getHeader().ifPresent(header -> { (3)
  /* customize header */
});
listing.getFooter().ifPresent(footer -> { (4)
  /* customize footer */
});
1 Use the builder to customize the header section
2 Use the builder to customize the footer section
3 Get the header section
4 Get the footer section

3.7.16. Handling row selection

The item listing supports both single and multiple row selection modes. When the listing is in multiple selection mode, a checkbox is shown before each listing row to allow user selection. Additionaly, a select all checkbox is available in the listing header first cell.

The item listing selection mode can be enabled either using:

  • The builder API, using the selectionMode method (NONE, SINGLE or MULTI). The singleSelect() and multiSelect() convenience methods are provided as shorters for the single and multiple selection mode.

  • The item listing API, using the setSelectionMode method (NONE, SINGLE or MULTI).

A SelectionListener can be added to the item listing in order to listen to item selection events and obtain the currently selected items.

Furthermore, the item listing API provides a set of methods to select or deselect one or more items programmatically.

PropertyListing listing = PropertyListing.builder(SUBJECT) //
    .selectionMode(SelectionMode.SINGLE) (1)
    .singleSelect() (2)
    .multiSelect() (3)
    .withSelectionListener(event -> { (4)
      Set<PropertyBox> items = event.getAllSelectedItems(); (5)
      Optional<PropertyBox> item = event.getFirstSelectedItem(); (6)
    }).build();

listing.setSelectionMode(SelectionMode.MULTI); (7)
listing.addSelectionListener(event -> { (8)
});

listing.select(myItem); (9)
listing.deselect(myItem); (10)
listing.deselectAll(); (11)
1 Set the listing selection mode
2 Set the listing selection mode as single
3 Set the listing selection mode as multiple
4 Add a selection listener
5 Get the selected items, if any
6 Get the first selected item, if any
7 Use the listing API to change the selection mode
8 Add a selection listener
9 Programmatically select an item
10 Programmatically deselect an item
11 Deselect all items

3.7.17. Editing the listing items

The item listing supports an edit mode, which can be enabled using the editable() builder method.

When a listing is editable, a row can be setted in edit mode using the editItem(T item) method of the item listing API. When editItem is called, each cell content is replaced by an input component to allow to change the item property value which corresponds to each column.

The updated row item values can be saved using the saveEditingItem() method of the item listing API, or discarded using the cancelEditing() method. Then the row editor is closed, restoring the default cell contents.

A set of listeners can be added to the listing to control the editing process:

  • EditorSaveListener: for editor save events, which corresponds to the saveEditingItem() method call. Can be used for example to update the data in the backend.

  • EditorCancelListener: for editor cancel events, which corresponds to the cancelEditing() method call.

  • EditorOpenListener / EditorCloseListener: for editor open and close events.

The component to use as input to edit each item property value can be customized using the editorComponent(…​) builder method.

The row editor provides a buffered mode: when enabled, the edited values are setted into the item instance only when the saveEditingItem() method is called, for example using a provided save button. The editorBuffered builder method can be used to enable or disable the buffered mode.

PropertyListing.builder(SUBJECT) //
    .editable() (1)
    .editorBuffered(true) (2)
    .withComponentColumn(item -> Components.button("Edit", e -> listing.editItem(item)) (3)
    ).editorComponent(new Div( (4)
        Components.button("Save", e -> listing.saveEditingItem()),
        Components.button("Cancel", e -> listing.cancelEditing())))
    .displayAsFirst() (5)
    .add() (6)
    .withEditorSaveListener(event -> { (7)
      PropertyBox item = event.getItem(); (8)
      /* update the item in backend */
    }).build();
1 Enable the editable mode
2 Set the editor in buffered mode
3 Add a new column to provide the Edit button for each row, using the editItem method to edit the current row item
4 Set the editor component for the added column: it will be displayed when the row is in edit mode, providing the Save and Cancel buttons, which invoke the saveEditingItem() and the cancelEditing() methods respectively
5 Configure the added column to be displayed as first column
6 Add the column to the listing
7 Add an editor save listener to update the item data in backend
8 The save event provides the edited item instance
Default editor components and customization

By default, when a row is turned into edit mode, the Holon PropertyRenderer architecture is used to obtain an Input type component for each item property to edit (See Input components and forms for information on the Input component API).

See the Property renderering documentation for details about the property renderers architecture.

The editor component for a listing item property (and so for a listing column), can be configured using the following builder methods:

  • editor: to set a Input as editor component. See Input components and forms.

  • editorField: to use a standard Vaadin HasValue component as editor component.

  • editorComponent: to use generic Component as editor component.

PropertyListing.builder(SUBJECT) //
    .editor(NAME, Input.string().build()) (1)
    .editor(NAME, property -> Input.string().build()) (2)
    .editor(ID, Input.string().build(), new StringToLongConverter("")) (3)
    .editorField(NAME, new TextField()) (4)
    .editorField(ID, new TextField(), new StringToLongConverter("")) (5)
    .editorComponent(ID, new Button()) (6)
    .build();
1 Set the editor Input to use for the NAME property/column
2 Set the editor Input to use for the NAME property/column
3 Set the editor Input to use for the ID property/column, providing a Long to String and back converter
4 Set the editor HasValue component to use for the NAME property/column
5 Set the editor HasValue component to use for the ID property/column, providing a Long to String and back converter
6 Set a generic component as ID property/column editor component

Since the PropertyRenderer based architecture is used to obtain the editor components by default using the Input rendering type, the editor component can customized also by adding a new PropertyRenderer for a property definition. This ensures consistency across the application, and can be used to automatically configure the editor components for any item listing instance.

PropertyRendererRegistry.getDefault().forProperty(NAME, (1)
    InputPropertyRenderer.create(property -> Input.stringArea().build()));

PropertyListing.builder(SUBJECT).editable().build(); (2)
1 Add a new PropertyRenderer to the default registry to render the NAME property using the provided stringArea type Input component.
2 Any item listing which includes the NAME property will use the registered renderer to obtain the Input component to use as the NAME property/column editor
Data validation

To validate the user input when a listing row is in edit mode, the standard Holon Validator API can be used.

When the editor save action is performed (using the saveEditingItem() item listing API method), all the registered validators are invoked and the save operation is interrupted if one ore more validators fails, notifying the validation errors to the user.

1. Property level validators:

One or more validators can be bound to an item property to perform the property value validation. The property value validation is performed when the user leaves an editor component and when the editor save action in invoked.

Any validator bound to a property at property definition time are automatically inherited as item property value validators.

New validators can be added to an item property using the withValidator item listing builder method.

final StringProperty NAME = StringProperty.create("name").withValidator(Validator.notBlank()); (1)

PropertyListing.builder(SUBJECT) //
    .withValidator(NAME, Validator.max(10)) (2)
    .build();
1 The notBlank validator is added to the NAME property at definition time, so it will be inherited from the item listing editor validation
2 A max validator is added to the NAME property to ensure the String value is maximum 10 characters long

2. Row level validators:

One or more row level validators can be added to an item listing using the withValidator builder method. The row level validators are invoked when the editor save action is performed, and the current item state s provided to validators to perform the required checks.

PropertyListing.builder(SUBJECT) //
    .withValidator(Validator.create(item -> item.getValue(ID) != null, "Id value must be not null")) (1)
    .build();
1 Add a row level validator
Validation status handler

By default, the validation errors originated by the registered validators are notified to the user in the following way:

  • For property level validators, if the editor component supports error messages notification (using the Vaadin HasValidation interface), the validation errors are setted as component errors.

  • For row level validators, a Dialog is used to notify the validation errors.

The validation status errors notification can be customized using the ValidationStatusHandler interface, both at property and row level.

The ValidationStatusHandler validation event provides the validation status (valid, invalid or unresolved) and the current validation errors, if any.

To customize both the property level and the row level validation status notification, the validationStatusHandler builder method can be used.

PropertyListing.builder(SUBJECT) //
    .validationStatusHandler(event -> { (1)
      if (event.isInvalid()) {
        Notification.show("Validation falied: " + event.getErrorMessage());
      }
    }).validationStatusHandler(NAME, event -> { (2)
      /* omitted */
    }).build();
1 Set a custom row level validation status handler
2 Set a custom property level validation status handler for the NAME property

Furthermore, the GroupValidationStatusHandler interface can be used to override all the validation status notification, customizing both the property level and the row level validation status notification using a single handler.

PropertyListing.builder(SUBJECT) //
    .groupValidationStatusHandler(event -> { (1)
      event.getGroupStatus(); (2)
      event.getInputsValidationStatus(); (3)
      event.getGroupErrorMessages(); (4)
      event.getInputsValidationStatus().forEach(s -> s.getErrorMessages()); (5)
    }).build();
1 Set the GroupValidationStatusHandler for the row editor
2 Get the row-level validation status
3 Get the single editor components validation status
4 Get the row-level validation errors
5 Get the single editor components validation errors
Listening to editor components value change

When a listing row is in edit mode, one ore more ValueChangeListener can be added to the editor input components to listen to value change, using the withValueChangeListener builder method.

PropertyListing.builder(SUBJECT) //
    .withValueChangeListener(NAME, event -> { (1)
      event.getOldValue();
      event.getValue();
    }).build();
1 Add a value change listener for the NAME property

3.8. View components and forms

The ViewComponent component can be used to display data, providing consistent formatting and style across the whole UI, including localization and internationalization support.

The ViewComponent API extends:

A ViewComponent instance can be created either using the ViewComponent API builder methods, providing the value type or the Components API, through the Components.view sub interface (see Component builders and providers).

The ViewComponent builder API provides methods to configure the UI component, including methods to set a localizable label for the component and to add component click listeners.

ViewComponent<String> viewComponent = ViewComponent.builder(String.class) (1)
    .fullWidth() (2)
    .styleName("my-style") (3)
    .label("My label") (4)
    .label("My label", "label.message.code") (5)
    .description("My description") (6)
    .onClick(event -> { (7)
      /* handle the click event */
    }).withValue("Initial value") (8)
    .withValueChangeListener(event -> { (9)
      String oldValue = event.getOldValue(); (10)
      String newValue = event.getValue(); (11)
    }).build();

viewComponent = ViewComponent.create(String.class); (12)

viewComponent = Components.view.component(String.class) (13)
    /* configuration omitted */
    .build();
1 Get a String type ViewComponent builder
2 Set the component width to 100%
3 Add a CSS style class name
4 Set the label
5 Set the label using a localizable message
6 Set the description
7 Add a click handler
8 Set an initial value
9 Add value change listener
10 The previous value
11 The changed value
12 Direct ViewComponent creation method
13 The Components.view.component method of the Components API can be used to create and configure a ViewComponent

A ViewComponent instance can be used as value holder to handle the value to display (see The ValueHolder interface).

ViewComponent<String> viewComponent = ViewComponent.create(String.class); (1)

new VerticalLayout().add(viewComponent.getComponent()); (2)

viewComponent.setValue("My value"); (3)
String value = viewComponent.getValue(); (4)
value = viewComponent.getValueIfPresent().orElse("Default value"); (5)

viewComponent.clear(); (6)
boolean empty = viewComponent.isEmpty(); (7)
1 Create a String type ViewComponent
2 Add the component to a layout using the getComponent() method
3 Set the component value
4 Get the component value
5 Get the component optional value
6 Clear the component value
7 Check if the component is empty

3.8.1. Value conversion and formatting

A ViewComponent converts the value it holds to a String in order to display it in UI. For value conversion and formatting the ViewComponent API relies on the Holon Platform StringValuePresenter API.

This ensures a consistent behaviour across the application on how the values are displayed and the current LocalizationContext is used, if available, to apply the proper internationalization conventions to format, for example, a numeric value.

See the StringValuePresenter API documentation for details.

To provide a custom value conversion logic, a value conversion function can be provided at ViewComponent build time:

ViewComponent<Integer> viewComponent = ViewComponent.<Integer>builder(value -> String.valueOf(value)).build(); (1)
1 Create a Integer type ViewComponent, providing the function to convert the Integer value to String

3.8.2. Using the property value presenters

When a Property value has to be displayed, a ViewComponent can be created and bound to the Property definition, inheriting the property value type as component type and enabling the PropertyValuePresenter based strategy to convert and format the value in a consistent way across the application.

See the Property value presentation documentation for details on property value presentation.
NumericProperty<Integer> MY_PROPERTY = NumericProperty.integerType("my_property"); (1)

ViewComponent<Integer> viewComponent = ViewComponent.create(MY_PROPERTY); (2)
1 Integer type property definition
2 Create a ViewComponent using the MY_PROPERTY property definition. The property value presenters will be used to convert the value to a String and apply formatting

This allows to generalize the property value presentation and make it consistent across all the application UI components which support property value presenters, including Input components (see Input components and forms) and listing components (see List data in grids).

New PropertyValuePresenter can be registered and used transparently to present the value for a specific type or a specific property configuration.

For example, suppose we want to add a # prefix to each value of the MY_PROPERTY property. We can declare a PropertyValuePresenter and bind it to the MY_PROPERTY property in the property presenters registry:

PropertyValuePresenterRegistry.getDefault() (1)
    .forProperty(MY_PROPERTY, (property, value) -> "#" + value); (2)
1 Get the default PropertyValuePresenterRegistry
2 Register a PropertyValuePresenter, bound to the MY_PROPERTY property, which adds a # prefix to the property value

From now on, any ViewComponent instance will use the new presenter to render the MY_PROPERTY property value.

ViewComponent<Integer> viewComponent = ViewComponent.create(MY_PROPERTY); (1)

String value = MY_PROPERTY.present(1); (2)
1 The ViewComponent instance will use the new presenter to render the MY_PROPERTY property value
2 Any other property presentation service will use the new presenter for the MY_PROPERTY property value. In this example, the MY_PROPERTY property is directly used to present the 1 value and the result will be the String #1.

3.8.3. Create a ViewComponent from a generic Component

You can use the ViewComponent API adapt static methods to transform any Component into a ViewComponent. The Component will be used as ViewComponent content and a suitable function must be provided to setup the Component instance each time the value changes, for example to display the value using the custom Component instance.

A generic Component can be turn into a ViewComponent either providing:

1. The Component to use a ViewComponent content and a consumer function to setup the Component instance each time the value changes:

ViewComponent<String> viewComponent = ViewComponent.adapt(String.class, new Span(), (span, value) -> {
  span.setText(value);
}).build(); (1)
1 A Span is used as String type ViewComponent content, setting the Span text with the provided value each time the ViewComponent value changes

2. A function to provided a custom Component instance each time the value changes, to be used as ViewComponent content:

ViewComponent<String> viewComponent = ViewComponent.adapt(String.class, value -> {
  if (value != null) {
    return new RouterLink(value, MyView.class, value); (1)
  }
  return null;
}).build();
1 Each time the value changes, a new RouterLink is created and used as ViewComponent content

The adapt methods return a builder API consistent with the default ViewComponent builder API, allowing to configure the ViewComponent component.

ViewComponent<String> viewComponent = ViewComponent.adapt(String.class, value -> {
  if (value != null) {
    return new RouterLink(value, MyView.class, value); (1)
  }
  return null;
}) (1)
    .fullWidth() (2)
    .styleName("my-style") (3)
    .label("My label") (4)
    .label("My label", "label.message.code") (5)
    .description("My description") (6)
    .onClick(event -> { (7)
      /* handle the click event */
    }).withValue("Initial value") (8)
    .withValueChangeListener(event -> { (9)
      String oldValue = event.getOldValue();
      String newValue = event.getValue();
    }).build();
1 Create a ViewComponent which shows a RouterLink each time the value changes
2 Set the component width to 100%
3 Add a CSS style class name
4 Set the label
5 Set the label using a localizable message
6 Set the description
7 Add a click handler
8 Set an initial value
9 Add value change listener

3.8.4. ViewComponent property renderer

When the holon-vaadin-flow artifact is in classpath, a default PropertyRenderer is automatically registered to render a Property as a ViewComponent.

See the Property renderering documentation for details about the property renderers architecture.

So the ViewComponent.class type can be used to render any property as a ViewComponent. In this scenario, the property localizable message is used if available as ViewComponent label.

BooleanProperty PROPERTY = BooleanProperty.create("test") (1)
    .message("caption").messageCode("caption.message.code"); (2)

ViewComponent<Boolean> viewComponent = PROPERTY.render(ViewComponent.class); (3)
1 Declare a boolean type property
2 Set the property localizable message
3 Create ViewComponent instance using the property renderer. The property localizable message will be used as ViewComponent label.

3.8.5. Organize view components in groups and forms

A set of ViewComponent type components can be grouped and organized using a PropertyViewGroup, using the Holon Platform Property model to bind each ViewComponent to a property definition.

The property definitions will be used as references to get and configure the ViewComponent instances managed by the group and to set the group values to display using a PropertyBox type.

The PropertyViewGroup API provides method to inspect the available properties and the ViewComponent instances bound to each property. Through the ValueHolder API, the group value can be setted and managed (see The ValueHolder interface).

To create a PropertyViewGroup, the property set to use must be provided at build time. Both the PropertyViewGroup API and the Components API (through the Components.view sub interface) can be used to obtain a property view group builder.

By default, the property rendering architecture is used to automatically generate the ViewComponent instances for each property of the group property set (see ViewComponent property renderer).

For example, given the following property model definition:

static final NumericProperty<Long> ID = NumericProperty.longType("id");
static final StringProperty NAME = StringProperty.create("name");
static final PropertySet<?> SUBJECT = PropertySet.of(ID, NAME);

A PropertyViewGroup can be created in the following way:

PropertyViewGroup group = PropertyViewGroup.builder(SUBJECT).build(); (1)
group = Components.view.propertyGroup(SUBJECT).build(); (2)

Collection<Property<?>> properties = group.getProperties(); (3)
Stream<ViewComponent<?>> components = group.getElements(); (4)
group.getBindings().forEach(binding -> { (5)
  Property<?> property = binding.getProperty();
  ViewComponent<?> component = binding.getElement();
});

Optional<ViewComponent<?>> element = group.getElement(NAME); (6)
ViewComponent<?> component = group.requireElement(NAME); (7)

PropertyBox value = PropertyBox.builder(SUBJECT).set(ID, 1L).set(NAME, "One").build();

group.setValue(value); (8)
value = group.getValue(); (9)

group.clear(); (10)
group.isEmpty(); (11)
1 Create a PropertyViewGroup using the SUBJECT property set. The ViewComponent for each property will be automatically generated using the property renderers
2 Create a PropertyViewGroup from the Components API
3 Get the group property set
4 Get the group ViewComponent elements
5 Get the group bindings, i.e. the ViewComponent bound to each property
6 Get the ViewComponent bound to the NAME property, if available
7 Require the ViewComponent bound to the NAME property
8 Set the group value using a PropertyBox: each property value available from the PropertyBox instance will be setted into the corresponding ViewComponent, if available
9 Get the current group value
10 Clear the group value
11 Check whether the group is empty, i.e. has no value

To customize this behaviour and provide the ViewComponent to use for one or more property, the PropertyViewGroup builder API provides a set of bind methods, which can be use to explicitly bind a ViewComponent instance to a property.

PropertyViewGroup group = PropertyViewGroup.builder(SUBJECT) (1)
    .bind(NAME, ViewComponent.create(String.class)) (2)
    .bind(NAME, property -> ViewComponent.create(String.class)) (3)
    .build();
1 Create a PropertyViewGroup using the SUBJECT property set
2 Bind a ViewComponent instance to the NAME property
3 Bind a ViewComponent instance to the NAME property using a function
Group configuration

The PropertyViewGroup builder API provides method to configure the group, allowing to:

  • Hide one or more property: the hidden property won’t be bound to a ViewComponent

  • Add a post processor to configure the ViewComponent instances just before the binding occurs

  • Add group value change listeners

PropertyViewGroup group = PropertyViewGroup.builder(SUBJECT) (1)
    .hidden(ID) (2)
    .withPostProcessor((property, component) -> { (3)
      /* ViewComponent configuration */
      component.hasStyle().ifPresent(s -> s.addClassName("my-style"));
    }) //
    .withValueChangeListener(event -> { (4)
      /* handle value change */
      PropertyBox value = event.getValue();
    }).build();
1 Create a PropertyViewGroup using the SUBJECT property set
2 Set the ID property as hidden
3 Add a binding post processor
4 Add a value change listener
Using a PropertyViewForm to make the group a UI component

The PropertyViewGroup API handles a group of view components bound to a property set, but is it not a UI component itself. The PropertyViewForm API can be used to provide a UI component for the group and use it as a form in application UI.

A PropertyViewGroup extends the HasComponent interface to provide the actual group UI component (see The HasComponent interface) and the Composable API to declare the UI component to use as group elements container and the strategy to apply to organize the ViewComponent group elements into the group content area.

The Composer API can be used to implement the composition logic, i.e. to add the available ViewComponent elements to the UI component declared as group content. The Composer function provides:

  • The group UI content element.

  • A reference to the group API to obtain the ViewComponent elements (and the corresponding property bindings) to add to the group UI content element.

A default Composer for a com.vaadin.flow.component.HasComponents type UI element can be obtain using the componentContainerComposer() method of the Composable API. This composer adds the group elements sequentially to the HasComponents group content element, in the order they are provided from the group property set.

PropertyViewForm form = PropertyViewForm.builder(new VerticalLayout(), SUBJECT) (1)
    .composer(Composable.componentContainerComposer()) (2)
    .build();

myLayout.add(form.getComponent()); (3)

form.setValue(PropertyBox.builder(SUBJECT).set(ID, 1L).set(NAME, "One").build()); (4)
1 Create a PropertyViewForm using the SUBJECT property set, a VerticalLayout as group UI element
2 The default component container composer is used
3 The getComponent() method can be used to add the PropertyViewForm to a layout
4 Set a form value

A set of convenience builder methods are available for the base Vaadin layouts:

PropertyViewForm form = PropertyViewForm.verticalLayout(SUBJECT).build(); (1)
form = PropertyViewForm.horizontalLayout(SUBJECT).build(); (2)
form = PropertyViewForm.formLayout(SUBJECT).build(); (3)

// Using the Components API:
form = Components.view.formVertical(SUBJECT).build();
form = Components.view.formHorizontal(SUBJECT).build();
form = Components.view.form(SUBJECT).build();
1 Create a PropertyViewForm using the SUBJECT property set and a VerticalLayout as group UI element
2 Create a PropertyViewForm using the SUBJECT property set and a HorizontalLayout as group UI element
3 Create a PropertyViewForm using the SUBJECT property set and a FormLayout as group UI element

A custom Composer can be used to control and customize the group element composition strategy:

PropertyViewForm form = PropertyViewForm.verticalLayout(SUBJECT) //
    .composer((content, group) -> { (1)
      group.getBindings().forEach(binding -> {
        content.add(binding.getElement().getComponent());
      });
    }).build();
1 Provide a custom composition logic
Form configuration

A PropertyViewForm can be configured using the builder API in order to:

  • Hide one or more property: the hidden property won’t be bound to a ViewComponent

  • Add an initializer to configure the form UI content

  • Add a post processor to configure the ViewComponent instances just before the binding occurs

  • Add form value change listeners

  • Configure the property captions, i.e. to set the label to use for the ViewComponent bound to a property

PropertyViewForm.formLayout(SUBJECT) (1)
    .hidden(ID) (2)
    .initializer(content -> { (3)
      /* content configuration */
      FormLayout fl = content;
    }).withPostProcessor((property, component) -> { (4)
      /* ViewComponent configuration */
      component.hasStyle().ifPresent(s -> s.addClassName("my-style"));
    }) //
    .withValueChangeListener(event -> { (5)
      /* handle value change */
      PropertyBox value = event.getValue();
    }) //
    .hidePropertyCaption(ID) (6)
    .propertyCaption(NAME, "My name", "name.message.code") (7)
    .build();
1 Create a PropertyViewForm using the SUBJECT property set and a FormLayout
2 Set the ID property as hidden
3 Set the form content initializer
4 Set the binding post processor
5 Add a value change listener
6 Hide the ID property caption
7 Set the localizable caption for the NAME property

3.9. Input components and forms

The Input API can be used to create UI components to handle user input, ensuring a consistent data type handling and input format patterns.

The Input API extends:

Furthermore, the Input API provides methods to set the UI component in read only mode and to display required indicators and validation errors.

A Input instance can be created either using the Input API builder methods, providing the value type, or the Components API, through the Components.input sub interface (see Component builders and providers).

The Input builder API provides methods to configure the UI component, including methods to set a localizable label for the component and a complete set of configuration methods according to the input value type.

Input<String> input = Input.string() (1)
    .fullWidth() (2)
    .styleName("my-style") (3)
    .label("My label") (4)
    .label("My label", "label.message.code") (5)
    .description("My description") (6)
    .blankValuesAsNull(true) (7)
    .autocapitalize(Autocapitalize.WORDS) (8)
    .maxLength(50) (9)
    .placeholder("My placeholder", "placeholder.message.code")(10)
    .autofocus(true)(11)
    .prefixComponent(new Button("Prefix"))(12)
    .tabIndex(99) (13)
    .readOnly() (14)
    .withBlurListener(event -> {(15)
      /* handle blur event */
    }).withValueChangeListener(event -> { (16)
      String oldValue = event.getOldValue(); (17)
      String newValue = event.getValue(); (18)
    }).build();

input = Components.input.string() (13)
    /* configuration omitted */
    .build();
1 Get a String type Input builder
2 Set the width to 100%
3 Add a CSS style class name
4 Set the input label
5 Set the input label using a localizable message
6 Set the input description
7 Set to treat blank values as null values.
8 Set the auto-capitalize mode
9 Set the max characters count allowed for input
10 Set the input placeholder using a localizable message
11 Enable auto focus
12 Set the prefix component
13 Set the input tab index
14 Set the input as read only, preventing the user from changing the input value
15 Add a blur (focus lost) listener
16 Add value change listener
17 The previous value
18 The changed value

A set of Input builders for the most common types are available both form the Input API and from the Components.input API.

Each Input is rendered using a suitable UI component according to the input value type. For example, a TextField is used for String type inputs and a Checkbox for boolean type inputs.

Input<String> input1 = Input.string().build(); (1)
Input<String> input2 = Input.stringArea().build(); (2)
Input<Boolean> input3 = Input.boolean_().build(); (3)
Input<Double> input4 = Input.number(Double.class).build(); (4)
Input<LocalDate> input5 = Input.localDate().build(); (5)
Input<LocalDateTime> input6 = Input.localDateTime().build(); (6)
Input<LocalTime> input7 = Input.localTime().build(); (7)
Input<Date> input8 = Input.date().build(); (8)

Optional<Input<String>> input = Input.create(String.class); (9)
1 Create an Input for String type values
2 Create an Input for String type values using a text area to render the input component
3 Create an Input for Boolean type values
4 Create an Input for a numeric type value, a Double in this case
5 Create an Input for LocalDate type values
6 Create an Input for LocalDateTime type values
7 Create an Input for LocalTime type values
8 Create an Input for Date type values
9 Create an Input for the given value type, if a suitable implementation is available
The Components.input API provides the same methods to obtain the input builders.

A Input instance can be used as value holder to handle the input value (see The ValueHolder interface).

Input<String> input = Input.string().build(); (1)

myLayout.add(input.getComponent()); (2)

input.setValue("My value"); (3)
String value = input.getValue(); (4)
value = input.getValueIfPresent().orElse("Default value"); (5)

input.clear(); (6)
boolean empty = input.isEmpty(); (7)

input.setReadOnly(true); (8)
input.isReadOnly(); (9)

input.addValueChangeListener(event -> { (10)
  /* handle value change */
});
1 Create a String type Input
2 Add the input component to a layout using the getComponent() method
3 Set the input value
4 Get the input value
5 Get the input optional value
6 Clear the input value
7 Check if the input is empty (i.e. has no value)
8 Set the input as read only
9 Check whether the input is read only
10 Add a value change listener

3.9.1. Input property renderer

When the holon-vaadin-flow artifact is in classpath, a default PropertyRenderer is automatically registered to render a Property as an Input.

See the Property renderering documentation for details about the property renderers architecture.

So the Input.class type can be used to render any property as a Input. In this scenario, the property localizable message is used if available as Input label.

The Input API makes available the create(Property<T> property) method to directly create an Input for a given property.

NumericProperty<Integer> MY_PROPERTY = NumericProperty.integerType("my_property"); (1)

Input<Integer> input = Input.create(MY_PROPERTY); (2)

Input<Integer> viewComponent = MY_PROPERTY.render(Input.class); (3)
1 Declare a Integer type property
2 Create a Input to handle the MY_PROPERTY property value using the Input API
3 Create a Input to handle the MY_PROPERTY property value using the property renderer for the Input class

The property renderers architecture is used consistently across the application UI, so when a renderer is bound to a specific property condition it is used by all the UI components with property rendering support, including input groups/forms (see Organize Input components in groups and forms) and item listing editors (see Editing the listing items).

For example, suppose we want to use a custom MyInput class to render the Input component for the MY_PROPERTY property definition. A custom property renderer can be created and registered:

PropertyRendererRegistry.getDefault() (1)
    .forProperty(MY_PROPERTY, InputPropertyRenderer.create(property -> new MyInput())); (2)
1 Get the default PropertyRendererRegistry
2 Register a InputPropertyRenderer, bound to the MY_PROPERTY property, to provide a MyInput instance

From now on, the Input component for the MY_PROPERTY property definition will be a MyInput instance.

3.9.2. Input value conversion

The Input API provides methods to use an Input with a different type than the value type to handle, providing a suitable com.vaadin.flow.data.converter.Converter to perform the value conversions to the required type.

Input<String> stringInput = Input.string().build(); (1)
Input<Integer> integerInput = Input.from(stringInput, new StringToIntegerConverter("Conversion failed")); (2)
1 Create a String type Input
2 Create a Integer type Input from the previous one, providing the String to Integer and back converter

A Holon Platform PropertyValueConverter can also be use to provide the value conversion logic when the input is bound to a Property definition.

BooleanProperty PROPERTY = BooleanProperty.create("test");

Input<Integer> integerInput = Input.number(Integer.class).build(); (1)
Input<Boolean> booleanInput = Input.from(integerInput, PROPERTY,
    PropertyValueConverter.numericBoolean(Integer.class)); (2)
1 Create a Integer type Input
2 Create a Boolean type Input for the PROPERTY property, using the previous input and a numeric boolean PropertyValueConverter

3.9.3. Input adapters for HasValue components

The Input API provides methods to adapt a standard Vaadin HasValue type input component as an Input, including value conversion support using a com.vaadin.flow.data.converter.Converter.

Input<String> stringInput = Input.builder(new TextField()).build(); (1)

Input<Integer> input = Input.from(new TextField(), new StringToIntegerConverter("Conversion failed")); (2)
1 Create a String type Input from a TextField
2 Create a Integer type Input from a TextField, providing the value converter

3.9.4. Select type inputs

The SingleSelect and MultiSelect APIs are Input extensions which represent selectable input components, i.e. input components which provide a set of items from which the input value can be selected, either supporting single or multiple selection values.

The Input API and the corresponding Components.input API provides methods to create SingleSelect and MultiSelect type input components, providing the selectable value type.

For SingleSelect input types, the input component can be rendered as:

  • Simple select: a Vaadin Select component is used as input component.

  • Filterable select: a Vaadin ComboBox component is used as input component.

  • Options select: a RadioButtonGroup component is used as input component.

SingleSelect<String> singleSelect = Input.singleSelect(String.class).build(); (1)
singleSelect = Input.singleOptionSelect(String.class).build(); (2)

MultiSelect<String> multiSelect = Input.multiOptionSelect(String.class).build(); (3)
1 Create a String type SingleSelect input
2 Create a String type SingleSelect input using the options rendering mode
3 Create a String type MultiSelect input

The select input components can be used just like any other value holder type component to set and get the selected item/s (see The ValueHolder interface). The MultiSelect type input, since it allows multiple items selection, uses a java.util.Set to handle the input value.

The Components.input API provides the same methods to obtain the select type input builders.
SingleSelect<String> singleSelect = Input.singleSelect(String.class).build();

singleSelect.setValue("A value"); (1)
String value = singleSelect.getValue(); (2)
singleSelect.clear(); (3)
singleSelect.isEmpty(); (4)

MultiSelect<String> multiSelect = Input.multiOptionSelect(String.class).build();

multiSelect.setValue(Arrays.asList("Value 1", "Value 2").stream().collect(Collectors.toSet())); (5)
Set<String> values = multiSelect.getValue(); (6)
1 Set the input value, i.e. select the given value
2 Get the input (selected) value
3 Clear the input value (deselect any value)
4 Check whether the input is empyt (no value is selected)
5 Set the input values, i.e. select the given values, using a Set
6 Get the input (selected) values as a Set

Besides the Input API, the selectable inputs implements the Selectable API, which allows to manage and obtain the selected items.

SingleSelect<String> singleSelect = Input.singleSelect(String.class).build();

singleSelect.select("A value"); (1)
String value = singleSelect.getSelectedItem().orElse(null); (2)
singleSelect.deselectAll(); (3)
1 Select given value
2 Get the selected value
3 Deselect any value
Select items data source

A select type Input, whether it’s a SingleSelect or a MultiSelect, requires an item set to be configured, which represents the source for the selectable values. Only the available items will be used as valid selectable input values.

The builder API provides the items and addItem methods to provides the selection items set.

SingleSelect<String> singleSelect = Input.singleSelect(String.class) //
    .addItem("Value 1") (1)
    .addItem("Value 2") (2)
    .items("Value 1", "Value 2") (3)
    .build();
1 Add a selection item
2 Add another selection item
3 Set the selection items, replacing any previous item set

A standard Vaadin DataProvider can be used as items data source.

SingleSelect<String> singleSelect = Input.singleSelect(String.class) //
    .dataSource(DataProvider.ofItems("Value 1", "Value 2")) (1)
    .build();
1 Use a DataProvider as items data source

When the item type does not match the selection value type, a ItemConverter con be used to provide the item to selection value and back conversion logic.

SingleSelect<String> singleSelect = Input.singleSelect(String.class, MyBean.class, ItemConverter.create( (1)
    item -> item.getName(), (2)
    value -> Optional.of(new MyBean(value)) (3)
)).addItem(new MyBean("One")).addItem(new MyBean("Two")).build();
1 Create a String type SingleSelect which uses a MyBean class as items type, providing a suitable ItemConverter
2 Convert a MyBean type item into a String type selection value
3 Convert a String type selection value into a MyBean type item
Use the Datastore API as items data source

When the property model is used to build a select type input, the selection items are represented using the PropertyBox type by default, and the property which represents the selection value has to be declared a construction time.

The select input type will match the selection property type and the value provided as selection value will be obtained using the selection property definition from the PropertyBox type item instances.

For example, given the following property model definition:

static final NumericProperty<Long> ID = NumericProperty.longType("id");
static final StringProperty NAME = StringProperty.create("name");
static final PropertySet<?> SUBJECT = PropertySet.of(ID, NAME);

A SingleSelect to select the ID property can be created as follows:

SingleSelect<Long> singleSelect = Input.singleSelect(ID) (1)
    .addItem(PropertyBox.builder(SUBJECT).set(ID, 1L).set(NAME, "One").build()) (2)
    .build();
1 Create a SingleSelect using the ID property as selection property: the select input type will be Long like the property type
2 Add a PropertyBox type selection item

When a property model bound select is used, the most simple and powerful way to provided the selection items is to use a Datastore as data source. The Datastore API makes easy and straightful to bind the select input to a backend data source.

When a Datastore is used, a DataTarget definition is required to declare the data model entity to use as items data source (for example a RDBMS table name, or a collection name in a document based database).

A Datastore based data source can be configured using a standard DataProvider, through the DatastoreDataProvider API: see the Datastore API integration section to learn how to create and configure a Datastore based DataProvider.

Furthermore, the select input builder APIs provides convenience methods to set and configure a data source using a Datastore.

By default, only the selection property is used as items property set. To specify a different property set (i.e. a different query projection), the property set must be provided at data source configuration time.

For example, given the following DataTarget definition;

static final DataTarget<?> TARGET = DataTarget.named("subjects");

The select of the previous example can be configured as follows:

Datastore datastore = getDatastore();

SingleSelect<Long> singleSelect = Input.singleSelect(ID) (1)
    .dataSource(datastore, TARGET) (2)
    .build();

singleSelect = Input.singleSelect(ID) //
    .dataSource(datastore, TARGET, SUBJECT) (3)
    .build();
1 Create a SingleSelect using the ID property as selection property
2 Use a Datastore as items data source, providing the DataTarget to use
3 Declare to use the SUBJECT property set as items property set (hence as query projection)

The Datastore based data source can be configured, for example to add query filters and sorts. The configuration methods provided by the builder API corresponds to the ones provided by the DatastoreDataProvider API. See Datastore Query configuration for details.

SingleSelect<Long> singleSelect = Input.singleSelect(ID) (1)
    .dataSource(getDatastore(), TARGET, SUBJECT) (2)
    .withQueryFilter(NAME.isNotNull()) (3)
    .withQuerySort(ID.asc()) (4)
    .withQueryConfigurationProvider(myQueryConfigurationProvider) (5)
    .build();
1 Create a SingleSelect using the ID property as selection property
2 Use a Datastore as items data source, providing the DataTarget to use
3 Add a fixed query filter
4 Add a fixed query sort
5 Add a QueryConfigurationProvider reference to provide dynamic query filters and sorts
Filterable select inputs

When a SingleSelect in the filterable select mode is used, since it is rendered using a ComboBox component, the selection items to select can be suggested by the input component, using the user input to filter the items set.

When a standard DataProvider is used, the filteringBy* methods can be used to provide the items filtering strategy.

SingleSelect<String> input = Input.singleSelect(String.class) (1)
    .dataSource(DataProvider.ofCollection(Arrays.asList("One", "Two", "Three")) (2)
        .filteringByPrefix(v -> v)) (3)
    .build();
1 Create a String type SingleSelect
2 Use a DataProvider as items data source
3 Set the filtering mode, in this case checking whether the item value starts with the query value

When a Datastore based data source is used (see Use the Datastore API as items data source), a function can be configured to transform the text typed by the user into the QueryFilter to add to the Datastore query to filter the returned items.

The filter converter function can be configured either using:

1. The dataSource builder method version which accepts the filter converter function:

SingleSelect<Long> singleSelect = Input.singleSelect(ID) (1)
    .dataSource(getDatastore(), TARGET, (2)
        text -> NAME.contains(text), (3)
        SUBJECT (4)
    ).build();
1 Create a SingleSelect using the ID property as selection property
2 Use a Datastore as items data source, providing the DataTarget to use
3 Provide the function to convert the user typed text value to a QueryFilter
4 Use the SUBJECT property set as items property set

1. The filterConverter builder method:

SingleSelect<Long> singleSelect = Input.singleSelect(ID) (1)
    .dataSource(getDatastore(), TARGET, SUBJECT) (2)
    .filterConverter(text -> NAME.contains(text)) (3)
    .build();
1 Create a SingleSelect using the ID property as selection property
2 Use a Datastore as items data source, providing the DataTarget to use and SUBJECT as items property set
3 Provide the function to convert the user typed text value to a QueryFilter
Use select inputs with enumerations

The Input API and the Components.input API provides convenience methods to create enumeration based select inputs, automatically adding all the available enumeration values to the selection items set.

A enum type select can be created both as a SingleSelect and as MultiSelect.

For example, given the following enum class:

enum MyEnum {

  FIRST, SECOND, THIRD;

}

A select type input for the enumeration values can be created as follows:

SingleSelect<MyEnum> singleSelect = Input.enumSelect(MyEnum.class).build(); (1)
singleSelect = Input.enumOptionSelect(MyEnum.class).build(); (2)

MultiSelect<MyEnum> multiSelect = Input.enumMultiSelect(MyEnum.class).build(); (3)
1 Create a SingleSelect for the MyEnum enumeration
2 Create a SingleSelect for the MyEnum enumeration using the options rendering mode
3 Create a MultiSelect for the MyEnum enumeration

The Caption annotation can be used on the enumeration class values to configure the select items caption, with localizable messages support.

enum MyEnum {

  @Caption(value = "The first", messageCode = "first.message.code")
  FIRST,

  @Caption(value = "The second", messageCode = "second.message.code")
  SECOND,

  @Caption(value = "The third", messageCode = "third.message.code")
  THIRD;

}

See the next section to learn how to configure the select items caption using the select input builder API.

Selection items caption

By default, the selectable items are displayed in UI using the toString() representation of the item itself.

To change this behaviour, the item captions can be configured either setting them explicitly using the builder API (through the itemCaption method), or using a ItemCaptionGenerator function to provide the caption for each selection item.

Example using explicit item captions:

SingleSelect<Integer> singleSelect = Input.singleSelect(Integer.class) (1)
    .items(1, 2) //
    .itemCaption(1, "One") (2)
    .itemCaption(2, "One", "two.message.code") (3)
    .build();
1 Create a Integer type SingleSelect
2 Set the 1 item caption
3 Set the 2 item caption using a localizable message

Example using a ItemCaptionGenerator:

SingleSelect<Long> singleSelect = Input.singleSelect(ID) (1)
    .dataSource(getDatastore(), TARGET, SUBJECT) (2)
    .itemCaptionGenerator(item -> item.getValue(NAME)) (3)
    .build();
1 Create a SingleSelect using the ID selection property
2 Use SUBJECT as items property set, which includes the NAME property
3 Set the ItemCaptionGenerator: the NAME property value is used as item caption

For Property bound selectable inputs, a convenience itemCaptionProperty builder method is available to provide the property to use to obtain the item value which will be used as items caption.

SingleSelect<Long> singleSelect = Input.singleSelect(ID) (1)
    .dataSource(getDatastore(), TARGET, SUBJECT) (2)
    .itemCaptionProperty(NAME) (3)
    .build();
1 Create a SingleSelect using the ID selection property
2 Use SUBJECT as items property set, which includes the NAME property
3 The NAME property value is used as item caption

3.9.5. Extend the Input API using adapters

The Input API can be extended using custom adapters, i.e. a class/interface to provide additional Input attributes and/or functionalities.

An adapter can be registered at Input build time using the withAdapter method, providing:

  • The adapter Class

  • A Function to provide the adapter instance when requested, with the Input instance as function argument.

An adapter instance can be obtained using the Input API method <A> Optional<A> as(Class<A> type). If an adapter of the given type is available (i.e. was registered as described above), the adapter function will be invoked to provide the adapter instance.

Input<String> input = Input.string().withAdapter(MyExtension.class, i -> new MyExtension()) (1)
    .build();

Optional<MyExtension> extension = input.as(MyExtension.class); (2)
1 Register and adapter for the MyExtension type
2 Get the MyExtension type instance, if available

3.9.6. Validatable inputs

The value of any Input component can be validated using the standard Holon Platform Validator API, through the ValidatableInput API.

An Input can be made validatable, adding support for Validator registration, using:

1. The validatable builder method:

ValidatableInput<String> validatableInput = Input.string() //
    .validatable() (1)
    .withValidator(Validator.max(10)) (2)
    .validateOnValueChange(true) (3)
    .build();
1 Make the input validatable
2 Add a input value Validator
3 Set whether to automatically validate the input value when it changes

2. The ValidatableInput adapter:

Input<String> input = Input.string().build();

ValidatableInput<String> validatableInput = ValidatableInput.builder(input) (1)
    .withValidator(Validator.max(10)) (2)
    .build();

validatableInput = ValidatableInput.from(input); (3)
validatableInput.addValidator(Validator.max(10)); (4)
1 Use the ValidatableInput builder method to create and configure a validatable input from a standard Input
2 Add a input value Validator
3 Use the ValidatableInput from method to directly create a validatable input from a standard Input
4 Add a input value Validator using the addValidator method

A ValidatableInput extends the Validatable API, which provides method to validate the input value using the registered validators.

ValidatableInput<String> validatableInput = Input.string().validatable().build(); (1)

validatableInput.isValid(); (2)

try {
  validatableInput.validate(); (3)
} catch (ValidationException e) {
  /* value is not valid */
}

Optional<String> value = validatableInput.getValueIfValid(); (4)
1 Create a ValidatableInput
2 Check whether the input value is valid
3 Validate the input value, throwing a ValidationException if the validation fails
4 Get the input value only if it is valid

By default, the validation errors are notified to the user using the UI component error message, if supported by the concrete input component.

The validation errors notification strategy can be customized using a ValidationStatusHandler function. The validation event provides the validation status (valid, invalid or unresolved) and the current validation errors, if any.

ValidatableInput<String> validatableInput = Input.string().validatable() //
    .validationStatusHandler(event -> { (1)
      Status validationStatus = event.getStatus();
      String error = event.getErrorMessage();
      /* notify the validation errors */
    }).build();
1 Set the ValidationStatusHandler to use for the ValidatableInput
the Validatable API provides a set of adapt methods to use a Vaadin com.vaadin.flow.data.binder.Validator as a Holon Platform Validator.

3.9.7. Organize Input components in groups and forms

A set of Input components can be grouped and organized using a PropertyInputGroup, using the Holon Platform Property model to bind each Input to a property definition.

The property definitions will be used as references to get and configure the Input instances managed by the group and to get and set the group values using a PropertyBox type.

The PropertyInputGroup API provides method to inspect the available properties and the Input instances bound to each property. Through the ValueHolder API, the group value can be setted and managed (see The ValueHolder interface).

When a PropertyBox type value is setted for the group, each Input value bound to a property is setted according to the property value provided by the PropertyBox instance, if available.

When a the group value is obtained using the getValue() method, the current Input values are reflected into the PropertyBox instance, using the property to which each Input is bound.

To create a PropertyInputGroup, the property set to use must be provided at build time. Both the PropertyInputGroup API and the Components API (through the Components.input sub interface) can be used to obtain a PropertyInputGroup builder.

By default, the property rendering architecture is used to automatically generate the Input components for each property of the group property set (see Input property renderer).

For example, given the following property model definition:

static final NumericProperty<Long> ID = NumericProperty.longType("id");
static final StringProperty NAME = StringProperty.create("name");
static final PropertySet<?> SUBJECT = PropertySet.of(ID, NAME);

A PropertyInputGroup can be created in the following way:

PropertyInputGroup group = PropertyInputGroup.builder(SUBJECT).build(); (1)
group = Components.input.propertyGroup(SUBJECT).build(); (2)

Collection<Property<?>> properties = group.getProperties(); (3)
Stream<Input<?>> components = group.getElements(); (4)
group.getBindings().forEach(binding -> { (5)
  Property<?> property = binding.getProperty();
  Input<?> component = binding.getElement();
});

Optional<Input<?>> element = group.getElement(NAME); (6)
Input<?> component = group.requireElement(NAME); (7)

PropertyBox value = PropertyBox.builder(SUBJECT).set(ID, 1L).set(NAME, "One").build();

group.setValue(value); (8)
value = group.getValue(); (9)

group.clear(); (10)
group.isEmpty(); (11)
1 Create a PropertyInputGroup using the SUBJECT property set. The Input for each property will be automatically generated using the property renderers
2 Create a PropertyInputGroup from the Components API
3 Get the group property set
4 Get the group Input elements
5 Get the group bindings, i.e. the Input bound to each property
6 Get the Input bound to the NAME property, if available
7 Require the Input bound to the NAME property
8 Set the group value using a PropertyBox: each property value available from the PropertyBox instance will be setted into the corresponding Input, if available
9 Get the current group value: the current Input values (which in the meanwhile could have been changed by the user) are updated into the PropertyBox instance, according to the property to which each Input is bound
10 Clear the group value
11 Check whether the group is empty, i.e. has no value

To customize this behaviour and provide the Input component to use for one or more property, the PropertyInputGroup builder API provides a set of bind methods, which can be use to explicitly bind a Input instance to a property.

PropertyInputGroup group = PropertyInputGroup.builder(SUBJECT) (1)
    .bind(NAME, Input.string().build()) (2)
    .bind(NAME, property -> Input.stringArea().build()) (3)
    .build();
1 Create a PropertyInputGroup using the SUBJECT property set
2 Bind a Input instance to the NAME property
3 Bind a Input instance to the NAME property using a function
Input group configuration

The PropertyInputGroup builder API provides method to configure the group, allowing to:

  • Hide one or more property: the hidden property won’t be bound to a Input

  • Add a post processor to configure the Input instances just before the binding occurs

  • Provide a default value for one or more Input components

  • Add group value change listeners

PropertyInputGroup group = PropertyInputGroup.builder(SUBJECT) (1)
    .hidden(ID) (2)
    .defaultValue(NAME, () -> "(no name)") (3)
    .withPostProcessor((property, component) -> { (4)
      /* Inputs configuration */
      component.hasStyle().ifPresent(s -> s.addClassName("my-style"));
    }) //
    .withValueChangeListener(event -> { (5)
      /* handle value change */
      PropertyBox value = event.getValue();
    }).build();
1 Create a PropertyInputGroup using the SUBJECT property set
2 Set the ID property as hidden
3 Set the default value supplier for the NAME property
4 Add a binding post processor
5 Add a value change listener
Using a PropertyInputForm to make the group a UI component

The PropertyInputForm API handles a group of Input components bound to a property set, but it is not a UI component itself. The PropertyInputForm API can be used to provide a UI component for the group and use it as a form in application UI.

A PropertyInputForm extends the HasComponent interface to provide the actual group UI component (see The HasComponent interface) and the Composable API to declare the UI component to use as group elements container and the strategy to apply to organize the Input group elements into the group content area.

The Composer API can be used to implement the composition logic, i.e. to add the available ViewComponent elements to the UI component declared as group content. The Composer function provides:

  • The group UI content element.

  • A reference to the group API to obtain the Input elements (and the corresponding property bindings) to add to the group UI content element.

A default Composer for a com.vaadin.flow.component.HasComponents type UI element can be obtain using the componentContainerComposer() method of the Composable API. This composer adds the group elements sequentially to the HasComponents group content element, in the order they are provided from the group property set.

PropertyInputForm form = PropertyInputForm.builder(new VerticalLayout(), SUBJECT) (1)
    .composer(Composable.componentContainerComposer()) (2)
    .build();

myLayout.add(form.getComponent()); (3)

form.setValue(PropertyBox.builder(SUBJECT).set(ID, 1L).set(NAME, "One").build()); (4)
1 Create a PropertyInputForm using the SUBJECT property set, a VerticalLayout as group UI element
2 The default component container composer is used
3 The getComponent() method can be used to add the PropertyInputForm to a layout
4 Set a form value

A set of convenience builder methods are available for the base Vaadin layouts:

PropertyInputForm form = PropertyInputForm.verticalLayout(SUBJECT).build(); (1)
form = PropertyInputForm.horizontalLayout(SUBJECT).build(); (2)
form = PropertyInputForm.formLayout(SUBJECT).build(); (3)

// Using the Components API:
form = Components.input.formVertical(SUBJECT).build();
form = Components.input.formHorizontal(SUBJECT).build();
form = Components.input.form(SUBJECT).build();
1 Create a PropertyInputForm using the SUBJECT property set and a VerticalLayout as group UI element
2 Create a PropertyInputForm using the SUBJECT property set and a HorizontalLayout as group UI element
3 Create a PropertyInputForm using the SUBJECT property set and a FormLayout as group UI element

A custom Composer can be used to control and customize the group element composition strategy:

PropertyInputForm form = PropertyInputForm.verticalLayout(SUBJECT) //
    .composer((content, group) -> { (1)
      group.getBindings().forEach(binding -> {
        content.add(binding.getElement().getComponent());
      });
    }).build();
1 Provide a custom composition logic
Form configuration

A PropertyInputForm can be configured using the builder API in order to:

  • Hide one or more property: the hidden property won’t be bound to a Input

  • Add an initializer to configure the form UI content

  • Add a post processor to configure the Input instances just before the binding occurs

  • Add form value change listeners

  • Configure the property captions, i.e. to set the label to use for the Input bound to a property

PropertyInputForm.formLayout(SUBJECT) (1)
    .hidden(ID) (2)
    .initializer(content -> { (3)
      /* content configuration */
      FormLayout fl = content;
    }).withPostProcessor((property, component) -> { (4)
      /* Inputs configuration */
      component.hasStyle().ifPresent(s -> s.addClassName("my-style"));
    }) //
    .withValueChangeListener(event -> { (5)
      /* handle value change */
      PropertyBox value = event.getValue();
    }) //
    .hidePropertyCaption(ID) (6)
    .propertyCaption(NAME, "My name", "name.message.code") (7)
    .build();
1 Create a PropertyInputForm using the SUBJECT property set and a FormLayout
2 Set the ID property as hidden
3 Set the form content initializer
4 Set the binding post processor
5 Add a value change listener
6 Hide the ID property caption
7 Set the localizable caption for the NAME property
Input group and form validation

The PropertyInputGroup and the PropertyInputForm APIs provides Input validation support using the standard Holon Platform Validator API. The value validation is supported:

  • At property level, to validate the single Input value for each property.

  • At group level, to validate the overall PropertyBox type group value.

The validators can be added using the PropertyInputGroup and the PropertyInputForm builder APIs.

PropertyInputForm.formLayout(SUBJECT) (1)
    .withValidator(NAME, Validator.max(10)) (2)
    .withValidator(propertyBox -> { (3)
      /* group value validation */
    }).validateOnValueChange(true) (4)
    .build();
1 Create a PropertyInputForm using the SUBJECT property set
2 Add a validator for the Input component bound to the NAME property
3 Add a group value validator
4 Set whether to validate the Input values when each Input value changes

The PropertyInputGroup and the PropertyInputForm APIs provides a set of methods to perform the group/form validation, a part of which is inherited from the Validatable API.

PropertyInputForm form = PropertyInputForm.formLayout(SUBJECT).build();

form.isValid(); (1)
try {
  form.validate(); (2)
} catch (ValidationException e) {
  /* value is not valid */
}

Optional<PropertyBox> valueIfValid = form.getValueIfValid(); (3)
PropertyBox value = form.getValue(); (4)
value = form.getValue(false); (5)
1 Check whether the form is valid
2 Validate the form, firing both property level and group level validators
3 Get the form value, returning an empty Optional if validation fails
4 By default, when the form value is requested, the form validation is performed
5 Get the form value skipping validation
Input groups validation status handler

By default, for the PropertyInputGroup and the PropertyInputForm components, the validation errors originated by the registered validators are notified to the user in the following way:

  • For property level validators, if the Input component supports error messages notification (using the Vaadin HasValidation interface), the validation errors are setted as component errors.

  • For group level validators, a Dialog is used to notify the validation errors.

The validation status errors notification can be customized using the ValidationStatusHandler interface, both at property and group level.

The ValidationStatusHandler validation event provides the validation status (valid, invalid or unresolved) and the current validation errors, if any.

To customize both the property level and the group level validation status notification, the validationStatusHandler builder method can be used.

PropertyInputForm form = PropertyInputForm.formLayout(SUBJECT) //
    .validationStatusHandler(event -> { (1)
      if (event.isInvalid()) {
        Notification.show("Validation falied: " + event.getErrorMessage());
      }
    }).validationStatusHandler(NAME, event -> { (2)
      /* omitted */
    }).build();
1 Set a custom group level validation status handler
2 Set a custom property level validation status handler for the NAME property

Furthermore, the GroupValidationStatusHandler interface can be used to override all the validation status notification, customizing both the property level and the group level validation status notification using a single handler.

PropertyInputForm form = PropertyInputForm.formLayout(SUBJECT) //
    .groupValidationStatusHandler(event -> { (1)
      event.getGroupStatus(); (2)
      event.getInputsValidationStatus(); (3)
      event.getGroupErrorMessages(); (4)
      event.getInputsValidationStatus().forEach(s -> s.getErrorMessages()); (5)
    }).build();
1 Set the GroupValidationStatusHandler for the form
2 Get the group level validation status
3 Get the single Input validation status
4 Get the group level validation errors
5 Get the single Input validation errors

4. Vaadin session scope

When the holon-vaadin-flow artifact is in classpath, a Holon Platform Context scope bound to the current Vaadin session is automatically registered and available to manage context resources.

The VaadinSessionScope API can be used to obtain the scope name. The VaadinSessionScope API also provides convenience methods to access the Vaadin session scope.

The Vaadin session attributes are used as context resource registry and the a current VaadinSession must be available in order for the scope to work.

Optional<ContextScope> scope = Context.get().scope(VaadinSessionScope.NAME); (1)
scope = VaadinSessionScope.get(); (2)
ContextScope sessionScope = VaadinSessionScope.require(); (3)
1 Get the session scope using the scope name
2 Get the session scope using the VaadinSessionScope API
3 Require the session scope using the VaadinSessionScope API

For example, if a LocalizationContext is bound to the current Vaadin session using a session attribute, it can be retrieved using the standard Holon Platform Context API.

VaadinSession.getCurrent().setAttribute(LocalizationContext.class, (1)
    LocalizationContext.builder().withInitialLocale(Locale.US).build());

Optional<LocalizationContext> localizationContext = Context.get().resource(LocalizationContext.class); (2)

localizationContext = LocalizationContext.getCurrent(); (3)
1 Bind a LocalizationContext instance to the current Vaadin session attribute
2 The LocalizationContext instance is now available as a context resource
3 The getCurrent() method will return the LocalizationContext instance

5. Device information

Since version 5.2.7, the DeviceInfo API is deprecated: use com.vaadin.flow.server.WebBrowser instead (it can be obtained from VaadinSession).

Maven coordinates:

<groupId>com.holon-platform.vaadin</groupId>
<artifactId>holon-vaadin-flow-navigator</artifactId>
<version>5.2.7</version>

The holon-vaadin-flow-navigator artifact provides URL query parameters marshalling and unmarshalling between URL representation and Java types using the @QueryParameter annotation and a Navigator API which can be used to handle the application UI views routing

Furthermore, a complete support for view authentication and authorization is provided, using the Holon Platform core authentication APIs.

When the holon-vaadin-flow-navigator artifact is in classpath, the URL query parameters marshalling and unmarshalling for application routes is automatically enabled and setted up at application bootstrap.

The URL query parameters value handling can thus be implemented using the QueryParameter annotation on routing target classes (i.e. UI component classes annotated with @Route) class fields.

For information about the Vaadin Flow routing architecture, including the @Route annotation and route layouts, see the Vaadin Flow documentation.

When a routing target class field is annotated with @QueryParameter, its value will be automatically setted using the corresponding URL query parameter value, if available, just before the component is rendered in application UI.

The String type URL query parameter value will be converted to the required Java type declared by the field type. See Built in query parameter types for the Java types supported by default and Adding query parameter type support to extend the supported Java types.

By default, the URL query parameter name bound to a @QueryParameter field is equal to the field name.

@Route("some/path")
public class View extends Div {

  @QueryParameter (1)
  private Integer parameter;

}
1 The bound URL query parameter name is the field name, i.e. parameter. For example, when the route some/path?parameter=1 is used, the parameter field value will be setted to the 1 Integer value.

The value() annotation attribute can be used to declare the URL query parameter name to which the field is bound, if you don’t want to use the field name.

@Route("some/path")
public class View extends Div {

  @QueryParameter("myparam") (1)
  private Integer parameter;

}
1 The URL query parameter name bound to the parameter field is myparam. For example, when the route some/path?myparam=1 is used, the parameter field value will be setted to the 1 Integer value.

The routing target class field declaration (even private) it’s enough to enable the the query parameter value handling, but the standard Java Beans convention can be also used, providing a setter method to set the field value.

If a field setter method is available in the routing target class, it will be used to set the query parameter field value. This allows, for example, to perform some operations one the query parameter value before it is setted as field value.

@Route("some/path")
public class View extends Div {

  @QueryParameter
  private Integer parameter;

  public void setParameter(Integer parameter) { (1)
    this.parameter = parameter;
  }

}
1 The setter method will be used to set the parameter query parameter field value

6.1.1. Query parameter URL encoding and decoding

By default, all the navigation related APIs automatically handle the query parameter names and values encoding, when serialized to a URL, and decoding, when deserialized from a URL.

The query parameter names and values encoding and decoding is performed using the application/x-www-form-urlencoded MIME type and the default UTF-8 charset.

Each API which handles URL query parameters provides method to skip the query parameter names and values encoding and decoding, if required.

See the specific API documentation below for further details.

6.1.2. Using the injected parameter values

The URL query paramter values are automatically injected in the routing target class @QueryParameter annotated fields at the *after navigation* routing lifecycle event, i.e. when the routing target component instance is added to the UI.

For this reason, consistent parameter values are only available from this phase on. To handle and use the @QueryParameter annotated fields value, you have to ensure to be at least in the after navigation lifecycle phase of the routing component.

For this purpose, the com.vaadin.flow.router.AfterNavigationObserver interface can be used, implementing it by the routing target class.

@Route("some/path")
public class View extends Div implements AfterNavigationObserver { (1)

  @QueryParameter
  private Integer parameter;

  @Override
  public void afterNavigation(AfterNavigationEvent event) { (2)
    /* handle the parameters value */
    Notification.show("Parameter value: " + this.parameter);
  }

}
1 Implement the AfterNavigationObserver interface to manage after navigation events
2 When the after navigation event occurs, all the @QueryParameter fields values are consistent and already injected using the URL query parameters

Since it is bound to the same after navigation lifecycle phase, a @OnShow annotated method can also be used.

@Route("some/path")
public class View extends Div {

  @QueryParameter
  private Integer parameter;

  @OnShow (1)
  public void processParameters() {
    /* handle the parameters value */
    Notification.show("Parameter value: " + this.parameter);
  }

}
1 When a @OnShow annotated method in invoked, all the @QueryParameter fields values are consistent and already injected using the URL query parameters

6.1.3. Direct query parameter values deserialization

The NavigationParameters API can be used to directly deserialize URL query parameter values into the supported Java types.

The NavigationParameters API provides a set of builder methods to create a NavigationParameters handler instance from a Map of query parameter name and values, from a com.vaadin.flow.router.QueryParameters instance or from a com.vaadin.flow.router.Location reference.

Map<String, List<String>> queryParameters = getQueryParameters();
NavigationParameters navigationParameters = NavigationParameters.create(queryParameters); (1)

navigationParameters = NavigationParameters
    .create(QueryParameters.simple(Collections.singletonMap("test", "value"))); (2)

navigationParameters = NavigationParameters.create(new Location("host.com/?test=value")); (3)
1 Create a NavigationParameters from a Map of query parameter name and values
2 Create a NavigationParameters from a com.vaadin.flow.router.QueryParameters instance
3 Create a NavigationParameters from a com.vaadin.flow.router.Location reference

By default, the query parameter names and values are decoded from the URL representation, using the application/x-www-form-urlencoded MIME type and the default UTF-8 charset.

To skip query parameter names and values decoding, the NavigationParameters API builder methods provides a decode parameter which can be set to false.

NavigationParameters navigationParameters = NavigationParameters.create(getQueryParameters(), false); (1)
1 Set the decode parameter to false to skip the query parameter names and values decoding

The NavigationParameters API provides a set of methods to obtain the query parameter values, deserialized to the required Java type, if supported.

See Built in query parameter types for the Java types supported by default and Adding query parameter type support to extend the supported Java types.
NavigationParameters navigationParameters = NavigationParameters.create(getQueryParameters()); (1)

boolean hasParameterAndValue = navigationParameters.hasQueryParameter("myparam"); (2)

List<Integer> values = navigationParameters.getQueryParameterValues("myparam", Integer.class); (3)

Optional<Integer> value = navigationParameters.getQueryParameterValue("myparam", Integer.class); (4)

Integer valueOrDefault = navigationParameters.getQueryParameterValue("myparam", Integer.class, 0); (5)
1 Create a NavigationParameters handler
2 Check if a parameter named myparam is present and has a value
3 Get the values of the parameter named myparam, deserialized using the Integer type
4 Get the single value of the parameter named myparam, deserialized using the Integer type, if available
5 Get the single value of the parameter named myparam, deserialized using the Integer type, or the default 0 value

6.1.4. Built in query parameter types

By default, the following Java types are supported for query parameter values conversion from the URL query string:

Type Sub type Format

String

[NONE]

Any text

Number

Integer, Long, Short, Byte, Float, Double, BigInteger, BigDecimal

For decimal numbers, the dot (.) character must be used as decimal positions separator. The - character can be used for negative numbers. Hexadecimal representation is supported using the 0x or 0X prefix.

Boolean

[NONE]

true or false

Enums

[NONE]

The enumeration value name

LocalDate

[NONE]

ISO date format, for example 2019-01-11

LocalTime

[NONE]

ISO time format, for example 10:15 or 10:15:30

LocalDateTime

[NONE]

ISO date and time format, for example 2019-01-11T10:15:30

OffsetTime

[NONE]

ISO time format with offset support, for example 10:15:30+01:00

OffsetDateTime

[NONE]

ISO date and time format with offset support, for example 2019-01-11T10:15:30+01:00 or 2019-01-11T10:15:30+01:00[Europe/Paris]

java.util.Date

[NONE]

ISO date and/or time format, for example 2019-01-11 or 2019-01-11T10:15:30

6.1.5. Optional query parameter values

The java.util.Optional class is supported for @QueryParameter field type declarations.

Using Optional, the field value will never be null, and when the parameter value is not available a Optional.empty() value is setted as field value.

@Route("some/path")
public class View extends Div {

  @QueryParameter
  private Optional<String> parameter; (1)

}
1 Optional query parameter String type declaration

6.1.6. Multiple query parameter values

Multiple query parameter values are supported either using the java.util.Set or java.util.List class.

For example, given the following routing target class:

@Route("some/path")
public class View extends Div {

  @QueryParameter
  private Set<String> parameter; (1)

}
1 Query parameter with multiple value support declaration using a java.util.Set

For the routing URL some/path?parameter=a&parameter=b, the parameter field value will be a java.util.Set instance containing the a and b String values.

When the URL query parameter value is not available, a empty java.util.Set or java.util.List is setted as field value.

6.1.7. Adding query parameter type support

The @QueryParameter annotated fields supported Java types set can be extended, adding support for new Java types marshalling and unmarshalling, using the NavigationParameterTypeMapper API.

To add support for a new query parameter type, a NavigationParameterTypeMapper instance bound to the required type can be created and automatically registered using the Java service extension architecture, i.e. providing a com.holonplatform.vaadin.flow.navigator.NavigationParameterTypeMapper named file in the META-INF/services folder of a jar, containing the NavigationParameterTypeMapper concrete class names to register.

For example, suppose we need to handle the following MyType parameter type:

public class MyType {

  private final int value;

  public MyType(int value) {
    this.value = value;
  }

  public int getValue() {
    return value;
  }

}

We can create a new NavigationParameterTypeMapper class, generalized on the MyType type, as follows:

public class MyTypeParameterMapper implements NavigationParameterTypeMapper<MyType> {

  @Override
  public Class<MyType> getParameterType() { (1)
    return MyType.class;
  }

  @Override
  public String serialize(MyType value) throws InvalidNavigationParameterException { (2)
    if (value != null) {
      return String.valueOf(value.getValue());
    }
    return null;
  }

  @Override
  public MyType deserialize(String value) throws InvalidNavigationParameterException { (3)
    if (value != null) {
      return new MyType(Integer.valueOf(value));
    }
    return null;
  }

}
1 Handled parameter type declaration
2 Parameter value unmarshalling to the String type URL query parameter value
3 Parameter value marshalling from the String type URL query parameter value

An then register the MyTypeParameterMapper class using the Java service extension architecture, creating a suitable file under the META-INF/services folder:

com.holonplatform.vaadin.flow.navigator.NavigationParameterTypeMapper file
my.package.MyTypeParameterMapper

This way, the MyType parameter type will be automatically handled using the @QueryParameter annotation.

@Route("some/path")
public class View extends Div {

  @QueryParameter
  private MyType parameter; (1)

}
1 The MyType type parameter is handled using the MyTypeParameterMapper class

6.1.8. Required query parameters

The required() attribute of the @QueryParameter annotation can be used to declare required query parameter values.

When a required query parameter value is not available from the URL query string, a navigation error is fired before the navigation to the routing target occurs.

The InvalidNavigationParameterException is used to notify the error and interrupt the navigation.

@Route("some/path")
public class View extends Div {

  @QueryParameter(required = true) (1)
  private Integer parameter;

}
1 The query parameter is required

See Default query parameter values to provide a default value for a parameter, which can be used to provide a default value for a required parameter when it is not available from the URL query string.

6.1.9. Default query parameter values

The defaultValue() attribute of the @QueryParameter annotation can be used to declare a default value for a query parameter. This value will be used and injected into the parameter field when the parameter value is not available from the URL query string.

The defaultValue() attribute value is a String, and the format conventions described in Built in query parameter types should be used as String format for the supported Java types.

@Route("some/path")
public class View extends Div {

  @QueryParameter(defaultValue = "0") (1)
  private Integer parameter;

}
1 Declare the parameter default value

6.2. Using @OnShow on route target classes

The OnShow annotation can be used un route target class methods to handle the *after navigation* routing lifecycle event, i.e. to execute code when the routing target component instance is added to the UI.

A method annotation with @OnShow:

  • Must be public.

  • Can declare either no parameters or a single parameter of type com.vaadin.flow.router.AfterNavigationEvent, to obtain the event which triggered the method invocation.

Any valid @OnShow annotated method (including super classes) in invoked at the after navigation phase of the routing target component. No invocation order is guaranteed.

@Route("some/path")
public class View extends Div {

  @OnShow (1)
  public void afterNavigation1() {
    /* ... */
  }

  @OnShow (2)
  public void afterNavigation2(AfterNavigationEvent event) {
    /* ... */
  }

}
1 Declare the method to be invoked just after the navigation to this routing target
2 Declare the method to be invoked just after the navigation to this routing target, providing the AfterNavigationEvent type event

The Navigator API can be used to handle the application routing, i.e. the navigation between the UI components which represent the application views.

Each Navigator is bound to a UI and the Navigator API for the current UI can be obtained using the get() static method. This method checks if a Navigator instance is available as a context resource and if not uses an internal registry to obtain the instance bound to the current UI, creating a new instance if none was bound to the UI yet.

See the Holon Platform Context documentation for information about the context resources management.
Navigator navigator = Navigator.get(); (1)
1 Get the Navigator API for the current UI

A Navigator instance can also be directly created for a specific UI using the create static method.

Navigator navigator = Navigator.create(myUI); (1)
1 Create a Navigator for the given UI

The Navigator API makes available a set of methods to handle the routing between the application’s declared route targets, ensuring a consistent query parameters management with Java types marshalling support (see Navigation parameters handling) and providing suitable routing URL builders using the supported Java types to provide the query parameters value.

See Built in query parameter types to learn about the default supported Java types for query parameters declaration and Adding query parameter type support to learn how to add support for additional Java types.

For information about the Vaadin Flow routing architecture, including the @Route annotation and route layouts, see the Vaadin Flow documentation.

The Navigator API provides a set of navigateTo(…​) methods to perform navigation towards a routing target. The routing target URL can be declared either using:

  • The route path, as declared using the @Route annotation.

  • The routing target class reference.

  • A full URL location, which includes the route path and the optional query part with the query parameters declaration.

Each Navigator API navigateTo(…​) method provides multiple versions in order to:

  • Specify the query parameter values, using a map of query parameter name and the corresponding value, provided as one of the supported Java type object. The Navigator API will take care of parameter value serialization to be used in the URL query part.

  • Specify one or more path parameter, if the routing target class support them through the com.vaadin.flow.router.HasUrlParameter interface.

For example, supposing the following routing target class is registered in the application:

@Route("some/path")
public class View extends Div {

  @QueryParameter("myparam")
  private Integer parameter; (1)

}
1 Declares a single Integer type query parameter named myparam

The Navigator API can be used as follows to navigate to the routing target:

Navigator navigator = Navigator.get(); (1)

navigator.navigateTo("some/path"); (2)
navigator.navigateTo("some/path", Collections.singletonMap("myparam", new Integer(1))); (3)

navigator.navigateTo(View.class); (4)
navigator.navigateTo(View.class, Collections.singletonMap("myparam", new Integer(1))); (5)

navigator.navigateToLocation("some/path?myparam=1"); (6)
1 Get the Navigator for the current UI
2 Navigate to the View component using the route path
3 Navigate to the View component using the route path and specifying the myparam query parameter value
4 Navigate to the View component using the component class
5 Navigate to the View component using the component class and specifying the myparam query parameter value
6 Navigate to the View component using the full location URL, including the route path and the query parameters

Supposing the View component declares a path parameter using the com.vaadin.flow.router.HasUrlParameter interface:

@Route("some/path")
public class View extends Div implements HasUrlParameter<String> {

  @QueryParameter("myparam")
  private Integer parameter;

  @Override
  public void setParameter(BeforeEvent event, String parameter) {
    /* handle the path parameter value */
  }

}

The Navigator API can be used as follows:

Navigator navigator = Navigator.get(); (1)

navigator.navigateTo(View.class, "value"); (2)
navigator.navigateTo(View.class, "value", Collections.singletonMap("myparam", new Integer(1))); (3)
1 Get the Navigator for the current UI
2 Navigate to the View component using the component class and providing the path parameter value
3 Navigate to the View component using the component class and providing both the path parameter value and the myparam query parameter value

Furthermore, the Navigator API provides provides two convenience methods:

  • navigateToDefault(): to navigate to the default route, i.e. the empty ("") route, if available.

  • navigateBack(): to navigate back in the browser navigation history.

navigator.navigateToDefault(); (1)
navigator.navigateBack(); (2)
1 Navigate to the default route
2 Navigate back in the browser navigation history

The Navigator API provides a navigation URL builder API, available through the navigation(…​) methods, which allows to declare a routing target URL and its parameters (both query and path parameter types) using a fluent style builder and then either perform the actual navigation or obtain the complete location URL.

The navigation builder API can be obtained either specifying the route path or the routing target class and provides methods to set the path or query parameters to use.

The query parameter values are provided using one of the supported Java types, the Navigator API will take care of parameter value serialization to be used in the URL query part.

The navigation is performed using the navigate() method.

To obtain the complete URL location and do not perform the actual navigation, the asLocation() or asLocationURL() methods can be used, which provide the navigation URL as a com.vaadin.flow.router.Location object or a String respectively.

Navigator navigator = Navigator.get(); (1)

navigator.navigation("some/path") (2)
    .withQueryParameter("myparam", new Integer(1)) (3)
    .withQueryParameter("multi", "a", "b", "c") (4)
    .navigate(); (5)

navigator.navigation(View.class) (6)
    .withPathParameter("value") (7)
    .navigate(); (8)

Location location = navigator.navigation("some/path") //
    .withQueryParameter("myparam", new Integer(1)) //
    .asLocation(); (9)

String url = navigator.navigation(View.class) //
    .withQueryParameter("myparam", new Integer(1)) //
    .asLocationURL(); (10)
1 Get the Navigator for the current UI
2 Build a navigation using a route path
3 Add a query parameter value
4 Add multiple query parameter values
5 Perform the navigation action
6 Build a navigation using a route target class
7 Set the path parameter value
8 Perform the navigation action
9 Get the complete navigation URL as a com.vaadin.flow.router.Location object
10 Get the complete navigation URL as a String

By default, the query parameter names and values are encoded to the URL representation, using the application/x-www-form-urlencoded MIME type and the default UTF-8 charset.

To skip the query parameter names and values encoding, the encodeQueryParameters builder method can be used:

Navigator.get().navigation("some/path") (1)
    .withQueryParameter("myparam", new Integer(1)) (2)
    .withQueryParameter("multi", "a", "b", "c") (3)
    .encodeQueryParameters(false) (4)
    .navigate(); (5)
1 Build a navigation using a route path
2 Add a query parameter value
3 Add multiple query parameter values
4 Disable the query parameters URL encoding
5 Perform the navigation action

The Navigator API provides a set of getUrl(…​) convenince methods to obtain the registered URL for a navigation target class.

String url = navigator.getUrl(View.class); (1)
url = navigator.getUrl(View.class, "path_param_value"); (2)
url = navigator.getUrl(View.class, "path_param_value1", "path_param_value2"); (3)
1 Get the View routing target class base URL
2 Get the View routing target class URL with the provided path parameter value
3 Get the View routing target class URL with the provided path parameter values

The NavigationChangeListener listener interface can be use to listen to navigation target changes.

The navigation change event provides:

  • The changed URL location.

  • The changed navigation target component instance.

A NavigationChangeListener can be registered using the Navigator API addNavigationChangeListener method and it is invoked when a routing action is performed, i.e. when the current navigation target changes.

Navigator navigator = Navigator.get(); (1)

navigator.addNavigationChangeListener(event -> { (2)
  Location location = event.getLocation(); (3)
  HasElement target = event.getNavigationTarget(); (4)
});
1 Get the Navigator for the current UI
2 Register a NavigationChangeListener
3 The new navigation location
4 The new navigation target instance

The NavigationLink API represents a navigation link component, which handles navigation internally instead of loading a new page in the browser.

The NavigationLink API provides a builder API to configure the navigation target URL, either specifying a route path or a routing target class, and providing any path or query parameter value.

The query parameter values are provided using one of the supported Java types, the link implementation will take care of parameter value serialization to be used in the URL query part.

The NavigationLink API extends HasComponent, allowing to use it as a UI component through the getComponent() method. See The HasComponent interface.

NavigationLink link = NavigationLink.builder("some/path") (1)
    .withQueryParameter("myparam", 123) (2)
    .text("Link text") (3)
    .build();

link = NavigationLink.builder(View.class) (4)
    .withQueryParameter("myparam", 123) (5)
    .withPathParameter("value") (6)
    .text("Link text") (7)
    .build();

myLayout.add(link.getComponent()); (8)
1 Build a NavigationLink using a route path
2 Add a query parameter value
3 Set the link text
4 Build a NavigationLink using a routing target class
5 Add a query parameter value
6 Add a path parameter value
7 Set the link text
8 Add the link component to a layout

By default, the query parameter names and values are encoded to the URL representation, using the application/x-www-form-urlencoded MIME type and the default UTF-8 charset.

To skip the query parameter names and values encoding, the encodeQueryParameters builder method can be used:

NavigationLink link = NavigationLink.builder("some/path") (1)
    .withQueryParameter("myparam", 123) (2)
    .encodeQueryParameters(false) (3)
    .text("Link text") (4)
    .build();
1 Build a NavigationLink using a route path
2 Add a query parameter value
3 Disable the query parameters URL encoding
4 Set the link text

6.4. Authentication support for UI routes

When the holon-vaadin-flow-navigator artifact is in classpath, the Authenticate annotation can be used on routing target classes to provide authentication support to the routing infrastructure.

A routing target class annotated with @Authenticate is under authentication control: only authenticated users can access the corresponding route.

An AuthContext definition is required in order to enable the authentication support and the AuthContext to use must be available as a context resource, for example bound to the Vaddin session.

See the Holon Platform AuthContext documentation for information about the authentication context and the Realm documentation for information about the authentication configuration.

The redirectURI() attribute of the @Authenticate annotation can be used to specify a redirection URL to use when a not authenticated request is performed against an authentication protected route.

When a redirect URI is not provided and the authentication is not available for an authentication protected route, a UnauthorizedNavigationException is used to notify the error and interrupt the navigation.

@Authenticate (1)
@Route("some/path")
public class View1 extends Div {

  /* content omitted */

}

@Authenticate(redirectURI = "login") (2)
@Route("some/path")
public class View2 extends Div {

  /* content omitted */

}
1 The View1 routing target class, and so the some/path route path, can be accessed only by an authenticated user
2 The View2 routing target class, and so the some/path route path, can be accessed only by an authenticated user and a login redirect URI is specified and will be used to redirect the user navigation when the authentication is not available

The @Authenticate annotation can be also used on a routing layout class: any routing target class child of the layout will inherit the @Authenticate annotation definition.

@Authenticate (1)
public class MainLayout extends Div implements RouterLayout {

}

@Route(value = "some/path", layout = MainLayout.class) (2)
public class View extends Div {

  /* content omitted */

}
1 The @Authenticate is declared on a RouterLayout: any child routing target class will inherit the authentication control declaration
2 The View routing target class declares MainLayout as parent layout, so it will be under authentication control

6.5. Authorization support for UI routes

When the holon-vaadin-flow-navigator artifact is in classpath, the javax.annotation.security.RolesAllowed and javax.annotation.security.PermitAll annotations can be used on routing target classes to perform a role based authorization access control on routing targets.

An AuthContext definition is required in order to enable the authorization support and the AuthContext to use must be available as a context resource, for example bound to the Vaddin session.

See the Holon Platform AuthContext documentation for information about the authentication context and the Realm documentation for information about the authentication configuration.

The user roles check is performed using the current Authentication permissions, provided by the AuthContext.

See the Holon Platform Authorization documentation for information about the permissions configuration and control.

When the javax.annotation.security.RolesAllowed annotation is used, any of the specified role name must be granted to the current authenticated user in order to allow the route access.

When the authorization control fails, a ForbiddenNavigationException is used to notify the error and interrupt the navigation.

@RolesAllowed({ "ROLE1", "ROLE2" }) (1)
@Route("some/path")
public class View extends Div {

  /* content omitted */

}
1 The View routing target class, and so the some/path route path, is under authorization control and can be accessed only if either the ROLE1 or ROLE2 role name is granted to the authenticated user

7. Spring integration

The holon-vaadin-flow-spring artifact provides support and integration with the Spring framework.

Maven coordinates:

<groupId>com.holon-platform.vaadin</groupId>
<artifactId>holon-vaadin-flow-spring</artifactId>
<version>5.2.7</version>

The holon-vaadin-flow-spring artifact includes the standard Vaadin Flow Spring integration support, declaring the vaadin-spring artifact as dependency. See the Vaadin Spring documentation for information about the standard integration features and setup.

7.1. Provide the Navigator API as a Spring bean

The EnableNavigator annotation can be used on a Spring configuration class to made available a UI-scoped Navigator instance as a Spring bean.

The Vaadin Spring UI scope must be enabled to use this configuration annotation, for example using the com.vaadin.flow.spring.annotation.EnableVaadin annotation or the Vaadin Spring Boot integration auto-configuration facilities.

@Configuration
@EnableVaadin
@EnableNavigator (1)
class Config {

}

@Autowired
Navigator navigator; (2)
1 Enable the Navigator API UI-scoped Spring bean configuration
2 Obtain the Navigator API instance for current UI using injection

7.2. Using Spring Security for authorization control

The EnableSecuredRoute annotation can be used on a Spring configuration class to enable the Spring Security org.springframework.security.access.annotation.Secured annotation on routing target classes to perform a role based route authorization access control.

The Spring Security dependency must be explicitly provided by the project and must be available on classpath.

In order to use the @Secured annotation based authorization control, a SecurityContext must be available and properly configured.

When the javax.annotation.security.RolesAllowed annotation is used, any of the specified role name must be granted to the current authenticated user in order to allow the route access.

When the authorization control fails, a ForbiddenNavigationException is used to notify the error and interrupt the navigation.

8. Spring Boot integration

The holon-vaadin-flow-spring-boot artifact provides integration with Spring Boot, dealing with Navigator API and LocalizationContext integration auto configuration.

To enable the Spring Boot auto-configuration the following artifact must be included in your project dependencies:

Maven coordinates:

<groupId>com.holon-platform.vaadin</groupId>
<artifactId>holon-vaadin-flow-spring-boot</artifactId>
<version>5.2.7</version>

The holon-vaadin-flow-spring-boot artifact includes the standard Vaadin Flow Spring Boot integration, declaring the vaadin-spring artifact as dependency. See the Vaadin Spring documentation for information about the standard integration features and setup.

The Navigator API auto configuration provides automatic setup of a UI-scoped Navigator bean, if a Navigator type bean is not already available in application context. See Provide the Navigator API as a Spring bean.

Furthermore, two default navigation error notification UI components are registered:

The holon.vaadin.navigator.errors.enabled application configuration property can be setted to false to disable the error components registration.

8.2. LocalizationContext integration auto configuration

The LocalizationContext integration auto-configuration provides the following features:

1. Localization context localization synchronization:

If a LocalizationContext type bean is available in context, and its scope is either UI (vaadin-ui) or Session (session) or Vaadin session (vaadin-session), when the LocalizationContext localization changes, the changed Locale is reflected to the current Vaadin UI or Vaadin session, according to bean scope.

This feature can be disabled setting the application configuration property holon.vaadin.localization-context.reflect-locale to false.

2. Localization context initialization:

If a LocalizationContext type bean is available in context, and its scope is either UI (vaadin-ui) or Session (session) or Vaadin session (vaadin-session), the initial Locale detected at session/UI initialization (according to bean scope) is setted to the LocalizationContext instance.

This feature can be disabled setting the application configuration property holon.vaadin.localization-context.auto-init to false.

3. Localization context I18NProvider integration:

If a LocalizationContext type bean is available in context, and a I18NProvider type bean is not available, the detected LocalizationContext is used as I18NProvider and registered as a Spring bean using the LocalizationContextI18NProvider API.

This feature can be disabled setting the application configuration property holon.vaadin.localization-context.i18nprovider to false.

8.3. Spring Boot starters

The following starter artifacts are available to provide a quick project configuration setup using Maven dependency system:

1. The Vaadin Flow starter provides the dependencies to the Vaadin Flow Spring Boot integration artifact holon-vaadin-flow-spring-boot, in addition to:

See the Spring Boot starters documentation for details on Spring Boot starters.

Maven coordinates:

<groupId>com.holon-platform.vaadin</groupId>
<artifactId>holon-starter-vaadin-flow</artifactId>
<version>5.2.7</version>

2. The Vaadin Flow starter using Undertow provides the same dependencies as the previous starter, but uses Undertow instead of Tomcat as embedded servlet container.

Maven coordinates:

<groupId>com.holon-platform.vaadin</groupId>
<artifactId>holon-starter-vaadin-flow-undertow</artifactId>
<version>5.2.7</version>

9. Loggers

By default, the Holon platform uses the SLF4J API for logging. The use of SLF4J is optional: it is enabled when the presence of SLF4J is detected in the classpath. Otherwise, logging will fall back to JUL (java.util.logging).

The logger name for the Vaadin module is com.holonplatform.vaadin.

10. System requirements

10.1. Java

The Holon Platform Vaadin module requires Java 8 or higher.

10.2. Vaadin

The Holon Platform Vaadin module requires the Vaadin Flow platform version 12 or higher.