diff --git a/dist/aspnet-validation.js b/dist/aspnet-validation.js index 45f98b4..73b4e14 100644 --- a/dist/aspnet-validation.js +++ b/dist/aspnet-validation.js @@ -959,16 +959,16 @@ var ValidationService = /** @class */ (function () { ValidationService.prototype.bootstrap = function (options) { var _this = this; options = options || {}; - options.root = options.root || window.document.body; this.addMvcProviders(); + var document = window.document; // If the document is done loading, scan it now. - if (window.document.readyState !== 'loading') { - this.scan(options.root); + if (document.readyState === 'complete' || document.readyState === 'interactive') { + this.scan(options.root || window.document.body); } else { // Otherwise wait until the document is done loading. window.document.addEventListener('DOMContentLoaded', function (event) { - _this.scan(options.root); + _this.scan(options.root || window.document.body); }); } // Watch for further mutations diff --git a/dist/aspnet-validation.min.js b/dist/aspnet-validation.min.js index 28ff35d..04c7f99 100644 --- a/dist/aspnet-validation.min.js +++ b/dist/aspnet-validation.min.js @@ -1,2 +1,2 @@ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.aspnetValidation=e():t.aspnetValidation=e()}(window,(function(){return function(t){var e={};function r(n){if(e[n])return e[n].exports;var i=e[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)r.d(n,i,function(e){return t[e]}.bind(null,i));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e,r){"use strict";r.r(e),r.d(e,"MvcValidationProviders",(function(){return s})),r.d(e,"ValidationService",(function(){return u}));var n=function(t,e,r,n){return new(r||(r=Promise))((function(i,a){function o(t){try{u(n.next(t))}catch(t){a(t)}}function s(t){try{u(n.throw(t))}catch(t){a(t)}}function u(t){var e;t.done?i(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e)}))).then(o,s)}u((n=n.apply(t,e||[])).next())}))},i=function(t,e){var r,n,i,a,o={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return a={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function s(a){return function(s){return function(a){if(r)throw new TypeError("Generator is already executing.");for(;o;)try{if(r=1,n&&(i=2&a[0]?n.return:a[0]?n.throw||((i=n.return)&&i.call(n),0):n.next)&&!(i=i.call(n,a[1])).done)return i;switch(n=0,i&&(a=[2&a[0],i.value]),a[0]){case 0:case 1:i=a;break;case 4:return o.label++,{value:a[1],done:!1};case 5:o.label++,n=a[1],a=[0];continue;case 7:a=o.ops.pop(),o.trys.pop();continue;default:if(!(i=o.trys,(i=i.length>0&&i[i.length-1])||6!==a[0]&&2!==a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]-1){var i=t.substr(0,n)+"."+r,a=document.getElementsByName(i)[0];if(a)return a}return document.getElementsByName(r)[0]}var s=function(){this.required=function(t,e,r){return Boolean(t)},this.stringLength=function(t,e,r){if(!t)return!0;if(r.min){var n=parseInt(r.min);if(t.lengthi)return!1}return!0},this.compare=function(t,e,r){if(!r.other)return!0;var n=o(e.name,r.other);return!n||n.value===t},this.range=function(t,e,r){if(!t)return!0;var n=parseFloat(t);return!isNaN(n)&&(!(r.min&&nparseFloat(r.max)))},this.regex=function(t,e,r){return!t||!r.pattern||new RegExp(r.pattern).test(t)},this.email=function(t,e,r){return!t||/^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*(\.\w{2,})+$/.test(t)},this.creditcard=function(t,e,r){if(!t)return!0;if(/[^0-9 \-]+/.test(t))return!1;var n,i,a=0,o=0,s=!1;if((t=t.replace(/\D/g,"")).length<13||t.length>19)return!1;for(n=t.length-1;n>=0;n--)i=t.charAt(n),o=parseInt(i,10),s&&(o*=2)>9&&(o-=9),a+=o,s=!s;return a%10==0},this.url=function(t,e,r){if(!t)return!0;var n=t.toLowerCase();return n.indexOf("http://")>-1||n.indexOf("https://")>-1||n.indexOf("ftp://")>-1},this.phone=function(t,e,r){return!t||!/[\+\-\s][\-\s]/g.test(t)&&/^\+?[0-9\-\s]+$/.test(t)},this.remote=function(t,e,r){if(!t)return!0;for(var n=r.additionalfields.split(","),i={},a=0,s=n;a=200&&n.status<300){var i=JSON.parse(n.responseText);t(i)}else e({status:n.status,statusText:n.statusText,data:n.responseText})},n.onerror=function(t){e({status:n.status,statusText:n.statusText,data:n.responseText})}}))}},u=function(){function t(t){var e=this;this.providers={},this.messageFor={},this.elementUIDs=[],this.elementByUID={},this.formInputs={},this.validators={},this.elementEvents={},this.summary={},this.debounce=300,this.allowHiddenFields=!1,this.validateForm=function(t,r){var n=e.getElementUID(t),i=e.elementEvents[n];i&&i(null,r)},this.focusFirstInvalid=function(t){var r=e.getElementUID(t),n=e.formInputs[r].filter((function(t){return e.summary[t]}));if(n.length>0){var i=e.elementByUID[n[0]];i&&i.focus()}},this.isValid=function(t,r,n){void 0===r&&(r=!0),r&&e.validateForm(t,n);var i=e.getElementUID(t);return 0==e.formInputs[i].filter((function(t){return e.summary[t]})).length},this.isFieldValid=function(t,r,n){if(void 0===r&&(r=!0),r){var i=t.closest("form");null!=i&&e.validateForm(i,n)}var a=e.getElementUID(t);return null!=e.summary[a]},this.logger=t||a}return t.prototype.addProvider=function(t,e){this.providers[t]||(this.logger.log("Registered provider: "+t),this.providers[t]=e)},t.prototype.addMvcProviders=function(){var t=new s;this.addProvider("required",t.required),this.addProvider("length",t.stringLength),this.addProvider("maxlength",t.stringLength),this.addProvider("minlength",t.stringLength),this.addProvider("equalto",t.compare),this.addProvider("range",t.range),this.addProvider("regex",t.regex),this.addProvider("creditcard",t.creditcard),this.addProvider("email",t.email),this.addProvider("url",t.url),this.addProvider("phone",t.phone),this.addProvider("remote",t.remote)},t.prototype.scanMessages=function(t){var e=Array.from(t.querySelectorAll("[data-valmsg-for]"));t.hasAttribute("data-valmsg-for")&&e.push(t);for(var r=0;r0&&i[i.length-1])||6!==a[0]&&2!==a[0])){o=0;continue}if(3===a[0]&&(!i||a[1]>i[0]&&a[1]-1){var i=t.substr(0,n)+"."+r,a=document.getElementsByName(i)[0];if(a)return a}return document.getElementsByName(r)[0]}var s=function(){this.required=function(t,e,r){return Boolean(t)},this.stringLength=function(t,e,r){if(!t)return!0;if(r.min){var n=parseInt(r.min);if(t.lengthi)return!1}return!0},this.compare=function(t,e,r){if(!r.other)return!0;var n=o(e.name,r.other);return!n||n.value===t},this.range=function(t,e,r){if(!t)return!0;var n=parseFloat(t);return!isNaN(n)&&(!(r.min&&nparseFloat(r.max)))},this.regex=function(t,e,r){return!t||!r.pattern||new RegExp(r.pattern).test(t)},this.email=function(t,e,r){return!t||/^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*(\.\w{2,})+$/.test(t)},this.creditcard=function(t,e,r){if(!t)return!0;if(/[^0-9 \-]+/.test(t))return!1;var n,i,a=0,o=0,s=!1;if((t=t.replace(/\D/g,"")).length<13||t.length>19)return!1;for(n=t.length-1;n>=0;n--)i=t.charAt(n),o=parseInt(i,10),s&&(o*=2)>9&&(o-=9),a+=o,s=!s;return a%10==0},this.url=function(t,e,r){if(!t)return!0;var n=t.toLowerCase();return n.indexOf("http://")>-1||n.indexOf("https://")>-1||n.indexOf("ftp://")>-1},this.phone=function(t,e,r){return!t||!/[\+\-\s][\-\s]/g.test(t)&&/^\+?[0-9\-\s]+$/.test(t)},this.remote=function(t,e,r){if(!t)return!0;for(var n=r.additionalfields.split(","),i={},a=0,s=n;a=200&&n.status<300){var i=JSON.parse(n.responseText);t(i)}else e({status:n.status,statusText:n.statusText,data:n.responseText})},n.onerror=function(t){e({status:n.status,statusText:n.statusText,data:n.responseText})}}))}},u=function(){function t(t){var e=this;this.providers={},this.messageFor={},this.elementUIDs=[],this.elementByUID={},this.formInputs={},this.validators={},this.elementEvents={},this.summary={},this.debounce=300,this.allowHiddenFields=!1,this.validateForm=function(t,r){var n=e.getElementUID(t),i=e.elementEvents[n];i&&i(null,r)},this.focusFirstInvalid=function(t){var r=e.getElementUID(t),n=e.formInputs[r].filter((function(t){return e.summary[t]}));if(n.length>0){var i=e.elementByUID[n[0]];i&&i.focus()}},this.isValid=function(t,r,n){void 0===r&&(r=!0),r&&e.validateForm(t,n);var i=e.getElementUID(t);return 0==e.formInputs[i].filter((function(t){return e.summary[t]})).length},this.isFieldValid=function(t,r,n){if(void 0===r&&(r=!0),r){var i=t.closest("form");null!=i&&e.validateForm(i,n)}var a=e.getElementUID(t);return null!=e.summary[a]},this.logger=t||a}return t.prototype.addProvider=function(t,e){this.providers[t]||(this.logger.log("Registered provider: "+t),this.providers[t]=e)},t.prototype.addMvcProviders=function(){var t=new s;this.addProvider("required",t.required),this.addProvider("length",t.stringLength),this.addProvider("maxlength",t.stringLength),this.addProvider("minlength",t.stringLength),this.addProvider("equalto",t.compare),this.addProvider("range",t.range),this.addProvider("regex",t.regex),this.addProvider("creditcard",t.creditcard),this.addProvider("email",t.email),this.addProvider("url",t.url),this.addProvider("phone",t.phone),this.addProvider("remote",t.remote)},t.prototype.scanMessages=function(t){var e=Array.from(t.querySelectorAll("[data-valmsg-for]"));t.hasAttribute("data-valmsg-for")&&e.push(t);for(var r=0;r\n */\nexport interface StringKeyValuePair {\n [key: string]: string\n}\n\n/**\n * A duplex key-value pair for an element, by GUID or its DOM object reference.\n */\ninterface ElementUID {\n node: Element,\n uid: string;\n}\n\n/**\n * A simple logging interface that mirrors the Console object.\n */\nexport interface Logger {\n log(message: string, ...args: any[]): void;\n}\n\nconst nullLogger = new (class implements Logger {\n log(_: string, ...args: any[]): void {}\n})();\n\n/**\n * Parameters passed into validation providers from the element attributes.\n * error property is read from data-val-[Provider Name] attribute.\n * params property is populated from data-val-[Provider Name]-[Parameter Name] attributes.\n */\nexport interface ValidationDirectiveBindings {\n error: string,\n params: StringKeyValuePair\n}\n\n/**\n * A key-value pair describing what validations to enforce to an input element, with respective parameters.\n */\nexport type ValidationDirective = {\n [key: string]: ValidationDirectiveBindings\n};\n\n/**\n * Validation plugin signature with multitype return.\n * Boolean return signifies the validation result, which uses the default validation error message read from the element attribute.\n * String return signifies failed validation, which then will be used as the validation error message.\n * Promise return signifies asynchronous plugin behavior, with same behavior as Boolean or String.\n */\nexport type ValidationProvider = (value: string, element: HTMLInputElement, params: StringKeyValuePair) => boolean | string | Promise;\n\n/**\n * A callback method signature that kickstarts a new validation task for an input element, as a Boolean Promise.\n */\ntype Validator = () => Promise;\n\n/**\n * Resolves and returns the element referred by original element using ASP.NET selector logic.\n * @param elementName\n */\nfunction getRelativeFormElement(elementName: string, selector: string) {\n // example elementName: Form.PasswordConfirm, Form.Email\n // example selector (dafuq): *.Password, *.__RequestVerificationToken\n // example result element name: Form.Password, __RequestVerificationToken\n\n let realSelector = selector.substr(2); // Password, __RequestVerificationToken\n let objectName = '';\n\n let dotLocation = elementName.lastIndexOf('.');\n if (dotLocation > -1) {\n // Form\n objectName = elementName.substr(0, dotLocation);\n\n // Form.Password\n let relativeElementName = objectName + '.' + realSelector;\n let relativeElement = document.getElementsByName(relativeElementName)[0];\n if (relativeElement) {\n return relativeElement;\n }\n }\n\n // __RequestVerificationToken\n return document.getElementsByName(realSelector)[0];\n}\n\n/**\n * Contains default implementations for ASP.NET Core MVC validation attributes.\n */\nexport class MvcValidationProviders {\n /**\n * Validates whether the input has a value.\n */\n required: ValidationProvider = (value, element, params) => {\n return Boolean(value);\n }\n\n /**\n * Validates whether the input value satisfies the length contstraint.\n */\n stringLength: ValidationProvider = (value, element, params) => {\n if (!value) {\n return true;\n }\n\n if (params.min) {\n let min = parseInt(params.min);\n if (value.length < min) {\n return false;\n }\n }\n\n if (params.max) {\n let max = parseInt(params.max);\n if (value.length > max) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Validates whether the input value is equal to another input value.\n */\n compare: ValidationProvider = (value, element, params) => {\n if (!params.other) {\n return true;\n }\n\n let otherElement = getRelativeFormElement(element.name, params.other) as HTMLInputElement;\n if (!otherElement) {\n return true;\n }\n\n return (otherElement.value === value);\n }\n\n /**\n * Validates whether the input value is a number within a given range.\n */\n range: ValidationProvider = (value, element, params) => {\n if (!value) {\n return true;\n }\n\n let val = parseFloat(value);\n if (isNaN(val)) {\n return false;\n }\n\n if (params.min) {\n let min = parseFloat(params.min);\n if (val < min) {\n return false;\n }\n }\n\n if (params.max) {\n let max = parseFloat(params.max);\n if (val > max) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Validates whether the input value satisfies a regular expression pattern.\n */\n regex: ValidationProvider = (value, element, params) => {\n if (!value || !params.pattern) {\n return true;\n }\n\n let r = new RegExp(params.pattern);\n return r.test(value);\n }\n\n /**\n * Validates whether the input value is an email in accordance to RFC822 specification, with a top level domain.\n */\n email: ValidationProvider = (value, element, params) => {\n if (!value) {\n return true;\n }\n\n // RFC822 email address with .TLD validation\n // (c) Richard Willis, Chris Ferdinandi, MIT Licensed\n // https://gist.github.com/badsyntax/719800\n // https://gist.github.com/cferdinandi/d04aad4ce064b8da3edf21e26f8944c4\n\n let r = /^([^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+|\\x22([^\\x0d\\x22\\x5c\\x80-\\xff]|\\x5c[\\x00-\\x7f])*\\x22)(\\x2e([^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+|\\x22([^\\x0d\\x22\\x5c\\x80-\\xff]|\\x5c[\\x00-\\x7f])*\\x22))*\\x40([^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+|\\x5b([^\\x0d\\x5b-\\x5d\\x80-\\xff]|\\x5c[\\x00-\\x7f])*\\x5d)(\\x2e([^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+|\\x5b([^\\x0d\\x5b-\\x5d\\x80-\\xff]|\\x5c[\\x00-\\x7f])*\\x5d))*(\\.\\w{2,})+$/;\n return r.test(value);\n }\n\n /**\n * Validates whether the input value is a credit card number, with Luhn's Algorithm.\n */\n creditcard: ValidationProvider = (value, element, params) => {\n if (!value) {\n return true;\n }\n\n // (c) jquery-validation, MIT Licensed\n // https://github.com/jquery-validation/jquery-validation/blob/master/src/additional/creditcard.js\n // based on https://en.wikipedia.org/wiki/Luhn_algorithm\n\n // Accept only spaces, digits and dashes\n if (/[^0-9 \\-]+/.test(value)) {\n return false;\n }\n\n var nCheck = 0,\n nDigit = 0,\n bEven = false,\n n, cDigit;\n\n value = value.replace(/\\D/g, \"\");\n\n // Basing min and max length on https://developer.ean.com/general_info/Valid_Credit_Card_Types\n if (value.length < 13 || value.length > 19) {\n return false;\n }\n\n for (n = value.length - 1; n >= 0; n--) {\n cDigit = value.charAt(n);\n nDigit = parseInt(cDigit, 10);\n if (bEven) {\n if ((nDigit *= 2) > 9) {\n nDigit -= 9;\n }\n }\n\n nCheck += nDigit;\n bEven = !bEven;\n }\n\n return (nCheck % 10) === 0;\n }\n\n /**\n * Validates whether the input value is a URL.\n */\n url: ValidationProvider = (value, element, params) => {\n if (!value) {\n return true;\n }\n\n let lowerCaseValue = value.toLowerCase();\n\n // Match the logic in `UrlAttribute`\n return lowerCaseValue.indexOf('http://') > -1\n || lowerCaseValue.indexOf('https://') > -1\n || lowerCaseValue.indexOf('ftp://') > -1;\n }\n\n /**\n * Validates whether the input value is a phone number.\n */\n phone: ValidationProvider = (value, element, params) => {\n if (!value) {\n return true;\n }\n\n // Allows whitespace or dash as number separator because some people like to do that...\n let consecutiveSeparator = /[\\+\\-\\s][\\-\\s]/g;\n if (consecutiveSeparator.test(value)) {\n return false;\n }\n\n let r = /^\\+?[0-9\\-\\s]+$/;\n return r.test(value);\n }\n\n /**\n * Asynchronously validates the input value to a JSON GET API endpoint.\n */\n remote: ValidationProvider = (value, element, params) => {\n if (!value) {\n return true;\n }\n\n // params.additionalfields: *.Email,*.Username\n let fieldSelectors: string[] = (params.additionalfields as string).split(',');\n let fields: StringKeyValuePair = {};\n\n for (let fieldSelector of fieldSelectors) {\n let fieldName = fieldSelector.substr(2);\n let fieldElement = getRelativeFormElement(element.name, fieldSelector) as HTMLInputElement;\n\n let hasValue = Boolean(fieldElement && fieldElement.value);\n if (!hasValue) {\n continue;\n }\n\n fields[fieldName] = fieldElement.value;\n }\n\n let url: string = params['url'];\n // console.log(fields);\n\n let encodedParams: string[] = [];\n for (let fieldName in fields) {\n let encodedParam = encodeURIComponent(fieldName) + '=' + encodeURIComponent(fields[fieldName]);\n encodedParams.push(encodedParam);\n }\n let payload = encodedParams.join('&');\n // console.log(payload);\n\n return new Promise((ok, reject) => {\n let request = new XMLHttpRequest();\n\n if (params.type === 'Post') {\n let postData = new FormData();\n for (let fieldName in fields) {\n postData.append(fieldName, fields[fieldName]);\n }\n request.open('post', url);\n request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');\n request.send(payload);\n } else {\n request.open('get', url + '?' + payload);\n request.send();\n }\n\n request.onload = e => {\n if (request.status >= 200 && request.status < 300) {\n let data = JSON.parse(request.responseText);\n ok(data);\n } else {\n reject({\n status: request.status,\n statusText: request.statusText,\n data: request.responseText\n });\n }\n };\n\n request.onerror = e => {\n reject({\n status: request.status,\n statusText: request.statusText,\n data: request.responseText\n });\n };\n });\n }\n}\n\n/**\n * Responsibles for managing the DOM elements and running the validation providers.\n */\nexport class ValidationService {\n /**\n * A key-value collection of loaded validation plugins.\n */\n private providers: { [name: string]: ValidationProvider } = {};\n\n /**\n * A key-value collection of elements for displaying validation messages for an input (by DOM ID).\n */\n private messageFor: { [id: string]: Element[] } = {};\n\n /**\n * A list of managed elements, each having a randomly assigned unique identifier (UID).\n */\n private elementUIDs: ElementUID[] = [];\n\n /**\n * A key-value collection of UID to Element for quick lookup.\n */\n private elementByUID: { [uid: string]: Element } = {};\n\n /**\n * A key-value collection of input UIDs for a
UID.\n */\n private formInputs: { [formUID: string]: string[] } = {};\n\n /**\n * A key-value map for input UID to its validator factory.\n */\n private validators: { [inputUID: string]: Validator } = {};\n\n /**\n * A key-value map for element UID to its trigger element (submit event for , input event for