JS Airport Geo-Proximity Radius
Airport geo-proximity logic that answers questions, like What are the three closest airports to me right now?
2022-12-13
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
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
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
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
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
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
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
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
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
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 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 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 */
.text-coy_2022 {color: #6667AB;} /* Very Peri */
.text-coy_2023 {color: #BE3455;} /* Viva Magenta */
/* Color of the Year end */
2020-12-07
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
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
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
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