@lion/ui
Version:
A package of extendable web components
213 lines (177 loc) • 7.29 kB
Markdown
---
parts:
- How To
- Create a custom field
title: 'How To: Create a custom field'
eleventyNavigation:
key: 'How To: Create a custom field'
order: 20
parent: How To
title: Create a custom field
---
# How To: Create a custom field
```js script
import { html, css, LitElement } from '@mdjs/mdjs-preview';
import { LionField } from '@lion/ui/form-core.js';
import '@lion/ui/define/lion-field.js';
import '@lion/ui/define/lion-validation-feedback.js';
import '../../fundamentals/systems/form/assets/h-output.js';
// A) the custom [slot=input] or 'HTMLElementWithValue'
class DummySlider extends LitElement {
// A1) it should have a .value property of type 'string'
static properties = { value: String, reflect: true };
static styles = [
css`
:host {
display: block;
padding-top: 16px;
padding-bottom: 16px;
}
[part='rail'] {
position: relative;
display: block;
background: #eee;
height: 8px;
border-radius: 8px;
}
[part='thumb'] {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
background: black;
color: white;
border-radius: 50%;
height: 24px;
width: 24px;
top: 50%;
transform: translateY(-50%) translateX(-50%);
transition: left 0.5s ease 0s;
}
`,
];
constructor() {
super();
this.value = '0';
this.addEventListener('click', ev => {
this.value = `${Math.round(
((ev.clientX - this.getClientRects()[0].x) / this.offsetWidth) * 5,
)}`;
// A2) it should have a way to tell LionField its value changed
this.dispatchEvent(new Event('dummy-slider-changed', { bubbles: true }));
});
}
connectedCallback() {
super.connectedCallback();
this.setAttribute('tabindex', 0);
}
render() {
return html` <div part="rail">
<span part="thumb" style="left:${Number(this.value) * 20}%;">${this.value}</span>
</div>`;
}
}
if (!customElements.get('dummy-slider')) {
customElements.define('dummy-slider', DummySlider);
}
```
Custom Fields can be created in just a few steps. All you need is an interaction element (like for instance a slider, a listbox or a combobox) and connect it to the [LionField](https://github.com/ing-bank/lion/blob/4c077fbf1de58ec799dd263ebe7d94dfef1262c0/docs/components/input/overview.md).
> In case you want to extend a native element, follow [Extend a native Input](https://github.com/ing-bank/lion/blob/4c077fbf1de58ec799dd263ebe7d94dfef1262c0/docs/guides/how-to/extend-a-native-input.md).
## A) an interaction element
An interaction element (.\_inputNode) provides the means for the end user to enter a certain value,
just like native elements provide in this (think of input, textarea and select).
An example of a non native element is the [slider design pattern](https://www.w3.org/TR/wai-aria-practices-1.1/#slider) described here.
For this tutorial, we create a dummy component 'dummy-slider' that exposes its value via
property `.value` and sends an event `dummy-slider-changed` on every value change.
To make it focusable, it has a tabindex=“0” applied.
```js preview-story
export const createAnInteractiveElement = () => {
// A) the custom [slot=input] or 'HTMLElementWithValue'
class DummySlider extends LitElement {
// A1) it should have a .value property of type 'string'
static properties = { value: String };
constructor() {
super();
this.value = 0;
this.addEventListener('click', ev => {
this.value = `${Math.round(
((ev.clientX - this.getClientRects()[0].x) / this.offsetWidth) * 5,
)}`;
// A2) it should have a way to tell LionField its value changed
this.dispatchEvent(new Event('dummy-slider-changed', { bubbles: true }));
});
}
connectedCallback() {
super.connectedCallback();
this.setAttribute('tabindex', 0);
}
render() {
return html` <div part="rail">
<span part="thumb" style="left:${Number(this.value) * 20}%;">${this.value}</span>
</div>`;
}
}
return html`<dummy-slider></dummy-slider>`;
};
```
## B) your LionField extension
Now we want to integrate the slider in our form system to enrich the user interface, get
validation support and all other [benefits of LionField](https://github.com/ing-bank/lion/blob/4c077fbf1de58ec799dd263ebe7d94dfef1262c0/docs/components/input/overview.md).
We start by creating a component `<slider-field>` that extends from `LionField`.
Then we follow the steps below:
- **Add your interaction element**
Here you return the element the user interacts with. By configuring it as a slot, it will end up in
light DOM, ensuring the best accessibility for the end user.
- **Connect modelValue**
The `user-input-changed` event is listened to by the FormatMixin: it should be regarded as the
equivalent of the `input` event of the platform, but for custom built interaction elements.
You now synchronized [modelValue](https://github.com/ing-bank/lion/blob/4c077fbf1de58ec799dd263ebe7d94dfef1262c0/docs/fundamentals/systems/form/model-value.md), which can be regarded as
the glue to integrate all other functionality like parsing/formatting/serializing, validating,
tracking interaction states etc.
> N.B. Make sure you never override other property getters than the one mentioned in this tutorial,
> because those properties will lose their reactivity (they won't be considered anymore in the
> update loop of LitElement).
> Whenever a .modelValue/.formattedValue/.serializedValue needs to be computed, use
> [parser/formatter/serializer](https://github.com/ing-bank/lion/blob/4c077fbf1de58ec799dd263ebe7d94dfef1262c0/docs/fundamentals/systems/form/formatting-and-parsing.md)
Implement with the following code:
```js preview-story
export const createAField = () => {
// B) your extension with all the Field goodness...
class SliderField extends LionField {
// B1) Add your interaction element as ‘input slot'
get slots() {
return {
...super.slots,
input: () => document.createElement('dummy-slider'),
};
}
// B2) Connect modelValue
constructor() {
super();
this.addEventListener('dummy-slider-changed', ev => {
ev.stopPropagation();
this.dispatchEvent(new Event('user-input-changed'));
});
}
get value() {
// Remember we should always return type 'string' here
return this._inputNode.value;
}
set value(newV) {
this._inputNode.value = newV;
}
}
customElements.define('slider-field', SliderField);
return html`<slider-field
label="SliderField"
help-text="Press to see how modelValue is synchronized"
></slider-field>
<h-output .show="${['modelValue', 'touched', 'dirty', 'focused']}"></h-output>`;
};
```
That was all!
Now that your .modelValue is connected your component is fully compatible with our form system
> Is your `.modelValue` still undefined? Please make sure your `.value` is of type 'string'.
Now you can enhance your slider by writing custom Validators for it or by
writing a parser to get a custom modelValue type.