github.com/pbberlin/go-pwa@v0.0.0-20220328105622-7c26e0ca1ab8/app-bucket/js/misc.js (about) 1 // miscellaneous stuff and registration on document load 2 3 4 function keyControls(e) { 5 6 // [enter] key opens 2nd level menu, just as space bar does 7 if (e.key === "Enter") { 8 var menuCheckbox = document.getElementById("mnu-1st-lvl-toggler"); 9 var isFocused = (document.activeElement === menuCheckbox); 10 if (isFocused) { 11 menuCheckbox.checked = true; 12 console.log("key listener ENTER fired"); 13 } 14 } 15 16 // [esc] key closes 2nd level menu, if its expanded 17 if (e.key === "Escape") { 18 document.getElementById("mnu-1st-lvl-toggler").checked = false; 19 20 21 // ExcelDB: hide all control-menu-2 22 // var mnu2s = document.getElementsByClassName("control-menu-2"); 23 // for (var i = 0; i < mnu2s.length; i++) { 24 // mnu2s[i].style.display = 'none'; 25 // } 26 // console.log("key listener ESC fired"); 27 } 28 29 // [enter] on inputs transformed into focus next input. 30 // Sending events to inputs is security forbidden. 31 // We find the next element and focus() it. 32 // 33 // TEXTAREA: SHIFT+ENTER mode is impossible on mobile - 34 // thus we cannot inlude TEXTAREA into the func 35 // 36 // optionally restrict to certain user agens: && /Android/.test(navigator.userAgent) 37 if (e.key === "Enter") { 38 39 var isShift = !!e.shiftKey; // convert to boolean 40 if (isShift) { 41 console.log("let SHIFT ENTER pass"); 42 return; 43 } 44 45 var el = document.activeElement; 46 47 // skip for <input type=submit> and <button>... 48 if ((el.tagName == "INPUT" && el.type != "submit") || el.tagName == "SELECT") { 49 50 e.preventDefault(); 51 var nextEl = null; 52 53 54 if (false) { 55 // first method for finding next element: 56 // adding succinct tab indize 57 // then taking current tab index and incrementing it 58 var elements = el.form.elements; 59 var cntr = 1; 60 for (var i = 0, lpEl; lpEl = elements[i++];) { 61 if (lpEl.type !== "hidden" && lpEl.type !== "fieldset") { 62 lpEl.tabIndex = cntr; 63 cntr++; 64 // console.log("tab index", element.name, " to ", i); 65 } else { 66 // console.log("SKIPPING tab index ", element.name, " - ", i); 67 } 68 } 69 var nextTabIndex = el.tabIndex + 1; 70 nextEl = el.form.elements[nextTabIndex]; 71 if (nextEl && nextEl.focus) nextEl.focus(); 72 } 73 74 75 // second method: simply follow the form elements order 76 var found = false; 77 if (el.form) { 78 for (var i = 0, lpEl; lpEl = el.form.elements[i++];) { 79 if (lpEl.type !== "hidden" && lpEl.type !== "fieldset") { 80 if (found) { 81 nextEl = lpEl; 82 // console.log(`found next ${lpEl.name} type ${lpEl.type} at `, i); 83 break; 84 } 85 if (el === lpEl) { 86 // console.log(`found current ${lpEl.name} type ${lpEl.type} at `, i); 87 found = true; 88 } 89 // console.log("iterating form elements", element.name, " to ", i); 90 } else { 91 // console.log("iterating form elements - skipping ", element.name, " - ", i); 92 } 93 } 94 } 95 if (nextEl && nextEl.focus) nextEl.focus(); 96 97 98 if (nextEl) { 99 // console.log("key listener ENTER - transformed into TAB:", el.tagName, el.name, nextEl.tagName, nextEl.name ); 100 } else { 101 // console.log("key listener ENTER - transformed into TAB:", el.tagName, el.name, " next element not found" ); 102 } 103 104 } else { 105 // console.log("key listener ENTER on tagname:", el.tagName, el.name ); 106 } 107 } 108 109 } 110 111 // click outside menu closes it 112 function outsideMenu(event) { 113 var elNav = document.getElementsByTagName('nav'); 114 var nav = elNav[0]; 115 // event.preventDefault(); 116 if (!nav.contains(event.target)) { 117 // console.log('click outside menu'); 118 document.getElementById("mnu-1st-lvl-toggler").checked = false; 119 } 120 } 121 122 // click on nde-2nd-lvl pulls up mnu-3rd-lvl 123 // 124 // we would love to change li.nde-2nd-lvl::before 125 // into an upward arrow too, but pseudo elements 126 // cannot be selected / styled via javascript 127 var closeLevel3 = function () { 128 for (let i = 0; i < this.children.length; i++) { 129 if (this.children[i].tagName == "UL") { 130 var el = this.children[i]; 131 var style = window.getComputedStyle(el); 132 if (style.opacity < 0.5) { 133 el.classList.remove("mnu-3rd-lvl-pull-up"); // remove means *show* ;this is the show / init branch - opacity 0 and growing 134 } else { 135 el.classList.add("mnu-3rd-lvl-pull-up"); // add means *hide* 136 } 137 break; 138 } 139 } 140 }; 141 142 143 // focus on first invalid input 144 // otherwise focus on first input, if visible, 145 // prevent scrolling down 146 function focusInput() { 147 148 var invalidInputs = false; // invalid by HTML5 149 var invalidFields = document.querySelectorAll("form :invalid"); // excluding invalid form itself 150 for (var i = 0; i < invalidFields.length; i++) { 151 /* first pages with first element after long text 152 => scrolls down 153 preventScroll supported only since 2018 154 */ 155 try { 156 invalidFields[i].focus({ 157 preventScroll: true 158 }); 159 } catch (error) { 160 // forgoing initial focussing 161 } 162 // console.log(`focus on first invalid input ${invalidFields[i].name}`); 163 invalidInputs = true; 164 break; 165 } 166 167 var invalidServerFields = document.querySelectorAll(".error-block-input"); // invalid by server rules 168 var firstErrMsgTop = 0; 169 if (invalidServerFields.length > 0) { 170 firstErrMsgTop = invalidServerFields[0].getBoundingClientRect().y 171 // console.log(`.error-block-input found at ${topPosOfErr}`); 172 } 173 174 175 if (!invalidInputs) { 176 // focus on first visible input 177 var elements = document.forms.frmMain.elements; 178 for (var i = 0, element; element = elements[i++];) { 179 if (element.type === "hidden") { 180 continue; 181 } 182 183 if (firstErrMsgTop > 0 && element.getBoundingClientRect().y < firstErrMsgTop) { 184 // console.log(`.error-block-input found ${element.getBoundingClientRect().y} < ${topPosOfErr}`); 185 continue; 186 } 187 188 /* first pages with first element after long text 189 => scrolls down 190 preventScroll supported only since 2018 191 */ 192 try { 193 element.focus({ 194 preventScroll: true 195 }); 196 } catch (error) { 197 // forgoing initial focussing 198 } 199 // console.log(`focus on ${i}th input ${element.name} of form main`); 200 break; 201 202 } 203 } 204 205 206 } 207 208 209 // window.onload = ... is *not* cumulative 210 // window.onload = function () { 211 // // 212 // }; 213 // 214 // addEventListener is cumulative 215 window.addEventListener("load", evt => { 216 217 if ('serviceWorker' in navigator) { 218 // must be in root 219 navigator.serviceWorker.register('/service-worker.js') 220 .then( (reg) => console.log( "service worker - registered", {reg}) ) 221 .catch( (err) => console.error("service worker - NOT registered", err ) ) 222 ; 223 224 225 // https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/background-syncs 226 227 228 // reg = registration 229 navigator.serviceWorker.ready.then( reg => { 230 231 if (reg.periodicSync) { 232 // requires pesky user permission 233 // console.log("background sync - periodic - supported") 234 } 235 236 if (reg.backgroundFetch) { 237 // requires pesky user permission 238 // console.log("background fetch - supported") 239 } 240 241 if (reg.sync) { 242 // reg.sync.register('tag-sync-onload'); 243 console.log("sync - supported"); 244 } else { 245 console.log("sync NOT - supported"); 246 } 247 248 }); 249 250 251 async function requestBackgroundSync(tag) { 252 const reg = await navigator.serviceWorker.ready; 253 await reg.sync.register(tag); 254 } 255 requestBackgroundSync('tag-sync-onload'); 256 257 258 } 259 260 261 document.addEventListener("keydown", keyControls, false); 262 console.log("global key listener registered"); 263 264 265 // menu support 266 var html = document.body.parentNode; 267 html.addEventListener("touchstart", outsideMenu, false); 268 html.addEventListener('click', outsideMenu, false); 269 var nodesLvl2 = document.getElementsByClassName("nde-2nd-lvl"); 270 for (var i = 0; i < nodesLvl2.length; i++) { 271 nodesLvl2[i].addEventListener('click', closeLevel3, false); 272 } 273 console.log("outsideMenu and closeLevel3 registered"); 274 275 276 const link1 = document.getElementById('dbExample'); 277 link1.addEventListener('click', dbExample, false); 278 console.log("dbExample registered"); 279 280 focusInput(); 281 282 283 }); 284 285 286 /* 287 this processes exceptions outside any catch block 288 */ 289 window.addEventListener('unhandledrejection', evt => { 290 console.log('unhandledrejection'); 291 let rqt = evt.target; // IndexedDB native request object 292 console.error(rqt); 293 let err = evt.reason; // Unhandled error object, same as request.error 294 console.error(err); 295 });