Go back

Posted |10 mins read

How to Build a Calculator App with HTML, CSS, and JavaScript (Part 2)

How to Build a Calculator App with HTML, CSS, and JavaScript (Part 2)

In the first part of this build, we learnt how to make a beautify UI for our Calculator App using HTML and CSS. If you came directly to this second part of the build, I highy recommend you read the part 1 in order to follow the project progressively.

In this tutorial, we will be focusing on building the calculator engine which will power the UI we have built in part one. We will use vanilla javascript for the purpose of this tutorial without any framework or library.

Objectives

At the end of this tutorial, you will:

  • Know how to work with Classes in javascript
  • Learn about functions
  • Learn how to query dom using data-attributes
  • Learn about constructor function
  • Manipulate a dom

Introduction

In this tutorial build, we will be focusing on the functional aspect of our calculator project using javascript. You can take a look at the live version of what we will be developing here An Online Calculator

Project Setup

To begin we already had an empty app.js file we created in the part 1 of this build and that is what we will be working with in todays build.

Define the Calculator Class

A JavaScript class is a blueprint for creating objects. A class encapsulates data and functions that manipulate data. To define a class, we use the class keyword to define a class. Let define our calculator class and some functions we will be working with:

class Calculator { constructor(previousOperandTextElement, currentOperandTextElement) {} clear() {} delete() {} appendNumber(number) {} chooseOperation(operation) {} compute() {} updateDisplay() {} }

We have defined our calculator class, and 7 functions inside the calculator class. By the names of the functions, you can be able to tell what each funtion will do in the calculator engine. We will leave our class and the functions for now and head over to querying the dom elements using the data-attributes.

Query DOM Elements

Now we need to query the dom elements using the data-attributes we earlier defined in the html file of our project in the part 1 of this build. Kindly reference the html file of this app to ensure you properly added the attributes in html elements. To query the dom, we will define some variables and assign the dom elements are there values. Add the following code snippet immediately after the closing curly bracket of the calcalator class:

class Calculator { ... } const numberButtons = document.querySelectorAll('[data-number]'); const operationButtons = document.querySelectorAll('[data-operation]'); const equalsButton = document.querySelector('[data-equals]'); const deleteButton = document.querySelector('[data-delete]'); const allClearButton = document.querySelector('[data-all-clear]'); const previousOperandTextElement = document.querySelector('[data-previous-operand]'); const currentOperandTextElement = document.querySelector('[data-current-operand]');

You will notice we have two variables using document.querySelectorAll and others using document.querySelector. The two variable numberButtons and operationButtons will return a NodeList because in our html file we have more than one button with the data attribute data-number and data-operation. So we are selecting all the buttons using the document.querySelectorAll method.

Instantiate Calculator Class

Next, we will instantiate the calculator class using the new keyword as follows. To do that add the following code snippet after the last variable we defined earlier; ie currentOperandTextElement.

const calculator = new Calculator( previousOperandTextElement, currentOperandTextElement );

Notice we stored the new Calculator instance to a variable called calculator and we are passing two arguments to the Calculator instance. We will use this variable calculator to access the Calculator instance and it's properties and variables. We will see this in use soon.

The two arguments been passed are coming from the constructor function we earlier defined in the class. This function is the first function that will be called anytime the Calculator class is instantiated. It takes two arguments previousOperandTextElement and currentOperandTextElement.

Next, we will update the constructor function in our class as follows:

constructor(previousOperandTextElement, currentOperandTextElement) { this.previousOperandTextElement = previousOperandTextElement; this.currentOperandTextElement = currentOperandTextElement; this.clear(); }

You current code should look as follows:

class Calculator { constructor(previousOperandTextElement, currentOperandTextElement) { this.previousOperandTextElement = previousOperandTextElement; this.currentOperandTextElement = currentOperandTextElement; this.clear(); } clear() {} delete() {} appendNumber(number) {} chooseOperation(operation) {} compute() {} updateDisplay() {} } const numberButtons = document.querySelectorAll("[data-number]"); const operationButtons = document.querySelectorAll("[data-operation]"); const equalsButton = document.querySelector("[data-equals]"); const deleteButton = document.querySelector("[data-delete]"); const allClearButton = document.querySelector("[data-all-clear]"); const previousOperandTextElement = document.querySelector( "[data-previous-operand]" ); const currentOperandTextElement = document.querySelector( "[data-current-operand]" ); const calculator = new Calculator( previousOperandTextElement, currentOperandTextElement );

Next, we will work on the appendNumber function and numberButtons variable.

Append Number Function

We will update the appendNumber function in the calculator class to reflect the following:

appendNumber(number) { if (number === "." && this.currentOperand.includes(".")) return; this.currentOperand = this.currentOperand.toString() + number.toString(); }

Next, we will loop through the numberButtons using the forEach method. To do that will add this code snippet immediately after instantiating the Calculator class.

// Loop through number buttons numberButtons.forEach((button) => { button.addEventListener("click", () => { calculator.appendNumber(button.innerText); calculator.updateDisplay(); }); });

Here we are listening for a click event on any of the buttons with the data attributes data-number. Once there's a click event, we append the innerText to the currentOperand as a string to the already existing currentOperand, then update the display of the calculator using updateDisplay. To access the appendNumber, updateDisplay and other variables in the Calculator class, we are using the calculator variable we defined earlier which stores the Calculator instance.

Choose Operation Function

Next we will update our chooseOperation function to reflect the following:

chooseOperation(operation) { if (this.currentOperand === "") return; if (this.previousOperand !== "") { this.compute(); } this.operation = operation; this.previousOperand = this.currentOperand; this.currentOperand = ""; }

After updating our chooseOperation function, we will loop through the operationButtons variable just like with did with numberButtons. To do that, we will add the following code snippet after the Loop through numbers buttons code snippet:

// Loop through the operation buttons operationButtons.forEach((button) => { button.addEventListener("click", () => { calculator.chooseOperation(button.innerText); calculator.updateDisplay(); }); });

Now we are looping through the operation buttons /, *, + and - using the forEach method and the listening for a click event on any of the buttons. Once there's a click event, we pass the innerText of the button as an argument to the chooseOperation function.

Then, in the chooseOperation function we are checking if the currentOperand is empty then the function will return because there's no value to compute. "You can add an error handler that returns an error, telling the user to enter a value." If the currentOperand is not empty then it checks that the previousOperand is not empty as well, then it calls the compute function before assigning values else it will skip computing and then assign values.

Now we need to work on the compute function of our calculator. This will handle the computations.

Compute Function

This funtion will handle all the operations and it's computations. We will update the compute function of the Calculator class as follows:

compute() { let computation; // This will be the result of our computation const prev = parseFloat(this.previousOperand); const current = parseFloat(this.currentOperand); if (isNaN(prev) || isNaN(current)) return; switch (this.operation) { case "+": computation = prev + current; break; case "/": computation = prev / current; break; case "*": computation = prev * current; break; case "-": computation = prev - current; break; default: return; } this.currentOperand = computation; this.operation = undefined; this.previousOperand = ""; }

After updating the compute function, we need to we attach a click event listener to equalsButton as follows:

equalsButton.addEventListener("click", () => { calculator.compute(); calculator.updateDisplay(); });

Here's a quick description of what we are doing here. We add a click event listener to the equalsButton, once there's a click event on the button, we call the the compute function and the updateDisplay function as well.

The compute function does not take any argument like the previous functions we called, rather we define some variables using the let and const keyword. Here we are assigning the value of previousOperand to prev and the value of currentOperand to current. If the value of prev or current is not set, then the function will return. "You can also add an error message here."

If all the checks are passed, then the functions carry out the mathematical operation depending on the operation selected and the set the values.

Clear Function

This is a simple function, just as the name sounds, it will clear the screen and set all values to null. So let's update the clear function as follows:

clear() { this.currentOperand = ""; this.previousOperand = ""; this.operation = undefined; }

Next, we will add a click event listener to the AC button using the allClearButton variable we defined earlier as follows:

allClearButton.addEventListener("click", () => { calculator.clear(); calculator.updateDisplay(); });

Once this button is clicked, it will call the clear function and the function will set every variable to "" and then update the display.

Delete Function

We are gradually getting close to the end of all functions. We will work on the delete function. As the name sounds, it will delete values from the screen one after the other when called. Let's update the delete function as follows:

delete() { this.currentOperand = this.currentOperand.toString().slice(0, -1); }

Next, we need to add a click event listener to the DEL button in other to call the delete() once clicked on:

deleteButton.addEventListener("click", () => { calculator.delete(); calculator.updateDisplay(); });

This is a very simple function, once DEL button is clicked on, it calls the delete function which uses the slice() method to remove the last value from currentOperand string and set the value back to itself.

We need one more function to finish this build and that is the updateDisplay function as you must already have noticed.

Update Display Function

This function updates the display of our calculator and that enables us to see the operations we are carrying out on the screen. Let's get that done by updating the function as follows:

updateDisplay() { this.currentOperandTextElement.innerText = this.currentOperand; if (this.operation != null) { this.previousOperandTextElement.innerText = `${this.previousOperand} ${this.operation}`; return; } this.previousOperandTextElement.innerText = this.previousOperand; }

This function assigns the value of currentOperand to currentOperandTextElement variable first and then checks if there's an operation to carry out. If it finds any, it assigns' previousOperandTextElement.innerText to be the concatination of previousOperand and operation, eg: 4 * and then return the value. But if there's no operation then it just assigns' previousOperand to previousOperandTextElement.innerText and wait until there's an operation to do otherwise.

This has been a long build and I hope you followed to the end. This should be how the final calculator engine code should look like:

class Calculator { constructor(previousOperandTextElement, currentOperandTextElement) { this.previousOperandTextElement = previousOperandTextElement; this.currentOperandTextElement = currentOperandTextElement; this.clear(); } clear() { this.currentOperand = ""; this.previousOperand = ""; this.operation = undefined; } delete() { this.currentOperand = this.currentOperand.toString().slice(0, -1); } appendNumber(number) { if (number === "." && this.currentOperand.includes(".")) return; this.currentOperand = this.currentOperand.toString() + number.toString(); } chooseOperation(operation) { if (this.currentOperand === "") return; if (this.previousOperand !== "") { this.compute(); } this.operation = operation; this.previousOperand = this.currentOperand; this.currentOperand = ""; } compute() { let computation; // This will be the result of our computation const prev = parseFloat(this.previousOperand); const current = parseFloat(this.currentOperand); if (isNaN(prev) || isNaN(current)) return; switch (this.operation) { case "+": computation = prev + current; break; case "/": computation = prev / current; break; case "*": computation = prev * current; break; case "-": computation = prev - current; break; default: return; } this.currentOperand = computation; this.operation = undefined; this.previousOperand = ""; } updateDisplay() { this.currentOperandTextElement.innerText = this.currentOperand; if (this.operation != null) { this.previousOperandTextElement.innerText = `${this.previousOperand} ${this.operation}`; return; } this.previousOperandTextElement.innerText = this.previousOperand; } } const numberButtons = document.querySelectorAll("[data-number]"); const operationButtons = document.querySelectorAll("[data-operation]"); const equalsButton = document.querySelector("[data-equals]"); const deleteButton = document.querySelector("[data-delete]"); const allClearButton = document.querySelector("[data-all-clear]"); const previousOperandTextElement = document.querySelector( "[data-previous-operand]" ); const currentOperandTextElement = document.querySelector( "[data-current-operand]" ); const calculator = new Calculator( previousOperandTextElement, currentOperandTextElement ); numberButtons.forEach((button) => { button.addEventListener("click", () => { calculator.appendNumber(button.innerText); calculator.updateDisplay(); }); }); operationButtons.forEach((button) => { button.addEventListener("click", () => { calculator.chooseOperation(button.innerText); calculator.updateDisplay(); }); }); equalsButton.addEventListener("click", () => { calculator.compute(); calculator.updateDisplay(); }); allClearButton.addEventListener("click", () => { calculator.clear(); calculator.updateDisplay(); }); deleteButton.addEventListener("click", () => { calculator.delete(); calculator.updateDisplay(); });

If you followed the tutorial progressively, you should have a working calculator with a beautify UI just like we have in this image.

image

Conclusion

Congratulation! you made it to this stage in this tutorial and I'm sure you learnt a lot in this second part of the Calculator App build today. You can refer to the part one here

In conclusion, we have been able to finish the entire Calculator App build working with HTML, CSS and Javascript. We were able to cover topics like classes, functions, dom manipulation, constructor and many more. I hope you found this helpful and I look forward to seeing the completed version of your app hosted live so I can test it out.

If you wish to take the build further by adding extra functionalities that I didn't cover in this tutorial, feel free to do that and share the new features with the community and so they all can learn along.

We will be going over the the next build in our series and that is the Linktree Clone in the next couple of days.

javascript

calculator app

series

classes

functions

constructor