UNPKG

formoosejs

Version:

A simple, lightweight, and flexible form validation library

304 lines (303 loc) 12.3 kB
var f = Object.defineProperty; var g = (i, t, e) => t in i ? f(i, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : i[t] = e; var m = (i, t, e) => g(i, typeof t != "symbol" ? t + "" : t, e); class b { constructor(t) { m(this, "handleInputEvents", (t) => { const e = t.target, { isValid: s, errors: r } = this.validateInput(e); s ? this.removeErrors(e) : (this.displayErrors(e, r[0]), this.disableSubmitButton(e.closest("form"))); }); m(this, "formHasErrors", (t) => t.querySelectorAll(".formoose-invalid").length); this.forms = t || document.querySelectorAll("[data-formoose-form]"), this.rules = { required: { test: (e, s) => { if (s.type === "radio" || s.type === "checkbox") { const r = s.closest("form"), a = s.name; return r.querySelectorAll( `input[name="${a}"]:checked` ).length > 0; } return e.trim() !== ""; }, message: "This field is required" }, email: { test: (e) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e), message: "Please enter a valid email address" }, min: { test: (e, s) => e.length >= parseInt(s.getAttribute("data-formoose-min")), message: (e) => `Please enter at least ${e.getAttribute( "data-formoose-min" )} characters` }, max: { test: (e, s) => e.length <= parseInt(s.getAttribute("data-formoose-max")), message: (e) => `Please enter less than or equal to ${e.getAttribute( "data-formoose-max" )} characters` }, number: { test: (e) => !isNaN(e), message: "Please enter a number" }, strongPassword: { test: (e) => /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z\d])/.test(e), message: "Password must contain at least one lowercase letter, one uppercase letter, one number, and one special character" }, same: { test: (e, s) => { const r = s.getAttribute("data-formoose-same"), a = document.querySelector( `[name="${r}"]` ).value; return e === a; }, message: (e) => `Please enter the same value as ${e.getAttribute( "data-formoose-same" )}` }, date: { test: (e) => !isNaN(Date.parse(e)), message: "Please enter a valid date" }, alpha: { test: (e) => /^[a-zA-Z]+$/.test(e), message: "Please enter only letters" }, alphaNumeric: { test: (e) => /^[a-zA-Z0-9]+$/.test(e), message: "Please enter only letters and numbers" }, alphaDash: { test: (e) => /^[a-zA-Z_-]+$/.test(e), message: "Please enter only letters, underscores, and dashes" }, accepted: { test: (e) => e === "on" || e === "yes" || e === "1", message: "The field must be accepted" }, array: { test: (e, s) => { const r = s.hasAttribute("data-formoose-array"), a = s.name; if (!r || !a) return !1; const o = document.querySelectorAll(`input[name="${a}"]`), l = Array.from(o).map((d) => d.value); return Array.isArray(l) && a.endsWith("[]"); }, message: "Please enter a valid array" }, dateBefore: { test: (e, s) => { const r = s.getAttribute("data-formoose-dateBefore"); return Date.parse(e) < Date.parse(r); }, message: (e) => `Please enter a date before ${e.getAttribute( "data-formoose-dateBefore" )}` }, dateAfter: { test: (e, s) => { const r = s.getAttribute("data-formoose-dateAfter"); return Date.parse(e) > Date.parse(r); }, message: (e) => `Please enter a date after ${e.getAttribute( "data-formoose-dateAfter" )}` }, between: { test: (e, s) => { const r = s.getAttribute("data-formoose-between").split(","), a = parseInt(r[0]), o = parseInt(r[1]); return e >= a && e <= o; }, message: (e) => { let r = e.getAttribute("data-formoose-between").split(","), a = parseInt(r[0]), o = parseInt(r[1]); return `Please enter a value between ${a} and ${o}`; } }, boolean: { test: (e) => e == "true" || e == "1" || e == "false" || e == "0", message: "Please enter a valid boolean" }, different: { test: (e, s) => { const r = document.querySelector( `input[name="${s.getAttribute("data-formoose-different")}"]` ).value; return e !== r; }, message: (e) => `Please enter a different value than ${e.getAttribute( "data-formoose-different" )}` }, url: { test: (e) => /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/.test( e ), message: "Please enter a valid URL" }, in: { test: (e, s) => s.getAttribute("data-formoose-in").split(",").includes(e), message: (e) => `Please enter a value in ${e.getAttribute( "data-formoose-in" )}` }, notIn: { test: (e, s) => !s.getAttribute("data-formoose-notIn").split(",").includes(e), message: (e) => `Please enter a value not in ${e.getAttribute( "data-formoose-notIn" )}` }, startsWith: { test: (e, s) => e.startsWith( s.getAttribute("data-formoose-startsWith") ), message: (e) => `Please enter a value that starts with ${e.getAttribute( "data-formoose-startsWith" )}` }, endsWith: { test: (e, s) => e.endsWith(s.getAttribute("data-formoose-endsWith")), message: (e) => `Please enter a value that ends with ${e.getAttribute( "data-formoose-endsWith" )}` }, contains: { test: (e, s) => e.includes(s.getAttribute("data-formoose-contains")), message: (e) => `Please enter a value that contains ${e.getAttribute( "data-formoose-contains" )}` }, notContains: { test: (e, s) => !e.includes( s.getAttribute("data-formoose-notContains") ), message: (e) => `Please enter a value that does not contain ${e.getAttribute( "data-formoose-notContains" )}` }, phone: { test: (e) => /^\+[1-9]{1}[0-9]{0,2}[ /-]?[2-9]{1}[0-9]{2}[ /-]?[2-9]{1}[0-9]{2}[ /-]?[0-9]{4}$/.test( e ), message: () => "Please enter a valid phone number" }, time: { test: (e) => /^([1-9]|0[1-9]|1[0-2]):[0-5][0-9] ([AaPp][Mm])$/.test(e), message: () => "Please enter a valid time e.g. 12:00 PM" }, time24: { test: (e) => /^(?:[01][0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?$/.test( e ), message: () => "Please enter a valid 24-hour time e.g. 23:59" }, ipv4: { test: (e) => /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$/.test( e ), message: () => "Please enter a valid IPv4 address" }, ipv6: { test: (e) => /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/.test(e), message: () => "Please enter a valid IPv6 address" }, macAddress: { test: (e) => /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.test(e), message: () => "Please enter a valid MAC address" }, image: { test: (e) => e instanceof File ? e.type && e.type.startsWith("image/") : !1, message: () => "Please upload a valid image" }, formats: { test: (e, s) => { const a = s.getAttribute("data-formoose-formats").split(",").map((o) => `image/${o}`); return e instanceof File && a.includes(e.type); }, message: (e) => `Please upload a file with one of the following formats: ${e.getAttribute( "data-formoose-formats" )}` } }, this.init(); } // Initialize Formoose init() { this.forms.forEach((t) => { t.addEventListener("submit", (s) => { s.preventDefault(), this.validateForm(t) ? (document.dispatchEvent( new Event("formoose:submitting", { detail: t }) ), this.disableSubmitButton(t), t.submit(), document.dispatchEvent( new Event("formoose:submitted", { detail: t }) )) : this.disableSubmitButton(t); }), t.querySelectorAll( "input, textarea, select, radio, checkbox" ).forEach((s) => { s.addEventListener("input", this.handleInputEvents); }); }); } disableSubmitButton(t) { t.querySelector('button[type="submit"]').setAttribute("disabled", "disabled"); } enableSubmitButton(t) { t.querySelector('button[type="submit"]').removeAttribute("disabled"); } validateForm(t) { const e = t.querySelectorAll( "input, textarea, select, radio, checkbox" ), s = /* @__PURE__ */ new Set(); return Array.from(e).map((a) => { if ((a.type === "radio" || a.type === "checkbox") && s.has(a.name)) return { input: a, isValid: !0 }; (a.type === "radio" || a.type === "checkbox") && s.add(a.name); const { isValid: o, errors: l } = this.validateInput(a); return o ? (new Event("formoose:success", { detail: t }), this.removeErrors(a)) : (new Event("formoose:error", { detail: t }), this.displayErrors(a, l[0])), { input: a, isValid: o }; }).every((a) => a.isValid); } validateInput(t) { const e = t.closest("form"), s = t.name, r = t.type === "radio" || t.type === "checkbox", a = t.type === "file", o = r && (e.querySelector('input[type="radio"]') || e.querySelector('input[type="checkbox"]')) ? e.querySelectorAll(`input[name="${s}"]`) : [t], l = a ? t.files[0] : r ? null : t.value.trim(), d = Object.keys(this.rules).filter((n) => r || !t.hasAttribute(`data-formoose-${n}`) ? !1 : a && n === "image" ? !this.rules[n].test(l, t) : !this.rules[n].test(l, t)).map((n) => this.getMessage(t, n)), c = d.length === 0; if (r) { const n = Object.keys(this.rules).filter( (u) => o[0].hasAttribute(`data-formoose-${u}`) && !this.rules[u].test(null, o[0]) ).map((u) => this.getMessage(o[0], u)); return { isValid: n.length === 0, errors: n }; } return { isValid: c, errors: d }; } getMessage(t, e) { const s = t.getAttribute(`data-formoose-${e}-message`); return typeof this.rules[e].message == "function" ? s || this.rules[e].message(t) : s || this.rules[e].message || "Invalid value"; } displayErrors(t, e) { this.removeErrors(t), t.classList.add("formoose-invalid"); const s = this.createErrorContainer(), r = this.createErrorElement(); r.textContent = e, s.appendChild(r), t.type === "radio" || t.type === "checkbox" || t.classList.contains("form-group") ? t.closest("fieldset").appendChild(s) : (s.appendChild(r), t.insertAdjacentElement("afterend", s)); } createErrorContainer() { const t = document.createElement("div"); return t.classList.add("formoose-invalid-feedback"), t; } createErrorElement() { const t = document.createElement("p"); return t.classList.add("formoose-error-message"), t; } removeErrors(t) { const e = t.closest("form"); if (t.type === "radio" || t.type === "checkbox" || t.classList.contains("form-group")) { const s = t.closest("fieldset").querySelector(".formoose-invalid-feedback"); s && (t.classList.remove("formoose-invalid"), s.remove()); } else t.classList.remove("formoose-invalid"), t.nextElementSibling && t.nextElementSibling.classList.contains("formoose-invalid-feedback") && t.nextElementSibling.remove(); this.formHasErrors(e) ? this.disableSubmitButton(e) : this.enableSubmitButton(e); } addRule(t, e, s) { if (typeof e != "function") throw new Error("The rule must be a function"); if (typeof s != "string" && typeof s != "function") throw new Error("The message must be a string or a function"); this.rules[t] = { test: e, message: s || "Invalid value" }; } } export { b as default };