221 lines
4.4 KiB
JavaScript
221 lines
4.4 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 ];
|
|
|
|
el = Array.from(el instanceof Element ? [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');
|
|
}
|
|
} |