Calculate Aspect Ratio of Viewport
Calculate Aspect Ratio of Viewport
Calculate Aspect Ratio of Viewport
// Desc: Calculate Aspect Ratio of Viewport
// Usage: Console log getDims() onresize event of body
/*                            _     _____       _   _       
    /\                       | |   |  __ \     | | (_)      
   /  \   ___ _ __   ___  ___| |_  | |__) |__ _| |_ _  ___  
  / /\ \ / __| '_ \ / _ \/ __| __| |  _  // _` | __| |/ _ \ 
 / ____ \\__ \ |_) |  __/ (__| |_  | | \ \ (_| | |_| | (_) |
/_/    \_\___/ .__/ \___|\___|\__| |_|  \_\__,_|\__|_|\___/ 
             | |                                            
             |_|  🎯 */

const gcd = (a, b) => {
    return b
      ? gcd(b, a % b)
      : a;
};
  
const aspectRatio = (width, height)  => {
    const divisor = gcd(width, height);
    return `${width / divisor}:${height / divisor}`;
};

const getDims = function(){
    if(window.innerWidth !== undefined && window.innerHeight !== undefined) { 
      var w = Number( window.innerWidth )
      var h = Number( window.innerHeight )
      var a = aspectRatio( w, h )
    } else {
      var w = Number( document.documentElement.clientWidth )
      var h = Number( document.documentElement.clientHeight )
      var a = aspectRatio( w, h )
    }
    return {"ratio": a, "h": h, "w": w};
}

2021-04-16

Javascript Generate and Download CSV
Javascript Generate and Download CSV
Produce CSV with client-side JS. Construct Blog and Download as CSV file.
// Desc: Produce CSV with client-side JS. Contruct Blob and Download as CSV file
/*    _________   _____________   ____ __________.__        ___.    
 *    \_   ___ \ /   _____/\   \ /   / \______   \  |   ____\_ |__  
 *    /    \  \/ \_____  \  \   Y   /   |    |  _/  |  /  _ \| __ \ 
 *    \     \____/        \  \     /    |    |   \  |_(  <_> ) \_\ \
 *     \______  /_______  /   \___/     |______  /____/\____/|___  /
 *            \/        \/                     \/                \/    CSV Report */

  The resulting CSV files will contain a header row deterministic column names
  The resulting CSV files will be quoted
  The file name is auto-generated timestamp
  Cell string data may contain a comma , however quotes will be removed
  Cell string data may contain only utf-8 characters

let nativeCSV = ( ( _d )=>{
let oCnt, jnCSV, sCSV, blCSV, elCSV;  //  config, json, array, blob, and element
let retObj = {
    "init": ( _oCnt )=>{
        oCnt = _oCnt;
        if( oCnt.fileName.indexOf("####") !== -1) {
            oCnt.fileName = oCnt.fileName.replace("####", Date.now() );}
        jnCSV = sCSV = blCSV = elCSV = "";
        return retObj;
    },
    "setArray": ( _jnCSV )=>{  //  An array (rows) of arrays (cols) !jagged
        jnCSV = _jnCSV;
        if( oCnt.header ) jnCSV.unshift( oCnt.header );
        jnCSV.forEach(( aRow )=>{
            aRow.forEach(( sCol )=>{
                if( typeof sCol === "string"){
                    sCSV += oCnt.delimQuote + sCol
                        .split( oCnt.delimQuote ).join("");
                    sCSV += oCnt.delimQuote + oCnt.delimCol;                       
                }
            });
            sCSV = sCSV.slice(0, -1) + oCnt.delimLine;
        });
        return retObj;
    },
    "getBlob": ()=>{
        blCSV = new Blob([ sCSV ], { type: "text/csv;charset=utf-8;" });
        return retObj;
    },
    "createLink": ()=>{
        elCSV = _d.createElement("a");
        elCSV.setAttribute("href", URL.createObjectURL( blCSV ));
        elCSV.setAttribute("download", oCnt.fileName );
        elCSV.style.visibility = 'hidden';
        _d.body.appendChild( elCSV );
        return retObj;
    },
    "clickLink": ()=>{
        elCSV.click();
        return retObj;
    },
    "removeLink": ()=>{
        _d.body.removeChild( elCSV );
        return retObj;
    }
};
return retObj;
})( document );

console.log( nativeCSV.init({  //  Usage: 
"delimCol": ",",
"delimQuote": '"',
"delimLine": "\n",
"fileName": "graph_nodes_####.csv",
"header": ["id","name", "FQDN"]})
.setArray( currentGraph2Array(jCurrentGraph) )
.getBlob()
.createLink()
.clickLink()
.removeLink()    
);

2021-02-27

PWA Add to Home Screen
PWA Add to Home Screen
Progressive Web App ⚡ Advanced Cache && Notification Patterns
/*     ______   __     __     ______    
      /\  == \ /\ \  _ \ \   /\  __ \   
      \ \  _-/ \ \ \/ ".\ \  \ \  __ \  
       \ \_\    \ \__/".~\_\  \ \_\ \_\ 
        \/_/     \/_/   \/_/   \/_/\/_/  ✨ Add to Home Screen
        chrome://serviceworker-internals/
 */
 if ("serviceWorker" in navigator) {
    window.addEventListener("load", () => {
      navigator.serviceWorker.register("sw.js");
    });
}

let eA2hs = oD.getElementsByClassName("js-a2hs")[0];
let eA2hsP = oD.getElementsByClassName("js-a2hs--post")[0];

eA2hs.addEventListener("click", (e) => {
    eA2hs.style.display = "none";
    eA2hsP.style.display = "block";
    evDefPrompt.prompt();
    evDefPrompt.userChoice
      .then((choiceResult) => {
        if (choiceResult.outcome === "accepted") {
          if( snck ) neodigmToast.q("Wow, Now I'm an App on your Desktop|How Convenient!");
          playAudioFile( 7 );  //  ggl tag event | User accepted the A2HS prompt
        } else {
          playAudioFile( 3 );  //  ggl tag event | User dismissed the A2HS prompt
        }
        evDefPrompt = null;
      });
  });

function displayMsg( sMsg ){
    //    System Tray Notification
    if (!("Notification" in window)) {
        console.log('Notification API not supported.');
        return;
    } else if (Notification.permission === "granted") {
        // If it's okay let's create a notification
        var notification = new Notification( Nowish(), {icon: "https://repository-images.githubusercontent.com/178555357/2b6ad880-7aa0-11ea-8dde-63e70187e3e9", body: sMsg} );
    } else if (Notification.permission !== "denied") {
        // Otherwise, we need to ask the user for permission
        Notification.requestPermission(function (permission) {
            // If the user accepts, let's create a notification
            if (permission === "granted") {
                var notification = new Notification( Nowish(), {icon: "https://repository-images.githubusercontent.com/178555357/2b6ad880-7aa0-11ea-8dde-63e70187e3e9", body: sMsg} );
            }
        });
    }
}

/*    ╔═╗┌─┐┬─┐┬  ┬┬┌─┐┌─┐
 *    ╚═╗├┤ ├┬┘└┐┌┘││  ├┤ 
 *    ╚═╝└─┘┴└─ └┘ ┴└─┘└─┘
 *    ╦ ╦┌─┐┬─┐┬┌─┌─┐┬─┐  
 *    ║║║│ │├┬┘├┴┐├┤ ├┬┘  
 *    ╚╩╝└─┘┴└─┴ ┴└─┘┴└─  Advanced Cache ⚡ Notifications
 */
importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.0.0/workbox-sw.js');
workbox.LOG_LEVEL = "debug";
self.addEventListener("fetch", event => {
  event.respondWith(caches.match(event.request)
    .then(cachedResponse => {
        if (cachedResponse) {
          return cachedResponse;
        }
        return fetch(event.request);
      })
    );
});
workbox.routing.registerRoute(
  // Cache CSS files
  /.*\.css/,
  // Use cache but update in the background ASAP
  workbox.strategies.staleWhileRevalidate({
    cacheName: 'css-cache',
  })
);
workbox.routing.registerRoute(
  // Cache image files
  /\.(?:png|gif|jpg|jpeg|webp|avif|svg|mp3|mp4|json|html|js)$/,
  // Use the cache if it's available
  workbox.strategies.cacheFirst({
    cacheName: 'image-cache',
    plugins: [
      new workbox.expiration.Plugin({
        maxEntries: 256, maxAgeSeconds: 172800,
      })
    ],
  })
);

class NeodigmPWA {
  constructor(){
  }
  init () {
      window.addEventListener('appinstalled', () => {
          setTimeout(function(){
              neodigmToast.q("##Application Installed|Neodigm UX ✨ Scott C. Krause")
              neodigmWired4Sound.play( 8 )
              if( dataLayer ) dataLayer.push({'event': 'appinstalled'})
          }, 1200)
        });
  }
}
let neodigmPWA = new NeodigmPWA()
neodigmPWA.init()

2020-12-21

HTML data attrib to JavaScript camel-case dataset
HTML data attrib to JavaScript camel-case dataset
Convert an HTML formatted data attrib name to a JS formatted name.
// Desc: data-is-whatever will be converted to isWhatever
// Usage: element.dataset[ data2prop("data-is-whatever") ]
/*______ _____ __  __           _____           _       _   
 |  ____/ ____|  \/  |   /\    / ____|         (_)     | |  
 | |__ | |    | \  / |  /  \  | (___   ___ _ __ _ _ __ | |_ 
 |  __|| |    | |\/| | / /\ \  \___ \ / __| '__| | '_ \| __|
 | |___| |____| |  | |/ ____ \ ____) | (__| |  | | |_) | |_ 
 |______\_____|_|  |_/_/    \_\_____/ \___|_|  |_| .__/ \__|
                                                 | |        
                                                 |_|        ES2021*/

function data2prop( sDset ){  //  Convert HTML data attrib name to JS dataset name
    sDset = sDset.replace("data-", "").toLowerCase();
    let aDset = sDset.split(""), aDret = [], bUpper = false;
    aDset.forEach( ( sChar ) => {
        if( sChar == "-" ){
            bUpper = true;
        }else{
            aDret.push( ( bUpper ) ? sChar.toUpperCase() : sChar );
            bUpper = false;
        }
    });
    return aDret.join("");
}

2020-12-19

Oracle PL/SQL Stored Procedure
Oracle PL/SQL Stored Procedure
Vintage Stored Procedure to denormalize department codes
--  ███████  ██████  ██      
--  ██      ██    ██ ██      
--  ███████ ██    ██ ██      
--       ██ ██ ▄▄ ██ ██      
--  ███████  ██████  ███████  Relational ⚡ Transactional
--              ▀▀           
PROCEDURE post_stage
(
    in_rowid_job            cmxlb.cmx_rowid,
    in_ldg_table_name       cmxlb.cmx_table_name,
    in_stg_table_name       cmxlb.cmx_table_name,
    out_error_msg      OUT  cmxlb.cmx_message,
    out_return_code    OUT  int
)
AS
sql_stmt varchar2(2000);
t_party_acct_id varchar2(14);
t_txn_div_cd varchar2(20);
t_txn_div_display varchar2(50);
commit_count NUMBER := 0;
commit_inc NUMBER := 1000;
--
CURSOR C_PTAC_TXN IS
SELECT PARTY_ACCT_ID, TXN_DIV_CD, TXN_DIV_DISPLAY
FROM   C_STG_PTAC_TXN_DIV;
--
BEGIN
--
    commit_inc := to_number(GET_PARAMETER('post_stage_commit', commit_inc));
    IF in_ldg_table_name = 'C_LDG_PTAC_TXN_DIV' AND in_stg_table_name = 'C_STG_PTAC_TXN_DIV' THEN
    --    20130225 SCK Update the stage txn_div_display col with a denormalized string derived
    --    from an aggregate of both staging and base object. 
    --    🏄 SQL ⚡ ETL MDM ⚡ PL/SQL ORM
        cmxlog.debug ('ADDUE: Landing table name is ' || in_ldg_table_name || ' Staging table name is ' || in_stg_table_name);
        BEGIN
              FOR R_PTAC_TXN in C_PTAC_TXN LOOP
                    post_stage_concat(R_PTAC_TXN.PARTY_ACCT_ID, t_txn_div_display);
                    UPDATE C_STG_PTAC_TXN_DIV
                    SET txn_div_display = t_txn_div_display, create_date = sysdate WHERE TXN_DIV_CD = R_PTAC_TXN.TXN_DIV_CD AND
                    PARTY_ACCT_ID = R_PTAC_TXN.PARTY_ACCT_ID;  -- CURRENT OF C_PTAC_TXN;
                    commit_count := commit_count + commit_inc;
                    IF MOD(commit_count, 1000) = 0 THEN
                        cmxlog.debug ('ADDUE: post_stage_concat is: ' || commit_count || ':' || R_PTAC_TXN.PARTY_ACCT_ID || ' : ' || t_txn_div_display);
                        COMMIT;
                    END IF;
              END LOOP;
              COMMIT;
        END;
    ELSE
      CMXlog.debug ('ADDUE Post Stage - no action taken');
    END IF;
END post_stage;
END ADD_UE;

2020-12-19

Dark Mode and Reduced Motion
Dark Mode and Reduced Motion
Making Dark Mode work with both a UI switch && the OS preference.
//  Desc: Listen to the OS for user preference
//  but override with a UI toggle.
/*  ______                  __        ____    ____               __        
   |_   _ `.               [  |  _   |_   \  /   _|             |  ]       
     | | `. \ ,--.   _ .--. | | / ]    |   \/   |   .--.    .--.| | .---.  
     | |  | |`'_\ : [ `/'`\]| '' <     | |\  /| | / .'`\ \/ /'`\' |/ /__\\ 
    _| |_.' /// | |, | |    | |`\ \   _| |_\/_| |_| \__. || \__/  || \__., 
   |______.' \'-;__/[___]  [__|  \_] |_____||_____|'.__.'  '.__.;__]'.__.'  User Prefs */


let doPrefersReducedMotion = function( bMotion ){// Stop 3D rotation
    o3Config.controls.autoRotate = !bMotion; 
}
let doPrefersColorScheme = function( bScheme ){ // UI | OS Semaphore
    document.body.dataset.n55AmpmTheme = ((bScheme) ? "dark" : "light"); //  🌙  /  ☀️
}

// Capture the prefers media queries
const mqPrefReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
const mqPrefColorScheme = window.matchMedia("(prefers-color-scheme: dark)");

doPrefersReducedMotion( (mqPrefReducedMotion && mqPrefReducedMotion.matches) );
doPrefersColorScheme( (mqPrefColorScheme && mqPrefColorScheme.matches) );

// listen to changes in the media query's value
mqPrefReducedMotion.addEventListener("change", () => {
    doPrefersReducedMotion( mqPrefReducedMotion.matches );
});
mqPrefColorScheme.addEventListener("change", () => {
    doPrefersColorScheme( mqPrefColorScheme.matches );
});

/* Dark Mode begin */
/*@media (prefers-color-scheme: dark) {*/

    body[data-n55-ampm-theme='dark'] [role='main'] {
        background: linear-gradient(to right, #555, #c2c2c2, #555)
    }
    body[data-n55-ampm-theme='dark'] .h-bg__stripe, body[data-n55-ampm-theme='dark'] .l-caro-design > article, body[data-n55-ampm-theme='dark'] article.l-caro-design {
        background: repeating-linear-gradient(45deg,#242424,#242424 24px,#444 24px,#444 48px);
    }
    body[data-n55-ampm-theme='dark'] section.pfmf-grid > div > article {
        border: solid 1px #888;
        border-top: solid 2px #888;
        box-shadow: 0px 2px 6px -2px rgba(164,164,164,0.6);
        background-color: #242424;
    }
    body[data-n55-ampm-theme='dark'] .readable__doc { color: #fff; }
    body[data-n55-ampm-theme='dark'] .readable__caption { color: #fff; }
    body[data-n55-ampm-theme='dark'] .h-vect-line-art { stroke: #fff;}
/*}*/
/* Dark Mode end */

2020-12-19

Vanilla JS Popover Microinteraction
Vanilla JS Popover Microinteraction
A popover is a transient view that shows on a content screen when a user clicks on a control button or within a defined area.
//  A popover is a transient view that shows on a content screen when
//  a user clicks on a control button or within a defined area.
/*   __  __     __           __  __     __  __    
    /\ \/\ \   /\ \         /\ \/\ \   /\_\_\_\   
    \ \ \_\ \  \ \ \        \ \ \_\ \  \/_/\_\/_  
     \ \_____\  \ \_\        \ \_____\   /\_\/\_\ 
      \/_____/   \/_/         \/_____/   \/_/\/_/   */

let oPopOver = ( ( _win, _doc, _qry ) => {
        let arPops = [], ePos, iOffTop=0, iOffLft=0;
        return {  // Popover UX pattern
            "init": function(){ // wire DOM events
                arPops= [].slice.call(_doc.querySelectorAll( _qry ));
                _win.addEventListener("resize", oPopOver.closeAll);
                _win.addEventListener("scroll", oPopOver.closeAll); 
                _doc.body.addEventListener("click", function( e ){ // 👁️ Outside Click
                    let eTarget = e.target, bInside = false;
                    while( eTarget.tagName !== "HTML" ){
                        if( eTarget.dataset.popover ){ bInside = true; break; }
                        eTarget = eTarget.parentNode;
                    }
                    if( !bInside ){ // Tapped Outside of Popover
                        oPopOver.closeAll();
                    }
                }, true);
            },
            "open": function(id, evPos){ // Open a single Popover
                if( arPops.length == 0) return false;
                oPopOver.closeAll();
                ePos = evPos.currentTarget;
                let elPop = arPops.filter(function(el){
                    return ( el.id == id );
                })[0];
                iOffTop = Number(elPop.dataset.popoverPos.split("|")[0]);
                iOffLft = Number(elPop.dataset.popoverPos.split("|")[1]);
                elPop.dataset.popover = "true"; // Open and Active
                elPop.style.left = oPopOver.position().left+"px";
                elPop.style.top = oPopOver.position().top+"px";
            },
            "closeAll": function(){ // Close all Popovers
                if( arPops.length == 0) return false;
                arPops.map(function(el){
                    el.dataset.popover = "false";
                });
            },
            "position": function(){ // Determine Popover position
                let rec = ePos.getBoundingClientRect(),
                pxLft = _win.pageXOffset || _doc.documentElement.scrollLeft,
                pxTop = _win.pageYOffset || _doc.documentElement.scrollTop;
                return { top: (rec.top + pxTop + iOffTop), left: (rec.left + pxLft + iOffLft) }
            }
        }
    })(window, document, "[data-popover]"); // Declarative implementation

2020-12-16

Vue.js double tap Microinteraction
Vue.js double tap Microinteraction
Firing both a tap and a double-tap on the same element
//  A Vue.js snippet that shows how to capture both a tap and
//  a double-tap on the same element within the template.
//
//  Canonical Use Case: Double-Tap to zoom into a hero image
//  and single-tap to zoom out.

/*    ____   ____                      __        
 *    \   \ /   /_ __   ____          |__| ______
 *     \   Y   /  |  \_/ __ \         |  |/  ___/
 *      \     /|  |  /\  ___/         |  |\___ \ 
 *       \___/ |____/  \___  > /\ /\__|  /____  >
 *                         \/  \/ \______|    \/ */

 methods: {
    "doHeroMobMouseUp": function( ev ){ // Double Tap
      var oHro = this.oHeroZmMob;
      if( oHro.isInit ){
        if( oHro.doubleTap ){ // Zoom In
          oHro.doubleTap = false;
          this.doHeroMobScale( .5 ); // Double Tap
        }else{
          oHro.doubleTap = true;
          setTimeout(function(){ this.doHeroMobMouseUp_expire() }, 380);
        }
      }
      this.oHeroZmMob.isDown = false;
    },
    "doHeroMobMouseUp_expire": function(){ // Single Tap
      var oHro = this.oHeroZmMob;
      if( oHro.isInit ){ // Zoom Out
        if( oHro.doubleTap ) this.doHeroMobScale( -.5 ); // Single Tap
        oHro.doubleTap = false;
      } 
    }
 }
 /*
 This is only part of a larger Vue gesture implementation supporting
 Pinch 🤏, Zoom, Pan, and Swipe. Reach out to me if you want to learn more.
 */

2020-12-15

CSS Advanced Accessibility
CSS Advanced Accessibility
Motion, theme, and skip A11Y CSS solutions
/* Skip to Main Content - CSS Focus rules that make the
link visible when focused from the omnibox.
========================================
====  ========  ========  =====  ====  =
===    =====    ======    =====   ==   =
==  ==  ======  ========  ======  ==  ==
=  ====  =====  ========  ======  ==  ==
=  ====  =====  ========  =======    ===
=        =====  ========  ========  ====
=  ====  =====  ========  ========  ====
=  ====  =====  ========  ========  ====
=  ====  ===      ====      ======  ====
======================================== */

a.skip__main:active, a.skip__main:focus {
    background-color: #fff;
    border-radius: 4px;
    border: 2px solid #000;
    color: #000;
    font-size: 1em;
    height: auto; width: 16%;
    left: auto;
    margin: 8px 42%;
    overflow: auto;
    padding: 4px;
    text-align: center;
    top: auto;
    z-index: 1024;
}
a.skip__main {
    left: -1024px;
    overflow: hidden;
    position: absolute;
    top: auto;
    width: 1px; height: 1px;
    z-index: -1024;
}
/* Dark Mode begin */
@media (prefers-color-scheme: dark) {
    body, [role='main'] {
        background: linear-gradient(to right, #555, #c2c2c2, #555)
    }
    .h-bg__stripe, .l-caro-design > article, article.l-caro-design {
        background: repeating-linear-gradient(45deg,#bbb,#bbb 24px,#ddd 24px,#ddd 48px);
    }
}
/* Dark Mode end */

/* Reduced Motion begin*/
@media (prefers-reduced-motion: reduce) {
    .hero__vect { animation: none; }
}
/* Reduced Motion end*/

<a class="js-skip__main--id skip__main"
href="#a11y-skipmain">Skip to Main Content</a>

2020-12-13

JS Airport Geo-Proximity Radius
JS Airport Geo-Proximity Radius
Airport geo-proximity logic that answers questions, like; “What are the three closest airports to me right now?”
// Desc: Get the closest airports by geolocation radius
// Usage: closestAirports.find(-99, -99, oAirports, 4); // 4 miles

/*       ___           _                 _   _             
 *      / _ \___  ___ | | ___   ___ __ _| |_(_) ___  _ __  
 *     / /_\/ _ \/ _ \| |/ _ \ / __/ _` | __| |/ _ \| '_ \ 
 *    / /_\\  __/ (_) | | (_) | (_| (_| | |_| | (_) | | | |
 *    \____/\___|\___/|_|\___/ \___\__,_|\__|_|\___/|_| |_|  ✈️ */

function getDistance(lat1, lon1, lat2, lon2) {
    let radlat1 = Math.PI * lat1/180;
    let radlat2 = Math.PI * lat2/180;
    let theta = lon1-lon2;
    let radtheta = Math.PI * theta/180;
    let dst = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
    if (dst > 1) dst = 1;
    dst = Math.acos(dst);
    dst = dst * (180/Math.PI) * (60 * 1.1515); // miles
    return dst;
  }

let closestAirports = ((_d) => {
    return {
      "find": function(nLat, nLon, objPorts, nRadius){
        if(nLat && nLon && objPorts){
          let arrPorts = [];
          for (let prop in objPorts) { // Sort Object
            if (objPorts.hasOwnProperty(prop)) {
              let lat = objPorts[prop].geoCode.split(",")[0];
              let lon = objPorts[prop].geoCode.split(",")[1];
              arrPorts.push({
                'key': prop, 'lat': lat, 'lon': lon,
                "dist": getDistance(lat, lon, nLat, nLon),
                "formattedAirport": objPorts[prop].formattedAirport
              });
            }
          }
          arrPorts.sort(function(a, b){
            // Sort by Distance
            return a.dist - b.dist;
          });
          return arrPorts.filter(function(aP){
            return (aP.dist <= nRadius);
          });
        }
      }
    };
})(document);

2020-12-13

Cypress E2E Quality Assurance
Cypress E2E Quality Assurance
End to End testing 🚀 Headless browser automation
/*    _____                               
     /  __ \                              
     | /  \/_   _ _ __  _ __ ___  ___ ___ 
     | |   | | | | '_ \| '__/ _ \/ __/ __|
     | \__/\ |_| | |_) | | |  __/\__ \__ \
      \____/\__, | .__/|_|  \___||___/___/.io
             __/ | |                      
            |___/|_|                      E2E

✅ Automatically capture a video when a test fails
✅ Test values persisted in the Vuex (Vue.js Vuex specific) store
✅ Apply optional configuration files via the command line
✅ Test the uploading of images
✅ Create custom reusable, and chainable commands, such as cy.signIn() or cy.turnOnFeature()
✅ Test responsive layout & Local Storage
✅ Test A11y WCAG success criteria  */

describe('E2E test | Hotel navigation, selection, and discovery', () => {
  context('Admin Add Hotel to Event', function () { // ignore CORS
      Cypress.on('uncaught:exception', (err, runnable) => { return false });
      it('Success Login then Save Event', () => {
          cy.viewport(1066, 600)  //  large laptop 66.563em
          cy.log( JSON.stringify(Cypress.env()) )
          let event_url;  //  The URL of the first event (default)
          let dMessage = new Date();  //  Now
          dMessage = dMessage.toDateString() + " " + dMessage.toLocaleTimeString();

          cy.tt_SignIn(Cypress.env( "mock_email" ), Cypress.env( "mock_password" ))
          .then(() => {
              cy.window().then( $win => {
                  cy.wrap( $win.system_output ).should("exist")
              })
          })
          cy.url().should('not.include', 'login.')
          cy.visit( Cypress.env( "e2e_url_events" ) )
          cy.url().should('include', 'events.')
          Cypress.Cookies.debug(true, { verbose: false })
          cy.getCookie('logintoken').should('exist')
          cy.getCookie('role_id').should('exist')
          cy.getCookie('username').should('exist')
          cy.getCookie('otapi_token').should('exist')
          cy.get("a[href*='event-edit']" ).first().click()  //  Find the first matching link in the table.
          cy.get("#messages" ).type("{selectall}{backspace}E2E Test: " + dMessage )
          cy.get("#eventForm > div.border-top.d-flex.pt-3.row > div > input" ).first().click()  //  Save change

          cy.get("#airTab" ).click()  //  select tab
          cy.get("#activate_flights" ).check();
          cy.get("#flightForm > div.border-top.d-flex.pt-3.row > div > input" ).click();           

          cy.get("#vehicleTab" ).click()  //  select tab
          cy.get("#activate_vehicle" ).uncheck();
          cy.get("#vehicleForm > div.border-top.d-flex.pt-3.row > div > input" ).click();   

          cy.get("#hotelTab" ).click()  //  select tab
          cy.get("#activate_hotels" ).check();
          cy.get("#hotelForm > div.border-top.d-flex.pt-3.row > div > input" ).click();   

          //  Extract URL from INPUT
          cy.get('#siteURL').invoke('val')
              .then( value => { event_url = value; });
          cy.then(() => { return cy.visit(event_url); });
      })
  })
  context('Choose Flight', function () {
      Cypress.on('uncaught:exception', (err, runnable) => { return false }); // ignore CORS
      it('Success Flight added to cart', () => {
          cy.viewport(1066, 600)  //  large laptop 66.563em 
          cy.get("#from_airport" ).type( "ORD" )
          cy.get("#to_airport" ).type( "LGA" )
          cy.get("input[name='from_date']" ).click({ force: true })

          cy.server()
          cy.route("*").as( "checkout" )

          cy.get("div.vdp-datepicker.flex-fill > div:nth-child(2) > div > span:nth-child(39)" ).first().click()
          cy.get("#search-widget-btn" ).click()
          cy.wait("@checkout" ).its('status').should('eq', 200)

          cy.get("h5.modal-title").should("not.be.visible")
              .then( ($ModalMsg) => {
                  cy.get("div.align-self-center.col-6.col-md.col-sm.col-xl.order-12.p-xs-1.text-right > button" ).first().click() 
              } )
      })
  })
  context('Hotel LightBox', function () {
      Cypress.on('uncaught:exception', (err, runnable) => { return false }); // ignore CORS
      it('Success Hotel added to cart', () => {
          cy.viewport(1066, 600)  //  large laptop 66.563em
          cy.wait(2000)
          cy.get("picture > img" ).first()
              .then( ( $picture )=>{
                  cy.wrap( $picture  ).click()
                  cy.wait( 6000 )
              })
          cy.get(".l-ltbx__image" ).first().click()  //  Cycle photos forward
          cy.get(".l-ltbx__vect--right" )
              .then( ( $arrow_right ) => {
                  cy.wait( 1000 )
                  cy.wrap( $arrow_right ).click()
                  cy.wait( 1000 )
                  cy.wrap( $arrow_right ).click()
                  cy.wait( 1000 )
                  cy.wrap( $arrow_right ).click()
          })
          cy.wait( 1000 )
          cy.get(".l-ltbx__btn" ).first()  //  Cycle photos backward
              .then( ( $arrow_left ) => {
                  cy.wrap( $arrow_left ).click()
                  cy.wait( 1000 )
              })
          cy.get(".l-ltbx__figcap").invoke("text").should("include", "4 of")
              .then( () => {
                  cy.get(".l-ltbx__vect" ).first().click()  //  Close Modal
                  cy.get("OUTPUT BUTTON.l-button" ).first().click()  //  Book Room
                      .then( () => {
                          cy.get( "A.ttfont-semibold.tttext-gray-700").first().click()  //  Change Tab
                          cy.wait( 1000 )
                          cy.get( "A.ttfont-semibold.tttext-gray-700").first().click()  //  Change Tab
                          cy.wait( 1000 )
                          cy.get( "ARTICLE SECTION BUTTON.l-button").first().click()  //  Book Room
                              .then( ()=>{
                                  cy.wait( 4000 )
                                  cy.url().should('include', '/checkout')
                              })
                      })
              })
      })
  })
})

2020-12-07

Asynchronous eCom Nav Category Count
Asynchronous eCom Nav Category Count
Asynchronous recursive crawl reports the total number of products by category.
// Desc:  Asynchronous recursive crawl report the total number of products by category
// Usage: Console SNIPPET catCount.init();

/*   @@@@@@ @@@  @@@ @@@ @@@@@@@  @@@@@@@  @@@@@@@@ @@@@@@@ 
!@@     @@!@!@@@ @@! @@!  @@@ @@!  @@@ @@!        @!!   
 !@@!!  @!@@!!@! !!@ @!@@!@!  @!@@!@!  @!!!:!     @!!   
    !:! !!:  !!! !!: !!:      !!:      !!:        !!:   
::.: :  ::    :  :    :        :       : :: ::     :     run in console */

var catCount = (function(_d,_q){
"use strict";
let aSub = [];
console.clear();

return {
    init: function(){
        // Get ref to all product categories in the left nav 🛒
        aSub = [ ... _d.querySelectorAll( _q ) ].filter( ( el ) => {
            return (( el.firstChild.nodeValue ) && ( el.href ));
        } );
        aSub.forEach( ( elLink ) => {
            if( elLink ) catCount.asyncTotal( elLink );
        } );
    },
    parse: function( _Name, _Contents ){
        let aTotl = _Contents.split("sizeTotalNumRecs");
        if( aTotl[1].split('"')[2] ){
             console.log( _Name, aTotl[1].split('"')[2]);
        }
        return true;
    },
    asyncTotal: function( _elLink ){
        let oXhr = new XMLHttpRequest();
        oXhr.open("GET", _elLink.href, true);
        oXhr.onreadystatechange = () => {
            if( this.readyState!==4 || this.status!==200 ) return;
            catCount.parse( _elLink.firstChild.nodeValue, this.responseText );
        };
        oXhr.send();
    }
}

})(document, "LI.item nav > a" );

2020-12-07

Color of the Year CSS Styles
Color of the Year CSS Styles
Color of the Year 2000 thru 2021 CSS Utility classes
/* Tailwind like CSS Utility classes for the
Pantone Color of the Years from 2000 thru 2021
/*      ____             _                   
 *     |  _ \ __ _ _ __ | |_ ___  _ __   ___ 
 *     | |_) / _` | '_ \| __/ _ \| '_ \ / _ \  2000- 2021
 *     |  __/ (_| | | | | || (_) | | | |  __/
 *     |_|   \__,_|_| |_|\__\___/|_| |_|\___|  🟥 🟩 🟦 🟪 🟨 */

 /* Color of the Year begin */
 .bg-coy_2000  {background-color: #9BB7D4;}  /* Cerulean */
 .bg-coy_2001  {background-color: #C74375;}  /* Fuchsia Rose */
 .bg-coy_2002  {background-color: #BF1932;}  /* True Red */
 .bg-coy_2003  {background-color: #7BC4C4;}  /* Aqua Sky */
 .bg-coy_2004  {background-color: #E2583E;}  /* Tigerlily */
 .bg-coy_2005  {background-color: #53B0AE;}  /* Blue Turquoise */
 .bg-coy_2006  {background-color: #DECDBE;}  /* Sand Dollar */
 .bg-coy_2007  {background-color: #9B1B30;}  /* Chili Pepper */
 .bg-coy_2008  {background-color: #5A5B9F;}  /* Blue Iris */
 .bg-coy_2009  {background-color: #F0C05A;}  /* Mimosa */
 .bg-coy_2010  {background-color: #45B5AA;}  /* Turquoise */
 .bg-coy_2011  {background-color: #D94F70;}  /* Honeysuckle */
 .bg-coy_2012  {background-color: #DD4124;}  /* Tangerine Tango */
 .bg-coy_2013  {background-color: #009473;}  /* Emerald */
 .bg-coy_2014  {background-color: #B163A3;}  /* Radiant Orchid */
 .bg-coy_2015  {background-color: #955251;}  /* Marsala */
 .bg-coy_2016  {background-color: #F7CAC9;}  /* Rose Quartz */
 .bg-coy_2016b {background-color: #92A8D1;}  /* Serenity */
 .bg-coy_2017  {background-color: #88B04B;}  /* Greenery */
 .bg-coy_2018  {background-color: #5F4B8B;}  /* Ultra Violet */
 .bg-coy_2019  {background-color: #FF6F61;}  /* Living Coral */
 .bg-coy_2020  {background-color: #0F4C81;}  /* Classic Blue */
 .bg-coy_2021  {background-color: #939597;}  /* Ultimate Gray */
 .bg-coy_2021b {background-color: #F5DF4D;}  /* Illuminating */

 .text-coy_2000  {color: #9BB7D4;}  /* Cerulean */
 .text-coy_2001  {color: #C74375;}  /* Fuchsia Rose */
 .text-coy_2002  {color: #BF1932;}  /* True Red */
 .text-coy_2003  {color: #7BC4C4;}  /* Aqua Sky */
 .text-coy_2004  {color: #E2583E;}  /* Tigerlily */
 .text-coy_2005  {color: #53B0AE;}  /* Blue Turquoise */
 .text-coy_2006  {color: #DECDBE;}  /* Sand Dollar */
 .text-coy_2007  {color: #9B1B30;}  /* Chili Pepper */
 .text-coy_2008  {color: #5A5B9F;}  /* Blue Iris */
 .text-coy_2009  {color: #F0C05A;}  /* Mimosa */
 .text-coy_2010  {color: #45B5AA;}  /* Turquoise */
 .text-coy_2011  {color: #D94F70;}  /* Honeysuckle */
 .text-coy_2012  {color: #DD4124;}  /* Tangerine Tango */
 .text-coy_2013  {color: #009473;}  /* Emerald */
 .text-coy_2014  {color: #B163A3;}  /* Radiant Orchid */
 .text-coy_2015  {color: #955251;}  /* Marsala */
 .text-coy_2016  {color: #F7CAC9;}  /* Rose Quartz */
 .text-coy_2016b {color: #92A8D1;}  /* Serenity */
 .text-coy_2017  {color: #88B04B;}  /* Greenery */
 .text-coy_2018  {color: #5F4B8B;}  /* Ultra Violet */
 .text-coy_2019  {color: #FF6F61;}  /* Living Coral */
 .text-coy_2020  {color: #0F4C81;}  /* Classic Blue */
 .text-coy_2021  {color: #939597;}  /* Ultimate Gray */
 .text-coy_2021b {color: #F5DF4D;}  /* Illuminating */
 /* Color of the Year end */

2020-12-07

Solve Anagram Puzzle
Solve Anagram Puzzle
Do two strings contain the exact amount of letters to form two words?
/* An anagram is a word or phrase formed by rearranging the letters
of a different word or phrase, typically using all the original
letters exactly once. For example, the word anagram itself can be
rearranged into nag a ram, also the word binary into brainy. 🎯 🍰 🔥
   _                                         
  /_\  _ __   __ _  __ _ _ __ __ _ _ __ ___  
 //_\\| '_ \ / _` |/ _` | '__/ _` | '_ ` _ \ 
/  _  \ | | | (_| | (_| | | | (_| | | | | | |
\_/ \_/_| |_|\__,_|\__, |_|  \__,_|_| |_| |_|
                 |___/                     */


//  Determine if two strings are Anagrams
function isAnagram( word1 = "DOCTORWHO", word2 = "TORCHWOOD"){
  let uc1 = word1.toUpperCase(), uc2 = word2.toUpperCase()
  return ([ ... uc1 ].filter(( c )=>{
    if( uc2.indexOf( c ) != -1 ){
      uc2 = uc2.replace( c, "" )  //  Replace First Occurrence
      return true;
    }
  }).length === uc1.length && (!uc2))
}
console.warn( isAnagram("neodigm", "dogimen") );

// Palindromes | They can be read the same backwards and forwards!
// Is TACOCAT spelled backward still TACOCAT?
// People have been asking this question for thousands of years until...
// I wrote a function in JavaScript to prove it and end the debate. Palindrome in JavaScript

let isPalindrome = ( sIn = "tacocat" ) => ( sIn.split("").reverse().join("") === sIn );
/*🐈‍🐱
 _._     _,-'""`-._
(,-.`._,'(       |\`-/|
    `-.-' \ )-`( , o o)
          `-    \`_`"'- My name is Taco! ^_^
*/

2020-12-07

Virtual Keyboard Extention Configuration
Virtual Keyboard Extention Configuration
TS Virtual Keyboard Chrome Extention
// TS Virtual Keyboard ⌨️ Chrome Extention | Configuration Class
/***
 *     _______                     _                _            
 *    (_______)                   | |              (_)      _    
 *     _      _   _ ____   ____    \ \   ____  ____ _ ____ | |_  
 *    | |    | | | |  _ \ / _  )    \ \ / ___)/ ___) |  _ \|  _) 
 *    | |____| |_| | | | ( (/ / _____) | (___| |   | | | | | |__ 
 *     \______)__  | ||_/ \____|______/ \____)_|   |_| ||_/ \___)
 *           (____/|_|                               |_|         
 npm install --save @types/chrome
 */

class AVKOptions {
    aOpts : Array<any>;
    constructor ( pAr : Array<any> = [] ) {
        this.aOpts = pAr;
    }
    setState (  sOpt : string, bState : boolean ) : boolean{
        this.aOpts = this.aOpts.filter( (e) => {
            if (e[0] === sOpt) e[1] = bState;
             return true;
        } );
        return bState;
    }
    getState ( sOpt : string ) : boolean {
        return this.aOpts.filter( (e) => {
            if (e[0] === sOpt) {
                return true;
            }            
        })[0][1];
    }
    getFeedback ( sOpt : string ) : string {
        return this.aOpts.filter( (e) => {
            if (e[0] === sOpt) {
                return true;
            }            
        })[0][2];
    }
}
export let options = new AVKOptions([["audio", false, "Click Sounds"],
["autohide", false, "Hide if not in use"], ["blur", false, "Blur Text"],
["hover", false, "Hover No Click"], ["opaque", false, "Cannot See Through"],
["scramble", false, "Rearrange Keys"], ["theme", false, "Daytime theme"]]);

2020-12-07

Web Music Ad Blocker Snippet
Web Music Ad Blocker Snippet
Automatically mute the Music player when Ads are playing and unmute when they are done (in Chrome).
/* Install: Open Chrome Dev Tools (Command+option+I on Mac). Menu > Sources > Snippets
   Install: Create a new Snippet named musicADify.js, Paste this script, Save (Command+S).
   Usage: Run the Snippet once each time you start the Music Web Player.
   Usage: Right-Click the snippet named musicADify.js and choose Run from the drop-down.
   Usage: Close Chrome Dev Tools. 🏖️ Play your Jams! 🎶

╔═╗┌─┐┌─┐┌┬┐┬┌─┐┬ ┬  ╔═╗┌┬┐┌─┐
╚═╗├─┘│ │ │ │├┤ └┬┘  ╠═╣ ││└─┐
╚═╝┴  └─┘ ┴ ┴└   ┴   ╩ ╩─┴┘└─┘ */

 let spotADify = ( (_d, _q, _t) => {
    let eS = _d.querySelector( _q ), bS = true;
    if( eS ){ // 🏖️ Play your Jams! 🎶
        bS = ( eS.getAttribute("aria-label") == "Mute" );
        setInterval( () => {spotADify.tick();}, _t);
        return {
        "tick":  () => {
          if((_d.title.indexOf("Adve") != -1) || (_d.title.indexOf("Spoti") != -1)){
              if(  bS ){ eS.click(); bS=!true; }
          }else{
              if( !bS ){ eS.click(); bS=true; }
          }
        }
      }
    }
  })( document, "[aria-label='Mute'],[aria-label='Unmute']", 256);

2020-12-07

Capture Entire DOM State into Inline CSS Snapshot
Capture Entire DOM State into Inline CSS Snapshot
Save As HTML a snapshot capture of entire DOM State with inline CSS
// Desc: Save As HTML a snapshot capture of entire DOM State with inline CSS
// Usage: Just paste this code into the console 🌴
/*  _________             __    __     ____  __.                                  
  /   _____/ ____  _____/  |__/  |_  |    |/ _|___________   __ __  ______ ____  
  \_____  \_/ ___\/  _ \   __\   __\ |      < \_  __ \__  \ |  |  \/  ___// __ \ 
  /        \  \__(  <_> )  |  |  |   |    |  \ |  | \// __ \|  |  /\___ \\  ___/ 
 /_______  /\___  >____/|__|  |__|   |____|__ \|__|  (____  /____//____  >\___  >   ES2022*/


function computedCSS2inline(element, options = {}) {
    if (!element) {
        throw new Error("No element specified.");
    }

    if (options.recursive) {
        Array.from( element.children ).forEach(child => {
        computedCSS2inline(child, options);
        });
    }

    const computedStyle = getComputedStyle(element);
    //(options.properties || computedStyle)::each(property => {
        Array.from( computedStyle ).forEach(property => {
        element.style[property] = computedStyle.getPropertyValue(property);
        //element.setAttribute("class", "")
    });
}

computedCSS2inline(document.body, {recursive: true});

[ ... document.querySelectorAll("script, link, style")].forEach(function(s){ s.outerHTML = ""})

async function saveToFile() {
    const handle = await showSaveFilePicker({
        suggestedName: 'grabbed.html',
        types: [{
            description: 'HTML',
            accept: {'text/html': ['.html']},
        }]
    });
    const writable = await handle.createWritable();
    await writable.write(document.body.parentNode.innerHTML);
    writable.close();
};
console.log("NOTE: Run saveToFile() in console!")

2020-09-16

Real-world Vetted Snippets