title | order | layout |
---|---|---|
Appendix: Binding Data to Vaadin Components |
100 |
page |
Client Form binder supports out-of-the-box the set of form elements present in Vaadin Components. It means that data binding, type mapping, required flag, and validation messages will work without any extra work.
When defining Bean objects in Java, apart from the type we can define validation and error messages.
For this article, the code in the client-side will be based on the data and endpoints defined in the following java classes:
public class MyEntity {
@AssertTrue(message = "Please agree this")
Boolean myBooleanField = false;
@NotEmpty(message = "Select at least one option")
List<String> myListField = Arrays.asList("item-1");
@Pattern(regexp = "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,}",
message = "must be 8+ characters, with uppercase, lowercase, and numbers")
String myPasswordField = "bar";
@Email(message = "must be a valid email address")
String myEmailField = "foo@bar.baz";
@PositiveOrZero(message = "Should be positive or zero")
Integer myIntegerField = 12;
@Positive(message = "Should be positive")
Double myDoubleField = 12.33d;
@Future(message = "Should be a date in the future")
LocalDate myDateField = LocalDate.now().plusDays(1);
LocalDateTime myDateTimeField = LocalDateTime.now();
LocalTime myTimeField = LocalTime.now();
@Min(0) @Max(1)
Integer mySelectField = 1;
}
@Endpoint
public class MyEndpoint {
public MyEntity getMyEntity() {
return new MyEntity();
}
public List<String> getMyOptions() {
return Arrays.asList("item-1", "item-2", "item-3");
}
}
Before using form binders, you need to import your entity models and instantiate the binder, as well as perform the endpoint call for getting the entity value, as it is explained in tutorials Binding Data to Client-Side Forms and Loading from and Saving to Business Objects respectively.
For this article, these are the significant lines needed in the view:
import * as endpoint from '../../generated/MyEndpoint';
import MyEntityModel from '../../generated/com/example/MyEntityModel';
...
const binder = new Binder(this, MyEntityModel);
...
async firstUpdated(arg: any) {
super.firstUpdated(arg);
this.binder.read(await endpoint.getMyEntity());
}
No extra action is needed for configuring the Vaadin components present in the vaadin-text-field
package.
import '@vaadin/vaadin-text-field';
...
render() {
return html`
<vaadin-text-field ...="${field(this.binder.model.myTextField)}" label="string"></vaadin-text-field>
<vaadin-password-field ...="${field(this.binder.model.myPasswordField)}" label="password"></vaadin-password-field>
<vaadin-integer-field ...="${field(this.binder.model.myIntegerField)}" label="integer" has-controls></vaadin-integer-field>
<vaadin-number-field ...="${field(this.binder.model.myDoubleField)}" label="number" has-controls></vaadin-number-field>
<vaadin-email-field ...="${field(this.binder.model.myEmailField)}" label="email"></vaadin-email-field>
<vaadin-text-area ...="${field(this.binder.model.myTextField)}" label="textarea"></vaadin-text-area>
`;
}
There are two elements in Vaadin to deal with booleans: vaadin-checkbox
and vaadin-radio-button
,
both work fine with form binders, but neither of them have validation and failure styling, thus you need some
extra work for giving error feedback. In the following snippet the background color is changed on validation error.
import '@vaadin/vaadin-checkbox';
import '@vaadin/vaadin-radio-button';
...
static get styles() {
return css`
vaadin-checkbox[invalid], vaadin-radio-button[invalid],
background: var(--lumo-error-color-10pct);
}`;
}
...
render() {
return html`
<vaadin-checkbox ...="${field(this.binder.model.myBooleanField)}">checkbox</vaadin-checkbox>
<vaadin-radio-button ...="${field(this.binder.model.myBooleanField)}">radio-button</vaadin-radio-button>
`;
}
Vaadin has several components for selection based on option lists, each one covering a specific purpose, hence the way to set their values and options differs.
Options for these components can be set by calling a server side service that provides the list of strings.
Since the call to the endpoint is asynchronous, one easy way is to combine the until
and repeat
methods in the lit-html
library.
As reference, the following snippet demonstrates how to repeat a pattern given an asynchronous method that returns a list of items. The same pattern will be used in the code blocks for each component below.
import {until} from 'lit-html/directives/until.js';
import {repeat} from 'lit-html/directives/repeat.js';
...
render() {
return html`
...
${until(endpoint.getMyOptions().then(opts => repeat(opts, (item) => html`
<div>${item}</div>
`)))}
...
`;
}
For a single selection vaadin-combo-box
, vaadin-radio-group
or vaadin-list-box
should be used.
All of them can take the selected item value as a string.
import '@vaadin/vaadin-combo-box';
import '@vaadin/vaadin-list-box';
import '@vaadin/vaadin-radio-button/vaadin-radio-group';
...
render() {
return html`
<vaadin-combo-box ...="${field(this.binder.model.mySingleSelectionField)}"
.items="${until(endpoint.getMyOptions())}" label="combo-box">
</vaadin-combo-box>
<vaadin-radio-group ...="${field(this.binder.model.mySingleSelectionField)}" label="radio-group">
${until(endpoint.getMyOptions().then(opts => repeat(opts, (item) => html`
<vaadin-radio-button value="${item}">${item}</vaadin-radio-button>
`)))}
</vaadin-radio-group>
<vaadin-list-box ...="${field(this.binder.model.mySingleSelectionField)}" label="list-box">
${until(endpoint.getMyOptions().then(opts => repeat(opts, (item) => html`
<vaadin-item><span>${item}</span></vaadin-item>
`)))}
</vaadin-list-box>
`;
}
To select items by index, the vaadin-select
component should be used. It accepts an integer for the index value.
Because this component is configurable via the template
tag, it’s not possible to set the options with an asynchronous call.
import '@vaadin/vaadin-select';
...
render() {
return html`
<vaadin-select ...="${field(this.binder.model.mySelectField)}" label="select">
<template>
<vaadin-list-box>
<vaadin-item><span>item-1</span></vaadin-item>
<vaadin-item><span>item-2</span></vaadin-item>
</vaadin-list-box>
</template>
</vaadin-select>
`;
}
The Vaadin component for multiple selection is the vaadin-checkbox-group
which accepts an array of strings.
import '@vaadin/vaadin-checkbox/vaadin-checkbox-group';
...
render() {
return html`
<vaadin-checkbox-group ...="${field(this.binder.model.myListField)}" label="check-group">
${until(endpoint.getMyOptions().then(opts => repeat(opts, (item) => html`
<vaadin-checkbox value="${item}">${item}</vaadin-checkbox>
`)))}
</vaadin-checkbox-group>
`;
}
Use vaadin-date-picker
for binding to Java LocalDate
, vaadin-time-picker
for LocalTime
, and vaadin-date-time-picker
for LocalDateTime
.
import '@vaadin/vaadin-date-picker';
import '@vaadin/vaadin-time-picker';
import '@vaadin/vaadin-date-time-picker';
...
render() {
return html`
<vaadin-date-picker ...="${field(this.binder.model.myDateField)}" label="date"></vaadin-date-picker>
<vaadin-time-picker ...="${field(this.binder.model.myTimeField)}" label="time"></vaadin-time-picker>
<vaadin-date-time-picker ...="${field(this.binder.model.myDateTimeField)}" label="date-time"></vaadin-date-time-picker>
`;
}
Vaadin provides the vaadin-custom-field
that can be used to wrap one or multiple vaadin fields.
It works with the following components:
-
vaadin-text-field
-
vaadin-number-field
-
vaadin-password-field
-
vaadin-text-area
-
vaadin-select
-
vaadin-combo-box
-
vaadin-date-picker
-
vaadin-time-picker
import '@vaadin/vaadin-custom-field';
import '@vaadin/vaadin-text-field';
...
render() {
return html`
<vaadin-custom-field ...="${field(this.binder.model.myTextField)}" label="custom-field">
<vaadin-text-field></vaadin-text-field>
</vaadin-custom-field>
`;
}
Note
|
There are limitations on using vaadin-custom-field with other elements listed above:
|
-
value to the custom field should be provided as a string
-
children should have the
value
property in their API.