Binding Data to Custom Components
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 asrequired
anderrorMessage
; -
create a custom binder subclass that associates the new custom field strategy with the custom web component tag.
Example of a Web Component
Consider the following LitElement component for use in form binding.
It doesn’t expose properties such as required
, invalid
and errorMessage
, which 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 '@hilla/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 the Field Strategy
Subclass the Binder class and override the getFieldStrategy(element)
method to use a custom field strategy for any my-text-field
components it finds in a form:
import { Binder } from '@hilla/form';
import type { AbstractModel, FieldStrategy, ModelConstructor } from '@hilla/form';
import { MyTextFieldStrategy } from './my-text-field-strategy';
export class MyBinder<T, M extends AbstractModel<T>> extends Binder<T, M> {
constructor(context: Element, model: ModelConstructor<T, M>) {
super(context, model);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getFieldStrategy(element: any): FieldStrategy {
if (element.localName === 'my-text-field') {
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 '@hilla/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>
`;
}
//...
}