Hilla Documentation

Binding Data to Input Fields

Implementing data binding, type mapping and error messages when binding data with Hilla components.

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 will function without any extra work.

Configuring Server Data and Endpoint

When defining Bean objects in Java, apart from the type, we can define validation and error messages.

For this article, 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 = "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");
    }
}

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 tutorials Binding Data to Forms and Loading from and Saving to Business Objects.

For 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 binder = new Binder(this, MyEntityModel);
...
async firstUpdated(arg: any) {
  super.firstUpdated(arg);
  this.binder.read(await MyEndpoint.getMyEntity());
}

Binding Data to Text and Number Fields

No extra action is needed to configure the Vaadin components present in the vaadin-text-field package.

import '@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>
  `;
}

Binding Data to Boolean Fields

There are three elements in Hilla to deal with booleans: vaadin-checkbox, vaadin-radio-button, and vaadin-select.

vaadin-checkbox and vaadin-radio-button 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.

import '@vaadin/checkbox';
import '@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
      label="checkbox"
      ${field(this.binder.model.myBooleanField)}
    ></vaadin-checkbox>
    <vaadin-radio-button
      label="radio-button"
      ${field(this.binder.model.myBooleanField)}
    ></vaadin-radio-button>
  `;
}

vaadin-select can be bound to a boolean, as in the following snippet:

import { selectRenderer } from '@vaadin/select/lit';
import '@vaadin/select';
import '@vaadin/list-box';
import '@vaadin/item';
...
<vaadin-select
  ${field(this.binder.model.myBooleanField)}
  ${selectRenderer(
    () => html`
      <vaadin-list-box>
        <vaadin-item value="true">Value is true</vaadin-item>
        <vaadin-item value="false">Value is false</vaadin-item>
      </vaadin-list-box>
    `,
    []
  )}
></vaadin-select>

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 the 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 combine the until() and repeat() methods from the Lit 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 that follow.

import { until } from 'lit/directives/until.js';
import { repeat } from 'lit/directives/repeat.js';
...
render() {
  return html`
  ...
    ${until(MyEndpoint.getMyOptions().then(opts => repeat(opts, (item) => html`
      <div>${item}</div>
    `)))}
  ...
  `;
}

Single Selection Using the Item Value

For a single selection, you should use vaadin-combo-box, vaadin-radio-group or vaadin-list-box. They can all take the selected item value as a string.

import '@vaadin/combo-box';
import '@vaadin/list-box';
import '@vaadin/radio-group';
...
render() {
  return html`
    <vaadin-combo-box
      label="combo-box"
      ${field(this.binder.model.mySingleSelectionField)}
      .items="${until(MyEndpoint.getMyOptions())}"
    ></vaadin-combo-box>

    <vaadin-radio-group
      label="radio-group"
      ${field(this.binder.model.mySingleSelectionField)}
    >
      ${until(
        MyEndpoint.getMyOptions().then((opts) =>
          repeat(
            opts,
            (item) => html`
              <vaadin-radio-button value="${item}" label="${item}"></vaadin-radio-button>
            `
          )
        )
      )}
    </vaadin-radio-group>

    <vaadin-list-box
      label="list-box"
      ${field(this.binder.model.mySingleSelectionField)}
    >
      ${until(
        MyEndpoint.getMyOptions().then((opts) =>
          repeat(opts, (item) => html`<vaadin-item>${item}</vaadin-item>`)
        )
      )}
    </vaadin-list-box>
  `;
}

Single Selection Using Index

To select items by index, you should use the vaadin-select component. This 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/select';
import { selectRenderer } from '@vaadin/select/lit';
...
render() {
  return html`
    <vaadin-select
      label="select"
      ${field(this.binder.model.mySelectField)}
      ${selectRenderer(
        () => html`
          <vaadin-list-box>
            <vaadin-item>item-1</vaadin-item>
            <vaadin-item>item-2</vaadin-item>
          </vaadin-list-box>
        `,
        []
      )}
    ></vaadin-select>
  `;
}

The inline .renderer() function is encapsulated by the guard directive for performance reasons.

Multiple Selection

The Vaadin component for multiple selection is the vaadin-checkbox-group, which accepts an array of strings.

import '@vaadin/checkbox-group';
...
render() {
  return html`
    <vaadin-checkbox-group label="check-group" ${field(this.binder.model.myListField)}>
      ${until(
        MyEndpoint.getMyOptions().then((opts) =>
          repeat(
            opts,
            (item) => html`<vaadin-checkbox value="${item}" label="${item}"></vaadin-checkbox>`
          )
        )
      )}
    </vaadin-checkbox-group>
  `;
}

Binding Data to Date and Time Fields

Use vaadin-date-picker to bind to Java LocalDate, vaadin-time-picker for LocalTime, and vaadin-date-time-picker for LocalDateTime.

import '@vaadin/date-picker';
import '@vaadin/time-picker';
import '@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
      label="date-time"
      ${field(this.binder.model.myDateTimeField)}
    ></vaadin-date-time-picker>
  `;
}

Wrapping Components in Custom Fields

Hilla provides the vaadin-custom-field, which can be used to wrap one or multiple Vaadin fields. This 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/custom-field';
import '@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 when using vaadin-custom-field with other elements previously listed:
  • the value of the custom field should be provided as a string;

  • children should have the value property in their API.