How to build a Simple Calculator App with JavaScript

So you’ve learnt the basics of JavaScript and you want to put your new skills to practice by building something. This tutorial provides an exercise in doing just that by describing how to build a simple calculator app.

I will explain each step at a higher level than in my previous tutorials, so if you have trouble following, try doing the Random Quote Machine and Wikipedia Search App tutorials in that order, then come back to this one at a later date.

Also, make sure you complete the previous tutorial which explains how the calculator layout was crafted using CSS Grid. This article focuses only on the JavaScript logic employed to make the calculator work.

Here’s a live demo of the complete project.

Before you begin

You can find the starting point for this tutorial on JSFiddle. It contains all the necessary markup to build the calculator layout.

The markup is almost identical to the final state of the previous tutorial but I made a few minor changes so go with this one instead.

Start by forking the code to a new fiddle, and follow along by typing each step out all the way to the end. Feel free to do this tutorial on another online playground or on your local machine if you prefer.

Getting started

Anyone should be able to perform the four most common arithmetic operations (addition, subtraction, multiplication and division) on our calculator by constructing a valid expression using the input buttons, and have the result displayed on the screen.

An example is: 12 + 10.

To construct a valid arithmetic expression, we need to keep track of a few things: the first operand (12), the operator (+) and the second operand (10).

Let’s start by creating an object to help us keep track of these values.

Type this at the top of the JavaScript pane in JSFiddle:

const calculator = {
  displayValue: '0',
  firstOperand: null,
  waitingForSecondOperand: false,
  operator: null,
};

The calculator object holds all the data that we need to construct a valid expression.

  • displayValue holds a string value that represents the input of the user or the result of an operation. This is what will be shown on the screen.
  • firstOperand will hold the first operand for any expression. This is set to null for now.
  • The operator key will hold the operator for an expression. Also set to null.
  • waitingForSecondOperand is essentially a flag that checks whether an expression can be evaluated or whether the second operand needs to be inputed.

Updating the display

Right now, the calculator screen shows nothing. We need to display the value of displayValue on the screen (‘0’ by default). We will create a function for this purpose so that anytime an operation is performed in the app, we can always invoke it to update the screen with the contents of displayValue.

Go ahead and type this below the calculator object:

function updateDisplay() {
  const display = document.querySelector('.calculator-screen');
  display.value = calculator.displayValue;
}

updateDisplay();

Our “screen” is really just a disable text input.

<input type="text" class="calculator-screen" value="" disabled />

We can’t type into it directly with the keyboard, but we can definitely change its value with JavaScript. And that’s what the updateDisplay function does.

Now you should see ‘0’ displayed on the screen of the calculator.

Handling key presses

We have four sets of keys: digits (0-9), operators (+, -, *, /, =), a decimal point (.) and a reset key (AC). Let us listen for clicks on the calculator and determine what type of key was clicked.

const keys = document.querySelector('.calculator-keys');
keys.addEventListener('click', (event) => {
  const { target } = event;
  if (!target.matches('button')) {
    return;
  }

  if (target.classList.contains('operator')) {
    console.log('operator', target.value);
    return;
  }

  if (target.classList.contains('decimal')) {
    console.log('decimal', target.value);
    return;
  }

  if (target.classList.contains('all-clear')) {
    console.log('clear', target.value);
    return;
  }

  console.log('digit', target.value);
});

Here, we listen for a click event on .calculator-keys. Since all the keys on the calculator are children of this element, the click event filters down to them too. This is known as event delegation.

Inside the callback function of the event listener, we extract the target property of the click event using object destructuring which makes it really easy to unpack object properties into distinct variables.

const { target } = event;
// is equivalent to
const target = event.target;

The target variable is an object that represents the element that was clicked. If this element is not a button (such as if you click the spaces between the buttons), we will exit the function early.

That’s what the first if statement does:

//If the element that was clicked is not a button...
if (!target.matches('button')) {
  //exit the function
  return;
}

Otherwise, we log the type of button that was clicked as well as its value.

Try it out. Open up your browser console and click any of the buttons. The appropirate key type and value should be logged to the console.

The correct type of key is detected and logged to the console
camera The correct type of key is detected and logged to the console

Inputting the digits

Next, let us make the digit buttons work so that when they are clicked, feedback is displayed to the user on the screen.

Feedback is displayed to the user when a digit key is clicked

Since the displayValue property of the calculator object represents the input of the user, we need to modify the value of this property when any of the digits is clicked.

Create a new function called inputDigit below the calculator object.

function inputDigit(digit) {
  const { displayValue } = calculator;
  // Overwrite `displayValue` if the current value is '0' otherwise append to it
  calculator.displayValue = displayValue === '0' ? digit : displayValue + digit;
}

Next, replace console.log('digit', target.value); with the following:

inputDigit(target.value);
updateDisplay();

In the inputDigit function, we use the ternary operator (?) to check if the current value displayed on the calculator is ‘0’ (the default).

If so, we overwrite calculator.displayValue with whatever digit was clicked. Otherwise, if the number is a non-zero number, we append to it. Finally we call updateDisplay() to update the information on the screen after each button is clicked.

Try it out by clicking any of the digit buttons. The display should be updated with whatever digit you clicked.

Inputting a decimal point

When the decimal point key is clicked, we need to append a decimal point to whatever is displayed on the screen except if it already contains a decimal point.

Calculator showing decimal point being inputted

Here’s how we can achieve that. Create a new function called inputDecimal below inputDigit:

function inputDecimal(dot) {
  // If the `displayValue` does not contain a decimal point
  if (!calculator.displayValue.includes(dot)) {
    // Append the decimal point
    calculator.displayValue += dot;
  }
}

Then replace console.log('decimal', target.value); with the following code:

inputDecimal(target.value);
updateDisplay();

Inside the inputDecimal function, we use the includes method to check if displayValue does not already contain a decimal point. Then we append the dot to the number. Otherwise, we do nothing.

Try it out:

Handling Operators

The next step is to get the operators (+, -, x, /, =) on the calculator working. There are three scenarios to account for:

1. When the user finishes entering the first operand and hits an operator

This indicates that he/she is ready to enter the second operand. What we need to do here is store the first operand and update the display with the new string of numbers.

Calculator showing operator being clicked after first operand is entered

Create a new function called handleOperator below inputDecimal:

function handleOperator(nextOperator) {
  const { firstOperand, displayValue, operator } = calculator
  const inputValue = parseFloat(displayValue);

  if (firstOperand === null) {
    calculator.firstOperand = inputValue;
  }

  calculator.waitingForSecondOperand = true;
  calculator.operator = nextOperator;
}

Then replace console.log('operator', target.value) with the following code:

handleOperator(target.value);
updateDisplay();

When an operator key is pressed, we convert the current number displayed on the screen to a number (parseFloat(displayValue)) and then store the result in calculator.firstOperand if it does not exist already.

We also set calculator.waitingForSecondOperand to true which indicates that the first operand has been entered and the second one is ready to begin, and calculator.operator to whatever operator key was clicked.

At this point, it is useful to see how the properties of the calculator object is being updated on each button press. Add the following code to the end of both inputDigit and handleOperator.

console.log(calculator);

Now, try to construct a valid arithmetic operation by clicking the following keys in turn: 12 + 10. Notice that when the + key is pressed, the values of firstOperand and operator is updated to 12 and + respectively while waitingForSecondOperand is set to true indicating that the calculator is waiting for the second operand (10) to be inputted.

However, there’s a bug here. The second operand does not overwrite the first operand which is currently displayed on the screen. Instead, it is appended to it.

Calculator showing how second operand is appended to the first with the first

Let’s fix that by updating the inputDigit function to look like this:

function inputDigit(digit) {
  const { displayValue, waitingForSecondOperand } = calculator;

  if (waitingForSecondOperand === true) {
    calculator.displayValue = digit;
    calculator.waitingForSecondOperand = false;
  } else {
    calculator.displayValue = displayValue === '0' ? digit : displayValue + digit;
  }

  console.log(calculator);
}

Here, we check if waitingForSecondOperand is true and set displayValue to the key that was clicked. Otherwise, we perform the same check as before, overwriting or appending to calculator.displayValue as appropriate.

Now, try out the expression as before. It should update the displayValue properly.

2. When the user finishes the second operand and hits an operator

The second scenario we want to handle is if the user has finished entering the second operand and an operator key is clicked. At this point, all the ingredients to perform a valid calculation is present so we need to display the result of the calculation to the user.

After 12 + 10, let’s say the user hits the = button. What should happen?

Well 22 should be presented on the screen as the result of the calculation and the firstOperand should be updated to the result so that it can be used in the next calculation.

Update handleOperator to look like this:

function handleOperator(nextOperator) {
  const { firstOperand, displayValue, operator } = calculator
  const inputValue = parseFloat(displayValue);

  if (firstOperand == null) {
    calculator.firstOperand = inputValue;
  } else if (operator) {
    const result = performCalculation[operator](firstOperand, inputValue);

    calculator.displayValue = String(result);
    calculator.firstOperand = result;
  }

  calculator.waitingForSecondOperand = true;
  calculator.operator = nextOperator;
  console.log(calculator);
}

Then create a new object called performCalculation below handleOperator with the following properties:

const performCalculation = {
  '/': (firstOperand, secondOperand) => firstOperand / secondOperand,

  '*': (firstOperand, secondOperand) => firstOperand * secondOperand,

  '+': (firstOperand, secondOperand) => firstOperand + secondOperand,

  '-': (firstOperand, secondOperand) => firstOperand - secondOperand,

  '=': (firstOperand, secondOperand) => secondOperand
};

The else if block added to handleFunction checks if an operator already exists. If so, property lookup is performed for the operator in the performCalculation object and the function that matches the operator is executed.

This function returns the result which is then stored in the result variable. We then display the result to the user by updating the displayValue with this result and also set the firstOperand to the result.

Try it out. Enter 12 + 10 = and notice that the correct result is displayed on the screen.

Calculator showing the result of 12 + 10

It also works when you chain a string of operations. So 5 * 20 - 14 = should give 86 as the result.

Calculator showing the result of 5 * 20 - 14

This is because hitting the - key triggers the calculation of the first operation (5 * 20) whose result (100) then set as the firstOperand for the next calculation so by the time we enter 14 as the second operand and hit the = key, the function that is defined in the - property of performOperation is executed giving 86 as the result which is also set as the firstOperand for the next operation.

Try out other expressions and confirm that the calculator works as expected.

3. When a user enters two or more operators consecutively

It’s quite common to change one’s mind about the type of operation one wants to perform so the calculator must handle this properly.

Let’s say you want to add 7 and 2 together, you will click 7 + 2 = which will produce the correct result. But let’s assume after hitting 7 +, you change your mind and decide to subtract 2 from 7. Instead of clearing the calculator and starting all over, you should be able to hit - to override the + that was previously entered.

Remember that at this point (7 +), waitingForSecondOperand will be set to true since the calculator expects a second operand to be entered after the operator key. We can use this quality to update the operator key and prevent any calculations until the second operand has been inputted.

Modify the handleOperator function to look like this:

function handleOperator(nextOperator) {
  const { firstOperand, displayValue, operator } = calculator
  const inputValue = parseFloat(displayValue);

  if (operator && calculator.waitingForSecondOperand)  {
    calculator.operator = nextOperator;
    console.log(calculator);
    return;
  }

  if (firstOperand == null) {
    calculator.firstOperand = inputValue;
  } else if (operator) {
    const currentValue = firstOperand || 0;
    const result = performCalculation[operator](currentValue, inputValue);

    calculator.displayValue = String(result);
    calculator.firstOperand = result;
  }

  calculator.waitingForSecondOperand = true;
  calculator.operator = nextOperator;
  console.log(calculator);
}

This is the relevant change:

if (operator && calculator.waitingForSecondOperand)  {
  calculator.operator = nextOperator;
  console.log(calculator);
  return;
}

The if statement checks if an operator already exists and if waitingForSecondOperand has a truthy value. We then update operator and exit from the function by using an early return statement so that the rest of the function is not executed and no calculations are performed.

Try it out. Click multiple operators after entering some digits and monitor the calculator object in the console. Notice that the operator property is updated each time and no calculations are performed until you provide the second operand.

Resetting the calculator

The final task is to make sure the user can reset the calculator to its initial state by pressing a key. In most calculators, The AC button is used to reset the calculator to its default state so that’s what we’re going to use here.

Go ahead and create a new function below performCalculation as shown below:

function resetCalculator() {
  calculator.displayValue = '0';
  calculator.firstOperand = null;
  calculator.waitingForSecondOperand = false;
  calculator.operator = null;
  console.log(calculator);
}

Then replace console.log('all clear', target.value) with the following:

resetCalculator();
updateDisplay();

The resetCalculator function sets all the properties of the calculator object to their original values. Now click the AC key on your calculator. It should work as expected. You can check the calculator object in the console to confirm.

One more thing…

There’s a bug that allows you to add a decimal point to the displayValue after clicking on an operator.

Calculator gif showing decimal bug

Although this bug does not affect the result of the expression, it’s not ideal for a calculator to behave like that. We can write a fix by making a modification to inputDecimal:

function inputDecimal(dot) {
  if (calculator.waitingForSecondOperand === true) return;

  // If the `displayValue` does not contain a decimal point
  if (!calculator.displayValue.includes(dot)) {
    // Append the decimal point
    calculator.displayValue += dot;
  }
}

Here’s the relevant change:

if (calculator.waitingForSecondOperand === true) return;

We prevent the decimal point from being appended to the displayValue while waitingForSecondOperand is true by using an early return statement just like we’ve done a few times now.

Note that you can execute one statement in an if block without enclosing it within curly braces.

Here’s the final state of the calculator:

Wrap Up

That concludes my tutorial. This calculator can be enhanced in a lot of ways though. Here are a few ideas that you can implement to improve upon what we’ve covered here:

  • Add the % function.
  • Add the square root and square functions.
  • Make it possible to clear an entry without resetting the calculator.
  • Make it possibe to type in negative numbers (e.g -3).

Take a stab at implementing those features in your application.

If this article helped you in anyway, please consider sharing it. Don’t forget to ask for help in the Gitter chatroom if you get stuck.

Thanks for reading.