Copyright © 2016-2018
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 Holon Vaadin 7 module represents the platform support for the Vaadin 7.7.x web applications framework.
This module makes available an API which matches at 99% the official Holon Vaadin module API (which requires Vaadin version 8.1.x), only not compatible classes or methods are different for obvious reasons.
The main differences between the two modules API are:
-
The Vaadin 7 module supports the
com.vaadin.ui.Fieldinterface forInputcomponents andPropertyrenderers, while the Vaadin 8 module relies on theHasValueinterface -
The Vaadin 7
Tablecomponent is available asItemListingbacking component, in addition to theGridcomponent -
The
com.vaadin.data.provider.DataProviderinterface is not available in Vaadin 7, so it is not supported as item components data source
See the official Holon Vaadin module documentation to use Vaadin version 8 instead.
1.1. Sources and contributions
The Holon Platform Vaadin 7 module source code is available from the GitHub repository https://github.com/holon-platform/holon-vaadin7.
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.vaadin7</groupId>
<artifactId>holon-vaadin-bom</artifactId>
<version>5.2.4</version>
The BOM can be imported in a Maven project in the following way:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.holon-platform.vaadin7</groupId>
<artifactId>holon-vaadin-bom</artifactId>
<version>5.2.4</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.
See Obtain the platform artifacts for details.
3. What’s new in version 5.2.x
-
Support for Spring version 5+ and Spring Boot 2.1+
-
Support for JDK 9+ module system using
Automatic-Module-Name.
4. Vaadin integration
The holon-vaadin artifact is the main entry point to use the Holon platform Vaadin integration.
Maven coordinates:
<groupId>com.holon-platform.vaadin7</groupId>
<artifactId>holon-vaadin</artifactId>
<version>5.2.4</version>
5. Component builders
A complete set of fluent builders is available to create and configure the most common Vaadin standard components and the ones provided by the Holon platform Vaadin integration module.
| See UI Components for a list of the additional components made available by the Holon platform Vaadin integration module. |
All the builders can be obtained using the Components interface, which is organized in sub-interfaces by component kind.
| 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 standard Vaadin components, such as |
input |
Builders for different data type |
view |
Builders for |
listing |
Builders for components which represent a list of items. |
For each UI displayed text of a component (for example, the component caption or description), the builders provide the opportunity to set a localizable text, using a Localizable object or directly providing the message code and the default message of the localizable text. In order for the localization to work, a LocalizationContext must be available as a context resource. See the Internationalization documentation for further information.
|
6. UI Components
The Vaadin module provides a set of components which extend or decorates the standard components set. The available components are described below.
6.1. Input
The Input interface represents a component that has a user-editable value, which can be read and setted using the methods provided by the The ValueHolder super interface.
Input components supports the read-only (to prevent the user from changing the value) and required (to show a suitable indicator along with the field) modes.
Input components supports ValueChangeListener registration to listen to input value changes.
Input<String> stringInput = Components.input.string().caption("String input").inputPrompt("Value here")
.maxLength(100).styleName("mystyle").build(); (1)
stringInput.setValue("test"); (2)
String value = stringInput.getValue(); (3)
stringInput.addValueChangeListener(e -> System.out.println("Value: " + e.getValue())); (4)
| 1 | Create and configure a String value type Input field. |
| 2 | Set a value |
| 3 | Get the value |
| 4 | Add a ValueChangeListener |
The concrete Vaadin Component, which represents the UI input element, can be obtained through the getComponent() method.
Input<LocalDate> dateInput = Components.input.localDate().caption("Date input").lenient(true).build(); (1)
Component inputComponent = dateInput.getComponent(); (2)
| 1 | Create and configure a LocalDate value type Input field. |
| 2 | Get the input Component, which can be used to display the input field in UI. |
An Input component can be obtained from a standard Vaadin Field component using the from(…) static method.
Input<String> stringInput = Input.from(new TextField());
Another way to obtain a standard Vaadin Field component from an input component is to use the asField() input builder method.
Field<Locale> localeField = Components.input.singleSelect(Locale.class).items(Locale.US, Locale.CANADA)
.caption("Select Locale").fullWidth().withValue(Locale.US).asField(); (1)
| 1 | Create select field using Locale data type and obtain it as Field component. |
6.2. ValidatableInput
The ValidatableInput interface represents an Input component which supports value validation using the standard Holon platform Validator interface.
Value validation can be triggered each time the input value changes, using setValidateOnValueChange method.
A ValidationStatusHandler can be used to control how the validation errors are notified to the user, listening to validation status change events.
By default, the standard Vaadin component error notification is used.
Input<String> stringInput = Components.input.string().build();
ValidatableInput<String> validatableInput = ValidatableInput.from(stringInput); (1)
validatableInput.addValidator(Validator.email()); (2)
validatableInput.addValidator(Validator.max(100)); (3)
validatableInput.setValidationStatusHandler(e -> { (4)
if (e.isInvalid()) {
Notification.show(e.getErrorMessage(), Type.ERROR_MESSAGE);
}
});
validatableInput.validate(); (5)
validatableInput.setValidateOnValueChange(true); (6)
| 1 | Create a ValidatableInput from a String input |
| 2 | Add a validator to check the input value is a valid e-mail address |
| 3 | Add a validator to check the input value is maximum 100 characters long |
| 4 | Set validation status handler which shows a notification when the input in invalid |
| 5 | Perform the input value validation |
| 6 | Triggers validation when input value changes |
A ValidatableInput can be obtained from an existing Input or Field component instance or using the appropriate builder.
ValidatableInput<String> validatableInput = ValidatableInput.from(Components.input.string().build()); (1)
validatableInput = ValidatableInput.from(new TextField()); (2)
validatableInput = Components.input.string().validatable() (3)
.required("Value is required") (4)
.withValidator(Validator.max(100)).build(); (5)
| 1 | Create a ValidatableInput from a String Input |
| 2 | Create a ValidatableInput from a String type Field component |
| 3 | Create a ValidatableInput using the input builder |
| 4 | Add a default required (the input is not empty) validator using the provided error message |
| 5 | Add a standard validator |
6.3. Selectable Input
When the Input values must be selected from a specific set, a selectable Input type can be used. Two selectable Input types are available:
-
The SingleSelect Input, in which at most one item can be selected at a time;
-
The MultiSelect Input, in which multiple items can be selected at the same time.
Both selectable Input types extends the Selectable interface, which provides methods to check if some item is selected, obtain the selected item/s and change the current selection. Furthermore, a SelectionListener can be registered to listen to selection changes.
SingleSelect<TestData> singleSelect = Components.input.singleSelect(TestData.class).caption("Single select")
.build(); (1)
singleSelect.setValue(new TestData(1)); (2)
singleSelect.select(new TestData(1)); (3)
singleSelect.clear(); (4)
singleSelect.deselectAll(); (5)
boolean selected = singleSelect.isSelected(new TestData(1)); (6)
singleSelect.addSelectionListener(
s -> s.getFirstSelectedItem().ifPresent(i -> Notification.show("Selected: " + i.getId()))); (7)
| 1 | Create a SingleSelect Input using the TestData class as selection item type |
| 2 | Select a value using the default setValue() Input method |
| 3 | Select a value using select() method |
| 4 | Deselect the value using the default clear() Input method |
| 5 | Deselect the value using the deselectAll() method |
| 6 | Check whether a value is selected |
| 7 | Add a selection listener |
MultiSelect<TestData> multiSelect = Components.input.multiSelect(TestData.class).caption("Multi select")
.build(); (1)
Set<TestData> values = new HashSet<>();
values.add(new TestData(1));
values.add(new TestData(2));
multiSelect.setValue(values); (2)
multiSelect.select(new TestData(3)); (3)
multiSelect.deselect(new TestData(3)); (4)
multiSelect.clear(); (5)
multiSelect.deselectAll(); (6)
boolean selected = multiSelect.isSelected(new TestData(1)); (7)
multiSelect.addSelectionListener(s -> Notification.show(s.getAllSelectedItems().stream()
.map(i -> String.valueOf(i.getId())).collect(Collectors.joining(";", "Selected: ", "")))); (8)
| 1 | Create a MultiSelect Input using the TestData class as selection item type |
| 2 | Select a set of values using the default setValue() Input method |
| 3 | Add a value to current selection using the select() method |
| 4 | Remove a value from current selection |
| 5 | Deselect the value using the default clear() Input method |
| 6 | Deselect the value using the deselectAll() method |
| 7 | Check whether a value is selected |
| 8 | Add a selection listener |
6.3.1. Rendering mode
The visual aspect of the selectable Input can be configured using the RenderingMode enumeration. According to the rendering mode, a suitable UI component will be used to implement the Input.
The default rendering modes are SELECT for SingleSelect inputs and OPTIONS for MultiSelect inputs.
|
| Rendering mode | SingleSelect UI component | MultiSelect UI component |
|---|---|---|
NATIVE_SELECT |
|
not supported |
SELECT |
|
|
OPTIONS |
|
|
SingleSelect<TestData> singleSelect = Components.input.singleSelect(TestData.class, RenderingMode.OPTIONS)
.build(); (1)
MultiSelect<TestData> multiSelect = Components.input.multiSelect(TestData.class, RenderingMode.SELECT).build(); (2)
| 1 | Create a SingleSelect using the OPTIONS rendering mode: a CheckBox group UI component will be used |
| 2 | Create a MultiSelect using the SELECT rendering mode: a ListSelect UI component will be used |
6.3.2. Selection items data source
The data source of the available items for a selectable input can be configured in the following ways:
-
Using a fixed set of items, which can be provided using the
addItem(…)oritems(…)methods of the select input builder; -
Using a Holon platform
ItemDataProviderdata provider;
SingleSelect<TestData> singleSelect = Components.input.singleSelect(TestData.class)
.items(new TestData(1), new TestData(2)).build(); (1)
singleSelect = Components.input.singleSelect(TestData.class)
.dataSource(ItemDataProvider.create(q -> 2, (q, o, l) -> Stream.of(new TestData(1), new TestData(2))))
.build(); (2)
| 1 | Create a select input using an explicitly provided items set |
| 2 | Create a select input using a Holon platform ItemDataProvider |
6.3.3. Item captions and icons
The selectable input builders allow to explicitly set the selection item captions and icons. As for the items caption, the Holon platform localization architecture is supported, and the builders provides methods to specify the caption using a Localizable or a message code.
In order for the localization to work, a LocalizationContext must be available as a context resource. See the Internationalization documentation for further information.
|
final TestData ONE = new TestData(1);
final TestData TWO = new TestData(2);
SingleSelect<TestData> singleSelect = Components.input.singleSelect(TestData.class).items(ONE, TWO)
.itemCaption(ONE, "One") (1)
.itemCaption(ONE, "One", "caption-one-message-code") (2)
.itemIcon(ONE, FontAwesome.STAR) (3)
.build();
| 1 | Set the item caption for the ONE item |
| 2 | Set the localizable item caption for the ONE item, providing a default caption and a translation message code |
| 3 | Set the item icon for the ONE item |
Furthermore, the ItemCaptionGenerator and the ItemIconGenerator interfaces can be used to provide custom selection items captions and icons. By default, the item caption is obtained using the toString() method of the item class and no icon is displayed.
SingleSelect<TestData> singleSelect = Components.input.singleSelect(TestData.class)
.items(new TestData(1), new TestData(2)) // set the items
.itemCaptionGenerator(i -> i.getDescription()) (1)
.itemIconGenerator(i -> i.getId() == 1 ? FontAwesome.STAR : FontAwesome.STAR_O) (2)
.build();
| 1 | Set an item caption generator which provides the item description as item caption |
| 2 | Set an item icon generator |
6.3.4. SingleSelect caption filter
For SingleSelect inputs using SELECT rendering mode, the UI component allows the user to type a filtering text to search for a selection item, which is referred to item captions.
The FilteringMode can be changed using the filteringMode(…) builder method.
SingleSelect<TestData> singleSelect = Components.input.singleSelect(TestData.class)
.items(new TestData(1), new TestData(2)) // set the items
.filteringMode(FilteringMode.CONTAINS) (1)
.build();
| 1 | Set the caption filtering mode |
6.3.5. Using the Property model with selectable Inputs
The Holon platform Property model is fully supported by the selectable input builder, which allow to create selectable inputs using PropertyBox type items and a specific Property as selection property (typically, the property which acts as item id).
The selection data type will be the same as the selection property type.
The simplest way to configure a Property based selectable input, is by using a Datastore.
See the Property documentation for further information about the Holon platform Property model.
|
Datastore datastore = obtainDatastore();
final PathProperty<Long> ID = PathProperty.create("id", Long.class);
final PathProperty<String> DESCRIPTION = PathProperty.create("description", String.class);
SingleSelect<Long> singleSelect = Components.input.singleSelect(ID) (1)
.dataSource(datastore, DataTarget.named("testData"), PropertySet.of(ID, DESCRIPTION)) (2)
.itemCaptionGenerator(propertyBox -> propertyBox.getValue(DESCRIPTION)) (3)
.build();
singleSelect.setValue(Long.valueOf(1)); (4)
Long selectedId = singleSelect.getValue(); (5)
singleSelect.refresh(); (6)
| 1 | Create a SingleSelect using the ID property as selection property |
| 2 | Set the select data source using a Datastore, specifying the DataTarget and the set of property to use as query projection |
| 3 | Set an item caption generator which provides the value of the DESCRIPTION property as item caption |
| 4 | Set the selected value, which will be of type Long, consistent with the ID selection property |
| 5 | Get the selected value, which will be of type Long, consistent with the ID selection property |
| 6 | Refresh the selection items, querying the Datastore |
One or more QueryConfigurationProvider can be used to control the Datastore query configuration, providing additional filters and/or sorts.
Datastore datastore = obtainDatastore();
final PathProperty<Long> ID = PathProperty.create("id", Long.class);
final PathProperty<String> DESCRIPTION = PathProperty.create("description", String.class);
SingleSelect<Long> singleSelect = Components.input.singleSelect(ID)
.dataSource(datastore, DataTarget.named("testData"), PropertySet.of(ID, DESCRIPTION)) //
.withQueryConfigurationProvider(() -> ID.gt(0L)) (1)
.itemCaptionGenerator(propertyBox -> propertyBox.getValue(DESCRIPTION)).build();
| 1 | Add a QueryConfigurationProvider which provides a QueryFilter to select only the items with the ID property that is greater than 0 |
6.4. Grouping Inputs
The PropertyInputGroup component allows to group and manage a set of Input fields, relying on the Property model and using PropertyBox objects to collect, set and provide the Input values.
See the Property documentation for further information about the Holon platform Property model.
|
A PropertyInputGroup is bound to a set of properties, and for each property an Input field is made available to set and get the property value. Each property has to be bound to an Input field with a consistent value type.
The property values are setted and obtained using the PropertyBox data container, with the same property set of the input group.
The property values are automatically updated in the current PropertyBox according to any user modification made through the input’s UI components.
The property input group supports ValueChangeListener registration to be notified when the group PropertyBox value changes.
final PathProperty<Long> ID = PathProperty.create("id", Long.class);
final PathProperty<String> DESCRIPTION = PathProperty.create("description", String.class);
final PropertySet<?> PROPERTIES = PropertySet.of(ID, DESCRIPTION);
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES) (1)
.bind(ID, Components.input.number(Long.class).build()) (2)
.bind(DESCRIPTION, Components.input.string().maxLength(100).build()) (3)
.build();
group.setValue(PropertyBox.builder(PROPERTIES).set(ID, 1L).set(DESCRIPTION, "TestDescription").build()); (4)
PropertyBox value = group.getValue(); (5)
group.addValueChangeListener(e -> { (6)
PropertyBox changedValue = e.getValue();
});
| 1 | Create a PropertyInputGroup using the PROPERTIES property set |
| 2 | Bind the ID property to a Long type Input field |
| 3 | Bind the DESCRIPTION property to a String type Input field |
| 4 | Set the property values using a PropertyBox instance |
| 5 | Get the property values as a PropertyBox instance |
| 6 | Add a group ValueChangeListener |
The default Holon platform PropertyRenderer architecture can be used to automatically generate a suitable Input component for a Property, according to its type.
The bind(…) builder method can therefore be used to override the default property input generation only when a custom Input type is required.
| See Property renderers and presenters for further information about property renderers. |
final PathProperty<Long> ID = PathProperty.create("id", Long.class);
final PathProperty<String> DESCRIPTION = PathProperty.create("description", String.class);
final PropertySet<?> PROPERTIES = PropertySet.of(ID, DESCRIPTION);
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES).build(); (1)
| 1 | The property Input fields are automatically generated using the available PropertyRenderer from the default registry |
6.4.1. Read-only properties
A property can be setted as read-only, to prevent the modification of its value. When a property is read-only, the Input component will be in read-only mode too, preventing the user from changing the value.
final PathProperty<Long> ID = PathProperty.create("id", Long.class);
final PathProperty<String> DESCRIPTION = PathProperty.create("description", String.class);
final PropertySet<?> PROPERTIES = PropertySet.of(ID, DESCRIPTION);
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES) //
.readOnly(ID) (1)
.build();
| 1 | The ID property is setted as read-only, preventing the user from changing the property value |
6.4.2. Hidden properties
A property can be setted as hidden. When a property is hidden, it is part of the group property set and its value will be preserved in the PropertyBox instances, but no Input component will be generated and bound to the property itself, so its value will never be visible on the user interface.
The hidden(Property property) method of the group builder can be used to set a property as hidden.
6.4.3. Default property values
A default value can be provided for a property. If available, the default value will be used as property value when the corresponding Input component is empty (i.e. has no value).
The defaultValue(Property<T> property, DefaultValueProvider<T> defaultValueProvider) method of the group builder can be used to set a default value for a property.
6.5. Input group validation
A PropertyInputGroup support both property value and group value validation, using both standard Holon platform Validator and Vaadin validators.
A set of required(…) group builder methods are provided to add a required validator to a property (i.e. a validator that check the value is not null or empty) and to show the required indicator symbol alog with the property input component.
When a validator in bound to the overall input group, the value to validate will be represented by the PropertyBox which contains all the current property values.
final PathProperty<Long> ID = PathProperty.create("id", Long.class);
final PathProperty<String> DESCRIPTION = PathProperty.create("description", String.class);
final PropertySet<?> PROPERTIES = PropertySet.of(ID, DESCRIPTION);
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES) //
.withValidator(DESCRIPTION, Validator.max(100)) (1)
.required(ID) (2)
.required(ID, "The ID value is required") (3)
.withValidator(Validator.create(propertyBox -> propertyBox.getValue(ID) > 0,
"The ID value must be greater than 0")) (4)
.build();
| 1 | Add a validator bound to the DESCRIPTION property to check the value size is not greater than 100 characters |
| 2 | Add a required validator to the ID property |
| 3 | Add a required validator to the ID property providing a custom validation error message |
| 4 | Add an overall validator which read the ID property value from the group PropertyBox and checks it is greater than 0 |
The input group validation is performed invoking all the available property validators, and then the overall group validators, if any.
PropertyInputGroup group = createInputGroup();
group.validate(); (1)
PropertyBox value = group.getValue(); (2)
value = group.getValue(false); (3)
value = group.getValueIfValid().orElse(null); (4)
| 1 | Explicit group validation: if some validation failure occurs, a ValidationException is thrown |
| 2 | Using the default getValue() method, the validation is triggered automatically, and a ValidationException is thrown if validation fails |
| 3 | The getValue(boolean validate) method can be used to skip value validation |
| 4 | The getValueIfValid() returns an Optional, which contains the PropertyBox value only if the validation succeded |
A ValidationStatusHandler can be used to control how the validation errors are notified to the user, listening to validation status change events.
The property input group support ValidationStatusHandler configuration both property and overall validation.
Furthermore, the input group builder provides two methods to control if the validation failures are to be collected and all notified to the user or if the validation has to stop at first failure, both from a properties or a group point of view.
final PathProperty<Long> ID = PathProperty.create("id", Long.class);
final PathProperty<String> DESCRIPTION = PathProperty.create("description", String.class);
final PropertySet<?> PROPERTIES = PropertySet.of(ID, DESCRIPTION);
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES) //
.stopValidationAtFirstFailure(true) (1)
.stopOverallValidationAtFirstFailure(true) (2)
.validationStatusHandler(validationEvent -> { (3)
// ...
}).propertiesValidationStatusHandler(validationEvent -> { (4)
// ...
}).build();
| 1 | Set to stop overall validation at first failure |
| 2 | Set to stop properties validation at first failure |
| 3 | Set the overall ValidationStatusHandler |
| 4 | Set the properties ValidationStatusHandler |
A property input group ValidationStatusHandler setter method which accept a Label is provided as a shorter to use a standard Vaadin Label to notify validation errors.
|
6.6. Input Forms
The PropertyInputForm represents a Input group bound to a Property model which is also a UI Component, and can be used to display and manage the Input components in the UI.
It is a Grouping Inputs, from which inherits all the Input generation, configuration and management features, using a PropertyBox to set and get the property values bound to each Input component.
It extends the ComposableComponent interface, which represents an UI component with a base layout and the capability to compose a set of UI components on this layout.
The Composer interface is used to compose the Input components on the UI, and must be provided to the PropertyInputForm, along with the base layout component on which the Input components will be organized.
The ComposableComponent interface provides the componentContainerComposer() static method to obtain a default composer which uses a Vaadin ComponentContainer as base layout.
|
The inputs composition is triggered using the compose() method. The PropertyInputForm builder provides a composeOnAttach(…) method to set whether to automatically trigger the components composition when the form is attached to a parent layout, and this is the standard behaviour.
Furthermore, a initializer(…) builder method is made available to perform custom configuration of the base layout component.
final PathProperty<Long> ID = PathProperty.create("id", Long.class);
final PathProperty<String> DESCRIPTION = PathProperty.create("description", String.class);
final PropertySet<?> PROPERTIES = PropertySet.of(ID, DESCRIPTION);
PropertyInputForm form = Components.input.form(new FormLayout()).properties(PROPERTIES).required(ID).build(); (1)
form = Components.input.form(new FormLayout()).properties(PROPERTIES).required(ID)
.composer((layout, source) -> { (2)
source.getValueComponents().forEach(c -> layout.addComponent(c.getComponent()));
}).build();
form.setValue(PropertyBox.builder(PROPERTIES).set(ID, 1L).set(DESCRIPTION, "Test").build()); (3)
PropertyBox value = form.getValue(); (4)
| 1 | Create a PropertyInputForm with given property set using a FormLayout as base layout, and using the default composer |
| 2 | Set a custom Composer to compose the Input components on the form layout |
| 3 | Set the form value using a PropertyBox |
| 4 | Get the form value |
A set of default builder factory methods are provided to use the most common Vaadin layout components as form base content.
PropertyInputForm form = Components.input.form().properties(PROPERTY_SET).build(); (1)
form = Components.input.formVertical().properties(PROPERTY_SET).build(); (2)
form = Components.input.formHorizontal().properties(PROPERTY_SET).build(); (3)
form = Components.input.formGrid().properties(PROPERTY_SET)
.initializer(gridLayout -> gridLayout.setSpacing(true)).build(); (4)
| 1 | Create a PropertyInputForm using a FormLayout as base content |
| 2 | Create a PropertyInputForm using a VerticalLayout as base content |
| 3 | Create a PropertyInputForm using a HorizontalLayout as base content |
| 4 | Create a PropertyInputForm using a GridLayout as base content and setting an initializer function to perform GridLayout configuration |
6.7. View components
The ViewComponent represents a UI component which can be used to display a value on the user interface. Unlike a Input component the value cannot be changed by the user.
As a ValueHolder, the view component value can be setted and obtained using the setValue and getValue methods.
The actual UI component to display is obtained form the getComponent() method.
A Vaadin Label is used for the default ViewComponent implementation, and a default view component can be built using the default builder which can be obtained from the builder(Class valueType) method or from the Components.view interface.
ViewComponent<String> view = Components.view.component(String.class)
.caption("TheCaption", "caption.message.code").icon(FontAwesome.CAMERA).styleName("my-style").build(); (1)
view.setValue("TestValue"); (2)
String value = view.getValue(); (3)
| 1 | Create and configure a ViewComponent with a String value type |
| 2 | Set the value |
| 3 | Get the value |
6.7.1. View component Groups and Forms
Just like the Input components, the ViewComponent s can be organized in groups (using the PropertyViewGroup) or in forms (using the PropertyViewForm). Unlike the group, the form is a UI component which can be displayed in the user interface, composing the view components on a base layout.
These component containers rely on the Property model to bind each view component to a Property value and they use a PropertyBox to provide the property values to show.
final PathProperty<Long> ID = PathProperty.create("id", Long.class);
final PathProperty<String> DESCRIPTION = PathProperty.create("description", String.class);
final PropertySet<?> PROPERTIES = PropertySet.of(ID, DESCRIPTION);
PropertyViewGroup viewGroup = Components.view.propertyGroup().properties(PROPERTIES).build(); (1)
PropertyViewForm viewForm = Components.view.formVertical().properties(PROPERTIES).build(); (2)
viewForm = Components.view.form(new FormLayout()).properties(PROPERTIES) //
.composer((layout, source) -> { (3)
source.getValueComponents().forEach(c -> layout.addComponent(c.getComponent()));
}).build();
viewForm.setValue(PropertyBox.builder(PROPERTIES).set(ID, 1L).set(DESCRIPTION, "Test").build()); (4)
PropertyBox value = viewForm.getValue(); (5)
| 1 | Create a PropertyViewGroup |
| 2 | Create a PropertyViewForm using a VerticalLayout as base layout |
| 3 | Create a PropertyViewForm using a FormLayout as base layout and providing a custom components composer |
| 4 | Set the form value using a PropertyBox |
| 5 | Get the form value |
7. Item listing
The ItemListing component can be used to display a set of items as tabular data. By default, it is backed by a Vaadin Grid as concrete UI component, but the Vaadin Table component is supported too.
Each item property to display is bound to a column, and a Property id is used to bind the item property to the listing column.
The ItemListing is a Selectable component, supporting items selection in single or multiple mode.
The ItemListing interface provides a set of methods to control the listing appearance, showing and hiding columns, sorting and refreshing the item set, and so on.
Two ItemListing sub-interfaces are available to use this component:
-
BeanListing: Uses a Java Bean as item type and the listing column ids are the bean property names.
-
PropertyListing: Uses a
PropertyBoxas item type and the listing column ids are thePropertyof the provided property set.
7.1. BeanListing
A BeanListing can be obtained providing the Java Bean class to use as item type.
The listing columns will be generated inspecting the bean class to detect the available bean properties, and the column ids will be the bean property names.
private class TestData {
private Long id;
private String description;
// getters and setters omitted
}
public void beanListing() {
BeanListing<TestData> listing = Components.listing.items(TestData.class) (1)
.header("id", "The ID") (2)
.header("description", "The description") (3)
.build();
listing.refresh(); (4)
listing = Components.listing.itemsUsingTable(TestData.class).build(); (5)
}
| 1 | Create a BeanListing using the TestData bean class |
| 2 | Set the column header for the id item (bean) property |
| 3 | Set the column header for the description item (bean) property |
| 4 | Refresh the items set |
| 5 | Create a BeanListing using a Table as backing component |
7.2. PropertyListing
A PropertyListing can be obtained providing the Property set to use for the listing items.
The listing columns will be generated according to the item property set Property types and each column will be identified by the Property itself.
For each property:
-
The property caption, if available, will be used as column header. The caption/header localization is fully supported and performed if a
LocalizationContextis available; -
The cells of the column which corresponds to a property will be rendered according to the property type and relying on the available property value presenters;
-
For any property of
com.vaadin.ui.Componenttype, aComponentRendererwill be automatically used to render the Vaadin components in the cells.
final PathProperty<Long> ID = PathProperty.create("id", Long.class);
final PathProperty<String> DESCRIPTION = PathProperty.create("description", String.class);
final PropertySet<?> PROPERTIES = PropertySet.of(ID, DESCRIPTION);
PropertyListing listing = Components.listing.properties(PROPERTIES).build(); (1)
listing = Components.listing.propertiesUsingTable(PROPERTIES).build(); (2)
| 1 | Create a PropertyListing using given Property set |
| 2 | Create a PropertyListing using a Table as backing component |
7.3. Items data source
To build an item listing, the data source of the items must be provided using the ItemDataProvider interface.
ItemDataProvider<PropertyBox> dataProvider = getDataProvider();
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.dataSource(dataProvider) (1)
.build();
| 1 | Create a PropertyListing and provide an ItemDataProvider as data source |
Furthermore, an ItemIdentifierProvider should be configured to provide the item identifiers, expecially for the item selection consistency. The default behaviour is to use item itself as its own identifier, which it is not always the right semantic to use, expecially for PropertyBox type objects.
ItemDataProvider<PropertyBox> dataProvider = getDataProvider();
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.dataSource(dataProvider, item -> item.getValue(ID)) (1)
.build();
listing = Components.listing.properties(PROPERTIES) //
.dataSource(dataProvider, ID) (2)
.build();
| 1 | Create a PropertyListing and provide an ItemDataProvider as data source, along with an ItemIdentifierProvider which provides the value of the ID property as item id |
| 2 | A shorter builder method to use the ID property as item identifier |
The item listing builders make available convenience methods to use a Datastore as data source, providing the DataTarget to use as target of the query on the backend data (for example, the name of a RDBMS table).
Datastore datastore = getDatastore();
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.dataSource(datastore, DataTarget.named("test"), ID) (1)
.build();
| 1 | Create a PropertyListing and use a Datastore to build a data source which performs a query using the test data target (which can be, for example, the name of a RDBMS table), the property set of the listing as query projection and the ID property as item identifier |
7.4. Columns configuration
The listing component columns can be configured in a number of ways:
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.header(ID, "Custom ID header") (1)
.columnHidingAllowed(true) (2)
.hidable(ID, false) (3)
.columnReorderingAllowed(true) (4)
.alignment(ID, ColumnAlignment.RIGHT) (5)
.hidden(DESCRIPTION, true) (6)
.resizable(ID, false) (7)
.width(ID, 100) (8)
.expandRatio(DESCRIPTION, 1) (9)
.minWidth(DESCRIPTION, 200) (10)
.maxWidth(DESCRIPTION, 300) (11)
.style(ID, (property, item) -> item.getValue(DESCRIPTION) != null ? "empty" : "not-empty") (12)
.withPropertyReorderListener((properties, userOriginated) -> { (13)
// ...
}).withPropertyResizeListener((property, widthInPixel, userOriginated) -> { (14)
// ...
}).withPropertyVisibilityListener((property, hidden, userOriginated) -> { (15)
// ...
}).build();
| 1 | Set a custom header caption for the ID property/column |
| 2 | Set whether the listing columns can be hidden |
| 3 | Set that the ID property/column cannot be hidden |
| 4 | Set whether the listing columns can reordered |
| 5 | Set the ID property/column cell contents alignment |
| 6 | Set the DESCRIPTION property/column as hidden by default |
| 7 | Set the ID property/column as not resizable |
| 8 | Set the ID property/column width in pixels |
| 9 | Set the DESCRIPTION property/column expand ratio |
| 10 | Set the DESCRIPTION property/column minimum width |
| 11 | Set the DESCRIPTION property/column maximum width |
| 12 | Set the ID property/column CSS style generator |
| 13 | Add a listener to be notified when the listing columns order changes |
| 14 | Add a listener to be notified when a property/column is resized |
| 15 | Add a listener to be notified when a property/column is shown or hidden |
7.5. Column rendering
The default column rendering can be overridden using a custom vaadin Renderer. The Renderer must handle the same data type of the property/column for which it is configured.
Furthermore, a property/column value can be converted to a different value type, and, in this case, a suitable Renderer for the converted type must be provided.
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.render(ID, new NumberRenderer(Locale.US)) (1)
.render(ID, new Converter<String, Long>() { (2)
@Override
public Long convertToModel(String value, Class<? extends Long> targetType, Locale locale)
throws ConversionException {
return Long.valueOf(value);
}
@Override
public String convertToPresentation(Long value, Class<? extends String> targetType, Locale locale)
throws ConversionException {
return (value == null) ? null : String.valueOf(value);
}
@Override
public Class<Long> getModelType() {
return Long.class;
}
@Override
public Class<String> getPresentationType() {
return String.class;
}
}, new TextRenderer()).build();
| 1 | Set a custom NumberRenderer for the ID property/column (which is of type Long) |
| 2 | Set a converter to convert the Long type value of the ID property/column into a String and a suitable render according to the new value type |
7.6. Listing configuration
The item listing component is highly configurable. Through the builders you can configure:
-
The header appeareance, adding and removing header rows and joining header columns
-
Showing and configuring a footer section
-
Set a number of column as frozen
-
Provide rows CSS style using a style generator
-
Provide item description (tooltip) for item rows
-
Provide a item click listener to listen for user clicks on item rows
-
Provide a row details component which can be shown for each item row
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.heightByContents() (1)
.frozenColumns(1) (2)
.hideHeaders() (3)
.withRowStyle(item -> { (4)
return item.getValue(DESCRIPTION) != null ? "has-des" : "no-nes";
}) //
.itemDescriptionGenerator(item -> item.getValue(DESCRIPTION)) (5)
.detailsGenerator(item -> { (6)
VerticalLayout component = new VerticalLayout();
// ...
return component;
}).withItemClickListener((item, property, event) -> { (7)
// ...
}).header(header -> { (8)
header.getDefaultRow().setStyleName("my-header");
}).footer(footer -> { (9)
footer.appendRow().setStyleName("my-footer");
}).footerGenerator((source, footer) -> { (10)
footer.getRowAt(0).getCell(ID).setText("ID footer");
}).withPostProcessor(grid -> { (11)
// ...
}).build();
| 1 | Set the height of the listing defined by its contents |
| 2 | Set the frozen columns count |
| 3 | Hide all the listing headers |
| 4 | Set the row CSS style generator |
| 5 | Set the item/row description (tooltip) generator |
| 6 | Set the item/row details component generator |
| 7 | Register a item click listener |
| 8 | Add a style class name to the default header row |
| 9 | Append a row to the listing footer and set a style class name for it |
| 10 | Set the footer generator to invoke to update the listing footer contents |
| 11 | Set a post-processor to customize the internal Grid component |
7.7. Item selection
The item listing component supports items selection, both in single and multiple mode. The listing can be made selectable using the setSelectionMode method or the selectionMode builder method.
Just like any other Selectable component, the item listing component provides methods to inspect the current selected item/s and change the current selection.
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.selectionMode(SelectionMode.SINGLE) (1)
.build();
final PropertyBox ITEM = PropertyBox.builder(PROPERTIES).set(ID, 1L).build();
PropertyBox selected = listing.getFirstSelectedItem().orElse(null); (2)
boolean isSelected = listing.isSelected(ITEM); (3)
listing.select(ITEM); (4)
listing.deselectAll(); (5)
| 1 | Set the listing selectable in SINGLE selection mode |
| 2 | Get the currently selected item, if any |
| 3 | Check whether the given item is selected |
| 4 | Select the given item |
| 5 | Deselect all (clear current selection) |
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.build();
listing.setSelectionMode(SelectionMode.MULTI); (1)
final PropertyBox ITEM = PropertyBox.builder(PROPERTIES).set(ID, 1L).build();
Set<PropertyBox> selected = listing.getSelectedItems(); (2)
boolean isSelected = listing.isSelected(ITEM); (3)
listing.select(ITEM); (4)
listing.deselectAll(); (5)
listing.selectAll(); (6)
| 1 | Set the listing selectable in MULTI selection mode |
| 2 | Get the selected items |
| 3 | Check whether the given item is selected |
| 4 | Select the given item |
| 5 | Deselect all items |
| 6 | Select all items |
7.8. Item query configuration
The item listing builder provides a number of methods to control the data source items query, allowing to:
-
Set a property/column as sortable or not and override the default sort behaviour, for example using another property as query sort target or providing a custom
QuerySort; -
Set a fixed sort, which will always to appended to current query sorts
-
Set a default sort, to be used when no other query sort is configured
-
Set a fixed filter, which will be always applied to the query
-
Register one or more
QueryConfigurationProviderto provide custom query configuration logic
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.sortable(ID, true) (1)
.sortUsing(ID, DESCRIPTION) (2)
.sortGenerator(ID, (property, ascending) -> { (3)
return ascending ? ID.asc() : ID.desc();
}) //
.fixedSort(ID.asc()) (4)
.defaultSort(DESCRIPTION.asc()) (5)
.fixedFilter(ID.gt(0L)) (6)
.withQueryConfigurationProvider(() -> DESCRIPTION.isNotNull()) (7)
.build();
| 1 | Set the ID property/column as sortable |
| 2 | Set to use the DESCRIPTION property to sort the ID property/column |
| 3 | Set a custom QuerySort for the ID property/column |
| 4 | Set a fixed QuerySort, which will always to appended to current query sorts |
| 5 | Set the default QuerySort, to be used when no other query sort is configured |
| 6 | Set a fixed QueryFilter |
| 7 | Add a QueryConfigurationProvider |
7.9. Item set management
The item set component provides methods to manage the item set which is provided as the listing data source, to add, update and remove one or more item in the set.
To reflect the item set management operations in the underlyng data source (for example, a RDBMS), the CommitHandler interface is used. The commit(…) method of that interface is invoked at each item set modification, providing the added, updated and removed items. The CommitHandler implementation should persist the item set modifications in the concrete data source.
When using the dataSource(…) builder method which accepts a Datastore as data source, a default CommitHandler is automatically configured, using the provided Datastore to perform the item persistence operations.
|
Datastore datastore = getDatastore();
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.dataSource(datastore, DataTarget.named("test"), ID) (1)
.commitHandler((addedItems, modifiedItems, removedItems) -> { (2)
// ...
}).build();
final PropertyBox ITEM = PropertyBox.builder(PROPERTIES).set(ID, 777L).set(DESCRIPTION, "A description")
.build();
listing.addItem(ITEM); (3)
listing.refreshItem(ITEM); (4)
listing.removeItem(ITEM); (5)
| 1 | When using the Datastore method of the data source builder, a default CommitHandler is automatically configured |
| 2 | You can provide a custom CommitHandler using the commitHandler(…) builder method |
| 3 | Add an item |
| 4 | Refresh (update) the item |
| 5 | Remove the item |
7.9.1. Buffered mode
The item set component supports a buffered mode to handle its items set. When in buffered mode, the item set modifications are cached internally and not reflected to the concrete data source until the commit() method is called.
A discard() method is provided to discards all changes since last commit.
The same CommitHandler as before is used to persist the item set modifications, but it is invoked only when the commit() method is called.
Datastore datastore = getDatastore();
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.dataSource(datastore, DataTarget.named("test"), ID) //
.buffered(true) (1)
.build();
final PropertyBox ITEM = PropertyBox.builder(PROPERTIES).set(ID, 777L).set(DESCRIPTION, "A description")
.build();
listing.addItem(ITEM); (2)
listing.refreshItem(ITEM); (3)
listing.removeItem(ITEM); (4)
listing.commit(); (5)
listing.discard(); (6)
| 1 | Set the listing in buffered mode |
| 2 | Add an item |
| 3 | Refresh (update) the item |
| 4 | Remove the item |
| 5 | commit the item set modifications |
| 6 | discard the item set modifications since the last commit |
7.10. Editing items
The item listing component supports line-based editing, where double-clicking a row opens the row editor, using the default Vaadin Grid editor. The item listing editor must be enabled using the editable(true) method of the builder in order to activate the row editor at double-click.
The editor fields to use for each listing property/column will be auto-generated according to the property/column type. This behaviour can be overridden providing custom editor fields for one or more of the the listing properties.
The item listing component editor supports validation, allowing to register value validators both for the single property/column fields and for the overall value validation.
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.editable(true) (1)
.editorSaveCaption("Save item") (2)
.editorCancelCaption("Discard") (3)
.editable(ID, false) (4)
.editor(DESCRIPTION, new TextField()) (5)
.withValidator(DESCRIPTION, Validator.max(100)) (6)
.required(ID) (7)
.build();
| 1 | Set the listing as editable |
| 2 | Set the editor save button caption |
| 3 | Set the editor cancel button caption |
| 4 | Set a property as not editable |
| 5 | Set a custom editor field |
| 6 | Add a property value validator |
| 7 | Set the ID property as required, automatically adding a not empty validator |
8. Dialogs
The Holon Vaadin module provides builders to create and show dialog windows.
A dialog window in represented by the Dialog interface, which makes available methods to set the dialog as modal, open and close the dialog window and registere a CloseListener to be invoked when the dialog is closed.
Dialog dialog = Components.dialog() (1)
.draggable(false) (2)
.closable(true) (3)
.resizable(true) (4)
.modal(true) (5)
.message("Dialog message", "dialog.message.code") (6)
.okButtonConfigurator(cfg -> cfg.caption("Done").icon(FontAwesome.CHECK_CIRCLE_O)) (7)
.withCloseListener((window, action) -> { (8)
// ...
}).build();
dialog.open(); (9)
dialog.close(); (10)
| 1 | Dialog builder |
| 2 | Set as not draggable |
| 3 | Set as closable |
| 4 | Set as resizable |
| 5 | Set as modal |
| 6 | Set the localizable dialog message |
| 7 | Configure the dialog OK button |
| 8 | Add a close listener |
| 9 | Open (show) the dialog |
| 10 | Close (hide) the dialog |
8.1. Question dialogs
A question dialog is a Dialog with a message and two buttons (instead of one) to answer yes or no to a question.
A QuestionCallback can be used when the user selects one of the two buttons and the dialog is closed. The callback notifies the selected answer.
Components.questionDialog() (1)
.message("Can I do it for you?") (2)
.yesButtonConfigurator(cfg -> cfg.caption("Ok, let's do it")) (3)
.noButtonConfigurator(cfg -> cfg.caption("No, thanks")) (4)
.callback(answeredYes -> { (5)
Notification.show("Ok selected: " + answeredYes);
}).build().open(); (6)
| 1 | Question dialog builder |
| 2 | Set the dialog message (the question) |
| 3 | Change the yes button caption |
| 4 | Change the no button caption |
| 5 | Set the answer callback |
| 6 | Build and open the dialog |
9. Property renderers and presenters
The Vaadin module takes advantage of the platform foundation property model to provide the most suitable UI components and display values when a Property is used.
When a Property must be rendered in UI, the components made available by the Vaadin module try to use a suitable PropertyRenderer, if available.
By default, the following renderers are automatically registered by the module:
-
A Input property renderer
-
A
com.vaadin.ui.Fieldproperty renderer -
A ViewComponent property renderer
The Input and Field property renderers generate a UI component to edit a property value, relying on the property type.
The following types are supported:
-
String: renders the property as a
TextField -
Boolean: renders the property as a
CheckBox -
Enum: renders the property as a
ComboBox -
LocalDate and LocalDateTime: renders the property as a
DateFieldor aDateTimeField -
java.util.Date: renders the property as a
DateFieldor aDateTimeField -
Number: renders the property as a
TextFieldwhich only accepts numbers and decimal/group separators
final PathProperty<String> TEXT = PathProperty.create("text", String.class);
final PathProperty<Long> LONG = PathProperty.create("long", Long.class);
Input<String> input = TEXT.render(Input.class); (1)
Field<Long> field = LONG.render(Field.class); (2)
ViewComponent<String> view = TEXT.render(ViewComponent.class); (3)
| 1 | Render the property as String type Input |
| 2 | Render the property as Long type Field |
| 3 | Render the property as String type ViewComponent |
9.1. Property localization
When a LocalizationContext is available as a Context resource, it is used to localize any element to display which supports localization. Concerning to a Property related UI component, it is used to:
-
Localize the property caption, for example when it is used as a listing column header or as a input field caption
-
Obtain the date and time format to use to display a property value or to edit the property value through a a input field
-
Obtain the number format to use to display a property value or to edit the property value through a a input field
-
Obtain the default boolean localized text for the
trueandfalsevalues
| See the Internationalization documentation for further information about localization. |
9.2. Custom property renderers
Relying on the standard property renderers features, custom renderers can be registered to:
-
Customize the rendered UI component under certain conditions for the default provided rendering types (
Input,FieldandViewComponent) -
Provide the rendering for a custom type
For example, to render a specific String type property as text area, instead of the default text field, an appropriate renderer can be registered and bound to a suitable property condition:
final PathProperty<String> TEXT = PathProperty.create("text", String.class);
InputPropertyRenderer<String> textAreaInputRenderer = p -> Components.input.string(true).build(); (1)
PropertyRendererRegistry.get().register(p -> p == TEXT, textAreaInputRenderer); (2)
Input<String> input = TEXT.render(Input.class); (3)
InputFieldPropertyRenderer<String> fieldRenderer = p -> new TextArea(); (4)
| 1 | Create a Input property renderer which renders the property as a text area |
| 2 | Register the renderer and bind it to a condition which checks the property is exactly the TEXT property |
| 3 | From now on, rendering the TEXT property as an Input field will generate a text area input component |
| 4 | Example of a Input property renderer which renders the property as a text area using a Vaadin Field |
10. Vaadin session scope
A Vaadin session platform scope is automatically registered by the Holon Vaadin module.
This ContextScope is bound to the current VaadinSession, looking up context resources using Vaadin Session attributes.
The scope uses VaadinSession.getCurrent() to obtain the current Vaadin session, so this scope is active only when invoked within a Vaadin server request thread.
| See the Context documentation for further information about context scopes and resources. |
VaadinSession.getCurrent().setAttribute(LocalizationContext.CONTEXT_KEY,
LocalizationContext.builder().withInitialLocale(Locale.US).build()); (1)
LocalizationContext.getCurrent().ifPresent(localizationContext -> { (2)
// LocalizationContext obtained from current Vaadin session
});
| 1 | Create a LocalizationContext and set it in Vaadin session using the default context key as attribute name |
| 2 | Get the current LocalizationContext context resource, which will be obtained form the Vaadin session, if available |
11. Device information
The Holon platform Vaadin module makes available the DeviceInfo interface, to obtain informations about the device in which the application is running.
The DeviceInfo object provides informations about the user agent and screen dimensions, and can be obtained using a VaadinRequest or relying on the currently available VaadinSession.
DeviceInfo.get().ifPresent(info -> {
info.getScreenWidth();
info.getScreenHeight();
info.getViewPortWidth();
info.getViewPortHeight();
info.isMobile();
info.isTablet();
info.isSmartphone();
info.isAndroid();
info.isIPhone();
// ...
});
12. Navigator
The holon-vaadin-navigator artifact makes available an extension of the default view navigator, represented by the ViewNavigator interface, which provides additional features and configuration capabilities:
-
Viewnavigation history tracking, allowing to navigate back to the previous view -
Viewparameters management, allowing to obtain the parameters values by injection in theViewinstance -
Easy
Viewlifecycle management, supportingOnShowandOnLeaveannotated methods to be notified when the view is loaded and unloaded in the UI -
Default view support, acting as the application’s "home page"
-
The possibility to show a view in a
Window, instead of using the default view display component -
Support for context resources injection in
Viewinstances
Maven coordinates:
<groupId>com.holon-platform.vaadin7</groupId>
<artifactId>holon-vaadin-navigator</artifactId>
<version>5.2.4</version>
12.1. View configuration
12.1.1. Parameters injection
The navigation parameters can be injected in the View class fields using the ViewParameter annotation.
The parameter name for which the value has to be injected in an annotated field is assumed to be equal to the field name. Otherwise, it can be specified using the annotation value() property.
When a named parameter value is not available (not provided by the current navigation state), the parameter field will be setted to null or the default value for primitive types (for example 0 for int, long, float and double types, false for boolean types).
The supported parameter value types are:
-
String -
Numbers (including primitive types) -
Boolean(including primitive type) -
Enum -
Date -
LocalDate -
LocalTime -
LocalDateTime
A view parameter can be declared as required using the required() annotation property. A navigation error will be thrown if a required parameter value is missing.
Furthermore, a default value can be provided for a parameter, using the defaultValue() annotation property. See the ViewParameter javadocs for information about the default value representation.
class ViewExample implements View {
@ViewParameter("myparam") (1)
private String stringParam;
@ViewParameter(defaultValue = "1") (2)
private Integer intParam;
@ViewParameter(required = true) (3)
private LocalDate requiredParam;
@Override
public void enter(ViewChangeEvent event) {
}
}
| 1 | View parameter injection using the myparam parameter name |
| 2 | View parameter with a default value. The parameter name will be the intParam field name |
| 3 | A required view parameter declaration |
12.1.2. View lifecycle hooks
In addition to the standard enter(…) method of the View interface, two annotations can be used to intercept View lifecycle events using public View methods:
If more than one OnShow or OnLeave annotated method in present in the View class or in it’s class hierarchy, all these methods will be called and the following behaviour will be adopted:
-
Methods will be called following the class hierarchy, starting from the top (the first superclass after
Object) -
For methods of the same class, no calling order is guaranteed
The OnShow and OnLeave annotated method supports an optional parameter, which can be either of the standard com.vaadin.navigator.ViewChangeEvent type or of the extended ViewNavigatorChangeEvent type, to obtain informations about the current view navigator, the previous or next View, the View name and parameters and the optional Window in which the View is displayed.
The OnShow annotation provides an additional onRefresh() property which, if setted to true, instruct the navigator to invoke the OnShow annotated method also at browser page refresh.
class ViewExample2 implements View {
@ViewParameter
private String myparam;
@OnShow
public void onShow() { (1)
// ...
}
@OnShow(onRefresh = true) (2)
public void onShowAtRefreshToo() {
// ...
}
@OnShow
public void onShow2(ViewChangeEvent event) { (3)
String name = event.getViewName(); (4)
View oldView = event.getOldView(); (5)
// ...
}
@OnShow
public void onShow3(ViewNavigatorChangeEvent event) { (6)
ViewNavigator navigator = event.getViewNavigator(); (7)
Optional<Window> viewWindow = event.getWindow(); (8)
// ...
}
@OnLeave
public void onLeave() { (9)
// ...
}
@OnLeave
public void onLeave2(ViewNavigatorChangeEvent event) { (10)
View nextView = event.getNewView(); (11)
// ...
}
@Override
public void enter(ViewChangeEvent event) {
}
}
| 1 | Basic OnShow annotated method, invoked right before the view is shown |
| 2 | OnShow method with onRefresh() setted to true: this method will be invoked also at browser page refresh |
| 3 | OnShow method with a default ViewChangeEvent parameter |
| 4 | Get the View name |
| 5 | Get the previous View |
| 6 | OnShow method with a ViewNavigatorChangeEvent parameter |
| 7 | Get the ViewNavigator |
| 8 | Get the Window in which the view is displayed if it was requested to open the View in a window |
| 9 | Basic onLeave annotated method |
| 10 | onLeave method with a ViewNavigatorChangeEvent parameter |
| 11 | Get the View being activated |
12.1.3. Providing the View contents
By default, the View is rendered as the UI component which is represented by the View class.
The ViewContentProvider interface can be implemented by a View class to control which UI content to provide through the getViewContent() method. The method is called by the ViewNavigator to display the View when it is activated.
class ViewExampleContent extends VerticalLayout implements View { (1)
public ViewExampleContent() {
super();
addComponent(new Label("View content"));
}
@Override
public void enter(ViewChangeEvent event) {
}
}
class ViewExampleContentProvider implements View, ViewContentProvider { (2)
@Override
public Component getViewContent() { (3)
boolean mobile = DeviceInfo.get().map(info -> info.isMobile()).orElse(false);
return mobile ? buildMobileViewContent() : buildDefaultViewContent();
}
@Override
public void enter(ViewChangeEvent event) {
}
}
| 1 | Default View implementation extending a Component class (a VerticalLayout). The displayed View content will be the component itself |
| 2 | A View implementing the ViewContentProvider interface: the displayed UI content will be provided by the getViewContent() method |
| 3 | Provide the View content. In this example, a different UI component is provided according to the device type, checking if the user is running the application in a mobile device |
12.2. Navigator Configuration
Just like the standard Vaadin navigator, the ViewNavigator requires two elements which must be configured in order to work properly:
-
A
ViewProviderto provide thViewinstances by view name -
A
Viewdisplay component to show the view contents in the UI, which can be aComponentContainer(replacing the contents of the container with the activeViewcontent), aSingleComponentContainer(using thesetContent(…)method to set the activeViewcontent) or aViewDisplayobject to implement a customViewdisplay logic.
In addition to the required configuration elements, the ViewNavigator supports:
-
Setting a default view name, which will be used as target of the
ViewNavigator.navigateToDefault()method and as a fallback by theViewNavigator.navigateBack()method if no otherViewis available in navigation history -
Setting a error view or a error
ViewProviderto provide theViewto be displayed when no otherViewmatches a navigation state -
Setting the max navigation history size
-
Register a
ViewChangeListenerfor listening toViewchanges before and after they occur
The builder() method of the ViewNavigator interface provides a fluent builder to configure a navigator and bind it to a Vaadin UI.
class AppUI extends UI {
@Override
protected void init(VaadinRequest request) {
ViewNavigator navigator = ViewNavigator.builder() (1)
.viewDisplay(this) (2)
.addProvider(getViewProvider()) (3)
.defaultViewName("home") (4)
.errorView(MY_ERROR_VIEW) (5)
.errorViewProvider(getErrorViewProvider()) (6)
.maxNavigationHistorySize(1000) (7)
.navigateToDefaultViewWhenViewNotAvailable(true) (8)
.withViewChangeListener(new ViewChangeListener() { (9)
@Override
public boolean beforeViewChange(ViewChangeEvent event) {
// ...
return true;
}
@Override
public void afterViewChange(ViewChangeEvent event) {
// ...
}
}).buildAndBind(this); (10)
}
}
| 1 | Obtain the ViewNavigator builder |
| 2 | Set the component to which the View contents display is delegated |
| 3 | Add a ViewProvider |
| 4 | Set the default View name |
| 5 | Set the error View |
| 6 | Set the error View provider |
| 7 | Set the max navigation history size |
| 8 | Configure the navigator to navigate to default View (if availablr) when a view is not available according to current navigation state |
| 9 | Add a ViewChangeListener |
| 10 | Build the ViewNavigator and bind it to the UI |
The ViewNavigator builder makes available a default View provider, which supports a fixed set of Views configured using a View name bound to a View class.
Stateful views are supported by this provider. To declare a View as stateful, the StatefulView annotation can be used on the View class.
View instances will be created according to view scope: for stateful views, an instance is created at first request (for each UI) and the same instance is returned to subsequent view requests. On the contrary, for standard views, a new instance is created and returned to navigator for every view request.
To register this view provider and configure the View set, the ViewNavigator builder method withView(String viewName, Class<? extends View> viewClass) can be used.
UI ui = getUI();
ViewNavigator.builder() //
.viewDisplay(ui) //
.withView("view1", View1.class) (1)
.withView("view2", View2.class) (2)
.defaultViewName("view1") (3)
.buildAndBind(ui);
| 1 | Register the view class View1 and bind it the view1 name |
| 2 | Register the view class View2 and bind it the view2 name |
| 3 | Set the view1 View as the default view |
12.3. Excluding a View form navigation history
By default, the ViewNavigator tacks views navigation history, to made available navigation operations such navigateBack() to navigate to the previous view.
To exclude a View from the navigation history, skipping such View when a navigateBack() operation is performed or the user presses the browser back button, the VolatileView annotation can be used on the View class.
@VolatileView
class VolatileViewExample implements View {
@Override
public void enter(ViewChangeEvent event) {
}
}
12.4. Opening Views in a Window
The ViewNavigator supports displaying a View content in a application Window. The view navigation is tracked in navigator history just like any other navigation operation.
To open a View in a Window, the navigateInWindow(…) view navigator methods can be used (see the next section), or a View class can be forced to be always opened in a window using the WindowView annotation.
If the @WindowView annotation is found on a view class, such view will be always opened in a window, regardless of the navigation operation that is used.
The Window is automatically created by the ViewNavigator, and can be configured using either a ViewWindowConfigurator, when the navigator method is used, or the @WindowView annotation attributes.
@WindowView(windowWidth = "50%")
class WindowViewExample implements View {
@Override
public void enter(ViewChangeEvent event) {
}
}
12.5. View navigation operations
The ViewNavigator API provides navigation methods to provide a set of parameters to the View being activated.
These parameters are serialized in the navigation state String, and are automatically bound to the any View class field annotated with the @ViewParameter annotation.
See Parameters injection for further informations about View parameters injection.
When using @ViewParameter annotated View methods, the parameter value types provided to a view navigation method through the name-value Map must be consistent with the corresponding @ViewParameter view class field type.
|
The ViewNavigator API provides the following operations for View navigation:
-
navigateTo(String viewName, Map<String, Object> parameters): navigate to givenViewname, providing an optional Map of parameters with parameters names and corresponding values. -
navigateTo(String viewName): navigate to givenViewname without providing any view parameter.
ViewNavigator navigator = getViewNavigator();
navigator.navigateTo("myView"); (1)
Map<String, Object> parameters = new HashMap<>();
parameters.put("parameter1", "test");
parameters.put("parameter2", 34.5);
navigator.navigateTo("myView", parameters); (2)
| 1 | Navigate to the view named myView |
| 2 | Navigate to the view named myView providing a map of parameters (names and values)
|
ViewNavigator navigator = getViewNavigator();
navigator.navigateInWindow("myView"); (1)
Map<String, Object> parameters = new HashMap<>();
parameters.put("parameter1", "test");
parameters.put("parameter2", 34.5);
navigator.navigateInWindow("myView", windowConfig -> {
windowConfig.fullWidth();
windowConfig.styleName("my-window-style");
}, parameters); (2)
| 1 | Navigate to the view named myView and display the view in a Window |
| 2 | Navigate to the view named myView and display the view in a Window, providing a window configuration consumer and a map of parameters
|
ViewNavigator navigator = getViewNavigator();
navigator.toView("myView").withParameter("parameter1", "test").withParameter("parameter2", 34.5).navigate(); (1)
navigator.toView("myView").navigateInWindow(); (2)
navigator.toView("myView").navigateInWindow(windowConfig -> {
windowConfig.fullWidth();
windowConfig.styleName("my-window-style");
}); (3)
| 1 | Navigate to the view named myView using given parameters |
| 2 | Navigate to the view named myView and display the view in a Window |
| 3 | Navigate to the view named myView and display the view in a Window, providing a window configuration consumer
|
12.6. Sub views
The ViewNavigator supports the concept of sub view: a sub view is a View which is always displayed using a parent view container, and the sub view display strategy is delegated to the container.
A sub view container is a conventional View which implements the SubViewOf interface.
Any sub view must declare its parent container view name using the WindowView annotation.
When the navigation to a sub view is triggered, the ViewNavigator first of all will navigate to the declared sub view container, displaying it as a normal View, than the display(View view, String viewName, Map<String, String> parameters) method of the sub view container is invoked providing the sub view instance, the sub view name and any navigation parameter. At this point, the container should display the sub view contents in a suitable way.
The getViewContent(View view) static method of the ViewNavigator interface can be used to obtain the view contents from a View instance.
|
class SubViewContainerExample extends TabSheet implements SubViewContainer { (1)
public SubViewContainerExample() {
super();
setSizeFull();
}
@Override
public boolean display(View view, String viewName, Map<String, String> parameters)
throws ViewNavigationException {
addTab(ViewNavigator.getViewContent(view), viewName, FontAwesome.PUZZLE_PIECE);
return true;
}
@Override
public View getCurrentView() {
return (View) getSelectedTab();
}
@Override
public void enter(ViewChangeEvent event) {
}
}
@SubViewOf("mycontainer")
public class SubViewExample extends VerticalLayout implements View { (2)
public SubViewExample() {
super();
setMargin(true);
addComponent(new Label("The sub view 1"));
}
@Override
public void enter(ViewChangeEvent event) {
}
}
| 1 | A view declared as SubViewContainer using a TabSheet to display each sub view in a new tab. We suppose this view is bound to the mycontainer view name |
| 2 | A sub view bound to the mycontainer sub view container using the @SubViewOf annotation |
12.7. Context resources injection
The ViewNavigator supports Context resources injection into the View instances. A resource available form the Holon platform Context registry can be injected in a View using the ViewContext annotation on a View field.
The resource to be injected is looked up by key, and by the default the resource key is assumed to be the fully qualified class name of the injectable field type. To override this strategy, the value() annotation attribute of the ViewContext annotation can be used to provide the resource key to look up.
| See the Context documentation for further information about context resources. |
class ViewContextExample implements View {
@ViewContext
private LocalizationContext localizationContext;
@ViewContext
private AuthContext authContext;
@Override
public void enter(ViewChangeEvent event) {
}
}
12.8. Obtain the ViewNavigator
The ViewNavigator interface provides methods to obtain the current navigator using the following strategy:
-
If the
ViewNavigatoris available as Context resource using the default navigator resource key, that instance is returned; -
If a current Vaadin
UIis available and aViewNavigatoris bound to theUI, that instance is returned.
| See the Context documentation for further information about context resources. |
Optional<ViewNavigator> navigator = ViewNavigator.getCurrent(); (1)
ViewNavigator viewNavigator = ViewNavigator.require(); (2)
| 1 | Try to obtain the ViewNavigator from context or current UI |
| 2 | Obtain the ViewNavigator from context or current UI, failing with an exception if not found |
12.9. Authentication support
The ViewNavigator architecture provides support for View authentication, relying on the default Holon platform AuthContext API.
| See the Realm and AuthContext documentation for information about the Holon platform authentication and authorization architecture. |
In order for the authentication to work, an AuthContext instance must be available as a context resource, and it will be used to perform user authentication and resource access control, relying on the Realm bound to the auth context.
The authentication support is enabled by default, but it can be configured using the authenticationEnabled(…) ViewNavigator builder method.
The authentication support is enabled through the standard com.holonplatform.auth.annotations.Authenticate annotation, which can be used on a View class or on the application UI class.
When the Authenticate annotation is used at UI level, all the views managed by the navigator bound to such UI will be under authentication, and the access to any View will be denied if an authenticated subject is not available from the current AuthContext.
Each time the navigation to a protected View is requested, the AuthContext is checked, and if it not authenticated, the following strategy is implemented:
-
An implicit authentication attempt is performed, using the current
VaadinRequestand the optional authentication schemes which can be specified using theschemes()attribute of theAuthenticateannotation. This behaviour can be used, for example, to support authentication using the current HTTP request and schemes such theAuthorizationHTTP header. -
If the implicit authentication is not successfull and a valid redirect URI is provided through the
redirectURI()property of theAuthenticateannotation, the navigation is redirected to that URI. If the redirect URI does not specify a scheme, or the scheme is equal to the specialview://scheme, the navigation is redirected to the navigation state specified by the redirect URI (escluding theview://part, if present). This way, the redirect URI can be used to delegate to aViewan explicit authentication entry point (for example using conventional username and password credentials).
13. Spring integration
The holon-vaadin-spring artifact provides support and integration with the Spring framework.
Maven coordinates:
<groupId>com.holon-platform.vaadin7</groupId>
<artifactId>holon-vaadin-spring</artifactId>
<version>5.2.4</version>
This artifact provides a ViewNavigator extension with Spring support, represented by the SpringViewNavigator interface.
The SpringViewNavigator implementation relies upon the standard Vaadin Spring integration add-on, and supports all its functionalities and configuration features.
See the Vaadin Spring tutorial for the documentation.
The following annotations are available for View configuration:
-
DefaultView: can be used on a
Viewclass to declare it as the default view, i.e the view which will be used as target of theViewNavigator.navigateToDefault()method and as a fallback by theViewNavigator.navigateBack()method if no otherViewis available in navigation history. -
ErrorView: can be used on a
Viewclass to declare it as the default error view, i.e. theViewto be displayed when no otherViewmatches a navigation state.
13.1. Spring view navigator configuration
The SpringViewNavigator API provides a builder to create a navigator instance, and can be used to explicitly build and configure a SpringViewNavigator instance. The bulder can be obtained through the static builder() method of the SpringViewNavigator interface.
The easiest way to setup a Spring view navigator, is to use the provided EnableViewNavigator configuration annotation.
The @EnableViewNavigator can be used on Spring configuration classes to automatically setup the default Vaadin Spring integration and registering a UI-scoped SpringViewNavigator bean. The standard @SpringViewDisplay annotation can be used to configure the views display component and the default Vaadin Spring ViewProvider will be used.
The @EnableViewNavigator annotation includes the standard com.vaadin.spring.annotation.@EnableVaadin annotation behaviour, which is not required anymore on configuration classes.
|
The @EnableViewNavigator annotation makes available a number of properties to control the navigator configuration, for example to explicitly configure the default and error views or to set the max navigation history size. See the EnableViewNavigator javadocs for further information.
@Configuration (1)
@ComponentScan(basePackageClasses = ViewOne.class) (2)
@EnableViewNavigator (3)
@EnableBeanContext (4)
class SpringConfig {
}
@SpringView(name = "view1") (5)
@DefaultView (6)
class ViewOne extends VerticalLayout implements View {
@Override
public void enter(ViewChangeEvent event) {
}
}
@SpringView(name = "view2") (7)
@UIScope (8)
class ViewTwo extends VerticalLayout implements View {
@Override
public void enter(ViewChangeEvent event) {
}
}
@SpringUI (9)
@SpringViewDisplay (10)
class AppUI extends UI {
@Autowired
ViewNavigator navigator; (11)
@Override
protected void init(VaadinRequest request) {
// ...
}
}
| 1 | Declare the class as a Spring configuration class |
| 2 | Set the component scan rule to auto detect the View beans |
| 3 | Enable the Spring ViewNavigator |
| 4 | Enable the Holon platform Spring context scope, to provide context resource instances as Spring beans |
| 5 | Declare the view as Spring view (which will be automatically registered in the navigator view provider), and bind it to the view1 name |
| 6 | Declare the view as the default view |
| 7 | Create another view and enable it as a Spring view using the view2 name |
| 8 | Declare the view bean scope as UI |
| 9 | Create the application UI and declare it as a Spring UI, which will be automatically detected and configured by Spring |
| 10 | Use the UI as View display container |
| 11 | The ViewNavigator will be made avaialable as Spring (UI-scoped) bean, so it can be obtained using dependency injection |
13.2. View context resources
The EnableViewContext annotation can be used on Spring configuration classes to enable View context resource injection using the ViewContext annotation.
See Context resources injection for further information.
13.3. View authorization support
In addition to the ViewNavigator authentication support (see Authentication support), the Spring view navigator provides View authorization support using default javax.annotation.security.* annotations (@RolesAllowed, @PermitAll, @DenyAll).
The authorization support can be enabled using the EnableViewAuthorization annotation and, just like the authentication support, relies on the current AuthContext to perform authorization control, so it must be available as a context resource.
By using the @EnableBeanContext configuration annotation, Spring beans can be automatically configured as context resources. See the SpringContextScope documentation for further information.
|
The default Vaadin Spring ViewAccessControl and ViewInstanceAccessControl view access control methods are fully supported too, and can be used along with the security annotations.
|
The AccessDeniedView annotation can be used on a Spring View class to declare it as the view to show when the user is not authorized to access a view, either according to a javax.annotation.security.* annotation or to a ViewAccessControl or ViewInstanceAccessControl rule.
@Configuration
@ComponentScan(basePackageClasses = ViewOne.class)
@EnableViewNavigator
@EnableBeanContext (1)
@EnableViewAuthorization (2)
class SpringConfig {
@Bean (3)
@VaadinSessionScope
public AuthContext authContext() {
AccountProvider ap = id -> {
// Only a user with username 'username1' is available
if ("username1".equals(id)) {
// setup the user password and assign the role 'role1'
return Optional.of(Account.builder(id).credentials(Credentials.builder().secret("s3cr3t").build())
.withPermission("role1").build());
}
return Optional.empty();
};
return AuthContext.create(Realm.builder()
// authenticator using the AccountProvider
.withAuthenticator(Account.authenticator(ap))
// default authorizer
.withDefaultAuthorizer().build());
}
}
@SpringView(name = "view1")
@PermitAll (4)
class ViewOne extends VerticalLayout implements View {
@Override
public void enter(ViewChangeEvent event) {
}
}
@SpringView(name = "view2")
@RolesAllowed("role1") (5)
class ViewTwo extends VerticalLayout implements View {
@Override
public void enter(ViewChangeEvent event) {
}
}
@AccessDeniedView (6)
@UIScope
@SpringView(name = "forbidden")
class AccessDenied extends VerticalLayout implements View {
private static final long serialVersionUID = 1L;
private final Label message;
public AccessDenied() {
super();
Components.configure(this).margin()
.add(message = Components.label().styleName(ValoTheme.LABEL_FAILURE).build());
}
@Override
public void enter(ViewChangeEvent event) {
message.setValue("Access denied [" + event.getViewName() + "]");
}
}
| 1 | Use @EnableBeanContext to enable Spring beans as context resources (in this example, the AuthContext bean will be available as context resource) |
| 2 | Enable views authorization using javax.annotation.security.* annotations |
| 3 | Configure the AuthContext and declare it as a session-scoped Spring bean |
| 4 | Use @PermitAll on this view to skip authorization control |
| 5 | Use @RolesAllowed to declare that the view is available only for the authenticated subjects with the role1 role |
| 6 | Create a custom access denied view using the @AccessDeniedView annotation |
14. Spring Boot integration
The holon-vaadin-spring-boot artifact provides integration with Spring Boot for Vaadin application and view navigator auto configuration.
To enable Spring Boot auto-configuration the following artifact must be included in your project dependencies:
Maven coordinates:
<groupId>com.holon-platform.vaadin7</groupId>
<artifactId>holon-vaadin-spring-boot</artifactId>
<version>5.2.4</version>
The Spring Boot auto-configuration includes the default Spring Boot Vaadin add-on auto configuration features, with the following additional behaviour:
-
The configured view navigator will be a Spring
ViewNavigator -
The
Viewauthorization support using thejavax.annotation.security.*annotations is enabled by default
To disable this auto-configuration feature the HolonVaadinAutoConfiguration class can be excluded:
@EnableAutoConfiguration(exclude={HolonVaadinAutoConfiguration.class})
14.1. Spring Boot starters
The following starter artifacts are available to provide a quick project configuration setup using Maven dependency system:
1. The Holon Vaadin application starter provides the dependencies to the Holon Vaadin Spring Boot integration artifact, in addition to default Holon core Spring Boot starters, the default spring-boot-starter-web starter and the spring-boot-starter-tomcat to use Tomcat as the embedded servlet container:
Maven coordinates:
<groupId>com.holon-platform.vaadin7</groupId>
<artifactId>holon-starter-vaadin</artifactId>
<version>5.2.4</version>
2. The Holon Vaadin application starter using Undertow, to use Undertow instead of Tomcat as embedded servlet container:
Maven coordinates:
<groupId>com.holon-platform.vaadin7</groupId>
<artifactId>holon-starter-vaadin-undertow</artifactId>
<version>5.2.4</version>
15. 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.