From 8f55fa6ef47b3cf677f9181f121848ea1ed3f83c Mon Sep 17 00:00:00 2001 From: Tony Air Date: Wed, 26 Jun 2024 01:17:51 +0200 Subject: [PATCH] IMPR: Add validate form/field functionality --- src/js/ui/validate.field.js | 64 +++++++++++++++++++ src/js/ui/validate.form.js | 122 ++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 src/js/ui/validate.field.js create mode 100644 src/js/ui/validate.form.js diff --git a/src/js/ui/validate.field.js b/src/js/ui/validate.field.js new file mode 100644 index 0000000..62695ca --- /dev/null +++ b/src/js/ui/validate.field.js @@ -0,0 +1,64 @@ +import Events from '../_events' +const NAME = 'ui.validate.field' + +class ValidateField { + #field + #extraChecks = [] + + constructor(field) { + this.#field = field + + // singleton per field + if (this.#field.ValidateField) { + return this.#field.ValidateField + } + + this.#field.ValidateField = this + + // prevent browsers checks (will do it using JS) + this.#field.setAttribute('novalidate', 'novalidate') + + this.#field.addEventListener('change', this.validate) + this.#field.addEventListener('focusout', this.validate) + + this.#field.classList.add(`${NAME}--active`) + this.#field.dispatchEvent(new Event(Events.FORM_INIT_VALIDATE_FIELD)) + } + + addExtraCheck = (func) => { + this.#extraChecks.push(func) + } + + validate = () => { + // browser check + if (!this.#field.checkValidity()) { + console.warn(`${NAME}: ${this.#field.getAttribute('name')} validation failed`) + + return false + } + + // run extra checks + let valid = true + for (const func in this.#extraChecks) { + valid = func(this.#field) + + if (!valid) { + break + } + } + + return valid + } + + destruct = () => { + this.#field.removeAttribute('novalidate') + this.#field.removeEventListener('change', this.validate) + this.#field.removeEventListener('focusout', this.validate) + + this.#field.ValidateField = null + + this.#field.classList.remove(`${NAME}--active`) + } +} + +export default ValidateField diff --git a/src/js/ui/validate.form.js b/src/js/ui/validate.form.js new file mode 100644 index 0000000..434186c --- /dev/null +++ b/src/js/ui/validate.form.js @@ -0,0 +1,122 @@ +import Events from '../_events' +import ValidateField from './validate.field' + +const NAME = 'ui.validate.form' + +class ValidateForm { + #steppedUI + #form + #extraChecks = [] + + constructor(form) { + this.#form = form + + // singleton per form + if (this.#form.ValidateForm) { + return this.#form.ValidateForm + } + + this.#form.ValidateForm = this + + console.log(`${NAME}: init`) + // prevent browsers checks (will do it using JS) + this.#form.setAttribute('novalidate', 'novalidate') + + // link extra UI API + this.#form.addEventListener(`${Events.FORM_INIT_STEPPED}`, this.setStepFormUI) + + // init fields validation + const fields = this.getFields() + fields.forEach((field) => { + new ValidateField(field) + }) + + this.#form.addEventListener('submit', this.submitHandler) + + this.#form.classList.add(`${NAME}--active`) + this.#form.dispatchEvent(new Event(Events.FORM_INIT_VALIDATE)) + } + + getFields = () => { + return this.#form.querySelectorAll('input,textarea,select') + } + + submitHandler = async () => { + console.log(`${NAME}: submitHandler`) + const valid = await this.validate() + + if (!valid) { + const alert = form.querySelector('.error,.alert-error') + + if (alert) { + alert.scrollIntoView(); + this.#form.dispatchEvent(new Event(Events.FORM_VALIDATION_FAILED)) + + // switch to step + if (this.#steppedUI) { + this.#steppedUI.step(alert.closest('.step')) + } + } + } + } + + addExtraCheck = (func) => { + this.#extraChecks.push(func) + } + + validate = async () => { + let valid = true + const fields = this.#form.querySelectorAll('input,textarea,select') + + // check fields + for (const field of fields) { + if (field.ValidateField) { + valid = await field.ValidateField.validate() + if (!valid) { + break + } + } + } + + if (!valid) { + return false + } + + // run extra checks + for (const func in this.#extraChecks) { + valid = func(this.#form) + + if (!valid) { + break + } + } + + return valid + } + + setStepFormUI = () => { + this.#steppedUI = this.#form.steppedForm + } + + destruct = () => { + console.log(`${NAME}: destruct`) + + this.#form.removeAttribute('novalidate') + this.#form.removeEventListener(`${Events.FORM_INIT_STEPPED}`, this.setStepFormUI) + + // remove fields validation + const fields = this.getFields() + fields.forEach((field) => { + if (field.ValidateField) { + field.ValidateField.destruct() + } + }) + + this.#form.removeEventListener('submit', this.submitHandler) + + this.#form.ValidateForm = null + this.#form.classList.remove(`${NAME}--active`) + } +} + +export default ValidateForm