Binding Data to Input Fields
Client Form binder supports out of the box the set of form elements present in Hilla components. This means that data binding, type mapping, required flag, and validation messages can function without any extra work.
Configuring Server Data & Endpoint
When defining Bean objects in Java, apart from the type, you can define validation and error messages.
For the examples here, the code on the client side is 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 = "[email protected]";
@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");
}
}
Configuring Client Fields
Before using form binders, you need to import your entity models and instantiate the binder. You also need to perform the endpoint call to get the entity value, as explained in the articles Binding Data to Forms and Loading from and Saving to Business Objects.
For the examples in this article, these are the significant lines needed in the view:
import { MyEndpoint } from 'Frontend/generated/MyEndpoint';
import MyEntityModel from 'Frontend/generated/com/example/MyEntityModel';
...
const form = useForm(MyEntityModel);
...
useEffect(() => {
MyEndpoint.getMyEntity().then(form.read);
}, []);
Binding Data to Text & Number Fields
No extra action is needed to configure the Vaadin components that represent Text & Number values.
import { EmailField } from '@vaadin/react-components/EmailField.js';
import { IntegerField } from '@vaadin/react-components/IntegerField';
import { NumberField } from '@vaadin/react-components/NumberField.js';
import { PasswordField } from '@vaadin/react-components/PasswordField.js';
import { TextArea } from '@vaadin/react-components/TextArea.js';
import { TextField } from '@vaadin/react-components/TextField.js';
...
const { model, field } = useForm(MyEntityModel);
...
return (
<>
<TextField label="string" {...field(model.myTextField)} />
<PasswordField label="password" {...field(model.myPasswordField)} />
<IntegerField label="integer" {...field(model.myIntegerField)} stepButtonsVisible />
<NumberField label="number" {...field(model.myDoubleField)} stepButtonsVisible />
<EmailField label="email" {...field(model.myEmailField)} />
<TextArea label="textarea" {...field(model.myTextField)} />
</>
);
Binding Data to Boolean Fields
In Hilla, three components are available for handling booleans: Checkbox
, RadioButton
, and Select
.
The Checkbox
and RadioButton
work fine with form binders, but neither of them has validation and failure styling. Hence, you need to do some extra work to give error feedback. In the following snippet, the background color is changed on validation error.
vaadin-checkbox[invalid], vaadin-radio-button[invalid] {
background: var(--lumo-error-color-10pct);
}
import { Checkbox } from '@vaadin/react-components/Checkbox.js';
import { RadioButton } from '@vaadin/react-components/RadioButton.js';
import './my-styles.module.css';
...
const { model, field } = useForm(MyEntityModel);
...
return (
<>
<Checkbox label="checkbox" {...field(model.myBooleanField)} />
<RadioButton label="radio-button" {...field(model.myBooleanField)} />
</>
);
The Select
can be bound to a boolean, as in the following snippet:
import { Select } from '@vaadin/react-components/Select.js';
...
const { model, field } = useForm(MyEntityModel);
const selectItems = [
{ label: 'Value is true', value: 'true' },
{ label: 'Value is false', value: 'false' }
];
...
return (
<Select
label="select"
{...field(model.myBooleanField)}
items={selectItems}
/>
);
Binding Data to List Fields
Hilla has several components for selection based on option lists, each one covering a specific purpose, Hence, there are various ways to set their values and options.
Configuring Options for Selection
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
use the Typescript map
on the list received from the endpoints.
As reference, the following snippet demonstrates how to repeat a pattern, given an asynchronous method that returns a list of items. The same pattern is used in the code blocks for each component that follow.
const options = useSignal<Array<Entity>>([]);
...
// using effects to execute only once
useEffect(() => {
MyEndpoint.getMyOptions().then(opts => options.value = opts)
}, []);
...
return (
<>
{options.value.map(opt => (
<div>{JSON.stringify(opt)}</div>
))}
</>
)
Single Selection Using Item Value
For a single selection, you should use ComboBox
, RadioGroup
or ListBox
. They can all take the selected item value as a string.
import { useEffect } from 'react';
import { ComboBox } from '@vaadin/react-components/ComboBox.js';
import { Item } from '@vaadin/react-components/Item.js';
import { ListBox } from '@vaadin/react-components/ListBox.js';
import { RadioButton } from '@vaadin/react-components/RadioButton.js';
import { RadioGroup } from '@vaadin/react-components/RadioGroup.js';
...
const { model, field } = useForm(MyEntityModel);
const myOptions = useSignal<Array<MyEntity>>([]);
...
useEffect(() => {
MyEndpoint.getMyOptions().then(myEntities => myOptions.value = myEntities)
}, []);
...
return (
<>
<ComboBox
label="combo-box"
{...field(model.mySingleSelectionField)}
items={myOptions.value}
/>
<RadioGroup label="radio-group" {...field(model.mySingleSelectionField)}>
{myOptions.value.map(option => (
<RadioButton value={option} label={option} />
))}
</RadioGroup>
<ListBox label="list-box" {...field(model.mySingleSelectionField)}>
{myOptions.value.map(option => (
<Item value={option} label={option} />
))}
</ListBox>
</>
);
Single Selection Using Index
To select items by index, you should use the Select
component. This accepts an integer for the index value.
import { useEffect } from 'react';
import { useSignal } from '@vaadin/hilla-react-signals';
import { Select } from '@vaadin/react-components/Select.js';
...
const { model, field } = useForm(MyEntityModel);
const myOptions = useSignal<Array<MyEntity>>([]);
...
useEffect(() => {
MyEndpoint.getMyOptions().then(myEntities => myOptions.value = myEntities);
}, []);
...
return (
<Select label="select" {...field(model.mySelectField)}>
{myOptions.value.map((option) => (
<Item value={option} label={option} />
))}
</Select>
);
Multiple Selection
The Vaadin components for multiple selection are CheckboxGroup
and MultiSelectComboBox
. Both of them accept an array of strings.
import { CheckboxGroup } from '@vaadin/react-components/CheckboxGroup.js';
import { Checkbox } from '@vaadin/react-components/Checkbox.js';
import { MultiSelectComboBox } from '@vaadin/react-components/MultiSelectComboBox.js';
...
const { model, field } = useForm(MyEntityModel);
const myOptions = useSignal<Array<MyEntity>>([]);
...
useEffect(() => {
MyEndpoint.getMyOptions().then(myEntities => myOptions.value = myEntities);
}, []);
...
return (
<>
<CheckboxGroup label="check-group" {...field(model.myListField)}>
{myOptions.value.map((option) => (
<Checkbox value={option} label={option} />
))}
</CheckboxGroup>
<MultiSelectComboBox label="multi-select" items={myOptions.value} />
</>
);
Binding Data to Date & Time Fields
Use DatePicker
to bind to Java LocalDate
, TimePicker
for LocalTime
, and vaadin-date-time-picker
for LocalDateTime
.
import { DatePicker } from '@vaadin/react-components/DatePicker.js';
import { DateTimePicker } from '@vaadin/react-components/DateTimePicker.js';
import { TimePicker } from '@vaadin/react-components/TimePicker.js';
...
return (
<>
<DatePicker label="date" {...field(model.myDateField)} />
<TimePicker label="time" {...field(model.myTimeField)} />
<DateTimePicker label="date-time" {...field(model.myDateTimeField)} />
</>
);
Wrapping Components in Custom Fields
Hilla provides the CustomField
, which can be used to wrap one or multiple Vaadin fields. This works with the following components:
-
TexField
-
NumberField
-
PasswordField
-
TextArea
-
Select
-
ComboBox
-
DatePicker
-
TimePicker
import { CustomField } from '@vaadin/react-components/CustomField.js';
import { TextField } from '@vaadin/react-components/TextField.js';
...
return (
<CustomField {...field(model.myTextField)}>
<TextField label="custom-field" />
</CustomField>
);
You should be aware of the limitations when using vaadin-custom-field
with other elements previously listed:
-
the value of the custom field should be provided as a string; and
-
children should have the
value
property in their API.