IMPR: Add validate form/field functionality

This commit is contained in:
Tony Air 2024-06-26 01:17:51 +02:00
parent b1952ee184
commit 8f55fa6ef4
2 changed files with 186 additions and 0 deletions

View File

@ -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

122
src/js/ui/validate.form.js Normal file
View File

@ -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