Curated Links Vue.js
Scott C. Krause | Saturday, Jul 24, 2021
Vue.js The Progressive JavaScript Framework
Unlike other monolithic frameworks, Vue is designed from the ground up to be incrementally adoptable. The core library is focused on the view layer only, and is easy to pick up and integrate with other libraries or existing projects.
Vue and TypeScript Expertise
Since 2014, I've implemented the best coding practices that reduce complexity and take advantage of the flexibility of the Vue.js ecosystem.
I am adept at using a wide range of modern Vue tools to build mission-critical applications. This includes understanding how to properly use the Composition API, TypeScript, Vue CLI, Vue Router, Vuex, Vuetify, Quasar, and Vue DevTools to maximize code reusability, easily expand application functionality, and standardize UI style across the organization.
If you have an existing Vue 2 project or library that you intend to upgrade to Vue 3 please contact me.
The Hybrid Graph Vue/Vuetify Component
The hybrid graph component displays a horizontal bar chart of both percentages and absolute values with a goal adornment. The goal adornment is a superimposed gray vertical line positioned on the X axis depicting the goal value.
Try it ⚡
Source Code 👁️
Template Vue SFC
<section class="l-hbr-grd" v-for="goal in goals" :key="goal.metric">
<aside>
<p>{ { goal.metric } }</p>
<p>Goal: { { goal.thresholdValue } }<span v-if="goal.thresholdType == 'Percentage'">%</span></p>
</aside>
<output>
<v-progress-linear
:value="renderAbsolute( goal )[0]"
background-color="#dae4ef"
color="#1ac9a8" height="24"
>
<template>
<span class="inline__text">{ { Math.ceil(renderAbsolute( goal )[2]) } }<span v-if="goal.thresholdType == 'Percentage'">%</span>
</span>
</template>
</v-progress-linear>
<div class="goal-line" :style="{ width: renderAbsolute( goal )[1] + '%' }">
</div>
</output>
</section>
JavaScript Vue SFC
exampleVue = new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
goals: [ // Assumptions: Goal Metric is unique. Threshold is the goal expressed in per cent or abs.
{"metric": "RINVOQ Market Share", "currentAchieved": "7", "thresholdType": "Percentage", "thresholdValue": "9"},
{"metric": "SKYRIZI Market Share", "currentAchieved": "44", "thresholdType": "Percentage", "thresholdValue": "49"},
{"metric": "ISA Utilization", "currentAchieved": "80", "thresholdType": "Percentage", "thresholdValue": "68"},
{"metric": "Call Plan Achievement", "currentAchieved": "55", "thresholdType": "Percentage", "thresholdValue": "76"},
{"metric": "Priority Target Reach", "currentAchieved": "72", "thresholdType": "Percentage", "thresholdValue": "86"},
{"metric": "Priority Target Frequency", "currentAchieved": "6", "thresholdType": "Number, 1 decimal place", "thresholdValue": "9"},
{"metric": "Priority Target Email", "currentAchieved": "19", "thresholdType": "Whole Number", "thresholdValue": "300"},
{"metric": "SUMMIT Ranking", "currentAchieved": "9", "thresholdType": "Top Half of Rank Group", "thresholdValue": "6"}
],
tmpGoals: "",
}),
mounted(){
this.tmpGoals = JSON.stringify( this.goals )
},
methods: {
renderAbsolute( _g ){
if( _g.thresholdType == "Percentage" ){
return [_g.currentAchieved, _g.thresholdValue, _g.currentAchieved];
}else{
if( _g.currentAchieved >= _g.thresholdValue ){ // Goal Acheived
return [100, Math.round((_g.thresholdValue / _g.currentAchieved) * 100), _g.currentAchieved];
}else{
return [Math.round((_g.currentAchieved / _g.thresholdValue) * 100), 100, _g.currentAchieved];
}
}
}
}
})
CSS Vue SFC
.l-hbr-grd {
display: grid;
grid-template-columns: 148px 1fr;
gap: 0px 0px;
width: 99%;
padding: 0;
}
.l-hbr-grd > aside {
position: relative;
border-width: 0; border-right: solid 1px #aaa;
text-align: right;
padding: 2px 1px 12px 2px;
}
.l-hbr-grd > aside > p {
color: #2d2926;
font-family: 'Lato', sans-serif;
margin: 2px; padding: 2px;
line-height: .98;
white-space: nowrap;
overflow: hidden;
}
.l-hbr-grd > aside > p:nth-of-type(1) { font-size: 12px; }
.l-hbr-grd > aside > p:nth-of-type(2) { font-size: 11px; }
.l-hbr-grd > output { margin-left: 6px; }
.l-hbr-grd .inline__text {
color: #2d2926;
font-family: 'Lato', sans-serif; font-size: 12px;
margin: 2px; padding: 2px;
line-height: .98;
white-space: nowrap;
overflow: hidden;
}
.l-hbr-grd .goal-line {
position: relative; top: -28px;
display: block;
border: 0;
border-right: solid 4px #aaa;
width: 0; height: 32px;
z-index: 64;
}
Emerging Tech
Neodigm 55 JavaScript UX micro-library
Popups, Toast, Parallax, and SFX
2022-07-25
Emerging Tech
Curated GA4 Links
Indispensable Curated Google Analytics 4 Links
2022-06-17
Emerging Tech
Curated SFDC Qlik Tableau Links
Indispensable Curated Salesforce Qlik Tableau Links
2022-03-25
Emerging Tech
Curated LWC Links
Indispensable Curated LWC Links
2021-08-09
Emerging Tech
Curated Links Vue.js
Indispensable Curated Vue Links
2021-07-24
Emerging Tech
Curated PWA Links
Indispensable Curated PWA Links
2021-07-09
Emerging Tech
Creative 3D animation resources
Indispensable Curated Creative Links
2021-06-25
Emerging Tech
Transition to TypeScript
Indispensable Curated TypeScript Links
2021-06-05
Emerging Tech
The Clandestine Dead Drop
The Ironclad Clandestine Dead Drop
2021-05-31
Emerging Tech
New Macbook Setup for Developers
New Macbook Config for Devs 2022
2021-04-24
Emerging Tech
Svelte
Indispensable Curated Svelte Links
2021-04-05
Emerging Tech
UX Usability Heuristics
HE Heuristic Evaluation
2021-03-19
Emerging Tech
Curated Blogfolios Links
Personal Websites
2021-03-15
Emerging Tech
Curated JavaScript Links
Indispensable Curated JavaScript Links
2021-03-12
Emerging Tech
Curated Emerging Tech Links
Indispensable Curated Tech Links
2021-03-04
Emerging Tech
Cytoscape Skills Data Visualization
Persuasive Infographics & Data Visualizations
2021-02-20
Emerging Tech
eCommerce Accessibility A11y
Accessibility Challenges Unique to eCommerce
2020-12-07
Emerging Tech
Roll Dice in High-Fidelity 3D
Create 3D Dice with Strong Random Entropy
2020-11-02
Calculate Aspect Ratio of Viewport
Calculate Aspect Ratio of Viewport
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
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
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 */
/* 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