-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
311 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
// @ts-check | ||
|
||
"use strict"; | ||
|
||
// ========================================================================== \\ | ||
|
||
var op = '+', result = 0, isShowResult = true; | ||
|
||
const KEY = 'keydown', CLICK = 'click', MAX_CHARS = 23; | ||
const ZERO = '0', DOT = '.', NEG = '-'; | ||
|
||
const calc = /** @type {HTMLDivElement} */ | ||
(document.getElementById('calculator')); | ||
|
||
const display = /** @type {HTMLSpanElement} */ | ||
(document.getElementById('display')); | ||
|
||
// ========================================================================== \\ | ||
|
||
const keyButtons = /** @type {HTMLCollectionOf<HTMLButtonElement>} */ | ||
(document.getElementsByClassName('key')); | ||
|
||
calc.addEventListener(KEY, btnKeyEvent); | ||
|
||
/** | ||
* Handles a 'keydown' event for all .key `button` elements inside #calculator. | ||
* If a 'key' corresponding to a .key `button`::`textContent` is pressed and | ||
* it's not a 'repeat', it triggers a 'click' event on that `button`. | ||
* | ||
* @param {KeyboardEvent} ev The KeyboardEvent object. | ||
* Callback uses event object's properties 'key', 'keyCode' and 'repeat'. | ||
* | ||
* @description Firstly it checks if backspace key was pressed. | ||
* If so, it invokes function deleteLastChar() to delete previous char entered. | ||
* Secondly it checks if delete key was pressed and invokes btnClearEntryEvent() | ||
* if that's the case, thus behaving the same as the CE's #ce `button`. | ||
* Then it checks if the event is not a 'repeat'. | ||
* If it's not a 'repeat', it iterates over each .key `button`. | ||
* If the 'key' property of the event matches the `textContent` of the | ||
* current `button`, it triggers a 'click' event on that `button`. | ||
* It also prematurely `break` the loop after the first match. | ||
*/ | ||
function btnKeyEvent({ key, keyCode, repeat }) { | ||
if (keyCode == 8) deleteLastChar(); // key: "Backspace" (ASCII 8) | ||
|
||
else if (keyCode == 46) btnClearEntryEvent(); // key: "Delete" (ASCII 46) | ||
|
||
else if (!repeat) for (const btn of keyButtons) if (key == btn.textContent) { | ||
btn.click(); | ||
break; | ||
} | ||
} | ||
|
||
/** | ||
* Deletes the last entered character from the #display `span` element. | ||
* | ||
* @description If `isShowResult` state is currently set `true` it does nothing. | ||
* If the #display text length is more than 1, it removes last char from it. | ||
* Othewise it changes it back to initial value '0'. | ||
* If final #display is a form of negative zero, sanitize it to just zero "0". | ||
*/ | ||
function deleteLastChar() { | ||
if (isShowResult) return; | ||
|
||
const { innerText } = display, { length } = innerText; | ||
|
||
const s = display.textContent = length > 1 && innerText.slice(0, -1) || ZERO; | ||
|
||
if (s == NEG || s == '-0' || s == '-0.') display.textContent = ZERO; | ||
} | ||
|
||
// ========================================================================== \\ | ||
|
||
const commonButtons = /** @type {HTMLCollectionOf<HTMLButtonElement>} */ | ||
(document.getElementsByClassName('common')); | ||
|
||
for (const btn of commonButtons) btn.addEventListener(CLICK, btnCommonEvent); | ||
|
||
/** | ||
* Handles the 'click' event of a .common `button` element. It appends the text | ||
* of the clicked button to the #display `span` element on the webpage. | ||
* | ||
* @this {HTMLButtonElement} `button` .common | ||
* | ||
* @description The function checks if `isShowResult` is true. If it is, it sets | ||
* `isShowResult` to false and sets the text content of the #display `span` | ||
* element to the text content of the clicked button. | ||
* If `isShowResult` is false, the function checks if the length of the text | ||
* content in the #display `span` element is 1 and if the 1st character is '0'. | ||
* If both conditions are true, it sets the text content of the #display `span` | ||
* element to the text content of the clicked button. | ||
* If none of the above conditions are met, it appends the text content of the | ||
* clicked button to the existing text content in the #display `span` element. | ||
*/ | ||
function btnCommonEvent() { | ||
const { innerText: { 0: head, length: len } } = display, { innerText } = this; | ||
|
||
if (isShowResult) { | ||
isShowResult = false; | ||
display.textContent = innerText; | ||
} | ||
|
||
else if (len == 1 && head == ZERO) display.textContent = innerText; | ||
|
||
else if (len < MAX_CHARS) display.innerText += innerText; | ||
} | ||
|
||
// ========================================================================== \\ | ||
|
||
document.getElementById('zero')?.addEventListener(CLICK, btnZeroEvent); | ||
|
||
/** | ||
* Handles the 'click' event of the .digit #zero `button` element. | ||
* | ||
* @description If the variable `isShowResult` is true, it sets it to false | ||
* and updates the text content of the #display `span` element to a zero ('0'). | ||
* If `isShowResult` is false, it checks if the length of the display text is | ||
* greater than 1 or if the first character of the display text is not '0'. | ||
* If either condition is true, append '0' to the display text. | ||
*/ | ||
function btnZeroEvent() { | ||
if (isShowResult) { | ||
isShowResult = false; | ||
display.textContent = ZERO; | ||
return; | ||
} | ||
|
||
const { innerText: { 0: head, length: len } } = display; | ||
|
||
if (len < MAX_CHARS && (len > 1 || head != ZERO)) display.innerText += ZERO; | ||
} | ||
|
||
// ========================================================================== \\ | ||
|
||
document.getElementById('decimal')?.addEventListener(CLICK, btnDotEvent); | ||
|
||
/** | ||
* Handles the 'click' event of the .digit #decimal `button` element. | ||
* | ||
* @description If the variable `isShowResult` is true, it sets it to false and | ||
* updates the text content of the #display `span` element to a zero dot ('0.'). | ||
* If `isShowResult` is false and the #display `span` element does not already | ||
* contain a dot, the function appends a dot to the existing text content | ||
* of the #display `span` element. | ||
*/ | ||
function btnDotEvent() { | ||
if (isShowResult) { | ||
isShowResult = false; | ||
display.textContent = '0.'; | ||
return; | ||
} | ||
|
||
const { innerText } = display, { length } = innerText; | ||
|
||
if (length < MAX_CHARS && !innerText.includes(DOT)) display.innerText += DOT; | ||
} | ||
|
||
// ========================================================================== \\ | ||
|
||
document.getElementById('ac')?.addEventListener(CLICK, btnAllClearEvent); | ||
|
||
/** | ||
* Handles the 'click' event of the C's #ac `button` element. It resets the | ||
* #calculator back to its initial state. | ||
* | ||
* @description This function is triggered when the C's #ac `button` is clicked. | ||
* It resets the #calculator to its initial state by setting the operation to | ||
* addition '+', the result to 0, and `isShowResult` to true. | ||
* It also sets the text content of the #display `span` element back to '0'. | ||
*/ | ||
function btnAllClearEvent() { | ||
op = '+', result = 0, isShowResult = true; | ||
display.textContent = ZERO; | ||
} | ||
|
||
document.getElementById('ce')?.addEventListener(CLICK, btnClearEntryEvent); | ||
|
||
/** | ||
* Handles the 'click' event of the CE's #ce `button` element. | ||
* It clears the last entry on the #calculator. | ||
* | ||
* @description It checks if current operator `op` is "=". If it is it calls | ||
* `btnAllClearEvent()`, effectively behaving as the C's #ac `button`. | ||
* Otherwise, it sets the `textContent` of the #display element back to '0', | ||
* clearing the last entry on the #calculator. | ||
*/ | ||
function btnClearEntryEvent() { | ||
if (op == '=') btnAllClearEvent(); | ||
else display.textContent = ZERO; | ||
} | ||
|
||
// ========================================================================== \\ | ||
|
||
document.getElementById('negate')?.addEventListener(CLICK, btnNegateEvent); | ||
|
||
/** | ||
* Handles the 'click' event of the #negate `button` element. | ||
* It negates the current value on the #calculator's #display. | ||
* | ||
* @description This callback triggers when #negate `button` is clicked. | ||
* If the current display value is '0' or '0.', it does nothing. | ||
* If `isShowResult` is true, it multiplies the result by -1. | ||
* Then it checks if current #display value is negative. If it is, it removes | ||
* the negative sign. If it isn't, it adds a '-' sign in front of the current | ||
* display value. | ||
*/ | ||
function btnNegateEvent() { | ||
const { innerText } = display, [ head ] = innerText; | ||
|
||
if (innerText == ZERO || innerText == '0.') return; | ||
|
||
if (isShowResult) result *= -1; | ||
|
||
display.textContent = head == NEG && innerText.substr(1) || NEG + innerText; | ||
} | ||
|
||
// ========================================================================== \\ |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
|
||
<head> | ||
<title>Calculator</title> | ||
<meta charset="utf-8"> | ||
|
||
<link rel="icon" href="favicon.ico"> | ||
<link rel="stylesheet" href="styles.css"> | ||
</head> | ||
|
||
<body onload="document.getElementById('calculator').focus()"> | ||
|
||
<script async src="calculator.js"></script> | ||
|
||
<div id="calculator" tabindex="-999"> | ||
|
||
<span id="display">0</span> | ||
|
||
<button class="delete" id="ce">CE</button> | ||
<button class="delete" id="ac">C</button> | ||
<button class="operator unary" id="negate">±</button> | ||
<button class="operator dyadic" id="pow">xⁿ</button> | ||
<button class="operator unary" id="sqrt">√</button> | ||
|
||
<button class="digit common key" id="seven">7</button> | ||
<button class="digit common key" id="eight">8</button> | ||
<button class="digit common key" id="nine">9</button> | ||
<button class="operator dyadic key" id="divide">/</button> | ||
<button class="operator unary" id="percent">%</button> | ||
|
||
<button class="digit common key" id="four">4</button> | ||
<button class="digit common key" id="five">5</button> | ||
<button class="digit common key" id="six">6</button> | ||
<button class="operator dyadic key" id="multiply">*</button> | ||
<button class="operator unary" id="reciprocal">1/x</button> | ||
|
||
<button class="digit common key" id="one">1</button> | ||
<button class="digit common key" id="two">2</button> | ||
<button class="digit common key" id="three">3</button> | ||
<button class="operator dyadic key" id="minus">-</button> | ||
<button class="operator unary key" id="equals">=</button> | ||
|
||
<button class="digit key" id="zero">0</button> | ||
<button class="digit key" id="decimal">.</button> | ||
<button class="operator dyadic key" id="plus">+</button> | ||
|
||
</div> | ||
|
||
</body> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
#calculator { | ||
display: grid; | ||
grid-template: repeat(6, 1fr) / repeat(5, 1fr); | ||
max-width: 400px; | ||
gap: 10px; | ||
border: 3px solid; | ||
padding: 10px; | ||
} | ||
|
||
#calculator button { | ||
font-size: larger; | ||
} | ||
|
||
#calculator button:hover { | ||
filter: brightness(1.1); | ||
} | ||
|
||
#display { | ||
grid-column: span 5; | ||
border: 2px solid; | ||
text-align: right; | ||
padding: 10px; | ||
font-size: xx-large; | ||
background-color: lightgreen; | ||
} | ||
|
||
#equals { | ||
grid-row: span 2; | ||
background-color: gold; | ||
} | ||
|
||
#zero { | ||
grid-column: span 2; | ||
} | ||
|
||
.delete { | ||
background-color: crimson; | ||
} | ||
|
||
.operator { | ||
background-color: lightgray; | ||
} |