Files
david/src/validator/FormValidator.js
T
2026-01-02 03:52:16 +02:00

223 lines
4.5 KiB
JavaScript

import './validator.css';
import { Ut } from '../utils/Ut';
import { El } from '../utils/dom';
import { Boot } from '../core/Boot';
import { Validator } from './Validator';
////////////////////////////
// Class Form Validator
////////////////////////////
export class FormValidator extends Validator {
constructor(target, opts) {
super(target);
this.target = target;
if (this.target.dxData('form')) {
return;
}
this.target.dxData('form', this);
this.opts = { ... {
submit : false,
display_err: true,
reset_form : false
}, ... opts };
Ut.trigger(this.opts.onInit, this.target.elements, this);
this.setEvents();
}
setEvents() {
// Click on error UI
this.target.dxOn('click.ui', '.sp-has-error', (_, t) => this.target.dxFind('input[name="' + t.dxFind('input').name + '"', item => item.parentNode?.classList.remove('sp-has-error')) );
// Input Focus - Handler
this.target.dxOn('click.form', 'input, select, textarea', (_, t) => {
if (this.opts.display_err) {
let fieldWrapper = t.dxParents('.form-field');
fieldWrapper?.classList.remove('form-field-error');
fieldWrapper?.dxFind('.sp-label-error')?.remove();
}
Ut.trigger(this.opts.onClick);
});
// Submit - Handler
this.target.dxOn('submit.form', e => {
if (!this.opts.submit) {
e.preventDefault();
}
this.entry = this.getData();
if (Ut.isFn(this.opts.valid) && !this.valid()) {
e.preventDefault();
return;
}
if (Ut.isFn(this.opts.onSubmit) && !this.opts.onSubmit(this.entry, this)) {
e.preventDefault();
return;
}
if (this.opts.submit) {
return;
}
if (Ut.isFn(this.opts.onSubmit)) {
this.opts.onSubmit.call(this);
}
if (this.opts.reset_form) {
this.target.dxResetForm();
}
});
// Fix keydown enter bug
let delayed = false;
this.target.dxOn('keydown.form', e => {
if (e.key == 'Enter' && e.target.tagName.toLowerCase() != 'textarea') {
e.preventDefault();
if (!e.target.readOnly && !this.pendingRequest && !delayed) {
delayed = true;
setTimeout(() => delayed = false, 200);
this.target.dxTrigger('submit');
}
}
});
}
valid () {
let pass = true;
this.target.dxFind('.form-field-error', item => item.classList.remove('form-field-error', 'sp-has-error'));
this.target.dxFind('.sp-label-error', item => item.remove());
let rules = this.opts.valid(this.entry);
for (let name in rules) {
if (!(name in this.target.elements)) {
continue;
}
let el = this.target.elements[ name ];
if (!('length' in el ) || el.nodeName.toLowerCase() == 'select') {
el = [ el ];
}
for (let input of el) {
let val = (input.type && input.type == 'checkbox')
? (input.checked ? input.value : '')
: El('p', {}, input.value).textContent.trim();
let ruleList = rules[ name ].split('|');
for (let tokenRule of ruleList) {
let chunks = tokenRule.split(':');
let args = chunks[1] ?? null;
let rule = chunks[0];
if (!this.handlers[rule]) {
throw new Error(`The rule "{${rule}}" does not exist`);
}
let format = { value: Validator.INPUT_STR };
let result = this.handlers[ rule ] (val ?? null, !!input, args, format);
if (result === false) {
pass = false;
let emsg = this.renderRuleMsg(format, rule, args);
if (this.opts.display_err) {
this.inputError(input, emsg);
}
Ut.trigger(this.opts.onError, emsg);
break;
}
if (result === null) {
break;
}
}
}
}
return pass;
}
renderRuleMsg(format, rule, args) {
args = args ? args.split(',') : null;
return Boot.intData.error[ this.formatName(format.value) + '_' + rule ].replace(/\$(\d+)/g, (_, i) => args[i - 1] ?? '')
}
formatName(val) {
switch (val) {
case Validator.INPUT_STR: return 'str';
case Validator.INPUT_NUM: return 'num';
case Validator.INPUT_FILE: return 'file';
default: return 'str';
}
}
inputError(input, emsg) {
if (input.type == 'checkbox' || input.type == 'radio') {
input.parentNode.classList.add('sp-has-error');
}
else {
let fieldWrapper = input.dxParents('.form-field');
if (!fieldWrapper) {
return false;
}
fieldWrapper.classList.add('form-field-error');
fieldWrapper.append( El('div', { className: 'sp-label-error' }, emsg ) );
}
return false;
}
destroy() {
this.target.dxOff('submit.form click.form keydown.form click.ui');
this.target.dxRemoveData('form');
}
}