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'); } }