From d3affc535a9e1f7fed71b16d229d481b63791653 Mon Sep 17 00:00:00 2001 From: Johnny Date: Tue, 14 May 2024 15:31:07 -0700 Subject: [PATCH 1/3] Initial html and css implementations --- calculator.js | 0 index.html | 49 +++++++++++++++++++++++++++++++++++++++++++ style.css | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 calculator.js create mode 100644 index.html create mode 100644 style.css diff --git a/calculator.js b/calculator.js new file mode 100644 index 00000000..e69de29b diff --git a/index.html b/index.html new file mode 100644 index 00000000..24a19111 --- /dev/null +++ b/index.html @@ -0,0 +1,49 @@ + + + + + + + 100Devs Calculator - Johnny J Wu + + + + + +
+
+

+
+
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
+
+
+
+ + + \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 00000000..ed9be834 --- /dev/null +++ b/style.css @@ -0,0 +1,57 @@ +.container { + display: flex; +} + +.container.center { + justify-content: center; + align-items: center; + height: 100vh; +} + +button { + background-color: white; + border: none; + border-radius: 50%; + cursor: pointer; +} + +button:hover { + opacity: 0.8; +} + +button:active { + filter: brightness(0.8); +} + +.calculator { + background-color: black; + border-radius: 2rem; + width: 400px; + padding: 2rem; +} + +.calculator h1 { + background-color: white; + height: 5rem; + line-height: 5rem; + margin: 2rem 0; + padding: 0 1rem; + text-align: right; +} + +.calculator button { + aspect-ratio: 1; + width: 20%; + font-weight: bold; + font-size: 2rem; +} + +.calculator>.container { + flex-direction: column; + gap: 1rem; +} + +.calculator>.container .container { + justify-content: space-between; + align-content: space-between; +} \ No newline at end of file From b1e090992b99b4fc956ef073efd032c374dd6610 Mon Sep 17 00:00:00 2001 From: Johnny Date: Thu, 16 May 2024 19:37:42 -0700 Subject: [PATCH 2/3] First implementation of calculator logic --- calculator.js | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 17 ++--- style.css | 42 +++++++++-- 3 files changed, 239 insertions(+), 13 deletions(-) diff --git a/calculator.js b/calculator.js index e69de29b..54caf430 100644 --- a/calculator.js +++ b/calculator.js @@ -0,0 +1,193 @@ +document + .querySelectorAll("button") + .forEach((button) => button.addEventListener("click", onButton)); + +document.addEventListener("keydown", onKey); + +const inputField = document.querySelector("input"); +inputField.addEventListener("input", onInput); + +const h2 = document.querySelector("h2"); + +function onButton(event) { + let button = event.target.id; + inputField.value += button; + inputField.dispatchEvent(new Event("input")); + + if (button === "=") { + onEqualOrEnter(); + } +} + +function onKey(event) { + document.querySelector("input").focus(); + if (event.key === "Enter") { + onEqualOrEnter(); + } +} + +function onInput(event) { + event.target.value = calculator.processInput(event.target.value); + h2.innerHTML = !isNaN(calculator.result) ? calculator.result : ""; +} + +function onEqualOrEnter() { + h2.innerHTML = ""; + inputField.value = calculator.evaluate(); +} + +// ==================================================================================================== +// CALCULATOR +// ==================================================================================================== +let calculator = new Calculator(); + +function Calculator() { + this.result; + this.entries = []; + + this.processInput = function (input) { + let filtered = filter(input); + console.log(this.entries); + this.result = _evaluate(); + return filtered; + }; + + this.evaluate = function () { + let result = _evaluate(); + + // TODO: handle consecutive evaluation + + this.entries = []; + this.result = undefined; + + return result; + }; + + this.reset = function () { + this.entries = []; + this.result = undefined; + }; + + this.operators = { + "+": function (a, b) { + return a + b; + }, + "-": function (a, b) { + return a - b; + }, + "*": function (a, b) { + return a * b; + }, + "/": function (a, b) { + return a / b; + }, + }; + + hasOperator = (str) => { + return str.split("").some((char) => isOperator(char)); + }; + + isOperator = (op) => { + return Object.keys(this.operators).includes(op); + }; + + _evaluate = () => { + let operator; + return this.entries.reduce((accumulator, currentValue) => { + // keep track of the last operator + if (isOperator(currentValue)) { + operator = currentValue; + } + // try to operate if there is an operator + else { + accumulator = operator + ? this.operators[operator](accumulator, parseFloat(currentValue)) + : parseFloat(currentValue); + } + return accumulator; + }, 0); + }; + + filter = (input) => { + + let lastChar = input.slice(-1); + let lastEntry = this.entries.at(-1); + + // if input length is less than entry (i.e. 'backspace' was pressed) + if (input.length < this.entries.join('').length) { + // if last entry is an operator, remove the last entry + if (isOperator(lastEntry)) { + this.entries.pop(); + } + // if not, just remove the last character of the entry + else { + this.entries[this.entries.length - 1] = lastEntry.slice(0, -1); + if (this.entries.at(-1) === '') { + this.entries.pop(); + } + } + return this.entries.join(""); + } + + // check if the last character is an operator + if (isOperator(lastChar)) { + // check if current entries is not empty + if (this.entries.length > 0) { + // check if last entry is an operator + if (isOperator(lastEntry)) { + // replace it + this.entries[this.entries.length - 1] = lastChar; + } + // if not, add the operator as a new entry + else { + this.entries.push(lastChar); + } + } + else { + // check if the input has a number (i.e. after = has been pressed) + if (!isNaN(input.slice(0, -1))) { + this.entries.push(input.slice(0, -1)); + this.entries.push(lastChar); + } + } + } + // check if it's a decimal + else if (lastChar === ".") { + // check if last current entry is empty or my last entry is an operator + if (this.entries.length == 0 || isOperator(lastEntry)) { + // add 0. + this.entries.push("0."); + } + // check if my last entry has NO decimal (i.e. this is the first decimal) + // *at this point, we know the last entry is a number + else if (!lastEntry.includes(".")) { + // add the decimal + this.entries[this.entries.length - 1] += lastChar; + } + } + // check if it's a number + else if (!isNaN(lastChar)) { + // if entries empty + if (this.entries.length == 0) { + this.entries.push(lastChar); + } else { + // check if last entry is an operator + if (isOperator(lastEntry)) { + // add new entry + this.entries.push(lastChar); + } + // otherwise, append to the last entry + else { + // if the last entry is a 0, replace it + if (lastEntry === '0') { + this.entries.splice(-1, lastChar); + } else { + this.entries[this.entries.length - 1] += lastChar; + } + } + } + } + + return this.entries.join(""); + }; +} diff --git a/index.html b/index.html index 24a19111..fc9768a2 100644 --- a/index.html +++ b/index.html @@ -6,44 +6,45 @@ 100Devs Calculator - Johnny J Wu -
-

+ +

- +
- +
- +
- - - + + +
+ \ No newline at end of file diff --git a/style.css b/style.css index ed9be834..4ed4b28a 100644 --- a/style.css +++ b/style.css @@ -1,3 +1,19 @@ +* { + box-sizing: border-box; +} + +/* Chrome, Safari, Edge, Opera */ +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/* Firefox */ +input[type=number] { + -moz-appearance: textfield; +} + .container { display: flex; } @@ -30,15 +46,31 @@ button:active { padding: 2rem; } -.calculator h1 { +.calculator input { background-color: white; - height: 5rem; - line-height: 5rem; - margin: 2rem 0; - padding: 0 1rem; + border: none; + font-size: 2rem; + margin: 0; + max-height: 5rem; + outline: none; + padding: 1rem; + text-align: right; + width: 100%; + overflow-wrap: break-word; +} + +.calculator h2 { + background-color: white; + color: grey; + line-height: 2rem; + margin: 0; + margin-bottom: 2rem; + padding: 0 1rem 1rem 1rem; text-align: right; + height: 3rem; } + .calculator button { aspect-ratio: 1; width: 20%; From d2fba3e9061ed07cfd0f06cbd9d6d8d29727841d Mon Sep 17 00:00:00 2001 From: Johnny Date: Fri, 17 May 2024 23:01:30 -0700 Subject: [PATCH 3/3] Further implementations for calculator.js (see calculator.js for TODOs) --- calculator.js | 259 +++++++++++++++++++++++++++----------------------- index.html | 2 +- 2 files changed, 139 insertions(+), 122 deletions(-) diff --git a/calculator.js b/calculator.js index 54caf430..3fed9e4a 100644 --- a/calculator.js +++ b/calculator.js @@ -1,39 +1,51 @@ +// ==================================================================================================== +// HELPERS +// ==================================================================================================== +hasWhitespace = (str) => /\s/g.test(str); + +isNumeric = (str) => + str === 0 || (!hasWhitespace(str) && !isNaN(parseFloat(str))); + +// ==================================================================================================== +// HTML +// ==================================================================================================== + +// Cache html elements +const inputField = document.querySelector("input"); +const h2 = document.querySelector("h2"); + +// Add listeners to all buttons document .querySelectorAll("button") .forEach((button) => button.addEventListener("click", onButton)); -document.addEventListener("keydown", onKey); +// Listen to key press +document.addEventListener("keypress", onKey); -const inputField = document.querySelector("input"); +// Listen to input inputField.addEventListener("input", onInput); -const h2 = document.querySelector("h2"); - +// Button event function onButton(event) { let button = event.target.id; inputField.value += button; inputField.dispatchEvent(new Event("input")); - - if (button === "=") { - onEqualOrEnter(); - } } +// Key event function onKey(event) { - document.querySelector("input").focus(); if (event.key === "Enter") { - onEqualOrEnter(); + inputField.value += "="; + inputField.dispatchEvent(new Event("input")); } } +// Input event function onInput(event) { event.target.value = calculator.processInput(event.target.value); - h2.innerHTML = !isNaN(calculator.result) ? calculator.result : ""; -} - -function onEqualOrEnter() { - h2.innerHTML = ""; - inputField.value = calculator.evaluate(); + h2.innerHTML = isNumeric(calculator.intermediate) + ? calculator.intermediate + : ""; } // ==================================================================================================== @@ -42,30 +54,24 @@ function onEqualOrEnter() { let calculator = new Calculator(); function Calculator() { - this.result; - this.entries = []; + //this.entries = []; + this.intermediate; + this.lastResult; + + this.leftOperand; + this.lastOperator; + this.rightOperand; this.processInput = function (input) { - let filtered = filter(input); - console.log(this.entries); - this.result = _evaluate(); + let filtered = filterInput(input); return filtered; }; - this.evaluate = function () { - let result = _evaluate(); - - // TODO: handle consecutive evaluation - - this.entries = []; - this.result = undefined; - - return result; - }; - this.reset = function () { this.entries = []; - this.result = undefined; + this.intermediate = undefined; + this.lastOperator = undefined; + this.rightOperand = undefined; }; this.operators = { @@ -91,103 +97,114 @@ function Calculator() { return Object.keys(this.operators).includes(op); }; - _evaluate = () => { - let operator; - return this.entries.reduce((accumulator, currentValue) => { - // keep track of the last operator - if (isOperator(currentValue)) { - operator = currentValue; - } - // try to operate if there is an operator - else { - accumulator = operator - ? this.operators[operator](accumulator, parseFloat(currentValue)) - : parseFloat(currentValue); - } - return accumulator; - }, 0); - }; - - filter = (input) => { + // TODO: Handle processing intermediate with decimals as entry # > 1 + // TODO: Handle negatives as the first input + // TODO: Handle floating point precision errors + filterInput = (input) => { + let entries = []; - let lastChar = input.slice(-1); - let lastEntry = this.entries.at(-1); - - // if input length is less than entry (i.e. 'backspace' was pressed) - if (input.length < this.entries.join('').length) { - // if last entry is an operator, remove the last entry - if (isOperator(lastEntry)) { - this.entries.pop(); + if (isNumeric(this.lastResult)) { + if (!input || !input.includes("=")) { + this.lastResult = undefined; } - // if not, just remove the last character of the entry - else { - this.entries[this.entries.length - 1] = lastEntry.slice(0, -1); - if (this.entries.at(-1) === '') { - this.entries.pop(); - } + else if (isNumeric(input.slice(-1)) || input.slice(-1) === '.') { + input = input.slice(-1); + this.lastResult = undefined; } - return this.entries.join(""); } - - // check if the last character is an operator - if (isOperator(lastChar)) { - // check if current entries is not empty - if (this.entries.length > 0) { - // check if last entry is an operator - if (isOperator(lastEntry)) { - // replace it - this.entries[this.entries.length - 1] = lastChar; - } - // if not, add the operator as a new entry - else { - this.entries.push(lastChar); + + [...input].forEach((char) => { + let lastEntry = entries.at(-1); + + // if this is the first entry + if (!lastEntry) { + switch (true) { + case isNumeric(char): + entries.push(char); + break; + case char === ".": + entries.push("0."); + break; } + this.leftOperand = parseFloat(char); } + // rest of the entries else { - // check if the input has a number (i.e. after = has been pressed) - if (!isNaN(input.slice(0, -1))) { - this.entries.push(input.slice(0, -1)); - this.entries.push(lastChar); + switch (true) { + case isNumeric(char): + // # or #. + if (isNumeric(lastEntry) || lastEntry === ".") { + entries[entries.length - 1] += char; + + if (entries.length > 1) { + this.rightOperand = parseFloat(entries[entries.length - 1]); + + if (entries.length == 3) + this.leftOperand = parseFloat(entries[0]); + processIntermediate(); + } + else { + this.leftOperand = parseFloat(entries[entries.length - 1]); + } + } + // operator + else if (isOperator(lastEntry)) { + entries.push(char); + this.rightOperand = parseFloat(char); + processIntermediate(); + } + break; + case char === ".": + // operator + if (isOperator(lastEntry)) { + entries.push("0."); + this.rightOperand = parseFloat(char); + processIntermediate(); + } + // # but NOT #. + else if (isNumeric(lastEntry) && !lastEntry.includes(".")) { + entries[entries.length - 1] += char; + } + break; + case isOperator(char): + this.intermediate = undefined; + // operator + if (isOperator(lastEntry)) { + entries[entries.length - 1] = char; + this.lastOperator = char; + } + // # or #. + else if (isNumeric(lastEntry) || lastEntry === ".") { + entries.push(char); + this.lastOperator = char; + } + break; + case char === "=": + if (isNumeric(this.intermediate)) { + this.lastResult = this.intermediate; + this.intermediate = undefined; + } else if (isNumeric(this.lastResult)) { + processIntermediate(); + this.lastResult = this.intermediate; + this.intermediate = undefined; + } + break; } } - } - // check if it's a decimal - else if (lastChar === ".") { - // check if last current entry is empty or my last entry is an operator - if (this.entries.length == 0 || isOperator(lastEntry)) { - // add 0. - this.entries.push("0."); - } - // check if my last entry has NO decimal (i.e. this is the first decimal) - // *at this point, we know the last entry is a number - else if (!lastEntry.includes(".")) { - // add the decimal - this.entries[this.entries.length - 1] += lastChar; - } - } - // check if it's a number - else if (!isNaN(lastChar)) { - // if entries empty - if (this.entries.length == 0) { - this.entries.push(lastChar); - } else { - // check if last entry is an operator - if (isOperator(lastEntry)) { - // add new entry - this.entries.push(lastChar); - } - // otherwise, append to the last entry - else { - // if the last entry is a 0, replace it - if (lastEntry === '0') { - this.entries.splice(-1, lastChar); - } else { - this.entries[this.entries.length - 1] += lastChar; - } - } - } - } + }); - return this.entries.join(""); + return isNumeric(this.lastResult) ? this.lastResult : entries.join(""); + }; + + processIntermediate = () => { + if (!isNumeric(this.leftOperand) || !this.lastOperator || !isNumeric(this.rightOperand)) + this.intermediate = undefined; + else { + this.intermediate = this.operators[this.lastOperator]( + this.leftOperand, + this.rightOperand + ); + this.leftOperand = this.intermediate; + } }; } diff --git a/index.html b/index.html index fc9768a2..3c147230 100644 --- a/index.html +++ b/index.html @@ -11,7 +11,7 @@
- +