Project 3: Interactive Calculator
In this project, we will build a fully functioning on-screen calculator. You will apply the Event Delegation pattern by binding a single listener to the calculator keys grid instead of binding listeners to every button.
1. Project Features
- Input numbers and perform arithmetic calculations (
+,-,*,/). - Handle decimals.
- Clear button to reset states.
- Immediate calculation evaluation.
2. The HTML Structure
We use custom data-* attributes to distinguish between numbers and action operators:
<!-- index.html -->
<div class="calculator">
<!-- Top display window -->
<div id="display">0</div>
<!-- Keys grid -->
<div class="keys-grid">
<button data-action="clear" class="btn-ctrl">AC</button>
<button data-action="delete" class="btn-ctrl">DEL</button>
<button data-action="operator" data-val="/" class="btn-op">/</button>
<button data-action="operator" data-val="*" class="btn-op">*</button>
<button data-val="7">7</button>
<button data-val="8">8</button>
<button data-val="9">9</button>
<button data-action="operator" data-val="-" class="btn-op">-</button>
<button data-val="4">4</button>
<button data-val="5">5</button>
<button data-val="6">6</button>
<button data-action="operator" data-val="+" class="btn-op">+</button>
<button data-val="1">1</button>
<button data-val="2">2</button>
<button data-val="3">3</button>
<button data-action="calculate" class="btn-equals">=</button>
<button data-val="0" class="btn-zero">0</button>
<button data-val=".">.</button>
</div>
</div>3. The JavaScript Implementation
Instead of attaching 19 separate click listeners, we attach a single listener to the parent .keys-grid and utilize event delegation.
// app.js
const display = document.querySelector('#display');
const keysGrid = document.querySelector('.keys-grid');
let currentInput = "0";
// 1. Updating the display text
function updateDisplay() {
display.textContent = currentInput;
}
// 2. Main Event Listener using Event Delegation
keysGrid.addEventListener('click', (e) => {
// Ensure the clicked element is a button
if (e.target.tagName !== 'BUTTON') return;
const action = e.target.getAttribute('data-action');
const val = e.target.getAttribute('data-val');
switch (action) {
case 'clear':
resetCalculator();
break;
case 'delete':
deleteLastCharacter();
break;
case 'operator':
handleOperator(val);
break;
case 'calculate':
evaluateExpression();
break;
default:
// Action is null: User clicked a number or decimal point
handleNumber(val);
}
updateDisplay();
});
// 3. Handle Numbers and Decimals
function handleNumber(num) {
if (currentInput === "0" && num !== ".") {
currentInput = num; // Replace initial 0
} else {
// Avoid double decimal points inside the same number block
if (num === "." && currentInput.slice(-1) === ".") return;
currentInput += num;
}
}
// 4. Handle Operators
function handleOperator(op) {
const lastChar = currentInput.slice(-1);
const operators = ['+', '-', '*', '/'];
// If the last character is already an operator, replace it
if (operators.includes(lastChar)) {
currentInput = currentInput.slice(0, -1) + op;
} else {
currentInput += op;
}
}
// 5. Delete and Reset helper functions
function resetCalculator() {
currentInput = "0";
}
function deleteLastCharacter() {
if (currentInput.length > 1) {
currentInput = currentInput.slice(0, -1);
} else {
currentInput = "0";
}
}
// 6. Safe Math Evaluation
function evaluateExpression() {
try {
// Note: eval() is unsafe for arbitrary user text, but safe here
// because we limit input strictly to valid numbers and mathematical operator buttons.
// In production, writing a simple parser is preferred.
const result = eval(currentInput);
// Check for division by zero
if (result === Infinity || isNaN(result)) {
currentInput = "Error";
} else {
currentInput = String(result);
}
} catch (error) {
currentInput = "Error";
}
}4. Key Takeaways
data-*attributes: Provide clean semantic markers inside HTML structure, making JavaScript queries and switches highly decoupled.- Robust Input Sanity: Standard calculators must restrict inputs (preventing double dots, consecutive operators, division by zero) before compiling equations.
In the next project, we will build a classic Image Carousel slider focusing on animations and timing loops.
Published on Last updated: