formoosejs
Version:
A simple, lightweight, and flexible form validation library
2 lines (1 loc) • 9.27 kB
JavaScript
(function(i,n){typeof exports=="object"&&typeof module<"u"?module.exports=n():typeof define=="function"&&define.amd?define(n):(i=typeof globalThis<"u"?globalThis:i||self,i.Formoose=n())})(this,function(){"use strict";var h=Object.defineProperty;var g=(i,n,t)=>n in i?h(i,n,{enumerable:!0,configurable:!0,writable:!0,value:t}):i[n]=t;var c=(i,n,t)=>g(i,typeof n!="symbol"?n+"":n,t);class i{constructor(t){c(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")))});c(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}"]`),u=Array.from(o).map(m=>m.value);return Array.isArray(u)&&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()}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=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:u}=this.validateInput(a);return o?(new Event("formoose:success",{detail:t}),this.removeErrors(a)):(new Event("formoose:error",{detail:t}),this.displayErrors(a,u[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],u=a?t.files[0]:r?null:t.value.trim(),m=Object.keys(this.rules).filter(l=>r||!t.hasAttribute(`data-formoose-${l}`)?!1:a&&l==="image"?!this.rules[l].test(u,t):!this.rules[l].test(u,t)).map(l=>this.getMessage(t,l)),f=m.length===0;if(r){const l=Object.keys(this.rules).filter(d=>o[0].hasAttribute(`data-formoose-${d}`)&&!this.rules[d].test(null,o[0])).map(d=>this.getMessage(o[0],d));return{isValid:l.length===0,errors:l}}return{isValid:f,errors:m}}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"}}}return i});