Documentation

Documentation versions (currently viewingVaadin 24.4 (pre))

Binding Data to Custom Components

How to create a form with a custom web component.

While Vaadin components are supported out-of-the-box, the client-side Form binder can be configured to support any web component. This article explains how to create a form with a custom <my-text-field> web component.

Using a custom web component in a form requires two steps:

  • Create a field strategy that tells the form binder how to get and set the component value and other properties, such as required and errorMessage; and

  • Create a custom binder subclass that associates the new custom field strategy with the custom web component tag.

Web Component Example

Consider the following LitElement component for use in form binding. It doesn’t expose properties such as required, invalid and errorMessage. This is why the default field strategy that works for all Vaadin components would not work here.

@customElement('my-text-field')
export class MyTextField extends LitElement {
  @property({ type: String }) label = '';
  @property({ type: String }) value = '';

  // custom properties that do not work with the default Binder
  @property({ type: Boolean }) mandatory = false;
  @property({ type: Boolean }) hasError = false;
  @property({ type: String }) error = '';

  //...
}

Defining a Field Strategy

The first step towards using a web component as a field is to define a field strategy. You need to implement the FieldStrategy interface, as in the following example:

import type { FieldStrategy } from '@vaadin/hilla-lit-form';
import type { MyTextField } from './my-text-field';

export class MyTextFieldStrategy implements FieldStrategy {
  public element: MyTextField;

  public constructor(element: MyTextField) {
    this.element = element;
  }

  set required(required: boolean) {
    this.element.mandatory = required;
  }

  set invalid(invalid: boolean) {
    this.element.hasError = invalid;
  }

  set errorMessage(errorMessage: string) {
    this.element.error = errorMessage;
  }
  // ...
}

Using a Field Strategy

The second step in using a custom web component is to make the Binder class a subclass and override the getFieldStrategy(element) method to use a custom field strategy for any my-text-field components it finds in a form. This method receives as arguments the element which is mapped to the component and the model, which can in turn affect the binding strategy. For example, the Combo Box component has specialized support for objects and arrays.

import { Binder, StringModel } from '@vaadin/hilla-lit-form';
import type { AbstractModel, DetachedModelConstructor, FieldStrategy } from '@vaadin/hilla-lit-form';
import { MyTextFieldStrategy } from './my-text-field-strategy';

export class MyBinder<M extends AbstractModel> extends Binder<M> {
  constructor(context: Element, model: DetachedModelConstructor<M>) {
    super(context, model);
  }

  override getFieldStrategy(element: any, model?: AbstractModel): FieldStrategy {
    if (element.localName === 'my-text-field' && model instanceof StringModel) {
      return new MyTextFieldStrategy(element);
    }
    return super.getFieldStrategy(element);
  }
}

You can now use my-text-field components in a form, provided that you use the extended MyBinder class to handle data binding in that form.

import { html, LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';
import { field } from '@vaadin/hilla-lit-form';
import './my-text-field';
import { MyBinder } from './my-binder';
import SamplePersonModel from 'Frontend/generated/com/vaadin/demo/fusion/forms/fieldstrategy/SamplePersonModel';

@customElement('person-form-view')
export class PersonFormViewElement extends LitElement {
  private binder = new MyBinder(this, SamplePersonModel);

  render() {
    return html`
      <h3>Personal information</h3>
      <vaadin-form-layout>
        <my-text-field
          label="First name"
          ...="${field(this.binder.model.firstName)}"
        ></my-text-field>
      </vaadin-form-layout>
    `;
  }
  //...
}