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  });