chore(form-system): add form fundament demos

This commit is contained in:
Thijs Louisse 2019-07-03 00:08:01 +02:00 committed by Thomas Allmer
parent 11ee5e99ec
commit 9d64cb36f5
5 changed files with 347 additions and 0 deletions

View file

@ -35,6 +35,7 @@
"@lion/checkbox": "^0.1.51",
"@lion/checkbox-group": "^0.1.57",
"@lion/core": "^0.1.13",
"@lion/field": "^0.2.3",
"@lion/fieldset": "^0.1.50",
"@lion/form": "^0.1.56",
"@lion/input": "^0.1.50",
@ -42,9 +43,11 @@
"@lion/input-date": "^0.1.51",
"@lion/input-email": "^0.1.50",
"@lion/input-iban": "^0.1.52",
"@lion/localize": "^0.4.14",
"@lion/radio": "^0.1.51",
"@lion/radio-group": "^0.1.57",
"@lion/textarea": "^0.1.53",
"@lion/validate": "^0.2.29",
"@open-wc/demoing-storybook": "^0.2.0",
"@open-wc/testing": "^2.0.6"
}

View file

@ -0,0 +1,131 @@
import { storiesOf, html } from '@open-wc/demoing-storybook';
import { Unparseable } from '@lion/validate';
import '@lion/input/lion-input.js';
import './helper-wc/h-output.js';
function newDateValid(d) {
const result = d ? new Date(d) : new Date();
return !isNaN(result.getTime()) ? result : null; // eslint-disable-line no-restricted-globals
}
storiesOf('Form Fundaments|Formatting and Parsing', module)
.add(
'model value',
() => html`
<p>
Note: we always use lion-input to demonstrate, but all things that extend lion-input have
this functionality!
</p>
<lion-input
help-text="Uses model value for data synchronisation"
.modelValue="${'myValue'}"
@model-value-changed="${({ target }) => {
console.log(target);
}}"
>
</lion-input>
<h-output .show="${['modelValue']}"></h-output>
`,
)
.add(
'parser',
() => html`
<lion-input
label="Number Example"
help-text="Uses .parser to create model values from view values"
.parser="${viewValue => Number(viewValue)}"
.modelValue="${1234567890}"
>
</lion-input>
<h-output .show="${['modelValue']}"></h-output>
`,
)
.add(
'formatter',
() => html`
<lion-input
label="Number Example"
help-text="Uses .formatter to create view value"
.parser="${viewValue => Number(viewValue)}"
.formatter="${modelValue => new Intl.NumberFormat('en-GB').format(modelValue)}"
.modelValue="${1234567890}"
>
</lion-input>
<h-output .show="${['modelValue']}"></h-output>
`,
)
/* .add(
'preprocessor',
() => html`
<lion-input
label="Date Example"
help-text="Uses .preprocessor to enhance user experience"
.parser="${viewValue => newDateValid(viewValue) || undefined}"
.formatter="${modelValue => new Intl.DateTimeFormat('en-GB').format(modelValue)}"
.preprocessor="${viewValue => viewValue.replace('/', '-')}"
.modelValue="${new Date()}"
>
</lion-input>
<h-output .show="${['modelValue']}"></h-output>
`,
) */
.add(
'(de)serializer',
() => html`
<lion-input
label="Date Example"
help-text="Uses .(de)serializer to restore serialized modelValues"
.parser="${viewValue => newDateValid(viewValue) || undefined}"
.formatter="${modelValue => new Intl.DateTimeFormat('en-GB').format(modelValue)}"
.serializer="${modelValue => modelValue.toISOString().slice(0, 10)}"
.deserializer="${serializedMv => newDateValid(serializedMv) || undefined}"
.modelValue="${new Date(Date.UTC(2012, 11, 20, 3, 0, 0))}"
>
</lion-input>
<h-output .show="${['modelValue']}"></h-output>
`,
)
.add(
'Unparseable',
() => html`
<div
@model-value-changed="${({ target: { modelValue, errorState } }) => {
if (modelValue instanceof Unparseable) {
console.log(`End user attempted to create a valid entry and most likely is in
the process of doing so. We can retrieve the intermediate state via modelValue.viewValue`);
} else if (errorState) {
console.log(`We know now end user entered a valid type, but some constraints
(for instance min date) were not met`);
} else {
console.log(`Now we know end user entered a valid input: the field is valid
and modelValue can be used in Application context for further processing`);
}
}}"
>
<lion-input
label="Date Example"
help-text="Creates modelValue of 'Unparseable' when modelValue can't be created. Please check the action logger for additional details."
.parser="${viewValue => newDateValid(viewValue) || undefined}"
.formatter="${modelValue => new Intl.DateTimeFormat('en-GB').format(modelValue)}"
.deserializer="${serializedMv => newDateValid(serializedMv) || undefined}"
.modelValue="${new Unparseable('2000/12f')}"
>
</lion-input>
<h-output .show="${['modelValue']}"></h-output>
</div>
`,
)
.add(
'Unparseable restore',
() => html`
<lion-input
label="Date Example"
help-text="Restored 'Unparseable' state"
.parser="${viewValue => new Date(viewValue)}"
.formatter="${modelValue => new Intl.DateTimeFormat('en-GB').format(modelValue)}"
.modelValue="${new Unparseable('2000/12f')}"
>
</lion-input>
<h-output .show="${['modelValue']}"></h-output>
`,
);

View file

@ -0,0 +1,105 @@
import { LitElement, html, css } from '@lion/core';
import { LionField } from '@lion/field';
export class HelperOutput extends LitElement {
static get properties() {
return {
field: Object,
show: Array,
};
}
static get styles() {
return [
css`
:host {
display: block;
margin-top: 16px;
}
table,
th,
td {
border: 1px solid #ccc;
padding: 4px;
font-size: 12px;
}
table {
border-collapse: collapse;
}
caption {
text-align: left;
}
`,
];
}
firstUpdated(c) {
super.firstUpdated(c);
if (!this.field) {
// Fuzzy logic, but... practical
const prev = this.previousElementSibling;
if (prev instanceof LionField) {
this.field = prev;
}
}
this.__rerender = this.__rerender.bind(this);
this.field.addEventListener('model-value-changed', this.__rerender);
this.field.addEventListener('mousemove', this.__rerender);
this.field.addEventListener('blur', this.__rerender);
this.field.addEventListener('focusin', this.__rerender);
this.field.addEventListener('focusout', this.__rerender);
if (this.field.inputElement.form) {
this.field.inputElement.form.addEventListener('submit', this.__rerender);
}
}
__rerender() {
setTimeout(() => {
const f = this.field;
this.field = null;
this.field = f;
});
}
__renderProp(p) {
if (typeof p === 'boolean') {
return p === true ? '✓' : '';
}
if (typeof p === 'undefined') {
return '?';
}
return p;
}
render() {
const field = this.field || {};
return html`
<table>
<caption>
Interaction States
</caption>
<tr>
${this.show.map(
prop => html`
<th>${prop}</th>
`,
)}
</tr>
<tr></tr>
<tr>
${this.show.map(
prop => html`
<td>${this.__renderProp(field[prop])}</td>
`,
)}
</tr>
</table>
`;
}
}
customElements.define('h-output', HelperOutput);

View file

@ -0,0 +1,105 @@
import { storiesOf, html } from '@open-wc/demoing-storybook';
import { render } from '@lion/core';
import { localize } from '@lion/localize';
import '@lion/checkbox/lion-checkbox.js';
import '@lion/checkbox-group/lion-checkbox-group.js';
import '@lion/form/lion-form.js';
import '@lion/input/lion-input.js';
import './helper-wc/h-output.js';
function renderOffline(litHtmlTemplate) {
const offlineRenderContainer = document.createElement('div');
render(litHtmlTemplate, offlineRenderContainer);
return offlineRenderContainer.firstElementChild;
}
function addTranslations(ns, data) {
if (!localize._isNamespaceInCache('en-GB', ns)) {
localize.addData('en-GB', ns, data);
}
}
storiesOf('Form Fundaments|Interaction States', module)
.add(
'States',
() => html`
<lion-input
help-text="Interact with this field to see how dirty, touched and prefilled change"
.modelValue="${'myValue'}"
>
<input slot="input" />
</lion-input>
<h-output .show="${['touched', 'dirty', 'prefilled', 'focused', 'filled', 'submitted']}">
</h-output>
`,
)
.add('Feedback condition', () => {
// 1. Initialize variables...
// properties on InteractionStateMixin we want to check conditions for
const props = ['touched', 'dirty', 'prefilled', 'focused', 'filled', 'submitted'];
// Here we will store the conditions (trigger for validation feedback)
// provided via the UI of the demo
let conditions = [];
// 2. Create a validator...
// Define a demo validator that should only be visible on an odd amount of characters
const oddValidator = [modelValue => ({ odd: modelValue.length % 2 !== 0 })];
addTranslations('lion-validate+odd', {
error: {
odd: '[ Error feedback ] : Add or remove one character',
},
});
// 3. Create field overriding .showErrorCondition...
// Here we will store a reference to the Field element that overrides the default condition
// (function `showErrorCondition`) for triggering validation feedback of `.errorValidators`
const fieldElement = renderOffline(html`
<lion-input
name="interactionField"
label="Only an odd amount of characters allowed"
help-text="Change feedback condition"
.modelValue="${'notodd'}"
.errorValidators="${[oddValidator]}"
.showErrorCondition="${newStates =>
newStates.error && conditions.every(p => fieldElement[p])}"
>
<input slot="input" />
</lion-input>
`);
function fetchConditionsAndReevaluate({ currentTarget: { modelValue } }) {
if (!modelValue['props[]']) {
return;
}
// Create props list like: ['touched', 'submitted']
conditions = modelValue['props[]'].filter(p => p.checked).map(p => p.value);
// Reevaluate
fieldElement.validate();
}
return html`
<lion-form>
<form>
${fieldElement}
<button>Submit</button>
</form>
</lion-form>
<h-output .field="${fieldElement}" .show="${[...props, 'errorState']}"> </h-output>
<h3>
Set conditions for validation feedback visibility
</h3>
<lion-checkbox-group name="props" @model-value-changed="${fetchConditionsAndReevaluate}">
${props.map(
p => html`
<lion-checkbox name="props[]" .label="${p}" .choiceValue="${p}"> </lion-checkbox>
`,
)}
</lion-checkbox-group>
`;
});

View file

@ -14,7 +14,10 @@ import '../packages/fieldset/stories/index.stories.js';
import '../packages/checkbox-group/stories/index.stories.js';
import '../packages/radio-group/stories/index.stories.js';
import '../packages/form/stories/index.stories.js';
import '../packages/form-system/stories/index.stories.js';
import '../packages/form-system/stories/formatting.stories.js';
import '../packages/form-system/stories/interactionStates.stories.js';
import '../packages/icon/stories/index.stories.js';
import '../packages/ajax/stories/index.stories.js';