feat(form): implement FormControl and ControlConfig prop triggerValidationOn (#224)

* Added ValidationHooks and attribute to FormControl

* Event listener uses data attribute to set type

* Changed default event listener to blur

* Add validation data attribute to other components

* Adjusted querySelector to remove condition

* Added nullish operator to prevent error

* Added optional chaining to validator
This commit is contained in:
Alexander Samaniego 2022-12-06 06:44:52 -05:00 committed by GitHub
parent 4b5b81a2b5
commit 5b539c809c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 33 additions and 21 deletions

View file

@ -1,4 +1,4 @@
import type { ValidatorRules } from "./validator.types"; import type { ValidationHooks, ValidatorRules } from "./validator.types";
/** /**
* `ControlType` determines the type of form control * `ControlType` determines the type of form control
@ -37,6 +37,7 @@ export interface ControlBase {
label?: string; label?: string;
placeholder?: string; placeholder?: string;
validators?: ValidatorRules; validators?: ValidatorRules;
triggerValidationOn?: ValidationHooks;
} }
export interface Checkbox extends ControlBase { export interface Checkbox extends ControlBase {

View file

@ -1,5 +1,7 @@
export type HookType = "onSubmit" | "onControlBlur" | "all"; export type HookType = "onSubmit" | "onControlBlur" | "all";
export type ValidationHooks = "" | "blur" | "keypress" | "click"; // More to be added
export type CategoryType = "error" | "warn" | "info"; export type CategoryType = "error" | "warn" | "info";
export type ValidatorRules = export type ValidatorRules =

View file

@ -6,7 +6,7 @@ import type { Dropdown, ControlOption } from '@astro-reactive/common';
export interface Props { export interface Props {
control: Dropdown; control: Dropdown;
readOnly?: boolean; readOnly?: boolean;
} }
const { control, readOnly } = Astro.props; const { control, readOnly } = Astro.props;
@ -26,22 +26,20 @@ const options = control.options.map((option: string | ControlOption) => {
name={control.name} name={control.name}
id={control.id} id={control.id}
disabled={readOnly || null} disabled={readOnly || null}
data-validation-on={control.triggerValidationOn ? control.triggerValidationOn : null}
> >
{ {
control?.placeholder && ( control?.placeholder && (
<option value="" disabled selected={!control?.value}> <option value="" disabled selected={!control?.value}>
{control.placeholder} {control.placeholder}
</option> </option>
) )
} }
{ {
options.map((option: ControlOption) => ( options.map((option: ControlOption) => (
<option <option value={option.value} selected={option.value === control.value}>
value={option.value} {option.label}
selected={option.value === control.value} </option>
> ))
{option.label} }
</option>
))
}
</select> </select>

View file

@ -46,6 +46,7 @@ const validatorAttributes: Record<string, string> = validators.reduce((prev, val
data-validator-error={hasError ? hasError.toString() : null} data-validator-error={hasError ? hasError.toString() : null}
data-validator-warn={hasWarn ? hasWarn.toString() : null} data-validator-warn={hasWarn ? hasWarn.toString() : null}
data-validator-info={hasInfo ? hasInfo.toString() : null} data-validator-info={hasInfo ? hasInfo.toString() : null}
data-validation-on={control.triggerValidationOn ? control.triggerValidationOn : null}
readonly={readOnly || null} readonly={readOnly || null}
disabled={(readOnly || null) && control.type === 'checkbox'} disabled={(readOnly || null) && control.type === 'checkbox'}
{...validatorAttributes} {...validatorAttributes}

View file

@ -33,6 +33,7 @@ const options = control.options.map((option: string | ControlOption) => {
checked={option.value === control.value} checked={option.value === control.value}
readonly={readOnly || null} readonly={readOnly || null}
disabled={readOnly || null} disabled={readOnly || null}
data-validation-on={control.triggerValidationOn ? control.triggerValidationOn : null}
/> />
<label for={control.id + '-' + index}>{option.label}</label> <label for={control.id + '-' + index}>{option.label}</label>
</div> </div>

View file

@ -38,6 +38,7 @@ const validatorAttributes: Record<string, string> = validators.reduce((prev, val
cols={control?.cols ?? 21} cols={control?.cols ?? 21}
data-label={control?.label} data-label={control?.label}
readonly={readOnly || null} readonly={readOnly || null}
data-validation-on={control.triggerValidationOn ? control.triggerValidationOn : null}
{...validatorAttributes} {...validatorAttributes}
> >
{control.value} {control.value}

View file

@ -10,6 +10,7 @@ import type {
TextArea, TextArea,
ControlBase, ControlBase,
ValidatorRules, ValidatorRules,
ValidationHooks,
} from '@astro-reactive/common'; } from '@astro-reactive/common';
import ShortUniqueId from 'short-unique-id'; import ShortUniqueId from 'short-unique-id';
@ -25,6 +26,7 @@ export class FormControl {
private _isPristine = true; private _isPristine = true;
private _placeholder: string | null = null; private _placeholder: string | null = null;
private _validators: ValidatorRules = []; private _validators: ValidatorRules = [];
private _triggerValidationOn: ValidationHooks;
private _errors: ValidationError[] = []; private _errors: ValidationError[] = [];
private _options: string[] | ControlOption[] = []; private _options: string[] | ControlOption[] = [];
private _rows: number | null = null; private _rows: number | null = null;
@ -47,6 +49,7 @@ export class FormControl {
label = '', label = '',
placeholder = null, placeholder = null,
validators = [], validators = [],
triggerValidationOn = 'blur',
} = config; } = config;
const uid = new ShortUniqueId({ length: 9 }); const uid = new ShortUniqueId({ length: 9 });
@ -57,6 +60,7 @@ export class FormControl {
this._label = label; this._label = label;
this._placeholder = placeholder; this._placeholder = placeholder;
this._validators = validators; this._validators = validators;
this._triggerValidationOn = triggerValidationOn;
if (type === 'radio' || type === 'dropdown') { if (type === 'radio' || type === 'dropdown') {
const { options = [] } = config as Radio | Dropdown; const { options = [] } = config as Radio | Dropdown;
@ -111,6 +115,10 @@ export class FormControl {
return this._validators; return this._validators;
} }
get triggerValidationOn() {
return this._triggerValidationOn;
}
get errors() { get errors() {
return this._errors; return this._errors;
} }

View file

@ -28,10 +28,10 @@ const { hook = 'all', displayErrorMessages = false } = Astro.props;
import { clearErrors, validate } from './core'; import { clearErrors, validate } from './core';
// const hook: HookType = (document.getElementById('hook') as HTMLInputElement).value as HookType; // const hook: HookType = (document.getElementById('hook') as HTMLInputElement).value as HookType;
const inputs = [...document.querySelectorAll('form input')] as HTMLInputElement[]; const inputs = [...document.querySelectorAll('body *[data-validation-on]')] as HTMLInputElement[];
inputs?.forEach((input) => { inputs?.forEach((input) => {
input.addEventListener('blur', (e: Event) => { input.addEventListener(input.dataset?.validationOn ?? 'blur', (e: Event) => {
// NOTE: event target attribute names are converted to lowercase // NOTE: event target attribute names are converted to lowercase
const element = e.target as HTMLInputElement; const element = e.target as HTMLInputElement;
const attributeNames = element?.getAttributeNames() || []; const attributeNames = element?.getAttributeNames() || [];