Thursday, August 6, 2020

Basic drag and drop (no back-end)

let dropArea;

document.addEventListener('DOMContentLoaded', () => {
    dropArea = document.getElementById('drop-area');

    ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => dropArea.addEventListener(eventName, preventDefaults, false));
    ['dragenter', 'dragover'].forEach(eventName => dropArea.addEventListener(eventName, highlight, false));
    ['dragleave', 'drop'].forEach(eventName => dropArea.addEventListener(eventName, unhighlight, false));

    dropArea.addEventListener('drop', handleDrop, false);
}, false);





function preventDefaults(e) {
    e.preventDefault();
    e.stopPropagation();
}



function highlight(e) {
    dropArea.classList.add('highlight');
}

function unhighlight(e) {
    dropArea.classList.remove('highlight')
}


function handleDrop(e) {
    const dt = e.dataTransfer;
    const files = dt.files;

    handleFiles(files);
}


function handleFiles(files) {
    // console.log(files);
    // alert(Array.isArray(files)); // false
    // alert(Array.isArray(Object.values(files))); // true
    // alert(Array.isArray([...files])); // true

    // this won't work:
    //      files.forEach(uploadFile);

    [...files].forEach(uploadFile);
}

function uploadFile(file) {
    alert('uploading ' + file.name);
}
<head>


    <script src='index.js'></script>

    <style>
        html {
            box-sizing: border-box;
        }

        *,
        *:before,
        *:after {
            box-sizing: inherit;
        }

        * {
            margin: 0;
            padding: 0;
        }

        #drop-area {
            border: 2px dashed #ccc;
            border-radius: 20px;
            width: 480px;
            font-family: sans-serif;
            margin: 100px auto;
            padding: 20px;
        }

        #drop-area.highlight {
            border-color: purple;
        }

        p {
            margin-top: 0;
        }

        .my-form {
            margin-bottom: 10px; 
        }

        #gallery {
            margin-top: 10px;
        }

        #gallery img {
            width: 150px;
            margin-bottom: 10px;
            margin-right: 10px;
            vertical-align: middle;
        }

        .button {
            display: inline-block;
            padding: 10px;
            background: #ccc;
            cursor: pointer;
            border-radius: 5px;
            border: 1px solid #ccc;
        }

        #fileElem {
            display: none;
        }
    </style>

</head>

<body>

    <div id="drop-area">
        <div id="my-form">
            <p>Upload multiple files with the file dialog or by dragging and dropping images onto the dashed region</p>
            <input type="file" id="fileElem" multiple="multiple" accept="image/*" onchange="handleFiles(this.files)">
            <label for="fileElem" class="button">Select some files</label>
        </div>
    </div>


</body>

Wednesday, August 5, 2020

FileList.forEach

We can't use forEach on FileList data structure. FileList is an object, not an array. To convert it to array, we have to use Object.values, or use spread operator:
function handleFiles(files) {
    console.log(files);
    alert(Array.isArray(files)); // false
    alert(Array.isArray(Object.values(files))); // true
    alert(Array.isArray([...files])); // true

    // this won't work:
    //      files.forEach(uploadFile);

    [...files].forEach(uploadFile);
}

function uploadFile(file) {
    alert('uploading ' + file.name);
}

Tuesday, July 28, 2020

Cash Register

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/javascript-algorithms-and-data-structures-projects/cash-register

JavaScript Algorithms and Data Structures Projects: Cash Register


Design a cash register drawer function checkCashRegister() that accepts purchase price as the first argument (price), payment as the second argument (cash), and cash-in-drawer (cid) as the third argument.

cid is a 2D array listing available currency.

The checkCashRegister() function should always return an object with a status key and a change key.

Return {status: "INSUFFICIENT_FUNDS", change: []} if cash-in-drawer is less than the change due, or if you cannot return the exact change.

Return {status: "CLOSED", change: [...]} with cash-in-drawer as the value for the key change if it is equal to the change due.

Otherwise, return {status: "OPEN", change: [...]}, with the change due in coins and bills, sorted in highest to lowest order, as the value of the change key.

Currency Unit Amount
Penny $0.01 (PENNY)
Nickel $0.05 (NICKEL)
Dime $0.1 (DIME)
Quarter $0.25 (QUARTER)
Dollar $1 (ONE)
Five Dollars $5 (FIVE)
Ten Dollars $10 (TEN)
Twenty Dollars $20 (TWENTY)
One-hundred Dollars $100 (ONE HUNDRED)


See below for an example of a cash-in-drawer array:
[
["PENNY", 1.01],
["NICKEL", 2.05],
["DIME", 3.1],
["QUARTER", 4.25],
["ONE", 90],
["FIVE", 55],
["TEN", 20],
["TWENTY", 60],
["ONE HUNDRED", 100]
]

Duration: 1 hour. See video: https://www.youtube.com/watch?v=rx7IUYq1wkU
const UNIT_VALUES = Object.freeze({
    'ONE HUNDRED': 100,
    TWENTY: 20,
    TEN: 10,
    FIVE: 5,
    ONE: 1,
    QUARTER: 0.25,
    DIME: 0.1,
    NICKEL: 0.05,
    PENNY: 0.01
});
 
 
function monify(obj) {
    if (typeof obj === 'number') {
        return Number(obj.toFixed(2).replace('.', ''));
    } else if (obj === undefined || obj === null || typeof obj === 'string' || obj instanceof Date) {
        return obj;
    } else if (Array.isArray(obj)) {
        return obj.map(el => monify(el));
    } else if (typeof obj === 'object') {
        return Object.entries(obj).reduce((acc, [key, value]) => ({ ...acc, [key]: monify(value) }), {});
    } else {
      throw new Error('Invalid Data', obj);
    }
}
 
console.log(monify(new Date()));
 
function floatify(obj) {
    if (typeof obj === 'number') {
        return Number(obj) / 100;
    } else if (obj === undefined || obj === null || typeof obj === 'string'|| obj instanceof Date) {
        return obj;
    } else if (Array.isArray(obj)) {
        return obj.map(el => floatify(el));
    } else if (typeof obj === 'object') {
        return Object.entries(obj).reduce((acc, [key, value]) => ({ ...acc, [key]: floatify(value) }), {});
    } else {
        throw new Error('Invalid Data', obj);
    }
}
 
function checkCashRegister(argPrice, argCash, argCid, argUnitValues = UNIT_VALUES) {
    const {
        argPrice: price,
        argCash: cash,
        argCid: cid,
        argUnitValues: UNIT_VALUES
    } = monify({ argPrice, argCash, argCid: [...argCid], argUnitValues });
 
    // From here onwards, we can pretend that we are still operating on original values, courtesy of monify function.
    // All values are all scaled to 100 times for us, to prevent inaccurate representation of cents in floating point number.
    // No need to change the code's logic.
    
    // If we are not using monify, floating point fraction produces inaccurate result, e.g.,
    // console.log(100 - 3.26 - 60);
    // 		produces incorrect result: 36.739999999999995
    // Correct result is: 36.74
 
    cid.sort(([aUnit], [bUnit]) => UNIT_VALUES[bUnit] - UNIT_VALUES[aUnit]);
 
    const change = cash - price;
    let uncollectedChange = change;
    const collectedChange = [];
    for (const [drawerUnit, drawerAmount] of cid) {
        const unitValue = UNIT_VALUES[drawerUnit];
 
        if (unitValue > change) {
            collectedChange.push([drawerUnit, 0]);
        } else {
            if (uncollectedChange >= drawerAmount) {
                collectedChange.push([drawerUnit, drawerAmount]);
                uncollectedChange -= drawerAmount;
            } else {
                const toDeduct = Math.floor(uncollectedChange / unitValue) * unitValue;
                collectedChange.push([drawerUnit, toDeduct]);
                uncollectedChange -= toDeduct;
            }
        }
    }
 

    if (uncollectedChange > 0) {
        return { status: 'INSUFFICIENT_FUNDS', change: [] };
    }
 
    const totalDrawerAmount = cid.reduce((sum, [, drawerAmount]) => sum + drawerAmount, 0);
    const status = totalDrawerAmount - change === 0 ? 'CLOSED' : 'OPEN';
 
    return floatify({
        status,
        change: collectedChange
            .filter(([, ccValue]) => status === 'OPEN' && ccValue > 0 || status === 'CLOSED')
            .sort(([aUnit, aValue], [bUnit, bValue]) => bValue - aValue || UNIT_VALUES[aUnit] - UNIT_VALUES[bUnit])
    });
}
 
[
    checkCashRegister(19.5, 20, [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100]]),
    checkCashRegister(3.26, 100, [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100]]),
    checkCashRegister(19.5, 20, [["PENNY", 0.01], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]]),
    checkCashRegister(19.5, 20, [["PENNY", 0.01], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 1], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]]),
    checkCashRegister(19.5, 20, [["PENNY", 0.5], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]]),
].forEach(result => console.log(result));

Output:
{ status: 'OPEN', change: [ [ 'QUARTER', 0.5 ] ] }
{ status: 'OPEN',
  change: 
   [ [ 'TWENTY', 60 ],
     [ 'TEN', 20 ],
     [ 'FIVE', 15 ],
     [ 'ONE', 1 ],
     [ 'QUARTER', 0.5 ],
     [ 'DIME', 0.2 ],
     [ 'PENNY', 0.04 ] ] }
{ status: 'INSUFFICIENT_FUNDS', change: [] }
{ status: 'INSUFFICIENT_FUNDS', change: [] }
{ status: 'CLOSED',
  change: 
   [ [ 'PENNY', 0.5 ],
     [ 'NICKEL', 0 ],
     [ 'DIME', 0 ],
     [ 'QUARTER', 0 ],
     [ 'ONE', 0 ],
     [ 'FIVE', 0 ],
     [ 'TEN', 0 ],
     [ 'TWENTY', 0 ],
     [ 'ONE HUNDRED', 0 ] ] }

Monday, July 27, 2020

Caesars Cipher

JavaScript Algorithms and Data Structures Projects: Caesars CipherPassed


One of the simplest and most widely known ciphers is a Caesar cipher, also known as a shift cipher. In a shift cipher the meanings of the letters are shifted by some set amount.

A common modern use is the ROT13 cipher, where the values of the letters are shifted by 13 places. Thus 'A' ↔ 'N', 'B' ↔ 'O' and so on.

Write a function which takes a ROT13 encoded string as input and returns a decoded string.

All letters will be uppercase. Do not transform any non-alphabetic character (i.e. spaces, punctuation), but do pass them on.


const rot13 = str =>
  String.fromCharCode(...str.split('').map(l => 
    /\w/.test(l) ? 
      (l.charCodeAt(0) - 'A'.charCodeAt(0) + 13) % 26 + 'A'.charCodeAt(0)
    :
      l.charCodeAt(0)
  ));

[
    rot13("SERR PBQR PNZC") ,
    rot13("SERR CVMMN!") ,
    rot13("SERR YBIR?") ,
    rot13("GUR DHVPX OEBJA SBK WHZCF BIRE GUR YNML QBT.")
].forEach(result => console.log(result));

FREE CODE CAMP
FREE PIZZA!
FREE LOVE?
THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.

Roman Numeral Converter

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/javascript-algorithms-and-data-structures-projects/roman-numeral-converter

const ROMAN_NUMERALS = Object.freeze({
    1000: 'M',
    900: 'CM',
    500: 'D',
    400: 'CD',
    100: 'C',
    90: 'XC',
    50: 'L',
    40: 'XL',
    10: 'X',
    9: 'IX',
    5: 'V',
    4: 'IV',
    1: 'I'
});

function convertToRoman(num) {
    const digit = Math.max(...Object.keys(ROMAN_NUMERALS).filter(key => num >= key));
    if (num === digit) {
        return ROMAN_NUMERALS[digit];
    }
    return ROMAN_NUMERALS[digit] + convertToRoman(num - digit);
}

[
    2,
    3,
    4,
    5,
    9,
    12,
    16,
    29,
    44,
    45,
    68,
    83, 
    97,
    99,
    400,
    500,
    501,
    649,
    798,
    891,
    1000,
    1004,
    1006,
    1023, 
    2014,
    3999
].forEach(arg => console.log(arg, convertToRoman(arg)));

Output:
2 II
3 III
4 IV
5 V
9 IX
12 XII
16 XVI
29 XXIX
44 XLIV
45 XLV
68 LXVIII
83 LXXXIII
97 XCVII
99 XCIX
400 CD
500 D
501 DI
649 DCXLIX
798 DCCXCVIII
891 DCCCXCI
1000 M
1004 MIV
1006 MVI
1023 MXXIII
2014 MMXIV
3999 MMMCMXCIX

Another approach:
const ROMAN_NUMERALS = Object.freeze({
    M: 1000,
    CM: 900,
    D: 500,
    CD: 400,
    C: 100,
    XC: 90,
    L: 50,
    XL: 40,
    X: 10,
    IX: 9,
    V: 5,
    IV: 4,
    I: 1
})


function convertToRoman(num) {
    let rn = '';
    for (const [rnLetter, rnValue] of Object.entries(ROMAN_NUMERALS)) {
        const q = Math.trunc(num / rnValue);
        rn += rnLetter.repeat(q);
        num -= q * rnValue;
    }
    return rn;
}


[
    2,
    3,
    4,
    5,
    9,
    12,
    16,
    29,
    44,
    45,
    68,
    83, 
    97,
    99,
    400,
    500,
    501,
    649,
    798,
    891,
    1000,
    1004,
    1006,
    1023, 
    2014,
    3999
].forEach(arg => console.log(arg, convertToRoman(arg)));


Output:
2 II
3 III
4 IV
5 V
9 IX
12 XII
16 XVI
29 XXIX
44 XLIV
45 XLV
68 LXVIII
83 LXXXIII
97 XCVII
99 XCIX
400 CD
500 D
501 DI
649 DCXLIX
798 DCCXCVIII
891 DCCCXCI
1000 M
1004 MIV
1006 MVI
1023 MXXIII
2014 MMXIV
3999 MMMCMXCIX

Palindrome Checker

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/javascript-algorithms-and-data-structures-projects/palindrome-checker

JavaScript Algorithms and Data Structures Projects: Palindrome Checker
Return true if the given string is a palindrome. Otherwise, return false.

A palindrome is a word or sentence that's spelled the same way both forward and backward, ignoring punctuation, case, and spacing.

Note
You'll need to remove all non-alphanumeric characters (punctuation, spaces and symbols) and turn everything into the same case (lower or upper case) in order to check for palindromes.

We'll pass strings with varying formats, such as "racecar", "RaceCar", and "race CAR" among others.

We'll also pass strings with special symbols, such as "2A3*3a2", "2A3 3a2", and "2_A3*3#A2".


const removeAllNonNumeric = str => str.replace(/[\W_]/g, '');

function palindrome(str) {
  const clean = removeAllNonNumeric(str).toLowerCase();
  const len = clean.length;
  const mid = len / 2;

  for (let start = 0, end = len - 1; start < mid; ++start, --end) {
      if (clean[start] !== clean[end]) {
          return false;
      }
  }
  return true;
}

[
    "eye",
    "eye",
    "_eye",
    "race car",
    "not a palindrome",
    "A man, a plan, a canal. Panama",
    "never odd or even",
    "nope",
    "almostomla",
    "My age is 0, 0 si ega ym.",
    "1 eye for of 1 eye.",
    "0_0 (: /-\\ :) 0-0",
    "five|\\_/|four",
].forEach(arg => console.log(arg, ':', palindrome(arg)));



Output:
eye : true
eye : true
_eye : true
race car : true
not a palindrome : false
A man, a plan, a canal. Panama : true
never odd or even : true
nope : false
almostomla : false
My age is 0, 0 si ega ym. : true
1 eye for of 1 eye. : false
0_0 (: /-\ :) 0-0 : true
five|\_/|four : false

Saturday, July 25, 2020

Everything Be True

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/intermediate-algorithm-scripting/everything-be-true

Intermediate Algorithm Scripting: Everything Be True


Check if the predicate (second argument) is truthy on all elements of a collection (first argument).

In other words, you are given an array collection of objects. The predicate pre will be an object property and you need to return true if its value is truthy. Otherwise, return false.

In JavaScript, truthy values are values that translate to true when evaluated in a Boolean context.

Remember, you can access object properties through either dot notation or [] notation.


truthCheck([{"user": "Tinky-Winky", "sex": "male"}, {"user": "Dipsy", "sex": "male"}, {"user": "Laa-Laa", "sex": "female"}, {"user": "Po", "sex": "female"}], "sex") should return true.

truthCheck([{"user": "Tinky-Winky", "sex": "male"}, {"user": "Dipsy"}, {"user": "Laa-Laa", "sex": "female"}, {"user": "Po", "sex": "female"}], "sex") should return false.

truthCheck([{"user": "Tinky-Winky", "sex": "male", "age": 0}, {"user": "Dipsy", "sex": "male", "age": 3}, {"user": "Laa-Laa", "sex": "female", "age": 5}, {"user": "Po", "sex": "female", "age": 4}], "age") should return false.

truthCheck([{"name": "Pete", "onBoat": true}, {"name": "Repeat", "onBoat": true}, {"name": "FastForward", "onBoat": null}], "onBoat") should return false

truthCheck([{"name": "Pete", "onBoat": true}, {"name": "Repeat", "onBoat": true, "alias": "Repete"}, {"name": "FastForward", "onBoat": true}], "onBoat") should return true

truthCheck([{"single": "yes"}], "single") should return true

truthCheck([{"single": ""}, {"single": "double"}], "single") should return false

truthCheck([{"single": "double"}, {"single": undefined}], "single") should return false

truthCheck([{"single": "double"}, {"single": NaN}], "single") should return false

const truthCheck = (collection, pre) => collection.every(el => el[pre]);

[
    truthCheck([{"user": "Tinky-Winky", "sex": "male"}, {"user": "Dipsy", "sex": "male"}, {"user": "Laa-Laa", "sex": "female"}, {"user": "Po", "sex": "female"}], "sex"),
    truthCheck([{"user": "Tinky-Winky", "sex": "male"}, {"user": "Dipsy"}, {"user": "Laa-Laa", "sex": "female"}, {"user": "Po", "sex": "female"}], "sex") ,
    truthCheck([{"user": "Tinky-Winky", "sex": "male", "age": 0}, {"user": "Dipsy", "sex": "male", "age": 3}, {"user": "Laa-Laa", "sex": "female", "age": 5}, {"user": "Po", "sex": "female", "age": 4}], "age") ,
    truthCheck([{"name": "Pete", "onBoat": true}, {"name": "Repeat", "onBoat": true}, {"name": "FastForward", "onBoat": null}], "onBoat") ,
    truthCheck([{"name": "Pete", "onBoat": true}, {"name": "Repeat", "onBoat": true, "alias": "Repete"}, {"name": "FastForward", "onBoat": true}], "onBoat") ,
    truthCheck([{"single": "yes"}], "single"),
    truthCheck([{"single": ""}, {"single": "double"}], "single") ,
    truthCheck([{"single": "double"}, {"single": undefined}], "single") ,
    truthCheck([{"single": "double"}, {"single": NaN}], "single") ,
].forEach(result => console.log(result));


Output:
true
false
false
false
true
true
false
false
false