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 module represents the platform support for the Vaadin web applications framework, focusing on the user interface components and data binding features.
This module provides integration with the platform foundation architecture, such as;
-
The Property model and the Datastore API.
-
The Holon Platform authentication and authentication architecture.
-
The Holon Platform internationalization support.
This module provides a view navigation system which allows to create web applications focusing on the application functions, which can be represented by virtual pages, relying on a robust navigation architecture, with parameters and view lifecycle hooks support.
Furthermore, a complete set of fluent builders is available, to build web application UI components quickly and easily.
A complete integration with the Spring framework and Spring Boot is also available.
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.
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-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.vaadin</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. What’s new in version 5.1.x
-
Support for the the
PropertySet
identifier properties to make more easy and quick to setup data bound UI objects. See Items type and identifiers. -
Full support of Vaadin version 8.3, including for example the Navigator
@PushStateNavigation
strategy support. -
Better support for bean based data bound UI objects, with new builders and a Java API to create and configure the UI components just like the
Property
based one. See BeanListing. -
Support for components drag and drop configuration at Java API level. See Drag and Drop support.
-
New component builder APIs for Vaadin
TabSheet
andAccordion
components. See Component builders.
4.1. Migrating from version 5.0.x
4.1.1. Deprecations
-
PropertyListing builder API: dataSource(ItemDataProvider dataProvider, Property… identifierProperties). Since the
PropertySet
identifier properties support is used by default by thePropertyListing
builder to obtain the item identifier properties, this method is no longer useful. To customize the item identifiers strategy, the more generaldataSource(ItemDataProvider dataProvider, ItemIdentifierProvider itemIdentifierProvider)
builder method can be used.
4.1.2. Item identifiers in Property
based Item listings
The Property
based Item listing components now supports the PropertySet identifier properties configuration and use them as the default strategy to identify a PropertyBox
type item.
So, when the PropertySet
used with an item listing component declares one ore more identifier properties, there is no longer the need to declare the item identifier properties, for example, when configuring a Datastore
based item data source. The identifier properties of the PropertySet
will be used by default to provide the item identifier values.
For example, given the following Property model definition:
private final static PathProperty<Long> ID = PathProperty.create("id", Long.class);
private final static PathProperty<String> DESCRIPTION = PathProperty.create("description", String.class);
private final static PropertySet<?> PROPERTIES = PropertySet.of(ID, DESCRIPTION);
A PropertyListing
component, prior to version 5.1.0, had to be built this way, for example using a Datastore
as item data source:
Datastore datastore = getDatastore();
PropertyListing listing = Components.listing.properties(PROPERTIES) (1)
.dataSource(datastore, DataTarget.named("test"), ID) (2)
.build();
1 | Provide the item listing PropertySet |
2 | Configure a Datastore as item data source, providing the ID property as item identifier property |
From version 5.1.x, the Property model definition can include the PropertySet
identifier properties like this:
private final static PathProperty<Long> ID = PathProperty.create("id", Long.class);
private final static PathProperty<String> DESCRIPTION = PathProperty.create("description", String.class);
private final static PropertySet<?> PROPERTIES = PropertySet.builderOf(ID, DESCRIPTION).identifier(ID).build(); (1)
1 | Set the ID property as property set identifier property |
And so the item identifier properties to use are no longer required during the PropertyListing
component data source configuration, since the PropertySet
identifier properties are used by default:
Datastore datastore = getDatastore();
PropertyListing listing = Components.listing.properties(PROPERTIES) (1)
.dataSource(datastore, DataTarget.named("test")) (2)
.build();
1 | The item listing PropertySet provides the property set identifier properties |
2 | There is no longer the need to declare the ID property as item identifier property at data source configuration time, since the configured PropertySet identifier properties will be used by default |
5. Vaadin UI integration
The holon-vaadin
artifact is the main entry point to use the Holon platform Vaadin integration.
Maven coordinates:
<groupId>com.holon-platform.vaadin</groupId>
<artifactId>holon-vaadin</artifactId>
<version>5.2.4</version>
6. Component builders
A complete set of fluent builders is available to create and configure the most common Vaadin components and the additional components provided by the Holon platform Vaadin module.
See UI Components for a list of the additional components made available by the Holon platform Vaadin module. |
All the builders can be obtained by using the Components API, which is organized in sub-APIs by components category.
The available component categories are the following:
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.
|
7. Component builders common features
The Holon Platform Vaadin component builders make available a set of common features, partly related to standard Vaadin features configuration and partly dedicated to the integration with some core Holon Platform APIs.
For each UI component which extends a standard Vaadin Component
hierarchy, the builder APIs provide methods to configure all the component attributes and register the supported listeners. In addition to the common component configuration attributes, each component builder makes available methods to configure the component specific attributes and supported behaviors.
Label lbl = Components.label() (1)
.fullWidth() (2)
.height(50, Unit.PIXELS) (3)
.styleName("my-style").styleName(ValoTheme.LABEL_BOLD) (4)
.icon(VaadinIcons.CHECK) (5)
.caption("The caption") (6)
.captionAsHtml() (7)
.description("The description") (8)
.withData(new MyData()) (9)
.hidden() (10)
.disabled() (11)
.responsive() (12)
.withAttachListener(event -> { (13)
// ...
}).withDetachListener(event -> { (14)
// ...
}).withContextClickListener(event -> { (15)
event.isDoubleClick();
// ...
}).errorHandler(event -> { (16)
})
// Label specific configuration
.content("Label content") (17)
.html() (18)
.build();
1 | Obtain a com.vaadin.ui.Label component builder |
2 | Set the component width to 100% |
3 | Set the component height to 50px |
4 | Add two css style class names |
5 | Set the component icon |
6 | Set the caption |
7 | Set the caption to be rendered as HTML |
8 | Set the description (tooltip) |
9 | Set a generic data value bound to the component |
10 | Set the component as hidden (not visible) |
11 | Set the component as disabled |
12 | Set the component as responsive |
13 | Register a AttachListener |
14 | Register a DetachListener |
15 | Register a ContextClickListener |
16 | Set the component ErrorHandler |
17 | Set the Label content |
18 | Set the Label content mode as HTML |
For component container component types (for example, Layout components), the builder APIs provides a set of methods to add components to the container to build, also providing layout configuration attributes such as alignment and expand ratio, when supported.
VerticalLayout verticalLayout = Components.vl() (1)
.margin() (2)
.spacing() (3)
.add(COMPONENT_1).add(COMPONENT_2, COMPONENT_3) (4)
.addAndAlign(COMPONENT_4, Alignment.TOP_CENTER) (5)
.addAndExpand(COMPONENT_5, 0.5f) (6)
.addAndExpandFull(COMPONENT_6) (7)
.addAlignAndExpand(COMPONENT_7, Alignment.MIDDLE_CENTER, 1f) (8)
.deferLocalization().build();
1 | Obtain a com.vaadin.ui.VerticalLayout component builder |
2 | Enable layout margins |
3 | Enable layout spacing |
4 | Add components to the layout in given order |
5 | Add a component and set the component alignment |
6 | Add a component and set the component expand ratio |
7 | Add a component and set the component expand ratio to 1 (full) |
8 | Add a component and set the component alignment and expand ratio |
7.1. Component configurators
When a component instance is already available, the Components API makes available a set of component configurator APIs, which can be used to configure a component instance, using the same API as the component builders.
The configure(AbstractComponent component)
method can be used to obtain a component configurator for a generic com.vaadin.ui.AbstractComponent
, to setup common component configuration attributes.
Other component confgurator APIs are provided to configure specific components, such a Label
and Button
, and the standard Vaadin layout components.
Label label = new Label();
Components.configure(label).fullWidth().content("my content").contentMode(ContentMode.PREFORMATTED); (1)
VerticalLayout verticalLayout = new VerticalLayout();
Components.configure(verticalLayout).spacing().addAndAlign(COMPONENT_1, Alignment.TOP_CENTER) (2)
.addAndExpandFull(COMPONENT_2);
1 | Label component configurator |
2 | VerticalLayout component configurator |
7.2. Messages localization support
The component builder methods that can be used to configure a text to display for a component supports the message localization using the standard Holon Platform internationalization APIs.
The message localization attributes can be provided either using:
-
A
Localizable
object instance; -
Or directly providing the message localization attributes, such as the message code, the default message and the optional message localization arguments.
A set of localizable message builder methods are available, for example, for standard Vaadin component attributes, such as the component caption and description.
Button btn = Components.button().caption("DefaultCaption", "button.caption.code") (1)
.build();
Label lbl = Components.label()
.content(Localizable.builder().message("DefaultMessage").messageCode("my.message.code").build()) (2)
.build();
1 | Set the Button caption using a default caption message (DefaultCaption ) and a message localization code (button.caption.code ) |
2 | Set a localizable Label content using a Localizable |
In order for the message localization to work, a valid LocalizationContext instance must be available as a context resource using the default LocalizationContext resource key (i.e. the LocalizationContext class name). See the LocalizationContext documentation for details about the LocalizationContext API and the Context resources documentation for information about the Holon Platform Context architecture.
|
This module makes available a Vaadin session scope to use the Vaadin session as context resources scope. |
7.2.1. Defer localization
By default, the component messages localization is performed immediately during the component building, when a localizable message is provided at component configuration time.
Since the Holon Platform localization APIs require that a LocalizationContext
resource is available to perform messages localization, when this is not possible the messages localization will not work at component configuration time. The LocalizationContext
resource must be also localized, i.e. the current Locale
is configured and available, in order to ensure messages localization support.
When the LocalizationContext
resource configuration or localization availability is expected after the component build time, the component builder APIs provide a method to defer the component messages localization: deferLocalization()
.
This method instructs the builder to resolve any message localization (for example the component caption and description) only when the component is attached to a parent layout, and this means only when the parent layout too is attached to the application UI.
Label lbl = Components.label() //
.caption("Default caption", "caption.message.code") //
.deferLocalization() (1)
.build();
1 | Set to defer the component messages localization at UI display time |
7.3. Drag and Drop support
The Vaadin module component builders supports component drag and drop configuration, allowing to declare a component as draggable and configure the drag mode and attributes and to declare a component as a drop target providing the target configuration and logic to react to a drop event.
The standard Vaadin DragSourceExtension
and DropTargetExtension
extensions are used to configure components drag and drop capabilities.
The dragSource
builder method can be used to declare a component as draggable. The provided Consumer
can be used to configure the DragSourceExtension
for the component.
The dropTarget
builder method can be used to declare a component as a drop target and the provided BiConsumer
can be used to configure the DropTargetExtension
for the component and to obtain the actual component instance when a drop event occurs.
Label lbl = Components.label().content("Draggable").dragSource(dragSource -> { (1)
dragSource.setEffectAllowed(EffectAllowed.MOVE); (2)
dragSource.setDataTransferText("hello receiver");
dragSource.addDragStartListener(e -> Notification.show("Drag event started"));
}).build();
VerticalLayout droppableLayout = Components.vl().fullWidth().caption("Drop things inside me")
.dropTarget((dropTarget, component) -> { (3)
dropTarget.setDropEffect(DropEffect.MOVE); (4)
dropTarget.addDropListener(dropEvent -> { (5)
dropEvent.getDragSourceComponent().ifPresent(dragged -> component.addComponent(dragged));
Notification.show("DropEvent with data transfer text: " + dropEvent.getDataTransferText());
});
}).build();
1 | Configure the Label as draggable |
2 | The DragSourceExtension can be configured using the provided Consumer function |
3 | Configure the VerticalLayout as a drop target |
4 | The DropTargetExtension can be configured using the provided BiConsumer function |
5 | The second argument of the BiConsumer function is the actual VerticalLayout instance, used in this case to add the dropped component to the layout when a drop event occurs |
8. UI Components
The Vaadin module provides a set of additional UI components which can be used besides the standard Vaadin components.
8.1. Input
The Input interface represents a component that provides a user-editable value, which can be read and setted using the methods provided by the The ValueHolder API.
The Input
API is parametrized on the value type it manages and the value management is provided by the ValueHolder API, which provides methods to:
-
Set the Input value.
-
Read the input value.
-
Provide and empty value representation.
-
Check whether a value is available.
-
Listen to value changes registering one or more ValueChangeListener.
Furthermore, the Input
API supports two component state configuration options:
-
Read-only mode: When an
Input
is in read-only mode, the user can’t change the Input value. -
Required mode: When an
Input
is in required mode, a required indicator symbol is visible.
The Input
API is a general and abstract representation of an UI object which holds a value, the actual Vaadin Component
representation can be obtained though the getComponent()
method and used with the standard Vaadin APIs.
Input<String> stringInput = Components.input.string().build(); (1)
Component component = stringInput.getComponent(); (2)
VerticalLayout vl = Components.vl().add(component).build(); (3)
1 | Create a String type input |
2 | Get the actual Input Component |
3 | Add the Input Component to a vertical layout |
8.1.1. Create an Input
component
An Input
component can be created using the Components API, through the input sub-interface methods.
A typed Input
builder method is available for each available Input
value type. Besides the base Input
types, as set of methods are available to create single and multi select Input components. See Selectable Input for details.
Input<String> stringInput = Components.input.string().build(); (1)
stringInput = Components.input.string(true).build(); (2)
Input<Integer> integerInput = Components.input.number(Integer.class).build(); (3)
Input<Boolean> booleanInput = Components.input.boolean_().build(); (4)
Input<Date> dateInput = Components.input.date(Resolution.DAY).build(); (5)
Input<LocalDate> localDateInput = Components.input.localDate().build(); (6)
Input<LocalDateTime> localDateTimeInput = Components.input.localDateTime().build(); (7)
SingleSelect<MyEnum> enumSingleSelect = Components.input.enumSingle(MyEnum.class).build(); (8)
MultiSelect<MyEnum> enumMultiSelect = Components.input.enumMulti(MyEnum.class).build(); (9)
1 | String type Input |
2 | String type Input represented as a text area |
3 | Numeric (Integer in this case) type Input |
4 | Boolean type Input |
5 | Date type Input with resolution configuration |
6 | LocalDate type Input |
7 | LocalDateTime type Input |
8 | Enum type Input represented with a single select |
9 | Enum type Input represented with a multi select |
Input component configuration:
The Input
builder API provides methods both for the Input
attributes al listeners configuration and for the associated Input Component
configuration, including API methods for specific Input
and Component
types.
Input<String> stringInput = Components.input.string() //
// Component configuration
.fullWidth().styleName("my-style").caption("The caption") (1)
// Input configuration
.readOnly() (2)
.tabIndex(10) (3)
.withValue("Initial value") (4)
.withValueChangeListener(event -> { (5)
event.getValue();
event.getOldValue();
// ...
}).withFocusListener(event -> { (6)
// focused
})
// Specific String Input configuration
.inputPrompt("The prompt") (7)
.maxLength(100) (8)
.blankValuesAsNull(true) (9)
.textChangeEventMode(ValueChangeMode.BLUR) (10)
.build();
1 | Input Component configur |
2 | Set the Input as read-only |
3 | Set the tabulator index |
4 | Set the Input initial value |
5 | Register a ValueChangeListener |
6 | Register a FocusListener |
7 | Set the Input prompt |
8 | Set the String Input max length (the maximum number of cahracters) |
9 | Set to treat blank String values (with 0 length or whitespaces only) as null values. |
10 | Set the mode how the Input triggers vaue change events |
See the next sections for details about the Input
configuration attributes and features.
8.1.2. Obtain an Input component from a HasValue
Component
An Input
component can be obtained from a standard Vaadin HasValue
Component using the from
static method of the Input
API.
TextField field = new TextField();
Input<String> stringInput = Input.from(field); (1)
1 | Create a String type Input using a Vaadin TextField as concrete implementation |
A com.vaadin.data.Converter
can also be provided to obtain an Input
component with a different value type from the original field type, providing the presentation to model type value conversion logic and vice-versa.
TextField field = new TextField();
Input<Integer> integerInput = Input.from(field, new StringToIntegerConverter("conversion error")); (1)
1 | Create a Integer type Input using a Vaadin TextField as concrete implementation and providing a Converter to convert String type presentation values into Integer type model values and vice-versa |
8.1.3. Obtain a HasValue
Component from Input builders
The Input
builder APIs can provide the Input
instance also a standard Vaadin HasValue
Component, which is represented by the convenience Field interface.
To obtain a Field
type Input component, the Input
builder API asField()
method can be used.
Field<LocalDate> field = Components.input.localDate() (1)
// configuration omitted
.asField(); (2)
HasValue<LocalDate> hasValue = field; (3)
Component component = field;
1 | Obtain a LocalDate type Input builder |
2 | Get the Input component as a LocalDate type Field |
3 | The obtained Field is a HasValue<LocalDate> and a Component |
8.1.4. Input value conversion
The Input
interface makes available a set of methods to create an Input
from another Input
or from a standard Vaadin HasValue
Component with a different value type, providing a suitable converter to perform value conversions from the presentation value type to the model value type and vice-versa.
The stardard Vaadin com.vaadin.data.Conveter
API is supported to provide the value conversion logic.
Input<String> stringInput = Components.input.string().build();
Input<Integer> integerInput = Input.from(stringInput, new StringToIntegerConverter("Conversion error")); (1)
Input<Boolean> booleanInput = Input.from(integerInput, (2)
Converter.from(value -> Result.ok((value == null) ? Boolean.FALSE : (value.intValue() > 0)),
value -> (value == null) ? null : (value ? 1 : 0)));
Input<Long> longInput = Input.from(new TextField(), new StringToLongConverter("Conversion error")); (3)
1 | Convert a String presentation type Input into a Integer model type Input using the standard Vaadin StringToIntegerConverter |
2 | Obtain a Boolean type Input from the Integer type one providing the value conversion logic |
3 | Obtain a Long type Input from a String type TextField using the StringToLongConverter converter |
Besides the standard Vaadin com.vaadin.data.Conveter
, the Holon PropertyValueConverter
can be used, providing the Property
to which the converter is bound.
See the Property value conversion documentation for information about the PropertyValueConverter API.
|
Input<Integer> integerInput = Components.input.number(Integer.class).build();
final Property<Boolean> BOOL_PROPERTY = PathProperty.create("bool", Boolean.class); (1)
Input<Boolean> booleanInput = Input.from(integerInput, BOOL_PROPERTY,
PropertyValueConverter.numericBoolean(Integer.class)); (2)
1 | The Boolean type Property to use for the PropertyValueConverter |
2 | Create a Boolean type Input from an Integer type Input , using a the default numericBoolean PropertyValueConverter |
8.1.5. Listening to Input
value changes
The Input
API supports ValueChangeListener registration to listen to Input
value changes.
The value change event provide the current (changed) Input
value, the previous (old) Input
value, the value change source (i.e. the reference to the Input
component which triggered the value change listeners) and whether the value change was originated by a user action or from server side code.
Input<String> stringInput = Components.input.string() //
.withValueChangeListener(event -> { (1)
String currentValue = event.getValue(); (2)
String previousValue = event.getOldValue(); (3)
boolean byUser = event.isUserOriginated(); (4)
ValueHolder<String> source = event.getSource(); (5)
// ...
}).build();
1 | Register a ValueChangeListener for the String type Input |
2 | Get the new value that triggered the value change event |
3 | Get the value of the Input before the value change event occurred |
4 | Get whether the event was triggered by user interaction, on the client side, or programmatically, on the server side |
5 | Get the source of the value change event, i.e. the Input component itself |
Value change mode
The mode and the frequency with which the value change events are triggered can be customized for the Input
components which supports it.
The MaySupportValueChangeMode API, extended by the Input
API, allows to check if the value change mode configuration is supported by a specific Input
component through the isValueChangeModeSupported()
method.
If so, the value change mode can be configured specifying:
-
The
com.vaadin.shared.ui.ValueChangeMode
enumeration value which represents when and how oftenInput
value changes are transmitted from the client to the server. -
Set the value change timeout, i.e. how often value change events are triggered, when the
ValueChangeMode
is eitherLAZY
orTIMEOUT
.
Input<String> stringInput = Components.input.string().withValueChangeListener(event -> {
// ...
}).valueChangeMode(ValueChangeMode.LAZY) (1)
.valueChangeTimeout(1000) (2)
.build();
boolean supported = stringInput.isValueChangeModeSupported(); (3)
if (supported) {
stringInput.setValueChangeMode(ValueChangeMode.BLUR); (4)
}
1 | Configure the ValueChangeMode using the Input builder. The builder methods to configure the value change mode are available only for Input components which support it. |
2 | Configure the value change timeout using the Input builder |
3 | Check if the value change mode configuration is supported by the Input component |
4 | Set the value change mode to BLUR using the Input API |
8.2. ValidatableInput
The ValidatableInput interface represents an Input
component which supports value validation using the standard Holon Platform Validator API.
A ValidatableInput
can be obtained in the following ways:
1. From an existing Input
or HasValue
component: Using the from
static method of the ValidatableInput
API.
Input<String> stringInput = Components.input.string().build();
ValidatableInput<String> validatableInput = ValidatableInput.from(stringInput); (1)
validatableInput = ValidatableInput.from(new TextField()); (2)
1 | Create a ValidatableInput from a String Input |
2 | Create a ValidatableInput from a TextField |
2. Using the Input
builder: Using the validatable()
builder method, which returns a ValidatableInputBuilder
API that can be used to configure the ValidatableInput
, for example adding one or more Validator
, before returning it.
validatableInput = Components.input.string() //
.caption("The caption") //
.validatable() (1)
.required("Value is required") (2)
.withValidator(Validator.max(100)) (3)
.build();
1 | Get the builder API to obtain a ValidatableInput |
2 | Add a default required (the input is not empty) validator using the provided error message |
3 | Add a `Validator |
8.2.1. Registering Validators and perform validation
The addValidator
method of the ValidatableInput
API can be used to register value validators for the Input
component.
The Validator
API can be used to obtain a set of builtin validators, allowing to specify the validation error message, even using a localizable message code. See the Holon Platform Validator API for details.
ValidatableInput<String> validatableInput = Components.input.string().validatable().build(); (1)
validatableInput.addValidator(Validator.max(100)); (2)
validatableInput.addValidator(Validator.email("Must be a valid e-mail address", "invalid.email.message.code")); (3)
validatableInput
.addValidator(Validator.create(value -> value.length() >= 3, "Must be at least 3 characters long")); (4)
1 | Create a String type ValidatableInput |
2 | Add a builtin Validator to check the Input value is maximum 100 characters long, using the default validation error message |
3 | Add a builtin Validator to check the Input value is a valid e-mail address, providing a localizable validation error message |
4 | Add a Validator to check the Input value is at least 3 characters long and provide the validation error message |
The actual value validation can be performed using the Validatable API, which provides the following methods:
-
validate()
: Validate the currentInput
value using the registeredValidator
s. Throws aValidationException
if the value is not valid, which provides the validation error message/s. TheValidationException
isLocalizable
, providing optional message code and arguments for validation message localization. -
isValid()
: Returns whether the currentInput
value is valid, swallowing anyValidationException
.
try {
validatableInput.validate(); (1)
} catch (ValidationException e) {
// do something at validation failure
}
boolean valid = validatableInput.isValid(); (2)
1 | Validate the Input value. The ValidationException is thrown if the validation fails. |
2 | Checks whether the current Input value is valid, swallowing any ValidationException . |
8.2.2. Automatic validation at Input
value change
Value validation can be automatically triggered each time the Input
value changes.
To enable this behaviour, either the setValidateOnValueChange
method of the ValidatableInput
API or the validateOnValueChange
method of the ValidatableInput
builder API can be used.
ValidatableInput<String> validatableInput = Components.input.string().validatable() //
.validateOnValueChange(true) (1)
.build();
validatableInput.setValidateOnValueChange(false); (2)
1 | Use the ValidatableInput builder API to enable the automatic value validation at Input value change |
2 | Use the ValidatableInput API to disable the the automatic value validation at Input value change |
8.2.3. Validation errors notification
By default, the standard Vaadin component error notification is used to notify validation errors on ValidatableInput
components. As per default behaviour, an invalid component is decorated with a proper CSS style class (for example to show a red border around the component itself) and the validation error is notified using a tooltip.
To change this behavior and to control how the validation errors are notified to the user, the ValidationStatusHandler API can be used.
A ValidationStatusHandler
listens to validation status change events and the provided validation event can be used to:
-
Obtain the current component validation status:
UNRESOLVED
(no value validation has been made yet),VALID
(validation passed),INVALID
(validation failed). -
Obtain the current validation error messages if the component is in the
INVALID
status. The error messages can be obtained asLocalizable
to support message localization. -
Get the source component against whom the validation was performed.
The ValidationStatusHandler
can be configured either using the setValidationStatusHandler
method of the ValidatableInput
API or the validationStatusHandler
method of the ValidatableInput
builder API.
ValidatableInput<String> validatableInput = Components.input.string().validatable() //
.validationStatusHandler(event -> { (1)
Status status = event.getStatus();
// ....
}).build();
validatableInput.setValidationStatusHandler(event -> { (2)
event.getError();
// ...
});
1 | Use the ValidatableInput builder API to configure a ValidationStatusHandler |
2 | Use the ValidatableInput API to configure a ValidationStatusHandler |
The ValidationStatusHandler
also provides static methods to obtain a default validation status handler either using a Label
component or a Vaadin Notification
component.
Label statusLabel = new Label();
ValidationStatusHandler vsh = ValidationStatusHandler.label(statusLabel); (1)
ValidationStatusHandler usingNotifications = ValidationStatusHandler.notification(); (2)
1 | Create a ValidationStatusHandler using a Label to display validation errors. |
2 | Create a ValidationStatusHandler using a Notification to notify validation errors. |
8.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 API, which provides methods to check if some item is selected, obtain the selected item/s and change the current selection.
A selectable Input
can be obtained using the Components API, through the singleSelect
and multiSelect
methods and their specializations.
Furthermore, a SelectionListener can be registered to listen to selection changes.
SingleSelect<TestData> singleSelect = Components.input.singleSelect(TestData.class) (1)
.caption("Single select").build();
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) (1)
.caption("Multi select").build();
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 |
8.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) (1)
.build();
MultiSelect<TestData> multiSelect = Components.input.multiSelect(TestData.class, RenderingMode.SELECT) (2)
.build();
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 |
8.3.2. Using selectable Inputs for Enumerations
The Components API provides convenience methods to build a selectable Input
using the values of an enumeration as selection items.
The enumeration selectable Input
can be obtained either as a single select or a multiple select, even specifying the RenderingMode
to use.
SingleSelect<MyEnum> singleSelect = Components.input.enumSingle(MyEnum.class).build(); (1)
singleSelect = Components.input.enumSingle(MyEnum.class, RenderingMode.NATIVE_SELECT).build(); (2)
MultiSelect<MyEnum> multiSelect = Components.input.enumMulti(MyEnum.class).build(); (3)
1 | Create a SingleSelect Input for the MyEnum enumeration |
2 | Create a SingleSelect Input for the MyEnum enumeration using NATIVE_SELECT as rendering mode |
3 | Create a MultiSelect Input for the MyEnum enumeration |
8.3.3. 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 ItemDataProvider.
-
Using a standard Vaadin
DataProvider
.
SingleSelect<TestData> singleSelect = Components.input.singleSelect(TestData.class)
.items(new TestData(1), new TestData(2)) (1)
.build();
singleSelect = Components.input.singleSelect(TestData.class)
.dataSource(ItemDataProvider.create(q -> 2, (q, o, l) -> Stream.of(new TestData(1), new TestData(2)))) (2)
.build();
singleSelect = Components.input.singleSelect(TestData.class)
.dataSource(DataProvider.ofItems(new TestData(1), new TestData(2))) (3)
.build();
1 | Create a select input using an explicitly provided items set |
2 | Create a select input using a Holon platform ItemDataProvider |
3 | Create a select input using a Vaadin DataProvider |
8.3.4. Item captions and icons
The selectable input builders allow to explicitly set the selection item captions and icons. For the items caption configuration, the Holon Platform localization architecture is supported, and the builders provide methods to specify the captions using a Localizable
object or a message code.
See Messages localization support for details about messages localization.
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, VaadinIcons.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 ? VaadinIcons.STAR : VaadinIcons.STAR_O) (2)
.build();
1 | Set an item caption generator which provides the item description as item caption |
2 | Set an item icon generator |
8.3.5. SingleSelect
caption filter
For the SingleSelect
type Inputs which use the SELECT rendering mode, the UI component allows the user to type a filtering text to search for a selection item, and such filter by default is referred to item captions.
When using a fixed item set, the in-memory caption filter can be customized using the filteringMode(…)
builder method.
SingleSelect<TestData> singleSelect = Components.input.singleSelect(TestData.class)
.items(new TestData(1), new TestData(2)) // set the items
.filteringMode(
(itemCaption, filterText) -> itemCaption.toLowerCase().startsWith(filterText.toLowerCase())) (1)
.build();
When using a ItemDataProvider
or a Vaadin DataProvider
, an overloaded form of the dataSource(…)
method can be used, which accept as second parameter a function to provide a suitable query filter using the current user filtering text.
final TestData ONE = new TestData(1);
final TestData TWO = new TestData(2);
SingleSelect<TestData> singleSelect = Components.input.singleSelect(TestData.class)
.dataSource(ItemDataProvider.create(q -> 2, (q, o, l) -> Stream.of(ONE, TWO)), filterText -> QueryFilter
.startsWith(ConstantConverterExpression.create("description"), filterText, true))
.build();
8.3.6. Using the Property
model with selectable Inputs
The Holon platform Property model is fully supported by the selectable Input builders, which allows 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.
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 query 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), // Datastore
() -> ID.gt(0L)) (1)
.itemCaptionGenerator(propertyBox -> propertyBox.getValue(DESCRIPTION)).build();
1 | Set a QueryConfigurationProvider which provides a QueryFilter to select only the items with the ID property that is greater than 0 |
Selection model converter
When the Holon platform Property
model is used for selectable Inputs and the selection value type is not a PropertyBox
, i.e. the selection items are not of the same type of the Input
value type, a conversion function is required to obtain the PropertyBox
item from a selection value when the value is setted in the Input component, either using setValue(…)
or select(…)
.
For example, if a String
type Property
is declared as selection property, the Input
value will be the the value of the selection property (of String
type), while the selection items are PropertyBox
.
When a Datastore
is provided as the data source of the select, a default item converter is configured. The default item converter tries to obtain the PropertyBox
item by performing a query on the Datastore
and using a filter predicate to get the item for which the selection property value is equal to the value to select, assuming that the selection property acts as primary key.
To change the default conversion strategy, a custom item converter can be provided to the select input builder using the itemConverter(…)
method:
Datastore datastore = obtainDatastore();
final PathProperty<Long> ID = PathProperty.create("id", Long.class);
final PathProperty<String> DESCRIPTION = PathProperty.create("description", String.class);
final DataTarget<?> TARGET = DataTarget.named("testData");
final PropertySet<?> PROPERTIES = PropertySet.of(ID, DESCRIPTION);
SingleSelect<Long> singleSelect = Components.input.singleSelect(ID)
.dataSource(datastore, DataTarget.named("testData"), PROPERTIES)
.itemConverter(
value -> datastore.query().target(TARGET).filter(ID.eq(value)).findOne(PROPERTIES).orElse(null)) (1)
.build();
1 | Set an ItemConverter providing the query to perform to obtain the item PropertyBox from the selection ID property value |
8.4. Input groups
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.
8.4.1. Property Inputs rendering
By default, the Holon platform Property rendering architecture is used to automatically generate a suitable Input
component for each Property
of Input group property set, according to the Property
type.
The Input
type is used as target rendering type to generate the Input
components.
See Property renderers and presenters for further information about Property renderers. |
final NumericProperty<Long> ID = NumericProperty.longType("id");
final StringProperty DESCRIPTION = StringProperty.create("description");
final PropertySet<?> PROPERTIES = PropertySet.of(ID, DESCRIPTION);
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES) (1)
.build();
1 | Configure the input group property set. The property Input fields are automatically generated using the available Property Rendereres from the default registry |
8.4.2. Explicit Input
and HasValue
binding
To explicitly set the Input
component to use for a Property
of the input group property set, the bind(…)
builder methods of the PropertyInputGroup
builder API can be used.
The bind(…)
builder method can therefore be used to override the default property Input
generation only when a custom Input
type is required.
The standard HasValue
Components are also supported to define a Property
binding.
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES)
.bind(ID, Components.input.number(Long.class).build()) (1)
.bind(DESCRIPTION, new TextField()) (2)
.build();
1 | Bind the ID property to a Long type Input component |
2 | Bind the DESCRIPTION property to a TextField |
8.4.3. Input value convertes
When an Input
component or HasValue
component must be bind to a Property with a different type, a value converter can be provided to convert the Input value to the required Property type and vice-versa.
Specialized bind
methods of the PropertyInputGroup
builder API are available to use a standard Vaadin com.vaadin.data.Converter
to provide the conversion logic.
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES)
.bind(ID, Components.input.string().build(), new StringToLongConverter("Conversion error")) (1)
.bind(ID, new TextField(), new StringToLongConverter("Conversion error")) (2)
.build();
1 | Bind the ID property to a String type Input component and use a StringToLongConverter to convert the String values to the required Long property type |
2 | Bind the ID property to a TextField and use a StringToLongConverter to convert the String values to the required Long property type |
8.4.4. Manage the property values using a PropertyBox
The property values are setted and obtained using the PropertyBox
data container, using 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.
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES).build();
group.setValue(PropertyBox.builder(PROPERTIES).set(ID, 1L).set(DESCRIPTION, "TestDescription").build()); (1)
PropertyBox value = group.getValue(); (2)
group.addValueChangeListener(e -> { (3)
PropertyBox changedValue = e.getValue();
});
1 | Set the input group property values using a PropertyBox with a matching property set |
2 | Get the input group property values as a PropertyBox instance |
3 | Add an input group value change listener |
8.4.5. 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.
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 |
8.4.6. 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.
Inputs bound to hidden properties are never returned from the PropertyInputGroup
methods which allow to inspect the available binding, such as getInputs()
, getInput(Property<T> property)
or stream()
.
The hidden(Property property)
method of the group builder can be used to set a property as hidden.
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES) //
.hidden(ID) (1)
.build();
1 | The ID property is setted as hidden and will not be displayed nor managed by the input group |
8.4.7. 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.
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES) //
.defaultValue(DESCRIPTION, property -> "Default") (1)
.build();
1 | Use a DefaultValueProvider to provide the DESCRIPTION property default value |
8.5. Input group validation
A PropertyInputGroup
support both property values and group value validation, supporting both the Holon Platform Validator
API and the standard Vaadin validators.
When a validator in bound to the overall Input group, the value to validate will be represented by the PropertyBox
type, which contains all the current property values.
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES) //
.withValidator(DESCRIPTION, Validator.max(100)) (1)
.withValidator(ID, com.vaadin.data.Validator.from(id -> id != null, "Id must be not null")) (2)
// group validation
.withValidator(Validator.create(propertyBox -> propertyBox.getValue(ID) > 0,
"The ID value must be greater than 0")) (3)
.build();
1 | Add a Validator bound to the DESCRIPTION property to check that the value size is not greater than 100 characters |
2 | Add a com.vaadin.data.Validator to check the ID property value is not null |
3 | Add an overall (group) Validator which reads the ID property value from the group PropertyBox value and checks it is greater than 0 |
Furthermore, 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 along with the property Input
component.
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES) //
.required(ID) (1)
.required(ID, "The ID value is required") (2)
.build();
1 | Add a required validator to the ID property |
2 | Add a required validator to the ID property providing a custom validation error message |
The input group validation can be requested using the validate()
and it is performed by invoking:
-
All the available
Property
validators for each property of the input group property set; -
Then the overall group validators, if any.
Besides the validate()
method, the input group validation is performed by default when the Input group PropertyBox
value is requested using the getValue()
method. To skip the group validation when requesting the Input group value, a getValue(boolean validate)
method is available and false
can be specified for the validate parameter.
A further getValueIfValid()
method can be used to obtain the Input group value only when it is valid, after performing the validations. If validation fails, an empty Optional
is returned.
PropertyInputGroup group = createInputGroup();
try {
group.validate(); (1)
PropertyBox value = group.getValue(); (2)
} catch (ValidationException e) {
// validation failed
}
PropertyBox value = group.getValue(false); (3)
group.getValueIfValid().ifPresent(propertyBox -> { (4)
// ...
});
1 | Explicit group validation: if some kind of 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 |
8.5.1. Property
validators
Since the Property API supports direct validators registration for each Property
instance, the property validators are inherited by default as Input group validators for each Property of the Input group property set.
This behavior can be disabled using the ignorePropertyValidation()
method of the PropertyInputGroup
builder API.
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES) //
.ignorePropertyValidation() (1)
.build();
1 | Set to ignore any Property Validator when binding the property to an Input component, i.e. to not inherit property validators when the property-Input binding is performed. |
8.5.2. Validation configuration
The Input group provides some configuration options to tune the validation strategy. The validation configuration can be controlled through the PropertyInputGroup
builder API in order to:
-
Enable or disable the automatic
Input
validation when the Input value changes (this behavior is enabled by default). -
Whether to stop property validation at first validation failure. When active, only the first
ValidationException
is thrown. Otherwise, aValidationException
which groups all the occurred validation exceptions is thrown, providing all the validation error messages (the default behavior). -
Whether to stop overall group validation at first validation failure. When active, only the first
ValidationException
is thrown. Otherwise, aValidationException
which groups all the occurred validation exceptions is thrown, providing all the validation error messages (the default behavior).
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES) //
.validateOnValueChange(false) (1)
.stopValidationAtFirstFailure(true) (2)
.stopOverallValidationAtFirstFailure(true) (3)
.build();
1 | Disable the automatic validation at Input value change |
2 | Stop property validation at first validation failure |
3 | Stop overall group validation at first validation failure |
8.5.3. Validation errors notification
A ValidationStatusHandler can be used to control how the validation errors are notified to the user, listening to validation status change events of the PropertyInputGroup
.
See Validation errors notification for details about the ValidationStatusHandler
API.
The property input group supports ValidationStatusHandler
configuration both for property and overall validation.
Label statusLabel = new Label();
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES) //
.validationStatusHandler(validationEvent -> { (1)
// ...
}).propertiesValidationStatusHandler(validationEvent -> { (2)
// ...
}).validationStatusHandler(statusLabel) (3)
.build();
1 | Configure a ValidationStatusHandler for the overall (group) validation |
2 | Configure a ValidationStatusHandler for the properties validation |
3 | Convenience method to use a Label as validation status handler |
8.6. Handling value changes
The PropertyInputGroup
builder API provides methods to register one or more ValueChangeListener
in order to:
-
Listen to
Input
value changes for theInput
components bound to group property set. -
Listen to the Input group value changes, where the value type is represented by the
PropertyBox
type.
Furthermore, the PropertyInputGroup
builder API makes available a set of methods to configure the value change notification strategy, i.e. the mode and the frequency with which the value change events are triggered, both for property level and group level value change events.
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES) //
.withValueChangeListener(e -> { (1)
PropertyBox value = e.getValue();
// ...
}).withValueChangeListener(DESCRIPTION, e -> { (2)
String description = e.getValue();
// ...
}) //
.valueChangeMode(ValueChangeMode.BLUR) (3)
.valueChangeMode(DESCRIPTION, ValueChangeMode.LAZY) (4)
.valueChangeTimeout(DESCRIPTION, 500) (5)
.build();
1 | Add an overall value change listener |
2 | Add a value change listener to the Input bound to the DESCRIPTION property |
3 | Set the default ValueChangeMode for all inputs |
4 | Set the ValueChangeMode only for the Input bound to the DESCRIPTION property |
5 | Set the value change timeout to 500 ms only for the Input bound to the DESCRIPTION property |
In addition to the standard ValueChangeListener
, the PropertyInputValueChangeListener interface is also supported to listen to Property/Input pairs value changes.
The PropertyInputValueChangeListener
provides the current PropertyInputBinder
reference, besides of the standard ValueChangeEvent
. The PropertyInputBinder
makes available several methods to inspect the input group to which the value change source belongs, for example in order to obtain the values of other Input
components.
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES) //
.withValueChangeListener(DESCRIPTION, (event, binder) -> { (1)
String description = event.getValue();
Long id = binder.requireInput(ID).getValue();
// ...
}).build();
1 | Add a PropertyInputValueChangeListener to the Input bound to the DESCRIPTION property, which provides the current PropertyInputBinder to inspect all available group Inputs |
8.7. Dealing with Virtual properties
By default, the PropertyInputGroup
renders as an Input all the properties of the property set that has been provided at build time, including any VirtualProperty.
Since a VirtualProperty
is a read-only property, the Input
bound to a VirtualProperty
is configured in read-only mode by default.
If the VirtualProperty
value is calculated using the values of other properties of the PropertyInputGroup
property set, can be useful to refresh the displayed Input
value bound to the virtual property when one or more property of set changes.
For this purpose, the refresh(…)
methods of the PropertyInputGroup
can be used. This methods are available from the PropertyInputBinder
super-interface. You can refresh a specific property or all the properties of the set, even only the read-only ones.
final VirtualProperty<String> VIRTUAL = VirtualProperty.create(String.class,
propertyBox -> propertyBox.getValue(ID) + " -" + propertyBox.getValue(DESCRIPTION));
final PropertySet<?> PROPERTIES = PropertySet.of(ID, DESCRIPTION, VIRTUAL);
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES) //
.withValueChangeListener(DESCRIPTION, (event, binder) -> { (1)
binder.refresh(VIRTUAL);
}) //
.build();
1 | Add a PropertyInputValueChangeListener to the Input bound to the DESCRIPTION property, and refresh the VIRTUAL property Input value when the DESCRIPTION property value changes |
To disable the read-only properties rendering and management, the excludeReadOnlyProperties()
builder method can be used.
PropertyInputGroup group = Components.input.propertyGroup().properties(PROPERTIES) //
.excludeReadOnlyProperties() (1)
.build();
1 | Exclude any read-only property from Input rendering and binding |
8.8. Input Forms
The PropertyInputForm component represents a PropertyInputGroup which is also a UI Component
, and thus can be directly used to display and manage the Input
components in the UI.
It extends the ComposableComponent API, which represents an UI component with a base layout and the capability to compose a set of UI components on this layout.
The Composer API is used to compose the Input
components on the UI, and must be provided to the PropertyInputForm
at configuration time, along with the base layout component on which the Input
components will be organized.
When a PropertyInputForm
component is obtained using the Components API, a set of methods are available to create a PropertyInputForm
using one of the standard Vaadin layouts. Using these methods, a default ComponentContainer
composer is provided, which adds the Input
components to the base layout in the order they are provided by the PropertyInputForm
property set.
The ComposableComponent interface provides the componentContainerComposer() static method to obtain a default composer which uses a Vaadin ComponentContainer as base layout.
|
PropertyInputForm form = Components.input.form().properties(PROPERTIES).build(); (1)
form = Components.input.formVertical().properties(PROPERTIES).build(); (2)
form = Components.input.formHorizontal().properties(PROPERTIES).build(); (3)
form = Components.input.formGrid().properties(PROPERTIES).build(); (4)
1 | Create a PropertyInputForm using a FormLayout as base content layout |
2 | Create a PropertyInputForm using a VerticalLayout as base content layout |
3 | Create a PropertyInputForm using a HorizontalLayout as base content layout |
4 | Create a PropertyInputForm using a GridLayout as base content layout |
8.8.1. Base layout initialization
A initializer(…)
builder method is available to perform custom configuration of the base layout component of the PropertyInputForm
.
PropertyInputForm form = Components.input.formGrid().properties(PROPERTIES) //
.initializer(gridLayout -> { (1)
gridLayout.setSpacing(true);
gridLayout.addStyleName("my-style");
}).build();
1 | Provide base layout component initialization using an initializer |
8.8.2. Input
components composition
The Composer API is used to compose the Input
components on the PropertyInputForm
base layout, and can be provided at configuration time either to provide a custom component composition strategy.
The Input
components composition using the currently configured Composer
is triggered by the compose()
method. By default, the components composition is automatically performed when the PropertyInputForm
component is a attached to a parent layout.
PropertyInputForm form = Components.input.form(new FormLayout()) (1)
.properties(PROPERTIES).composer((layout, source) -> { (2)
source.getValueComponents().forEach(c -> layout.addComponent(c.getComponent()));
}).build();
1 | A FormLayout instance is provided as base layout component |
2 | Configure a Composer to compose the Input components on the form base layout. The PropertyComponentSource type argument can be used to obtain the available Input components |
To disable this behaviour, the composeOnAttach(…)
method of the PropertyInputForm
builder API can be used.
PropertyInputForm form = Components.input.form(new FormLayout()).properties(PROPERTIES) //
.composer((layout, source) -> {
source.getValueComponents().forEach(c -> layout.addComponent(c.getComponent()));
}).composeOnAttach(false) (1)
.build();
form.compose(); (2)
1 | Disable the automatic Input components composition when the PropertyInputForm component is a attached to a parent layout |
2 | Explicitly use the compose() method to perform components composition |
8.8.3. Input
components width mode
When the Input
components are composed on a PropertyInputForm
content component, the ComponentsWidthMode enumeration can be used to configure the strategy to use to handle the component widths.
The available strategies are:
-
NONE: The width of each
Input
component will not be altered. -
AUTO: The width of each
Input
component will adjusted according to parent layout width: if the parent layout is 100% wide, the components width will be setted to 100%. -
FULL: The width of each
Input
component will always be setted to 100%.
The default strategy is AUTO
.
The strategy can be changed using the componentsWidthMode
method of the PropertyInputForm
builder API.
PropertyInputForm form = Components.input.form().properties(PROPERTIES)
.componentsWidthMode(ComponentsWidthMode.FULL) (1)
.build();
1 | Set the ComponentsWidthMode to FULL : all the form Input components will be 100% wide |
8.8.4. Input
components configuration
The ComposableComponent
type builders, including the PropertyInputForm
builder API, allow to provide custom configurator functions in order to configure the Input
components just after they are rendered.
For each Property
of the PropertyInputForm
property set, you can provide a Consumer
to use as ComponentConfigurator
, which makes available a complete set of methods to configure the Input Component
associated to a Property
before rendering it in UI. For example, you can set the component size, the icon, the description and so on.
PropertyInputForm form = Components.input.form().properties(PROPERTIES)
.componentConfigurator(ID, cfg -> cfg.styleName("id-input").description("The ID")) (1)
.componentConfigurator(DESCRIPTION, cfg -> cfg.icon(VaadinIcons.CLIPBOARD_TEXT)) (2)
.build();
1 | Configure the ID property Component |
2 | Configure the DESCRIPTION property Component |
8.9. View components
The ViewComponent API represents a UI component which can be used to display a value on the user interface. Unlike an Input
type component, the value cannot be changed by the use.
As a ValueHolder
, the view component value can be setted and obtained using the setValue
and getValue
methods.
Just like an Input
type component, the actual UI component to display is obtained form the getComponent()
method.
The Components API can be used to obtain a default ViewComponent
implementation (backed by a Vaadin Label
component) for a specific value type, using the view.component(Class<? extends T> valueType)
method.
ViewComponent<String> view = Components.view.component(String.class) (1)
.caption("TheCaption", "caption.message.code") (2)
.icon(VaadinIcons.CAMERA) //
.styleName("my-style") //
.build();
view.setValue("TestValue"); (3)
String value = view.getValue(); (4)
1 | Get a ViewComponent builder using a String value type |
2 | Configure the ViewComponent component |
3 | Set the ViewComponent value |
4 | Get the ViewComponent value |
8.9.1. View component Groups and Forms
Just like the Input
components, a ViewComponent
set can be organized in groups and handled using forms.
-
The PropertyViewGroup API con be used to create
ViewComponent
groups. -
The PropertyViewForm API con be used to create
ViewComponent
form components.
These component containers rely on the Holon Platform the Property model to bind each ViewComponent
to a Property
and they use the PropertyBox
type to provide the property values to show.
final NumericProperty<Long> ID = NumericProperty.longType("id");
final StringProperty DESCRIPTION = StringProperty.create("description");
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.setValue(PropertyBox.builder(PROPERTIES).set(ID, 1L).set(DESCRIPTION, "Test").build()); (3)
PropertyBox value = viewForm.getValue(); (4)
1 | Create a PropertyViewGroup using the PROPERTIES property set |
2 | Create a PropertyViewForm using the PROPERTIES property set and a VerticalLayout as base layout |
3 | Set the form value using a PropertyBox |
4 | Get the form value as a PropertyBox |
The PropertyViewGroup
and PropertyViewForm
provide all the base components configuration capabilities of their corresponding PropertyInputGroup and PropertyInputForm implementations.
So, for example, it is possible to configure a Composer
to customize the ViewComponent
components composition strategy for a PropertyViewForm
or to provide component initializers at configuration time.
PropertyViewForm viewForm = Components.view.form(new FormLayout()).properties(PROPERTIES) //
.initializer(layout -> layout.setMargin(true)) (1)
.composer((layout, source) -> { (2)
source.getValueComponents().forEach(c -> layout.addComponent(c.getComponent()));
}) //
.componentConfigurator(DESCRIPTION, cfg -> cfg.styleName("my-style")) (3)
.componentsWidthMode(ComponentsWidthMode.FULL) (4)
.build();
1 | Base layout initializer |
2 | Custom component Composer |
3 | DESCRIPTION property ViewComponent component configurator |
4 | Set the ComponentsWidthMode to FULL : all the form ViewComponent components will be 100% wide |
8.9.2. ViewComponent
captions
When the PropertyViewForm
builder API makes available some additional methods to directly configure the captions of the ViewComponent
managed by the form.
For each Property
of the form property set, the ViewComponent
caption can be setted using the propertyCaption
method or hidden using the hidePropertyCaption
method.
PropertyViewForm viewForm = Components.view.form(new FormLayout()).properties(PROPERTIES) //
.propertyCaption(DESCRIPTION, "My caption") (1)
.hidePropertyCaption(ID) (2)
.build();
1 | Set the caption for the ViewComponent bound to the DESCRIPTION property |
2 | Hide the caption for the ViewComponent bound to the ID property |
9. Item listing
The ItemListing API can be used to display a set of items as tabular data using a Grid
type UI component and to manage the data items.
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
API provides a set of methods to control the listing appearance, showing and hiding columns, sorting and refreshing the item set, and so on. See Item listing API and configuration.
Two ItemListing
implementations are available to use this component:
-
PropertyListing: Uses a
PropertyBox
as item type and the listing column ids are theProperty
instances of the provided property set. -
BeanListing: Uses a Java Bean as item type and the listing column ids are the Bean property names.
9.1. Item listing properties
An ItemListing component manages the its data as a set of items and each item attribute is identified by a property which also acts as listing column id.
The available item attributes represent the item property set and can be used to access and display the item data in the listing.
Each concrete ItemListing implementation is bound to a specific item type and property type.
Since each property id represents an item listing column id, it is used be the builder API to identify the listing columns and provide a set of column configuration methods. See Item listing API and configuration for details.
9.1.1. Visible properties
By default, all the properties of the listing property set are rendered as listing columns, in the same order they are provided by the property set itself.
The listing columns display can be changed as described below.
1. Explicit column selection and display order:
To explicitly select which properties are to be rendered as listing columns and to declare the columns order, the ItemListing
builder API provides specialized versions of the build()
method, which accept an array or an Iterable
of property ids (whose type will be the concrete type used by the specific ItemListing
implementation) to declare the visible columns and their order.
For example, when using the PropertyListing implementation, the item’s property ids will be represented using the Property abstraction and the visible item listing columns (and their order) can be configured as in the example below:
final NumericProperty<Long> ID = NumericProperty.longType("id");
final StringProperty DESCRIPTION = StringProperty.create("description");
final BooleanProperty ACTIVE = BooleanProperty.create("active");
final PropertySet<?> PROPERTIES = PropertySet.of(ID, DESCRIPTION, ACTIVE);
PropertyListing listing = Components.listing.properties(PROPERTIES) (1)
.build(DESCRIPTION, ID); (2)
1 | The PROPERTIES property set is used as item property set. By default, the displayed column set and ordering would be: ID , DESCRIPTION , ACTIVE . |
2 | Change the listing visible columns and their order: the DESCRIPTION property first and then the ID property |
2. Default column display ordering configuration:
The ItemListing
builder API provides a set of methods to configure the default column display ordering, which is applied only when an explicit ordering is not provided using the specialized build(…)
methods as described in the previous section.
The available column position configuration methods are:
-
displayAsFirst(P property)
: Display the column represented by given property id before any other listing column. -
displayAsLast(P property)
: Display the column represented by given property id after any other listing column. -
displayBefore(P property, P beforeProperty)
: Display the column represented by given property id before the listing column represented by thebeforeProperty
property id. -
displayAfter(P property, P afterProperty)
: Display the column represented by given property id after the listing column represented by theafterProperty
property id.
The property id type (P
) depends on the concrete ItemListing
implementation.
final NumericProperty<Long> ID = NumericProperty.longType("id");
final StringProperty DESCRIPTION = StringProperty.create("description");
final BooleanProperty ACTIVE = BooleanProperty.create("active");
final PropertySet<?> PROPERTIES = PropertySet.of(ID, DESCRIPTION, ACTIVE);
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.displayAsFirst(ACTIVE) (1)
.displayBefore(ID, DESCRIPTION) (2)
.displayAfter(DESCRIPTION, ACTIVE) (3)
.build();
1 | Configure the column which corresponds to the ACTIVE property id to be displayed as first |
2 | Configure the ID column to be displayed before the DESCRIPTION column |
3 | Configure the DESCRIPTION column to be displayed after the ACTIVE column |
9.2. PropertyListing
A PropertyListing implementation can be obtained using the Components API, through the listing.properties
method.
The Property
set to use to configure the PropertyListing
must be provided, for each Property
of the property set a listing column and configured to the Property
types. Each column will be identified by the Property
itself.
In particular, for each Property
of the property set:
-
The property caption, if available, will be used as column header. The caption/header localization is fully supported and performed if a
LocalizationContext
is available. See Messages localization support for details. -
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.Component
type, aComponentRenderer
will be automatically used to render the Vaadin Component in the listing column cells.
final NumericProperty<Long> ID = NumericProperty.longType("id");
final StringProperty DESCRIPTION = StringProperty.create("description");
final PropertySet<?> PROPERTIES = PropertySet.of(ID, DESCRIPTION);
PropertyListing listing = Components.listing.properties(PROPERTIES).build(); (1)
1 | Create a PropertyListing using given PROPERTIES property set |
See the Item listing API and configuration to learn about the item listing API operations and configuration options. |
9.2.1. Items type and identifiers
The PropertyBox type is used to represent a PropertyListing
item, i.e. a listing row. For each property listing row, the PropertyBox
type item collects the values of each Property
bound to the listing columns.
By default, the listing property set identifier properties are used to provide the PropertyBox
items identifiers, if available. See the property set identifier properties documentation for details.
final NumericProperty<Long> ID = NumericProperty.longType("id");
final StringProperty DESCRIPTION = StringProperty.create("description");
final PropertySet<?> PROPERTIES = PropertySet.builderOf(ID, DESCRIPTION).identifier(ID).build(); (1)
PropertyListing listing = Components.listing.properties(PROPERTIES).build();
1 | Set the ID property as property set identifier property |
9.2.2. Items data source
The PropertyBox
type item set handled by the PropertyListing
is provided using an items data source, which can be configured using the PropertyListing
builder API using the dataSource
methods.
The ItemDataProvider API is used to represent the items data source.
An ItemDataProvider
implementation provides methods to obtain the item set size and to load a subset of the items using a limit which represents the subset size and a offset index to specify which subset to return. For a PropertyListing
, the items subset is returned as a Stream of PropertyBox
instances.
The ItemDataProvider
methods accepts a QueryConfigurationProvider
instance, to provide the QueryFilter
and the QuerySort
to be used to query the item set.
See the Query documentation for information about the query definition using the Holon Platform Property model. |
ItemDataProvider<PropertyBox> dataProvider = ItemDataProvider.create(cfg -> 0, (1)
(cfg, offset, limit) -> Stream.empty()); (2)
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.dataSource(dataProvider) (3)
.build();
1 | Create a ItemDataProvider which always returns a 0 size item set |
2 | Return an empty Stream as item set |
3 | Create a PropertyListing using the dataProvider as items data source |
A convenience method is available to use the Datastore API as items data source, providing the DataTarget
to use to perform the query on the concrete persistence source.
Datastore datastore = getDatastore();
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.dataSource(datastore, DataTarget.named("test")) (1)
.build();
1 | Use a Datastore as data source. The Datastore API will be used to perform the queries to obtain the items set, with the given DataTarget as query target |
9.2.3. Item identifier configuration
When the property set of the PropertyListing
, used to represent the PropertyBox
type items, does not provide one or more identifier properties as described in the Items type and identifiers section, the ItemIdentifierProvider interface can be used to provide the item identifiers.
ItemDataProvider<PropertyBox> dataProvider = getDataProvider();
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.dataSource(dataProvider, item -> item.getValue(ID)) (1)
.build();
listing = Components.listing.properties(PROPERTIES) //
.dataSource(getDatastore(), DataTarget.named("test"), 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 identifier |
2 | A shorter builder method to use the ID property as item identifier when a Datastore is used as items data source |
9.2.4. Item query configuration
One or more QueryConfigurationProvider
can be registered in the PropertyListing
to provide additional and dynamic items query configuration elements, such as QueryFilter
and QuerySort
.
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.dataSource(getDatastore(), DataTarget.named("test")) //
.withQueryConfigurationProvider(new QueryConfigurationProvider() { (1)
@Override
public QueryFilter getQueryFilter() {
return ID.gt(0L);
}
@Override
public QuerySort getQuerySort() {
return DESCRIPTION.asc();
}
}).build();
1 | Add a QueryConfigurationProvider to the PropertyListing |
9.2.5. Using VirtualProperty
for "generated" columns
The VirtualProperty abstraction can be used to declare generated columns for a PropertyListing
component, i.e. columns whose contents are not directly bound to an item property value, but are instead generated using a function, for example relying on the item data (represented by a PropertyBox
) for each listing row (i.e. for each item instance).
Since the VirtualProperty
provides its value through a PropertyValueProvider
, the provider function is invoked when the listing column cells are rendered to obtain the column content to display, providing the current item/row value as a PropertyBox
instance.
The generated column content type will match the VirtualProperty
type, so if it is not of String
type a suitable renderer should be configured to consistently display the column contents. See Columns rendering to learn how to configure column renderers and Using VirtualProperty
for Component
type columns.
A VirtualProperty
type column can be added to a PropertyListing
component in the following ways:
1. Include the VirtualProperty
in the listing property set:
When one or more VirtualProperty
type property is included in the default PropertyListing
property set, a column bound to each VirtualProperty
is included in the listing by default and no further configuration is required.
In this scenario, the VirtualProperty
will be part of the item property set and available as item property through the PropertyBox
item representation.
final NumericProperty<Long> ID = NumericProperty.longType("id");
final StringProperty TEXT = StringProperty.create("txt");
final VirtualProperty<String> DESCRIPTION = VirtualProperty.create(String.class,
item -> "ID: " + item.getValue(ID)); (1)
PropertyListing listing = Components.listing.properties(ID, TEXT) //
.build(ID, TEXT, DESCRIPTION); (2)
1 | A String type VirtualProperty which uses the ID item property value to provide the generated value |
2 | The DESCRIPTION virtual property is included in the PROPERTIES listing property set, so it will be included and displayed as a column by default |
2. Provide a VirtualProperty
as a visible column:
When a VirtualProperty
is declared within the visible columns list through the appropriate build(…)
methods, it will be automatically configured as listing column even if it is not part of the item listing property set.
final NumericProperty<Long> ID = NumericProperty.longType("id");
final VirtualProperty<String> DESCRIPTION = VirtualProperty.create(String.class,
item -> "ID: " + item.getValue(ID)); (1)
final PropertySet<?> PROPERTIES = PropertySet.of(ID, DESCRIPTION);
PropertyListing listing = Components.listing.properties(PROPERTIES).build(); (2)
1 | A String type VirtualProperty which uses the ID item property value to provide the generated value |
2 | The DESCRIPTION virtual property is included in the visible properties list, so it will be automatically included and displayed as a column |
3. Use the withVirtualProperty(…)
builder API methods:
The PropertyListing
builder API provide a set of convenience methods to add and configure a generated column using a VirtualProperty
.
The withVirtualProperty(…)
methods accept the property type and the property value provider function (or an explicit VirtualProperty
reference) and provide a specific builder API to configure the column which corresponds to the virtual property before adding it to the listing through the add()
method.
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.withVirtualProperty(String.class, item -> "ID: " + item.getValue(ID)).add() (1)
.withVirtualProperty(item -> "ID: " + item.getValue(ID)).add() (2)
.build();
1 | Add a String type virtual property providing the property value provider function and using add() to include the generated column in the listing |
2 | For String type virtual properties, a convenience shorter method is available |
The virtual property column builder API provides a set of configuration methods to configure the column header, the width, the visibility, the style generator, the column renderer and the sorting behaviour:
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.withVirtualProperty(String.class, item -> "ID: " + item.getValue(ID)) (1)
.header("The header") (2)
.headerHTML("The <strong>header</strong>") (3)
.alignment(ColumnAlignment.CENTER) (4)
.width(100) (5)
.minWidth(50) (6)
.maxWidth(200) (7)
.expandRatio(1) (8)
.resizable(true) (9)
.hidable(true) (10)
.hidden(false) (11)
.hidingToggleCaption("Show/hide") (12)
.style("my-style") (13)
.style((property, item) -> "stylename") (14)
.render(new HtmlRenderer()) (15)
.sortUsing(ID) (16)
.sortGenerator((property, asc) -> QuerySort.of(ID, asc)) (17)
.add() (18)
.build();
1 | Declare a String type virtual property providing the property value provider function |
2 | Configure the column header |
3 | Configure the column header as HTML |
4 | Configure the column alignment |
5 | Configure the column width |
6 | Configure the column minimum width |
7 | Configure the column maximum width |
8 | Configure the column expand ratio |
9 | Set the column as resizable |
10 | Set the column as hidable |
11 | Set the column as not hidden |
12 | Configure the column hiding toggle caption |
13 | Provide a fixed column CSS style class name |
14 | Provide the column CSS style class provider |
15 | Set the column renderer |
16 | Declare to use the ID property to implement the column sorting behaviour |
17 | Provide a column sorting generator |
18 | Add the virtual property column to the listing |
By default, the column added using the withVirtualProperty(…)
methods are appended at the end of the listing columns. To control the virtual property column positioning within the listing columns, the following builder API methods can be used:
-
displayAsFirst()
: Display the virtual property column before any other listing column. -
displayAsLast()
: Display the virtual property column after any other listing column. -
displayBefore(Property property)
: Display the virtual property column before the listing column represented by theproperty
property id. -
displayAfter(Property property)
: Display the virtual property column after the listing column represented by theproperty
property id.
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.withVirtualProperty(String.class, item -> "ID: " + item.getValue(ID)) (1)
.displayAsFirst() (2)
.displayAsLast() (3)
.displayBefore(DESCRIPTION) (4)
.displayAfter(ID) (5)
.add().build();
1 | Declare a String type virtual property providing the property value provider function |
2 | Display the virtual property column before any other listing column |
3 | Display the virtual property column after any other listing column |
4 | Display the virtual property column before the listing column bound the DESCRIPTION property |
5 | Display the virtual property column after the listing column bound the ID property |
When a virtual property column has to be positioned relative to another virtual property column, you need a property id to which to refer. For this reason, specialized withVirtualProperty(…)
methods which accept the virtual property name are available.
Furthermore, the displayBeforeColumnId(String columnId)
and displayAfterColumnId(String columnId)
builder API methods can be use to display a virtual property column before or after another virtual property column with an assigned name.
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.withVirtualProperty(String.class, "_myid", item -> "ID: " + item.getValue(ID)).displayAfter(ID).add() (1)
.withVirtualProperty(String.class, item -> "ID2: " + item.getValue(ID)).displayBeforeColumnId("_myid") (2)
.add().build();
1 | Declare a String type virtual property providing the property value provider function and _myid as virtual property name |
2 | Declare another virtual property column and display it before the _myid virtual property |
9.2.6. Using VirtualProperty
for Component
type columns
When a Property
of the PropertyListing
property set is declared as com.vaadin.ui.Component
type, the listing configures by default a suitable renderer to display the property value as a Vaadin Component, instead of a String
.
When a listing Component
type column has to be dinamically generated when the listing is rendered, the VirtualProperty property type can be used.
Since the VirtualProperty
provides its value through a PropertyValueProvider
, the provider is invoked when the listing column cells are rendered to obtain the Component
to display, providing the current row values as PropertyBox
, which can be used for component generation.
The VirtualProperty
can be declared in the listing property set as any other Property, but when it is only used for a specific item listing column to render a Component
it can be only declared as visible column, in the build(…)
method. This way, it will not be part of the listing property set (and it will not be included in the row PropertyBox
properties) and will only be used as generated column.
final VirtualProperty<Component> EDIT = VirtualProperty.create(Component.class).message("Edit") (1)
.valueProvider( (2)
row -> Components.button().styleName(ValoTheme.BUTTON_ICON_ONLY).icon(VaadinIcons.EDIT)
.onClick(e -> {
Long rowId = row.getValue(ID); (3)
// perform edit action ...
}).build());
PropertyListing listing = Components.listing.properties(PROPERTIES) (4)
.build(EDIT, ID, DESCRIPTION); (5)
listing = Components.listing.properties(PROPERTIES) //
.build(PropertySet.builder().add(PROPERTIES).add(EDIT).build()); (6)
1 | Declare a VirtualProperty of Component type. The configured message will be used as column header. |
2 | The property value provider returs a Button component |
3 | Since the row PropertyBox is provided at column cell generation time, we can use it to obtain a property value for the current row. In this example, the ID property value is obtained. |
4 | The listing property set do not includes the EDIT virtual property |
5 | The EDIT virtual property is declared as visible column, in the first position in this case |
6 | Another example in which the visible columns property set is composed joining the PROPERTIES set and the EDIT virtual property |
9.3. BeanListing
A BeanListing implementation can be obtained using the Components API, through the listing.items
method.
The Bean class to use as items type must be provided as construction time.
The Holon Platform Bean introspection API is used to detect the Bean class properties to use as listing columns and the Bean property names are used as column ids.
All the suitable bean property configuration attributes supported by the Holon Platform Bean introspection API are used to automatically configure the listing component, for example to set the column headers using the localizable property captions.
See the Bean property configuration section to learn about the Bean introspection API configuration capabilities and the available builtin annotations which can be used to provide a set of configuration attributes, such as @Caption
or @Sequence
.
For example, given the following bean class:
private class TestData {
private Long id;
private String description;
// getters and setters omitted
A BeanListing
which uses the bean class can be obtained as follows:
BeanListing<TestData> listing = Components.listing.items(TestData.class) (1)
.build();
1 | Create a BeanListing using TestData as items data type |
See the Item listing API and configuration to learn about the item listing API operations and configuration options. |
9.3.1. Bean items data source
The data source to provide the bean items for a BeanListing
can be configured using the dataSource
builder API methods.
1. Using an ItemDataProvider
as items data source:
The ItemDataProvider API can be used as items data source.
For example, given a List
of TestData
bean instances, an in-memory ItemDataProvider
can be configured in the following way:
final List<TestData> data = Arrays.asList(new TestData(1, "One"), new TestData(2, "Two"),
new TestData(3, "Three"));
BeanListing<TestData> listing = Components.listing.items(TestData.class) //
.dataSource(ItemDataProvider.create(cfg -> data.size(), (1)
(cfg, offset, limit) -> data.stream().skip(offset).limit(limit)))
.build();
1 | Set an ItemDataProvider which uses the data List as items data source |
2. Using a Datastore
as items data source:
A Datastore implementation can be used as items data source to obtain the bean items from a persistence source.
The properties which will be used to perform query operations using the Datastore
API will be the Bean properties obtained as a BeanPropertySet using the Holon Platform Bean introspection API.
final Datastore datastore = getDatastore();
BeanListing<TestData> listing = Components.listing.items(TestData.class) //
.dataSource(datastore, DataTarget.named("test")) (1)
.build();
1 | Use a Datastore and given DataTarget as items data source |
The BeanListing
API makes available a refresh()
method to refresh the current items set, causing items reloading from the items data source.
BeanListing<TestData> listing = Components.listing.items(TestData.class) //
.dataSource(getDatastore(), DataTarget.named("test")).build();
listing.refresh(); (1)
1 | Refresh the items set |
9.3.2. Dynamically generated columns
The BeanListing
component supports virtual columns definition to declare generated column types, which contents are generated when the listing is rendered, for example to provide Component
type column contents.
The withVirtualColumn(String id, Class<V> type, ValueProvider<T, V> valueProvider)
method of the BeanListing
builder API allows to declare a virtual column, providing:
-
The virtual column id and type
-
A
ValueProvider
which will be used to generate the column cells, using the row item bean instance
The declared virtual column id can later be used to include the virtual column in the visible column in the required position.
BeanListing<TestData> listing = Components.listing.items(TestData.class)
.withVirtualColumn("delete", Button.class, bean -> { (1)
return Components.button().icon(VaadinIcons.TRASH).onClick(e -> {
Long rowId = bean.getId(); (2)
}).build();
}).build("delete", "id", "description"); (3)
1 | Declare a virtual column of Button type and delete as column id |
2 | The bean instance of the current row can be used to obtain row values, for example the id attribute in this case |
3 | Declare the delete column as visible and render it as first column |
9.4. Item listing API and configuration
The item listing component builder API provides a wide set of configuration methods which can be used to:
-
Configure the listing columns, header and footer sections.
-
Register a number of listeners for different event types.
-
Configure the items data source query.
See the next sections for details.
9.4.1. Columns configuration
The listing component columns can be configured in a number of ways. Below is provided an example which uses a PropertyListing
and shows the available column configuration methods:
PropertyListing listing = Components.listing.properties(PROPERTIES) //
.header(ID, "Custom ID header") (1)
.header(ID, "Default header", "id-header-message-code") (2)
.headerHTML(ID, "HTML <strong>header</strong>") (3)
.columnHidingAllowed(true) (4)
.hidable(ID, false) (5)
.columnReorderingAllowed(true) (6)
.alignment(ID, ColumnAlignment.RIGHT) (7)
.hidden(DESCRIPTION, true) (8)
.resizable(ID, false) (9)
.width(ID, 100) (10)
.expandRatio(DESCRIPTION, 1) (11)
.minWidth(DESCRIPTION, 200) (12)
.maxWidth(DESCRIPTION, 300) (13)
.minimumWidthFromContent(DESCRIPTION, true) (14)
.style(ID, (property, item) -> item.getValue(DESCRIPTION) != null ? "empty" : "not-empty") (15)
.withPropertyReorderListener((properties, userOriginated) -> { (16)
// ...
}).withPropertyResizeListener((property, widthInPixel, userOriginated) -> { (17)
// ...
}).withPropertyVisibilityListener((property, hidden, userOriginated) -> { (18)
// ...
}).build();
1 | Set a custom header caption for the ID property/column |
2 | Set the ID property/column localizable header caption providing the default message and the localization message code (See the Internationalization documentation for further information about localization) |
3 | Set a the header caption for the ID property/column using HTML markup |
4 | Set whether the listing columns can be hidden |
5 | Set that the ID property/column cannot be hidden |
6 | Set whether the listing columns can reordered |
7 | Set the ID property/column cell contents alignment |
8 | Set the DESCRIPTION property/column as hidden by default |
9 | Set the ID property/column as not resizable |
10 | Set the ID property/column width in pixels |
11 | Set the DESCRIPTION property/column expand ratio |
12 | Set the DESCRIPTION property/column minimum width |
13 | Set the DESCRIPTION property/column maximum width |
14 | Set to use the width of the contents in the column as DESCRIPTION property/column width |
15 | Set the ID property/column CSS style generator |
16 | Add a listener to be notified when the listing columns order changes |
17 | Add a listener to be notified when a property/column is resized |
18 | Add a listener to be notified when a property/column is shown or hidden |
9.4.2. Columns rendering
By default, the cells of a column bound to a Property
are rendered using the Holon Platform presenters available from the default registry, to provide the String
representation of the cell value.
See the String value presenters documentation for details. |
The default column rendering can be overridden using a custom Vaadin Renderer
. The provided 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, value -> String.valueOf(value), new TextRenderer()) (2)
.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 |
9.4.3. Item 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, index, 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 |
9.4.4. Items selection
The item listing component supports items selection, both in single and multiple mode. The listing can be made selectable by 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 |
The select all checkbox shown in the top left corner of the listing is visible by default. You can control the checkbox visibility using the selectAllCheckBoxVisibility(…) method of the item listing builder.
|
9.4.5. Items 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 be always 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
QueryConfigurationProvider
to 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 |
9.4.6. 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 items 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 |
9.4.7. 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 |
9.4.8. 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 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)
.editorBuffered(true) (2)
.editorSaveCaption("Save item") (3)
.editorCancelCaption("Discard") (4)
.editable(ID, false) (5)
.editor(DESCRIPTION, new TextField()) (6)
.withValidator(Validator.create(pb -> pb.getValue(DESCRIPTION) != null, "Description must be not null")) (7)
.withValidator(DESCRIPTION, Validator.max(100)) (8)
.required(ID) (9)
.build();
1 | Set the listing as editable |
2 | Set the row editor in buffered mode |
3 | Set the editor save button caption |
4 | Set the editor cancel button caption |
5 | Set a property as not editable |
6 | Set a custom editor field |
7 | Add an overall value validator |
8 | Add a property value validator |
9 | Set the ID property as required, automatically adding a not empty validator |
10. 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(VaadinIcons.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 |
10.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 |
11. 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 Field property renderer, which represents a
HasValue
VaadinComponent
-
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
DateField
or aDateTimeField
-
java.util.Date: renders the property as a
DateField
or aDateTimeField
-
Number: renders the property as a
TextField
which 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 (HasValue component) |
3 | Render the property as String type ViewComponent |
11.1. Property localization
When a LocalizationContext
is available as a context resource, it is used to localize any element to display which supports localization.
See the LocalizationContext documentation for details about the LocalizationContext API and the Context resources documentation for information about the Holon Platform Context architecture.
|
Concerning to a Property
related UI component, the LocalizationContext
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 an input field
-
Obtain the number format to be used to display a property value or to edit the property value through an input field
-
Obtain the default boolean localized text for the
true
andfalse
values
See the Internationalization documentation for further information about localization. |
11.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
,Field
andViewComponent
) -
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)
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 |
12. 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 |
13. 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 by 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();
// ...
});
14. Navigator
Maven coordinates:
<groupId>com.holon-platform.vaadin</groupId>
<artifactId>holon-vaadin-navigator</artifactId>
<version>5.2.4</version>
The holon-vaadin-navigator
artifact makes available an extension of the default Vaadin view navigator, represented by the ViewNavigator API.
The ViewNavigator
API, compared to the standard Vaadin Navigator one, provides additional features and configuration capabilities:
-
View
navigation history tracking, allowing to navigate back to the previous view. -
View
parameters management, allowing to obtain the parameters values by injection in aView
instance. -
Easy
View
lifecycle management, supporting@OnShow
and@OnLeave
annotated 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
View
instances.
14.1. View 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
-
Number
(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;
}
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 |
14.2. View lifecycle management
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 is present in the View
class or in its 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)
// ...
}
}
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 |
14.3. Providing the View contents
By default, the View
is rendered as the UI component which is represented by the View
class.
You can override the standard Vaadin 8 getViewComponent()
method of the View
class to control which Component
to provide as View
content.
class ViewExampleContent extends VerticalLayout implements View { (1)
public ViewExampleContent() {
super();
addComponent(new Label("View content"));
}
}
class ViewExampleContentProvider implements View {
@Override
public Component getViewComponent() { (2)
boolean mobile = DeviceInfo.get().map(info -> info.isMobile()).orElse(false);
return mobile ? buildMobileViewContent() : buildDefaultViewContent();
}
}
1 | Default View implementation extending a Component class (a VerticalLayout ). The displayed View content will be the component itself |
2 | Provide the View content using the getViewComponent() method. 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 |
14.4. ViewNavigator Configuration
Just like the standard Vaadin navigator, the ViewNavigator
implementation requires two elements which must be configured in order to work properly:
-
A
ViewProvider
to provide theView
instances by view name -
A
View
display component to show the view contents in the UI, which can be aComponentContainer
(replacing the contents of the container with the activeView
content), aSingleComponentContainer
(using thesetContent(…)
method to set the activeView
content) or aViewDisplay
object to implement a customView
display 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 otherView
is available in navigation history -
Setting a error view or a error
ViewProvider
to provide theView
to be displayed when no otherView
matches a navigation state -
Setting the max navigation history size
-
Register a
ViewChangeListener
for listening toView
changes 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(e -> { (9)
// ...
return true;
}).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 |
14.5. Excluding a View form navigation history
By default, the ViewNavigator
tracks 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 {
}
14.6. Opening Views in a Window
The ViewNavigator
API supports displaying a View
content in an application Window
. Instead of using the configured view display component, the View
contents will be displayed in a popup dialog, using the com.vaadin.ui.Window
component.
The view navigation state is tracked in the navigator history just like any other navigation operation.
To open a View
in a Window
, the navigateInWindow(…)
view navigator methods can be used.
ViewNavigator.require().navigateInWindow("myView"); (1)
1 | Opens the View named myView in a Window |
14.6.1. @WindowView
annotation
The WindowView annotation can be used to declare that a View
should always be displayed in a Window
by the ViewNavigator
.
When 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.
@WindowView (1)
class MyWindowView1 implements View {
}
void openTheView() {
ViewNavigator.require().navigateTo("myView"); (2)
}
1 | Configure the View class to be always displayed in a Window |
2 | Supposing that the MyWindowView1 class is bound to the myView name, the ViewNavigator will display such View in a Window since the @WindowView annotation is found on the view class |
14.6.2. View Window
configuration
When a View
is displayed in a Window
, the Window
component which contains the View
can be configured in the following ways:
1. Using the provided ViewWindowConfigurator
: The ViewNavigator
API provides a navigateInWindow(…)
method version which provides a ViewWindowConfigurator API to configure the Window
component into which the View
will be displayed.
ViewNavigator.require().navigateInWindow("myView", configurator -> {
configurator.fullWidth().caption("Title").closable(false).resizable(false); (1)
});
1 | Use the ViewWindowConfigurator API to configure the Window component |
2. Using the @WindowView
annotation attributes: When a View
is configured to be displayed in a Window
using the @WindowView
annotation, the available annotation attributes can be used to configure some basic Window
component features.
@WindowView(windowWidth = "50%", windowHeigth = "50%", closable = false, resizable = true) (1)
class MyWindowView2 implements View {
}
1 | Use the @WindowView annotation attributes to configure the view Window |
3. Using a @ViewWindowConfiguration
annotated View method: If a View
class public
method is annotated with the @ViewWindowConfiguration
annotation and accepts a single parameter of ViewWindowConfigurator type, the method is called by the ViewNavigator
, providing the ViewWindowConfigurator
instance which can be used to configure the view Window
component.
@WindowView
class MyWindowView3 implements View {
@ViewWindowConfiguration (1)
public void configure(ViewWindowConfigurator configurator) {
configurator.fullWidth().caption("Title").closable(false).resizable(false);
}
}
1 | This method will be called by the ViewNavigator before displaying the View, allowing to configure the view Window component using the provided ViewWindowConfigurator |
14.7. ViewNavigator
API operations
The ViewNavigator
API makes available 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 View 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 givenView
name, providing an optional Map of parameters with parameters names and corresponding values. -
navigateTo(String viewName)
: navigate to givenView
name 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
|
14.8. 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
, then 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(view.getViewComponent(), viewName, VaadinIcons.PIN);
return true;
}
@Override
public View getCurrentView() {
return (View) getSelectedTab();
}
}
@SubViewOf("mycontainer")
public class SubViewExample extends VerticalLayout implements View { (2)
public SubViewExample() {
super();
setMargin(true);
addComponent(new Label("The sub view 1"));
}
}
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 |
14.9. 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;
}
14.10. Obtain the current ViewNavigator
The ViewNavigator
interface provides methods to obtain the current navigator using the following strategy:
-
If the
ViewNavigator
is available as Context resource using the default navigator resource key, that instance is returned; -
If a current Vaadin
UI
is available and aViewNavigator
is 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 |
14.11. 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
VaadinRequest
and the optional authentication schemes which can be specified using theschemes()
attribute of theAuthenticate
annotation. This behaviour can be used, for example, to support authentication using the current HTTP request and schemes such theAuthorization
HTTP header. -
If the implicit authentication is not successfull and a valid redirect URI is provided through the
redirectURI()
property of theAuthenticate
annotation, 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 aView
an explicit authentication entry point (for example using conventional username and password credentials).
In order for the View authentication process to work properly, when the @Authenticate annotation is used on one or more specific View class (and not at UI level), a ViewClassProvider instance has to be bound to each registered ViewProvider to provide the View class which corresponds to a specific view name. The ViewNavigator builder API provides the viewClassProvider(…) method to configure the ViewClassProvider . When the Spring integration is enabled and the default SpringViewNavigator is used, a default ViewClassProvider is automatically configured, without needing to configure it explicitly.
|
15. Spring integration
The holon-vaadin-spring
artifact provides support and integration with the Spring framework.
Maven coordinates:
<groupId>com.holon-platform.vaadin</groupId>
<artifactId>holon-vaadin-spring</artifactId>
<version>5.2.4</version>
This artifact provides a ViewNavigator
extension with Spring support, represented by the SpringViewNavigator API.
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
View
class 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 otherView
is available in navigation history. -
ErrorView: can be used on a
View
class to declare it as the default error view, i.e. theView
to be displayed when no otherView
matches a navigation state.
15.1. Default View navigation strategy
When a statically declared default View
(using for example the @DefaultView
annotation) is not suitable for an application, the DefaultViewNavigationStrategy interface can be used to implement a custom strategy to provide the default navigation state.
When a DefaultViewNavigationStrategy
type Spring bean is found in application context, it is automatically configured in View navigator as default view navigation stategy implementation.
15.2. 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 register 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 {
}
@SpringView(name = "view2") (7)
@UIScope (8)
class ViewTwo extends VerticalLayout implements View {
}
@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 |
15.3. 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.
15.4. 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 Spring context scope 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 {
}
@SpringView(name = "view2")
@RolesAllowed("role1") (5)
class ViewTwo extends VerticalLayout implements View {
}
@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 |
15.5. Spring Security support
The View
access control using the Spring Security @Secured
annotation can be enabled using the EnableSecuredView annotation.
This access control strategy relies on the Spring Security SecurityContext
to check current authentication and performs View access control checking current user granted authorities against the security attributes specified through the @Secured
annotation.
The Spring Security artifacts must be explicitly included as a project dependency. |
16. 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.vaadin</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
View
authorization support using thejavax.annotation.security.*
annotations is enabled by default. -
If Spring Security is available and configured, the
@Secured
annotation basedView
access control is enabled.
To disable this auto-configuration feature the HolonVaadinAutoConfiguration
class can be excluded:
@EnableAutoConfiguration(exclude={HolonVaadinAutoConfiguration.class})
To disable the Spring Security @Secured
access control support the HolonVaadinSpringSecurityAutoConfiguration
class can be excluded:
@EnableAutoConfiguration(exclude={HolonVaadinSpringSecurityAutoConfiguration.class})
16.1. Vaadin servlet configuration
By default, a Holon extension of the Vaadin servlet is auto-configured.
This servlet allows automatic com.vaadin.server.SessionInitListener
and com.vaadin.server.SessionDestroyListener
registration if they are defined as Spring beans and made available in the Spring context.
To disable this auto-configuration feature, the HolonVaadinServletAutoConfiguration
class can be excluded:
@EnableAutoConfiguration(exclude={HolonVaadinServletAutoConfiguration.class})
16.2. Spring Boot starters
The following starter artifacts are available to provide a quick project configuration setup using Maven dependency system:
1. The Vaadin application starter provides the dependencies to the Holon Platform Vaadin Spring Boot integration artifact holon-vaadin-spring-boot
, in addition to:
-
The Holon Platform Core Module Spring Boot integration base starter (
holon-starter
). -
The Spring Boot
spring-boot-starter-web
starter. -
The Spring Boot
spring-boot-starter-tomcat
to use Tomcat as the embedded servlet container.
See the Spring Boot starters documentation for details on Spring Boot starters.
Maven coordinates:
<groupId>com.holon-platform.vaadin</groupId>
<artifactId>holon-starter-vaadin</artifactId>
<version>5.2.4</version>
2. The Vaadin application starter using Undertow provides the same dependencies as the default Vaadin application starter, but using Undertow instead of Tomcat as embedded servlet container.
Maven coordinates:
<groupId>com.holon-platform.vaadin</groupId>
<artifactId>holon-starter-vaadin-undertow</artifactId>
<version>5.2.4</version>
17. 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
.