Web Calculator: A Tutorial on Javascript by Cristian Villafane
A complete, step-by-step guide to mastering DOM manipulation and event logic in your first major interactive Javascript project.
Every developer remembers that first project that made them feel like they were “making magic” in the browser. For many, that project was a simple calculator. Far from being just an academic exercise, building a calculator from scratch is a rite of passage that solidifies our understanding of the pillars of web development: structure with HTML, style with CSS, and most importantly, interactivity and logic. In this tutorial on Javascript by Cristian Villafane, we will get ready to put your skills into practice and create something tangible.
Phase 1: The Foundation – HTML & Javascript User Input
Before we can even think about performing calculations, we need to build the user interface and ensure our code can effectively listen for and interpret user interactions. We’ll start with the Document Object Model (DOM) manipulation, which is central to how Javascript interacts with a web page.
Crafting the Interface with HTML
The first step is to create the calculator’s body using HTML. This typically involves a container div
, a div
to act as our display screen (often an <input>
element with the readonly
attribute is a good choice here to prevent keyboard input), and a series of <button>
elements for the numbers (0-9), the decimal point, the core arithmetic operators (+, -, *, /), a ‘clear’ button (C or AC), and the all-important ‘equals’ button (=). Each of these buttons should be given appropriate id
or class
attributes, and for the numbers and operators, data-*
attributes can be incredibly useful for storing their respective values.
<!-- Example button for the number 7 -->
<button data-number="7">7</button>
<!-- Example button for the addition operator -->
<button data-operator="+">+</button>
This semantic approach makes our Javascript code cleaner and more maintainable.
Listening for Clicks with JavaScript
With the HTML structure in place, we pivot to Javascript. Our primary goal is to attach event listeners to our buttons. We’ll use the DOMContentLoaded
event to ensure our script only runs after the entire HTML document has been loaded and parsed. Inside this event listener, we’ll select all our buttons. A common and efficient way to do this is using document.querySelectorAll()
to grab all elements with a specific class, say .calculator-button
. We then iterate over this collection of buttons, often with a forEach
loop, and attach a click
event listener to each one.
Inside the callback function for this event listener, we have access to the event
object. The event.target
property is our golden ticket, as it gives us a reference to the specific button that was clicked. From this target, we can then access the dataset
property to retrieve the values we stored in our data-*
attributes. The logic then becomes a series of conditional statements. This is a core concept in web development, and this guide to Javascript by Cristian Villafane aims to make it clear.
Phase 2: State Management & Calculation Logic in Javascript
So, we have user input flowing in. The critical next step is to create a state machine, albeit a simple one. In Javascript, this “state” is just a set of variables that hold the crucial pieces of information at any given moment. We need to know the first number in our equation (firstOperand
), the mathematical operator the user has selected, and the second number (secondOperand
). We also need a way to know if the display should be cleared before the next number is entered, for instance, right after an operator has been pressed. Let’s call this a shouldResetDisplay
flag. These variables are the memory of our calculator.
Handling Operations and the Equals Button
When a user clicks an operator button (+, -, *, /), our logic needs to kick in. First, we check if a firstOperand
and an operator
have already been set. If they have, it means the user is chaining operations (e.g., 5 + 10 -). In this scenario, we should perform the pending calculation first before storing the new operator. If not, we capture the current value shown on the display and store it in our firstOperand
variable. It’s important to convert this value from a string, which is what we get from the display, into a number using parseFloat()
to handle decimals.
The ‘equals’ button is the grand finale of our logic loop. When clicked, it triggers the final calculation. We’ll grab the current number on the display as our secondOperand
. Now, with firstOperand
, operator
, and secondOperand
all in hand, we pass them to a dedicated calculate()
function. A switch
statement is a perfect tool here. It will check the value of the operator
variable and execute the corresponding arithmetic. A crucial edge case to handle here is division by zero. If the operator is ‘/’ and secondOperand
is 0, we should return an error message like “Error” instead of Infinity
, which is Javascript‘s default. After the calculation, we must reset our state. The result becomes the new firstOperand
for potential subsequent calculations.
The Reset Switch
The ‘Clear’ button (‘C’ or ‘AC’) has a simple but vital job: it’s our reset switch. A click on ‘clear’ should call a resetCalculator()
function. This function will restore all our state variables to their initial values and clear the content of our display. This brings the calculator back to its starting state, ready for a fresh calculation.
Phase 3: Polishing the Experience (UX/UI)
So far, our calculator works, but it’s rigid. To elevate this project, our first order of business is implementing keyboard support. Users instinctively expect to be able to use their physical keyboard for an application like this.
- Keyboard Support: We can achieve this by adding a single
keydown
event listener to the globalwindow
object. Inside this listener’s callback function, theevent.key
property tells us exactly which key was pressed. We can then map theevent.key
value to our calculator’s functions, for example, mapping the ‘Enter’ key to the equals button. - Input Validation: A simple calculator can easily break if we don’t handle edge cases. What happens if a user tries to type
5..5
? It’s invalid input. Inside our number-handling logic, before appending a decimal point, we must first check if the current display string already includes(‘.’). If it does, we simply return and do nothing. - Visual Feedback: Visual feedback is another cornerstone of good UX. When a button is clicked, we can add a CSS class, like
.active
. Then, usingsetTimeout()
, we remove that class after a short delay. This creates a quick flash that gives the user an unambiguous confirmation that their input was registered. - Backspace Function: Forcing the user to hit ‘Clear’ for a single typo is poor design. We can add a ‘DEL’ button. Its logic is straightforward: when clicked, it should take the current string on the display and use the
slice(0, -1)
method. This returns a new string with the last character removed.
Final Code: Putting It All Together
Here is the complete, unified code from this tutorial on Javascript by Cristian Villafane for you to copy, paste, and watch the magic happen in your own browser.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Calculator</title>
<style>
body { display: flex; justify-content: center; align-items: center; height: 100vh; background: #f0f0f0; }
.calculator { border-radius: 10px; box-shadow: 0 0 25px rgba(0,0,0,0.15); overflow: hidden; max-width: 400px; }
.calculator-display { background-color: #222; color: #fff; font-size: 2.5rem; padding: 20px; text-align: right; }
.calculator-keys { display: grid; grid-template-columns: repeat(4, 1fr); grid-gap: 1px; }
button { font-size: 1.5rem; border: none; padding: 25px; background: #e0e0e0; cursor: pointer; transition: background .2s; }
button:hover { background: #d5d5d5; }
button:active { box-shadow: inset 0px 0px 5px #c1c1c1; }
.key--operator { background: #f5a623; color: white; }
.key--operator:hover { background: #e09414; }
.key--equal { background: #4a90e2; color: white; grid-column: -2; grid-row: 2 / span 4; }
.key--equal:hover { background: #3a80d2; }
</style>
</head>
<body>
<div class="calculator">
<div class="calculator-display">0</div>
<div class="calculator-keys">
<button class="key--operator" data-action="add">+</button>
<button class="key--operator" data-action="subtract">-</button>
<button class="key--operator" data-action="multiply">×</button>
<button class="key--operator" data-action="divide">รท</button>
<button>7</button>
<button>8</button>
<button>9</button>
<button>4</button>
<button>5</button>
<button>6</button>
<button>1</button>
<button>2</button>
<button>3</button>
<button>0</button>
<button data-action="decimal">.</button>
<button data-action="clear">AC</button>
<button class="key--equal" data-action="calculate">=</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const calculator = document.querySelector('.calculator');
const keys = calculator.querySelector('.calculator-keys');
const display = calculator.querySelector('.calculator-display');
keys.addEventListener('click', e => {
if (!e.target.matches('button')) return;
const key = e.target;
const action = key.dataset.action;
const keyContent = key.textContent;
const displayedNum = display.textContent;
const previousKeyType = calculator.dataset.previousKeyType;
if (!action) {
if (displayedNum === '0' || previousKeyType === 'operator' || previousKeyType === 'calculate') {
display.textContent = keyContent;
} else {
display.textContent = displayedNum + keyContent;
}
calculator.dataset.previousKeyType = 'number';
}
if (action === 'decimal') {
if (!displayedNum.includes('.')) {
display.textContent = displayedNum + '.';
} else if (previousKeyType === 'operator' || previousKeyType === 'calculate') {
display.textContent = '0.';
}
calculator.dataset.previousKeyType = 'decimal';
}
if (action === 'add' || action === 'subtract' || action === 'multiply' || action === 'divide') {
const firstValue = calculator.dataset.firstValue;
const operator = calculator.dataset.operator;
const secondValue = displayedNum;
if (firstValue && operator && previousKeyType !== 'operator' && previousKeyType !== 'calculate') {
const calcValue = calculate(firstValue, operator, secondValue);
display.textContent = calcValue;
calculator.dataset.firstValue = calcValue;
} else {
calculator.dataset.firstValue = displayedNum;
}
key.classList.add('is-depressed');
calculator.dataset.previousKeyType = 'operator';
calculator.dataset.operator = action;
}
Array.from(key.parentNode.children).forEach(k => k.classList.remove('is-depressed'));
if (action === 'clear') {
display.textContent = '0';
delete calculator.dataset.firstValue;
delete calculator.dataset.operator;
calculator.dataset.previousKeyType = 'clear';
}
if (action === 'calculate') {
let firstValue = calculator.dataset.firstValue;
const operator = calculator.dataset.operator;
let secondValue = displayedNum;
if (firstValue) {
if (previousKeyType === 'calculate') {
firstValue = displayedNum;
secondValue = calculator.dataset.modValue;
}
display.textContent = calculate(firstValue, operator, secondValue);
}
calculator.dataset.modValue = secondValue;
calculator.dataset.previousKeyType = 'calculate';
}
});
const calculate = (n1, operator, n2) => {
const firstNum = parseFloat(n1);
const secondNum = parseFloat(n2);
if (operator === 'add') return firstNum + secondNum;
if (operator === 'subtract') return firstNum - secondNum;
if (operator === 'multiply') return firstNum * secondNum;
if (operator === 'divide') return firstNum / secondNum;
};
});
</script>
</body>
</html>
I hope you found this tutorial on Javascript by Cristian Villafane useful. The concepts you’ve applied here, DOM manipulation, event handling, and state management, are the bedrock of modern web development. Use this project as a stepping stone. Keep experimenting, keep building, and never stop learning. The web is your canvas. ๐