@stormid/validate
Version:
Client-side form validation
3 lines (2 loc) • 15.5 kB
JavaScript
function e(){return e=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var a in r)({}).hasOwnProperty.call(r,a)&&(e[a]=r[a])}return e},e.apply(null,arguments)}const t="SET_INITIAL_STATE",r="CLEAR_ERRORS",a="VALIDATION_ERRORS",s="VALIDATION_ERROR",i="CLEAR_ERROR",o="ADD_VALIDATION_METHOD",u="ADD_GROUP",n="REMOVE_GROUP",l="START_REALTIME",d="data-valmsg-for",g=["remote-additionalfields","equalto-other"],c={length:["length-min","length-max"],stringlength:["length-max"],range:["range-min","range-max"],min:["min-min"],max:["max-max"],minlength:["minlength-min"],maxlength:["maxlength-max"],regex:["regex-pattern"],equalto:["equalto-other"],remote:["remote-url","remote-additionalfields","remote-type"]},m=["required","stringlength","dateISO","regex","digits","email","number","url","length","min","max","minlength","maxlength","range","equalto","remote"],p="field-validation-valid",b="error-message",h="group";var v={[t]:(e,t)=>Object.assign({},e,t),[r]:e=>Object.assign({},e,{groups:Object.keys(e.groups).reduce((t,r)=>(t[r]=Object.assign({},e.groups[r],{errorMessages:[],valid:!0}),t),{})}),[i]:(e,t)=>{const r={};return r[t]=Object.assign({},e.groups[t],{errorMessages:[],valid:!0}),Object.assign({},e,{groups:Object.assign({},e.groups,r)})},[u]:(e,t,r)=>Object.assign({},e,{groups:Object.assign({},e.groups,t),errors:Object.assign({},e.errors,r)}),[n]:(t,r)=>Object.assign({},t,e({groups:Object.keys(t.groups).reduce((e,a)=>(a!==r&&(e[a]=t.groups[a]),e),{})},void 0!==t.errors?{errors:Object.keys(t.errors).reduce((e,a)=>(a!==r&&(e[a]=t.errors[a]),e),{})}:{})),[o]:(e,t)=>{const r=Object.assign({},e.groups[t.groupName]?e.groups[t.groupName]:{},e.groups[t.groupName]?{validators:[...e.groups[t.groupName].validators,t.validator]}:{fields:t.fields||(document.querySelector(`[data-val-${h}="${t.groupName}"]`)?[].slice.call(document.querySelectorAll(`[data-val-${h}="${t.groupName}"]`)):[].slice.call(document.getElementsByName(t.groupName))),serverErrorNode:document.querySelector(`[${d}="${t.groupName}"]`)||!1,valid:!1,validators:[t.validator]});return Object.assign({},e,{groups:Object.assign({},e.groups,{[t.groupName]:r})})},[a]:(e,t)=>Object.assign({},e,{realTimeValidation:!0,groups:Object.keys(e.groups).reduce((r,a)=>(r[a]=Object.assign({},e.groups[a],t[a]),r),{})}),[s]:(e,t)=>Object.assign({},e,{groups:Object.assign({},e.groups,{[t.group]:Object.assign({},e.groups[t.group],{errorMessages:t.errorMessages,valid:!1})})}),[l]:(e,t)=>Object.assign({},e,t)};const f=e=>/radio|checkbox/i.test(e.type),A=e=>"hidden"===e.getAttribute("type"),y=(e,t)=>(f(t)||A(t)||!(e=>null!=e.value&&e.value.length>0)(t)||(e=t.value.trim()),f(t)&&t.checked&&(Array.isArray(e)?e.push(t.value.trim()):e=[t.value.trim()]),e),E=e=>e.map(e=>`${encodeURIComponent(e[0].getAttribute("name"))}=${encodeURIComponent(x(e))}`).join("&"),x=e=>Object.prototype.hasOwnProperty.call(e,"fields")?e.fields.reduce(y,""):e.reduce(y,""),O=e=>Object.keys(e).reduce((t,r)=>{if(e[r].serverErrorNode){const a=e[r].serverErrorNode.textContent;a&&(t[r]=a)}return t},{}),S=e=>!(e=>e.validators.filter(e=>"required"===e.type).length>0)(e)&&""===x(e),N=e=>t=>S(t)||t.fields.reduce((t,r)=>e.test(r.value),!1),j=(e,t)=>r=>S(r)||r.fields.reduce(t(((e,t)=>e.validators.filter(e=>e.type===t)[0].params)(r,e)),!1),$=e=>void 0!==e;var q={required:e=>""!==x(e),email:N(/^[A-Za-zŽžÀ-ÿŠ0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/),url:N(/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i),dateISO:N(/^\d{4}[/-](0?[1-9]|1[012])[/-](0?[1-9]|[12][0-9]|3[01])$/),number:N(/^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/),digits:N(/^\d+$/),minlength:j("minlength",e=>(t,r)=>+r.value.length>=+e.min),maxlength:j("maxlength",e=>(t,r)=>+r.value.length<=+e.max),equalto:j("equalto",e=>(t,r)=>e.other.reduce((e,t)=>(x(t)!==r.value&&(e=!1),e),!0)),pattern:j("pattern",e=>(t,r)=>RegExp(e.regex).test(r.value)),regex:j("regex",e=>(t,r)=>RegExp(e.pattern).test(r.value)),min:j("min",e=>(t,r)=>!isNaN(parseInt(r.value,10))&&+r.value>=+e.min),max:j("max",e=>(t,r)=>!isNaN(parseInt(r.value,10))&&+r.value<=+e.max),stringlength:j("stringlength",e=>(t,r)=>+r.value.length<=+e.max),length:j("length",e=>(t,r)=>+r.value.length>=+e.min&&(void 0===e.max||+r.value.length<=+e.max)),range:j("range",e=>(t,r)=>(!$(e.min)||+r.value>=+e.min)&&(!$(e.max)||+r.value<=+e.max)),remote:(e,t)=>new Promise((r,a)=>{const s=x(e);var i,o;(i="get"!==t.type?t.url:`${t.url}?${e.fields[0].name}=${s}&${E(t.additionalfields)}`,o={method:t.type&&t.type.toUpperCase()||"POST",body:"get"!==t.type?JSON.stringify({[e.fields[0].name]:s}):E(t.additionalfields),headers:{"Content-Type":"application/x-www-form-urlencoded;charset=UTF-8"}},new Promise((e,t)=>{let r=new XMLHttpRequest;r.open(o.method||"GET",i),o.headers&&Object.keys(o.headers).forEach(e=>{r.setRequestHeader(e,o.headers[e])}),r.onload=()=>{r.status>=200&&r.status<300?e(r.response):t(r.statusText)},r.onerror=()=>t(r.statusText),r.send(o.body)})).then(e=>r(e))}),custom:(e,t)=>S(t)||e(x(t),t.fields)};const T=(e,t)=>!!c[t]&&{params:c[t].reduce((t,r)=>e.hasAttribute(`data-val-${r}`)?Object.assign(t,((e,t)=>{let r=t.getAttribute(`data-val-${e}`);return{[e.split("-")[1]]:~g.indexOf(e)?(a=r,a.split(",").map(e=>[].slice.call(document.querySelectorAll(`[name=${(e=>e.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g,"\\$1"))(e)}]`)))):r};var a})(r,e)):t,{})},R=(e,t)=>e.getAttribute(`data-val-${t}`)?{message:e.getAttribute(`data-val-${t}`)}:{},w=t=>"true"===t.getAttribute("data-val")?(e=>m.reduce((t,r)=>e.getAttribute(`data-val-${r}`)?[...t,Object.assign({type:r,message:e.getAttribute(`data-val-${r}`)},T(e,r))]:t,[]))(t):(t=>{let r=[];return!t.hasAttribute("required")&&!t.hasAttribute("aria-required")||"false"===t.getAttribute("required")&&"false"===t.getAttribute("aria-required")||r.push(e({type:"required"},R(t,"required"))),"email"===t.getAttribute("type")&&r.push(e({type:"email"},R(t,"email"))),"url"===t.getAttribute("type")&&r.push(e({type:"url"},R(t,"url"))),"number"===t.getAttribute("type")&&r.push(e({type:"number"},R(t,"number"))),t.getAttribute("minlength")&&"false"!==t.getAttribute("minlength")&&r.push(e({type:"minlength",params:{min:t.getAttribute("minlength")}},R(t,"minlength"))),t.getAttribute("maxlength")&&"false"!==t.getAttribute("maxlength")&&r.push(e({type:"maxlength",params:{max:t.getAttribute("maxlength")}},R(t,"maxlength"))),t.getAttribute("min")&&"false"!==t.getAttribute("min")&&r.push(e({type:"min",params:{min:t.getAttribute("min")}},R(t,"min"))),t.getAttribute("max")&&"false"!==t.getAttribute("max")&&r.push(e({type:"max",params:{max:t.getAttribute("max")}},R(t,"max"))),t.getAttribute("pattern")&&"false"!==t.getAttribute("pattern")&&r.push(e({type:"pattern",params:{regex:t.getAttribute("pattern")}},R(t,"pattern"))),r})(t),L=(e,t)=>{let r=t.getAttribute("data-val-"+h)?t.getAttribute("data-val-"+h):t.getAttribute("name");if(!r)return console.warn("Missing data group or name attribute"),e;if(e[r]&&A(t))return e;const a=document.querySelector(`[${d}="${r}"]`)||!1;return e[r]=e[r]?Object.assign(e[r],{fields:[...e[r].fields,t]}):{valid:!1,validators:w(t),fields:[t],serverErrorNode:a},e},k=(e,t)=>(r,a,s)=>{return!0===a?r:[...r,"boolean"==typeof a?(i=t.settings.messages,o=t.groups[e].validators[s],o.message||i[o.type](void 0!==o.params?o.params:null)):a];var i,o},M=e=>{let t={};for(let r in e)e[r].validators.length>0&&!e[r].fields.reduce((e,t)=>("hidden"!==t.type&&(e=!1),e),!0)&&(t[r]=e[r]);return t},I=(e,t)=>(!0!==t&&(e=!1),e),P=e=>e.fields.reduce((e,t)=>(t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")&&(e=!0),e),!1)?Promise.resolve([!0]):Promise.all(e.validators.map(t=>new Promise((r,a)=>{((e,t)=>new Promise((r,a)=>{try{r("custom"===t.type?q.custom(t.method,e):q[t.type](e,t.params))}catch(e){console.warn(e),r(e)}}))(e,t).then(e=>{"true"!==String(e)?r("false"!==String(e)&&e):r(!0)}).catch(e=>console.warn(e))}))),C=e=>t=>{t.groups[e].serverErrorNode?(t.groups[e].serverErrorNode.innerHTML="",t.groups[e].serverErrorNode.classList.remove(b),t.groups[e].serverErrorNode.classList.add(p)):t.errors[e].parentNode.removeChild(t.errors[e]),t.groups[e].fields.forEach(r=>{r.parentNode.classList.remove("is--invalid"),r.removeAttribute("aria-invalid");const a=(t.groups[e].serverErrorNode||t.errors[e]).id;r.hasAttribute("aria-describedby")&&(r.getAttribute("aria-describedby")===a?r.removeAttribute("aria-describedby"):r.setAttribute("aria-describedby",r.getAttribute("aria-describedby").replace(` ${a}`,"")))}),delete t.errors[e]},z=e=>{e.errors&&Object.keys(e.errors).forEach(t=>{C(t)(e)})},_=e=>{Object.keys(e.groups).forEach(t=>{e.groups[t].valid||D(t)(e)})},D=e=>t=>{t.errors[e]&&C(e)(t);let r=((e,t)=>{let r=e.groups[t].errorMessages[0],a=e.groups[t].fields.reduce((e,t,r,a)=>r===a.length-1?e+t.value:t.value+", ","");return r.replace("{{value}}",a)})(t,e);if(t.groups[e].serverErrorNode)t.errors[e]=((e,t)=>{let r=document.createTextNode(t);return e.serverErrorNode.classList.remove(p),e.serverErrorNode.classList.add(b),e.serverErrorNode.appendChild(r)})(t.groups[e],r);else{const a=document.querySelector(`[for="${t.groups[e].fields[t.groups[e].fields.length-1].getAttribute("id")}"]`);if(null===a)return void console.error(`No matching HTML label or server error node found for validation group: ${e}. Error message: '${r}' cannot be displayed. Form will not be submitted.`);t.errors[e]=a.parentNode.insertBefore(((e,t,r)=>{let a=document.createElement("span");for(let e in t)a.setAttribute(e,t[e]);return void 0!==r&&r.length&&a.appendChild(document.createTextNode(r)),a})(0,{class:b,id:`${e}-error-message`},r),a.nextSibling)}const a=t.groups[e].serverErrorNode||t.errors[e];t.groups[e].fields.forEach(e=>{e.parentNode.classList.add("is--invalid"),e.setAttribute("aria-invalid","true"),e.hasAttribute("aria-describedby")&&H(e,a.getAttribute("id"))||e.setAttribute("aria-describedby",e.hasAttribute("aria-describedby")?`${e.getAttribute("aria-describedby")} ${a.getAttribute("id")}`:a.getAttribute("id"))})},H=(e,t)=>{const r=e.getAttribute("aria-describedby").split(" ");return r.length>0&&r.reduce((e,r)=>e||r===t,!1)},V=e=>{const t=Object.keys(e.groups).reduce((t,r)=>(t||e.groups[r].valid||(t=e.groups[r].fields[0]),t),!1);t&&t.focus()},G=e=>{Object.keys(e.groups).forEach(t=>{var r;e.groups[t].serverErrorNode&&!e.groups[t].serverErrorNode.hasAttribute("id")&&e.groups[t].serverErrorNode.setAttribute("id",`${t}-error-message`),(r=e.groups[t].fields).forEach(e=>{(e.hasAttribute("required")||e.hasAttribute("data-val-required"))&&("radio"!==e.getAttribute("type")&&"checkbox"!==e.getAttribute("type")||"checkbox"===e.getAttribute("type")&&1===r.length)&&e.setAttribute("aria-required","true")})})},U=t=>{const r=e=>()=>{const{groups:r,errors:a}=t.getState();!r[e].valid&&a[e]&&t.update(v[i](t.getState(),e),[C(e)]),P(r[e]).then(r=>{r.reduce(I,!0)||t.update(v[s](t.getState(),{group:e,errorMessages:r.reduce(k(e,t.getState()),[])}),[D(e)])})};Object.keys(t.getState().groups).forEach(a=>{const{groups:s}=t.getState(),i=e({},s);if(!i[a].hasEvent){i[a].fields.forEach(e=>{e.addEventListener((e=>{return["input","change"][Number(f(e)||(t=e,"select"===t.nodeName.toLowerCase())||(e=>"file"===e.getAttribute("type"))(e))];var t})(e),r(a))});const e=i[a].validators.filter(e=>"equalto"===e.type);e.length>0&&e[0].params.other.forEach(e=>{e.forEach(e=>{e.addEventListener("blur",r(a))})}),i[a].hasEvent=!0}t.update(v[l](t.getState(),{groups:i}))})},Z=e=>t=>(t&&t.preventDefault(),e.update(v[r](e.getState()),[z]),new Promise(r=>{const s=e.getState(),{groups:i,realTimeValidation:o}=s;(e=>Promise.all(Object.keys(e).map(t=>P(e[t]))))(i).then(u=>(e=>[].concat(...e).reduce(I,!0))(u)?((e,t,r)=>{const{settings:a,form:s}=r.getState();let i=!1,o=!1;const u=()=>{var e;a.submit?a.submit():s.submit(),i&&(e=i).parentNode.removeChild(e),o&&s.setAttribute("action",o)};return Array.from(s.querySelectorAll('[type="submit"]')).forEach(e=>{var t;(t=e).hasAttribute("name")&&t.hasAttribute("value")&&(i=((e,t)=>{const r=document.createElement("input");return r.setAttribute("type","hidden"),r.setAttribute("name",e.getAttribute("name")),r.setAttribute("value",e.getAttribute("value")),t.appendChild(r)})(e,s)),(e=>e.hasAttribute("formaction")&&""!==e.getAttribute("formaction"))(e)&&(o=s.getAttribute("action"),s.setAttribute("action",e.getAttribute("formaction")))}),e&&e.target&&(a.preSubmitHook?(a.preSubmitHook(),window.setTimeout(u,16)):u()),t(!0)})(t,r,e):(!1===o&&U(e),e.update(v[a](e.getState(),Object.keys(i).reduce((e,t,r)=>(e[t]={valid:u[r].reduce(I,!0),errorMessages:u[r].reduce(k(t,s),[])},e),{})),[_,V]),r(!1))).catch(e=>console.warn(e))})),B=e=>(t,r,a,s)=>{if(void 0===t||void 0===r||void 0===a||!e.getState()[t]&&0===document.getElementsByName(t).length&&0===[].slice.call(document.querySelectorAll(`[data-val-group="${t}"]`)).length&&!s)return console.warn("Custom validation method cannot be added.");e.update(v[o](e.getState(),{groupName:t,fields:s,validator:{type:"custom",method:r,message:a}}))},F=e=>t=>{const r=M(t.reduce(L,{}));if(0===Object.keys(r).length)return console.warn("Group cannot be added.");e.update(v[u](e.getState(),r,O(r)),[G,()=>{e.getState().realTimeValidation&&U(e)}])},J=e=>t=>new Promise(r=>{!e.getState().groups[t].valid&&e.getState().errors[t]&&e.update(v[i](e.getState(),t),[C(t)]),P(e.getState().groups[t]).then(a=>a.reduce(I,!0)?r(!0):(e.update(v[s](e.getState(),{group:t,errorMessages:a.reduce(k(t,e.getState()),[])}),[D(t)]),r(!1)))}),X=e=>t=>{const r=e.getState();r.errors[t]&&C(t)(r),e.update(v[n](e.getState(),t))};var Y={messages:{required:()=>"You must answer this question.",email:()=>"Enter a valid email address, for example: example@example.com.",pattern:()=>"The value must match the pattern",url:()=>"Enter a valid URL",number:()=>"Enter a valid number",maxlength:e=>`Enter no more than ${e.max} characters`,minlength:e=>`Enter at least ${e.min} characters`,max:e=>`Enter a number lower than or equal to ${e.max}`,min:e=>`Enter a number higher than or equal to ${e.min}`}},K=(a,s)=>{let i=(e=>"string"==typeof e?[].slice.call(document.querySelectorAll(e)):e instanceof Array?e:Object.prototype.isPrototypeOf.call(NodeList.prototype,e)?[].slice.call(e):e instanceof HTMLElement?[e]:[])(a);return i.reduce((a,i)=>(i.hasAttribute("novalidate")||(a.push(Object.create(((e,a)=>{const s=(()=>{let e={};return{update:(t,r)=>{e=null!=t?t:e,r&&r.forEach(t=>t(e))},getState:()=>e}})();return s.update(v[t](((e,t)=>{const r=M([].slice.call(e.querySelectorAll("input:not([type=submit]), textarea, select")).reduce(L,{}));return{form:e,settings:t,errors:O(r),realTimeValidation:!1,groups:r}})(e,a)),[G]),e.addEventListener("submit",Z(s)),e.addEventListener("reset",()=>s.update(v[r](s.getState()),[z])),{getState:s.getState,validate:Z(s),addMethod:B(s),addGroup:F(s),validateGroup:J(s),removeGroup:X(s)}})(i,e({},Y,s)))),i.setAttribute("novalidate","novalidate")),a),[])};export{K as default};
//# sourceMappingURL=index.modern.mjs.map