github.com/pbberlin/go-pwa@v0.0.0-20220328105622-7c26e0ca1ab8/app-bucket/js/validation.js (about) 1 /* 2 using a factory function 3 https://isamatov.com/encapsulation-in-javascript-es6/ 4 first concept 5 6 */ 7 function Validator(argForm) { 8 9 this.PublicVar = "we dont want public vars" 10 11 let form = argForm; 12 13 // internet suggests three modes to accomplish suppression of builtin bubbles 14 // see if() branches below 15 let suppressBuiltinBubbles = 3; 16 17 let onFocusRemove = false; // unexported parameter 18 let onInputRemove = true; 19 let onInputShowAndRemove = false; 20 let lockFocus = false; 21 22 23 // default is showing custom popups for every faulty input 24 // onlyOne changes this 25 let onlyOne = true; 26 27 28 // custom popups are sized and positioned relative 29 // to the input's parent or grandparent. 30 // Parent or grandparent provide us with a reliable width 31 // that does not overflow the screen width; 32 // unexported parameter 33 let attachGrandparent = true; 34 35 36 this.SetOnInputRemove = function(newVal) { 37 onInputRemove = newVal; 38 } 39 40 this.SetOnInputShowAndRemove = function(newVal) { 41 onInputShowAndRemove = newVal; 42 } 43 44 this.SetLockFocus = function(newVal) { 45 lockFocus = newVal; 46 } 47 48 this.SetOnlySingleCustomPopup = function(newVal) { 49 onlyOne = newVal; 50 } 51 52 // "exporting" the func 53 // keeping the internal version, because internal callers have another 'this' 54 this.ShowCustomPopup = showPopup; 55 56 57 // if any input elements are not clean yet 58 // => dont show any compound errors 59 this.IsCleanForm = function(event) { 60 61 let frmLoc = null; 62 if (event.target.tagName == "FORM") { 63 frmLoc = event.target; 64 } else { 65 frmLoc = event.target.form; 66 } 67 68 try { 69 if (!frmLoc.checkValidity()) { 70 return false; 71 } 72 } catch (error) { 73 logFn("Exception: isCleanForm() was fired for non-form-element; event.target: ", event.target.tagName) 74 } 75 return true; 76 } 77 78 79 function hasPopup(el) { 80 var elErrors = el.parentNode.querySelectorAll(":scope > .popup-invalid-anchor"); 81 if (attachGrandparent) { 82 elErrors = el.parentNode.parentNode.querySelectorAll(":scope > .popup-invalid-anchor"); 83 } 84 for (var i = 0; i < elErrors.length; i++) { 85 // console.log(`found-a ${i + 1}of${elErrors.length} - oldID${oldChild.getAttribute('id')} `); 86 return true; 87 } 88 return false; 89 } 90 91 92 // removing previous message from element el 93 function clearPopup(el) { 94 var elErrors = el.parentNode.querySelectorAll(":scope > .popup-invalid-anchor"); 95 if (attachGrandparent) { 96 elErrors = el.parentNode.parentNode.querySelectorAll(":scope > .popup-invalid-anchor"); 97 } 98 for (var i = 0; i < elErrors.length; i++) { 99 var oldChild = elErrors[i].parentNode.removeChild(elErrors[i]); 100 // console.log(`removed-a ${i + 1}of${elErrors.length} - oldID${oldChild.getAttribute('id')} `); 101 } 102 } 103 104 105 // removing any previous custom messages 106 function clearAllPopups() { 107 var errorMessages = form.querySelectorAll(".popup-invalid-anchor"); 108 for (var i = 0; i < errorMessages.length; i++) { 109 var oldChild = errorMessages[i].parentNode.removeChild(errorMessages[i]); 110 // console.log(`removed-b ${i + 1}of${errorMessages.length} - oldID${oldChild.getAttribute('id')} `); 111 } 112 } 113 114 // clearing and re-creating a custom message 115 // right-beside or -below DOM element el 116 function showPopup(el, msg, overrideCheckValidity) { 117 118 if (!el) { 119 console.log("flagInvalid() el not defined - return "); 120 return; 121 } 122 123 if (msg === undefined) { // typeof msg == "undefined" 124 msg = el.dataset.validation_msg 125 if (msg === undefined) { 126 msg = el.validationMessage // not localized, too mathematical 127 } 128 } 129 130 if (onlyOne) { 131 clearAllPopups(); 132 } else { 133 clearPopup(el); 134 } 135 136 if (!el.checkValidity() || overrideCheckValidity === true) { 137 var parent = el.parentNode; 138 if (attachGrandparent) { 139 parent = el.parentNode.parentNode; 140 } 141 // el.validationMessage is mathematical has is always in browser local 142 parent.insertAdjacentHTML( 143 "beforeend", 144 `<div class='popup-invalid-anchor' id='err-${el.getAttribute('name')}' > 145 <div class='popup-invalid-content'> 146 ${msg} 147 </div> 148 </div>` 149 ); 150 } 151 152 } 153 154 // for onsubmit 155 // for each invalid input element of a form 156 // a custom popup message is displayed right-next or -below 157 function onSubmitCustomPopupsForInvalids(event) { 158 159 clearAllPopups(); 160 161 // insert new messages at the end of parent 162 // `this` to select descendents of <form> - excluding invalid <form> itself 163 var invalidFields = this.querySelectorAll(":invalid"); 164 for (var i = 0; i < invalidFields.length; i++) { 165 showPopup(invalidFields[i]); 166 if (onlyOne) { 167 break; 168 } 169 } 170 171 // focus first invalid field 172 if (invalidFields.length > 0) { 173 invalidFields[0].focus(); 174 } 175 176 if (invalidFields.length > 0) { 177 return false; 178 } 179 return true; 180 } 181 182 183 184 185 186 this.ValidateFormWithCustomPopups = function() { 187 188 189 if (suppressBuiltinBubbles == 1) { 190 // => form.submit() no longer works; only submit buttons clicks still effect a submission 191 form.addEventListener( 192 "invalid", 193 function (event) { 194 console.log("form invalid: ", event.target.getAttribute("name"), " - default prevented"); 195 event.preventDefault(); 196 }, 197 true 198 ); 199 } 200 201 202 if (suppressBuiltinBubbles == 2) { 203 // => form.submit() no longer works; only submit buttons clicks still effect a submission 204 var inputs = form.querySelectorAll("input[type=number]"); 205 for (var i = 0; i < inputs.length; i++) { 206 var inp = inputs[i]; 207 var funcInv = function (event) { 208 console.log("input invalid: ", event.target.getAttribute("name"), " - default prevented"); 209 event.preventDefault(); 210 }; 211 inp.addEventListener("invalid", funcInv, true); 212 } 213 } 214 215 216 if (suppressBuiltinBubbles == 3) { 217 218 // disable form validation 219 // form.submit() validation disabled 220 // form.submit() works and still goes through 221 form.setAttribute("novalidate", true); 222 223 224 // "re-enable" validation of inputs using an explicit event handler 225 form.addEventListener( 226 "submit", 227 function (event) { 228 if (!this.checkValidity()) { 229 var name = event.target.getAttribute("name"); 230 console.log(`prevented submitting form ${name}: invalid inputs`); 231 // emulating cancellation of form.submit() on invalid input 232 event.preventDefault(); 233 } 234 }, 235 true 236 ); 237 // form submit now stalls on invalid 238 // but without any bubbles nor any other messages 239 console.log(`suppressBuiltinBubbles complete`); 240 } 241 242 // add custom popups 243 form.addEventListener( 244 "submit", 245 onSubmitCustomPopupsForInvalids, 246 true 247 ); 248 console.log(`on submit: custom popups for invalids attached`); 249 250 251 } 252 253 254 255 // showing custom popups on form submit is too late? 256 // => show them on blur 257 this.ShowPopupOnBlurOrInput = function() { 258 259 // if we dont apply ValidateFormWithCustomPopups(), 260 // then we still need this for standalone functionality 261 form.setAttribute("novalidate", true); 262 263 var funcReport = function (event) { 264 // event.target.reportValidity(); 265 if (onInputRemove && event.type == "input") { 266 if (event.target.checkValidity()) { 267 clearPopup(event.target); 268 } 269 } else { 270 showPopup(event.target); 271 } 272 var lgMsg = "blur"; 273 if (onInputShowAndRemove || onInputRemove) { 274 lgMsg = "blur+input"; 275 } 276 console.log(` ${lgMsg} inp.reportValidity() ${event.target.getAttribute('name')} ${event.target.checkValidity()}`); 277 if (lockFocus) { 278 if (event.type == "blur") { 279 if (!event.target.checkValidity()) { 280 event.target.focus(); 281 console.log(` blur focus reclaimed ${event.target.getAttribute('name')}`); 282 } 283 } 284 } 285 }; 286 287 var inputs = form.querySelectorAll("input[type=number]"); 288 for (var i = 0; i < inputs.length; i++) { 289 var inp = inputs[i]; 290 inp.addEventListener("blur", funcReport); // blur does not bubble up 291 if (onInputShowAndRemove || onInputRemove) { 292 inp.addEventListener("input", funcReport); 293 } 294 295 if (onFocusRemove) { // remove on entering input 296 var removeOnEntering = function (event) { 297 clearPopup(event.target); 298 }; 299 inp.addEventListener("focus", removeOnEntering); 300 } else { 301 var flagOnEntry = function (event) { 302 if (!event.target.checkValidity()) { 303 // console.log(` show popup on focus - ${event.target.name}`) 304 showPopup(event.target); 305 } 306 }; 307 inp.addEventListener("focus", flagOnEntry); 308 } 309 var lgMsg = "blur"; 310 if (onInputShowAndRemove || onInputRemove) { 311 lgMsg = "blur+input"; 312 } 313 314 var logLen = 1 315 if (i < logLen || i > (inputs.length - 1 - logLen)) { 316 console.log(` ${lgMsg} handler added to ${inp.getAttribute('name')}`); 317 } 318 if (i == logLen) { 319 console.log(` ...`); 320 } 321 } 322 } 323 324 325 326 }