forms-reactive
Version:
Reactive Form Web Component
167 lines (166 loc) • 19.9 kB
JavaScript
import "@ionic/core"; // Do not remove me! needed to render ion-elements
import { h } from "@stencil/core";
import { FormBuilder, Validators } from "../../utils/forms";
export class TestComponent {
constructor() {
this.printedForm = {};
this.isValidating = false;
this.forceUpdate = 0;
this.isLoading = false;
this.debounceTime = 100;
this.options = [];
this.subscriptions = [];
}
componentWillLoad() {
this.options = ['Option 1', 'Option 2', 'Option 3'];
this.formGroup = new FormBuilder().group({
textInputEmpty: ['', [Validators.required, Validators.email]],
textInput: ['default text at initializer', [Validators.required, Validators.minLength(30)]],
textInputPatched: ['', [Validators.required, Validators.maxLength(5)]],
numericInputEmpty: [null, [Validators.required, Validators.email]],
numericInput: [10, [Validators.required, Validators.minLength(30)]],
numericInputPatched: [null, [Validators.required, Validators.maxLength(5)]],
selectPickerInputEmpty: ['', null, this.asyncValidator('Option 3')],
selectPickerInput: ['Option 2', Validators.required],
selectPickerInputPatched: ['', Validators.required],
checkboxEmpty: ['', Validators.required],
checkboxFalse: [false, Validators.requiredTrue],
checkbox: [true],
checkboxPatched: [''],
toggleEmpty: [null, Validators.required],
toggleFalse: [false, Validators.requiredTrue],
toggle: [true],
togglePatched: [null],
radioEmpty: [''],
radio: ['tesla'],
radioPatched: [''],
rangeEmpty: [],
range: [10, Validators.min(20)],
rangePatched: [{}, Validators.max(10)],
rangeDualEmpty: ['', this.rangeMinDistance(10)],
rangeDual: [{ lower: 3, upper: 9 }],
rangeDualPatched: [],
});
this.isLoading = true;
setTimeout(() => {
this.formGroup.patchValue({
textInputPatched: 'patched value',
numericInputPatched: 20,
selectPickerInputPatched: 'Option 3',
checkboxPatched: true,
togglePatched: true,
radioPatched: 'tesla',
rangePatched: 30,
rangeDualPatched: { lower: 6, upper: 12 },
});
this.isLoading = false;
}, 1000);
this.printedForm = Object.assign({}, this.formGroup.value);
// We can subscribe or we can use reactive-form events. They are not affected by debounceTime
// this.subscriptions.push(this.formGroup.valueChanges.subscribe(value => this.handleValueChanges(value)));
// this.subscriptions.push(this.formGroup.statusChanges.subscribe(state => this.handleStatusChanges(state)));
}
disconnectedCallback() {
this.subscriptions.forEach(s => s.unsubscribe());
}
rangeMinDistance(distance) {
return (control) => {
const { lower, upper } = (control.value || { lower: 0, upper: 0 });
const actual = upper - lower;
return actual < distance ? { 'min distance': { distance, actual } } : null;
};
}
asyncValidator(option) {
return (control) => new Promise((resolve) => {
this.isValidating = true;
setTimeout(() => {
resolve(control.value !== option ? { 'wrong option': { option, actual: control.value } } : null);
this.isValidating = false;
}, 2000);
});
}
handleValueChanges(ev) {
this.printedForm = Object.assign({}, ev.detail);
}
handleStatusChanges(_state) {
// Async validators and touch events require to update the ui after validation occurs
// otherwise the component won't be able to render f`ormGroup.controls['controlName'].errors`:
// They are updated asyncronously but they only change `formGroup`, which is not a `@State()`
// So it's needed to change a `@State()` variable to force ui to re-render
this.forceUpdate += 1;
}
handlePatchRandom() {
this.formGroup.patchValue({
textInput: Math.random().toString(36).replace(/[^a-z]+/g, ''),
rangeEmpty: Math.floor(Math.random() * 101),
}, { emitEvent: true });
}
renderChips(control) {
var _a;
const el = typeof control === 'string' ? (_a = this.formGroup) === null || _a === void 0 ? void 0 : _a.controls[control] : control;
return (h("ion-item", { lines: "none" }, h("ion-chip", { color: (el === null || el === void 0 ? void 0 : el.valid) ? 'success' : 'danger', mode: "ios", outline: el === null || el === void 0 ? void 0 : el.valid }, h("ion-label", null, "valid: ", (el === null || el === void 0 ? void 0 : el.valid) ? 'true' : 'false')), h("ion-chip", { color: (el === null || el === void 0 ? void 0 : el.invalid) ? 'danger' : 'success', mode: "ios", outline: el === null || el === void 0 ? void 0 : el.invalid }, h("ion-label", null, "invalid: ", (el === null || el === void 0 ? void 0 : el.invalid) ? 'true' : 'false')), h("ion-chip", { color: (el === null || el === void 0 ? void 0 : el.pristine) ? 'primary' : 'secondary', mode: "ios", outline: el === null || el === void 0 ? void 0 : el.pristine }, h("ion-label", null, "pristine: ", (el === null || el === void 0 ? void 0 : el.pristine) ? 'true' : 'false')), h("ion-chip", { color: (el === null || el === void 0 ? void 0 : el.touched) ? 'primary' : 'secondary', mode: "ios", outline: el === null || el === void 0 ? void 0 : el.touched }, h("ion-label", null, "touched: ", (el === null || el === void 0 ? void 0 : el.touched) ? 'true' : 'false')), h("ion-chip", { color: (el === null || el === void 0 ? void 0 : el.dirty) ? 'primary' : 'secondary', mode: "ios", outline: el === null || el === void 0 ? void 0 : el.dirty }, h("ion-label", null, "dirty: ", (el === null || el === void 0 ? void 0 : el.dirty) ? 'true' : 'false'))));
}
renderErrors(control, isAsyncValidation = false) {
var _a;
const el = typeof control === 'string' ? (_a = this.formGroup) === null || _a === void 0 ? void 0 : _a.controls[control] : control;
const isWithErrors = Object.keys((el === null || el === void 0 ? void 0 : el.errors) || {}).length > 0;
return (h("ion-item", { lines: "none" }, h("ion-label", { color: isWithErrors ? 'danger' : 'success' }, isWithErrors ? 'Errors: ' : 'No errors', Object.keys((el === null || el === void 0 ? void 0 : el.errors) || {}).map(k => `${k}: ${JSON.stringify(el === null || el === void 0 ? void 0 : el.getError(k))}`)), isAsyncValidation && this.isValidating ? h("ion-spinner", { name: "dots" }) : null));
}
renderTextInput() {
return (h("span", null, h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Email empty value")), h("ion-item", { lines: "full" }, h("ion-input", { type: "text", "rf-ctrl": "textInputEmpty" })), this.renderChips('textInputEmpty'), this.renderErrors('textInputEmpty')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Text input default value")), h("ion-item", { lines: "full" }, h("ion-input", { type: "text", "rf-ctrl": "textInput" })), this.renderChips('textInput'), this.renderErrors('textInput')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Text input patched value")), h("ion-item", { lines: "full" }, h("ion-input", { type: "text", "rf-ctrl": "textInputPatched" })), this.renderChips('textInputPatched'), this.renderErrors('textInputPatched')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Text input missing control in form group")), h("ion-item", { lines: "full" }, h("ion-input", { type: "text", "rf-ctrl": "textInputMissingControl" })), this.renderChips('textInputMissingControl'), this.renderErrors('textInputMissingControl')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Text input error: empty html attribute")), h("ion-item", { lines: "full" }, h("ion-input", { type: "text", "rf-ctrl": "" })))));
}
renderNumericInput() {
return (h("span", null, h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Numeric input default value")), h("ion-item", { lines: "full" }, h("ion-input", { type: "number", "rf-ctrl": "numericInput" })), this.renderChips('numericInput'), this.renderErrors('numericInput')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Numeric input patched value")), h("ion-item", { lines: "full" }, h("ion-input", { type: "number", "rf-ctrl": "numericInputPatched" })), this.renderChips('numericInputPatched'), this.renderErrors('numericInputPatched')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Numeric input missing control in form group")), h("ion-item", { lines: "full" }, h("ion-input", { type: "number", "rf-ctrl": "numericInputMissingControl" })), this.renderChips('numericInputMissingControl'), this.renderErrors('numericInputMissingControl'))));
}
renderSelect() {
return (h("span", null, h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Select empty value, async validators")), h("ion-item", { lines: "full" }, h("ion-label", null, "Select:"), h("ion-select", { "rf-ctrl": "selectPickerInputEmpty", "rf-self-hosted": true, interface: "popover" }, this.options.map(opt => h("ion-select-option", { value: opt }, opt)))), this.renderChips('selectPickerInputEmpty'), this.renderErrors('selectPickerInputEmpty', true)), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Select default value")), h("ion-item", { lines: "full" }, h("ion-label", null, "Select:"), h("ion-select", { "rf-ctrl": "selectPickerInput", interface: "popover" }, this.options.map(opt => h("ion-select-option", { value: opt }, opt)))), this.renderChips('selectPickerInput'), this.renderErrors('selectPickerInput')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Select patched value")), h("ion-item", { lines: "full" }, h("ion-label", null, "Select:"), h("ion-select", { "rf-ctrl": "selectPickerInputPatched", "rf-self-hosted": true, interface: "popover" }, this.options.map(opt => h("ion-select-option", { value: opt }, opt)))), this.renderChips('selectPickerInputPatched'), this.renderErrors('selectPickerInputPatched'))));
}
renderCheckbox() {
return (h("span", null, h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Checkbox empty value, required to be changed")), h("ion-item", { lines: "full" }, h("ion-label", null, "Checkbox"), h("ion-checkbox", { "rf-ctrl": "checkboxEmpty", slot: "start" })), this.renderChips('checkboxEmpty'), this.renderErrors('checkboxEmpty')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Checkbox default value (False), required True")), h("ion-item", { lines: "full" }, h("ion-label", null, "Checkbox"), h("ion-checkbox", { "rf-ctrl": "checkboxFalse", slot: "start" })), this.renderChips('checkboxFalse'), this.renderErrors('checkboxFalse')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Checkbox default value (True)")), h("ion-item", { lines: "full" }, h("ion-label", null, "Checkbox"), h("ion-checkbox", { "rf-ctrl": "checkbox", slot: "start" })), this.renderChips('checkbox'), this.renderErrors('checkbox')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Checkbox patched value")), h("ion-item", { lines: "full" }, h("ion-label", null, "Checkbox"), h("ion-checkbox", { "rf-ctrl": "checkboxPatched", slot: "start" })), this.renderChips('checkboxPatched'), this.renderErrors('checkboxPatched'))));
}
renderToggle() {
return (h("span", null, h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Toggle empty value, required to be changed")), h("ion-item", { lines: "full" }, h("ion-label", null, "Toggle"), h("ion-toggle", { "rf-ctrl": "toggleEmpty", slot: "start" })), this.renderChips('toggleEmpty'), this.renderErrors('toggleEmpty')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Toggle default value (False), required True")), h("ion-item", { lines: "full" }, h("ion-label", null, "Toggle"), h("ion-toggle", { "rf-ctrl": "toggleFalse", slot: "start" })), this.renderChips('toggleFalse'), this.renderErrors('toggleFalse')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Toggle default value (True)")), h("ion-item", { lines: "full" }, h("ion-label", null, "Toggle"), h("ion-toggle", { "rf-ctrl": "toggle", slot: "start" })), this.renderChips('toggle'), this.renderErrors('toggle')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Toggle patched value")), h("ion-item", { lines: "full" }, h("ion-label", null, "Toggle"), h("ion-toggle", { "rf-ctrl": "togglePatched", slot: "start" })), this.renderChips('togglePatched'), this.renderErrors('togglePatched'))));
}
renderRadio() {
return (h("span", null, h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Radio empty value")), h("ion-list", { lines: "none" }, h("ion-radio-group", { "rf-ctrl": "radioEmpty" }, h("ion-item", null, h("ion-label", null, "Tesla"), h("ion-radio", { value: "tesla" })), h("ion-item", { lines: "full" }, h("ion-label", null, "Ford"), h("ion-radio", { value: "ford" }))), this.renderChips('radioEmpty'), this.renderErrors('radioEmpty'))), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Radio default value")), h("ion-list", { lines: "none" }, h("ion-radio-group", { "rf-ctrl": "radio" }, h("ion-item", null, h("ion-label", null, "Tesla"), h("ion-radio", { value: "tesla" })), h("ion-item", { lines: "full" }, h("ion-label", null, "Ford"), h("ion-radio", { value: "ford" }))), this.renderChips('radio'), this.renderErrors('radio'))), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Radio patched value")), h("ion-list", { lines: "none" }, h("ion-radio-group", { "rf-ctrl": "radioPatched" }, h("ion-item", null, h("ion-label", null, "Tesla"), h("ion-radio", { value: "tesla" })), h("ion-item", { lines: "full" }, h("ion-label", null, "Ford"), h("ion-radio", { value: "ford" }))), this.renderChips('radioPatched'), this.renderErrors('radioPatched')))));
}
renderRange() {
return (h("span", null, h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Range empty value")), h("ion-item", null, h("ion-range", { "rf-ctrl": "rangeEmpty", pin: true })), this.renderChips('rangeEmpty'), this.renderErrors('rangeEmpty')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Range default value")), h("ion-item", null, h("ion-range", { "rf-ctrl": "range", pin: true })), this.renderChips('range'), this.renderErrors('range')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Range patched value")), h("ion-item", null, h("ion-range", { "rf-ctrl": "rangePatched", pin: true })), this.renderChips('rangePatched'), this.renderErrors('rangePatched'))));
}
renderRangeDual() {
return (h("span", null, h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Range Dual empty value")), h("ion-item", null, h("ion-range", { "rf-ctrl": "rangeDualEmpty", dualKnobs: true, min: 0, max: 20, step: 3, snaps: true })), this.renderChips('rangeDualEmpty'), this.renderErrors('rangeDualEmpty')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Range Dual default value")), h("ion-item", null, h("ion-range", { "rf-ctrl": "rangeDual", dualKnobs: true, min: 0, max: 20, step: 3, snaps: true })), this.renderChips('rangeDual'), this.renderErrors('rangeDual')), h("ion-card", null, h("ion-card-header", null, h("ion-card-title", null, "Range Dual patched value")), h("ion-item", null, h("ion-range", { "rf-ctrl": "rangeDualPatched", dualKnobs: true, min: 0, max: 20, step: 3, snaps: true })), this.renderChips('rangeDualPatched'), this.renderErrors('rangeDualPatched'))));
}
renderForm() {
return (h("reactive-form", { dataFormGroup: this.formGroup, dataAttributeName: "rf-ctrl", onValueChanges: value => this.handleValueChanges(value), onStatusChanges: state => this.handleStatusChanges(state), dataDebounceTime: this.debounceTime }, h("ion-card", null, h("ion-card-content", null, h("ion-item", { lines: "none" }, h("ion-label", { class: "ion-text-wrap" }, "Reactive form allows to bind a json to a collection of controls. In this example we simulate a timeout of 2 seconds to patch values on the form."), this.isLoading ? h("ion-spinner", { name: "circular", slot: "end" }) : h("ion-icon", { name: "checkmark-outline", slot: "end" })))), h("ion-card", null, h("ion-card-content", null, h("ion-item", null, h("ion-label", { position: "floating" }, "Debounce time (milliseconds)"), h("ion-input", { type: "number", value: this.debounceTime, onIonChange: ev => this.debounceTime = parseInt(ev.detail.value) })))), this.renderTextInput(), this.renderNumericInput(), this.renderSelect(), this.renderCheckbox(), this.renderToggle(), this.renderRadio(), this.renderRange(), this.renderRangeDual()));
}
renderFormStatus() {
return (h("ion-card", { class: "form-values" }, h("ion-card-header", null, h("ion-card-title", null, "Form")), h("ion-card-content", null, h("p", null, "This is the value of the form updated via ", h("code", { class: "code" }, "\u00A0onValueChanges\u00A0"), " event."), h("p", null, "Dirty is triggered by patched values."), h("p", null, this.renderChips(this.formGroup)), this.isValidating ? h("ion-item", { lines: "none" }, "Running async validators \u00A0", h("ion-spinner", { name: "dots" })) : null, h("div", { class: "scrollable" }, h("pre", null, JSON.stringify(this.printedForm, undefined, 4)))), h("ion-footer", null, h("ion-toolbar", null, h("ion-buttons", { slot: "end" }, h("ion-button", { onClick: () => this.handlePatchRandom() }, h("ion-icon", { slot: "start", name: "refresh-outline" }), "Click to patch random value on text input and range"))))));
}
render() {
return [
h("ion-header", { key: '323acb9ca967bc586e2b7a15076471b0fdad01aa' }, h("ion-toolbar", { key: 'ff057ae85d6d36656b7b4d1e40540d7eeacf2769' }, h("ion-title", { key: '04e68a248503273da8d21b96d7c16452ba8ebb70' }, "Reactive forms Web Component", h("br", { key: '542fa9904709a775079b861c293dc9e2b040cbf0' }), h("ion-note", { key: 'dd49011e5ff467f8b4c93def1a2a73e8f54f1729' }, "Developed by AppFeel, a brand of ", h("a", { key: 'a6854e7a25a8a8b6e8c1dba80b821af793526e67', href: "https://bitgenoma.com", target: "_blank", rel: "noreferrer" }, "https://bitgenoma.com"))))),
h("ion-content", { key: '798ef79f98d2a9cdc7a64d5659fc80746cff21ca' }, h("ion-grid", { key: '3989a2add0c54814408ef181dc6e5ddd9d0efa90' }, h("ion-row", { key: '3296ea948253b06651ab123afed56bd65cc5a722' }, h("ion-col", { key: 'e0e4a5cb0bf682c783f187588218cc7c2f09685b', size: "6" }, this.renderForm()), h("ion-col", { key: '2a4cb07130e386a6050c6f041d8d32fad34d928c', size: "6" }, this.renderFormStatus())))),
];
}
static get is() { return "test-component"; }
static get originalStyleUrls() {
return {
"$": ["test-component.css"]
};
}
static get styleUrls() {
return {
"$": ["test-component.css"]
};
}
static get states() {
return {
"printedForm": {},
"isValidating": {},
"forceUpdate": {},
"isLoading": {},
"debounceTime": {}
};
}
}
//# sourceMappingURL=test-component.js.map