Just another Calculator App?

A Personal Project

This week, I made a simple Calculator App. This might not seem as exciting as a lot of projects out there but it is a learning experience regardless.

Screenshot

It is all too easy to structure an HTML Document in the layout of a basic calculator and use CSS to style it to look convincingly like a calculator. The real challenge comes in using JavaScript to add logic to the program.

This is a tiered process where you need to list out the basic functions you want to do and then pick a way to manipulate the DOM to give you the output you want.

This is my walkthrough to building a calculator :

  1. What your App needs to do

  2. How to manipulate the DOM to do it

  3. Logic Scripting with JavaScript

  4. Putting it all together

  5. Mistakes made and ways to improve

  6. Future updates

What the App needs to do -

A calculator needs to do basic arithmetic at the very least, addition, subtraction, multiplication and division(I added exponents to mine).

Before we do this, we need to structure the layout using HTML.

This was my structure for the layout -

body -> main -> div(calc-wrapper) -> div(style) -> div(calc-body) -> div(result) ->
div(symbol) -> div(equals) -> div(clear)

https://github.com/David4bay/Calculator/blob/main/index.html

Calculator-HTML

The layout is structured semantically, starting with the main element that houses the app, followed by a div that starts the body of the calculator. This will hold all the keys, the result bar and its div used for styling(div.style).

How to manipulate the DOM -

The Document Object Model is the application programming interface that helps target the node elements that HTML Documents are constructed.

Manipulating the DOM, we have several ways to do this, we could get HTML elements by their id, class, and even their selectors.

The method used to save the numpad of the calculator is through the id. This is done by targetting the id name of the element that holds the number value and saving that element as a variable, in this case, const.

const addition = document.getElementById("+");

The above code serves as the addition key by going into the document and grabbing an element with the id '+' and saving it in the variable addition.

Now that we have our arithmetic variable we need to do the same for the numpad, other arithmetic operations and then finally the evaluation of a result.

const subtraction = document.getElementById("-");

const multiply = document.getElementById("*");

const exponent = document.getElementById("**");

const division = document.getElementById("/");

const equals = document.getElementById("=");

We would do the same for the number keys as well.

const numOne = document.getElementById("1");

const numTwo = document.getElementById("2");

const numThree = document.getElementById("3");

const numFour = document.getElementById("4");

const numFive = document.getElementById("5");

const numSix = document.getElementById("6");

const numSeven = document.getElementById("7");

const numEight = document.getElementById("8");

const numNine = document.getElementById("9");

const zero = document.getElementById("0");

const clear = document.getElementById("clear");

Now for the fun part.

Logic Scripting for JavaScript -

To add logic we need to set a way for JavaScript to take note of click events every time we click an element holding a number.

We would add an event listener to our variables.

numOne.addEventListener("click", (e) => { document.getElementById("result").innerText += "1"; });

The above code is appending an event listener to the numOne variable which listens for a click event in the document holding the string of "1".

The function executed adds the string of "1" to the element with id "result", so this builds the functionality that adds numbers to the result box when applied across all elements, this applies to all other numbers.

numTwo.addEventListener("click", (e) => {
document.getElementById("result").innerText += "2";});

numThree.addEventListener("click", (e) => { document.getElementById("result").innerText += "3"; });

numFour.addEventListener("click", (e) => { document.getElementById("result").innerText += "4"; });

numFive.addEventListener("click", (e) => { document.getElementById("result").innerText += "5"; });

and so on...

Which then lends the question, how do we evaluate the strings in the result box to an arithmetic expression that can be executed?

We create a special function that sets the document.getElementById("result") to be represented as a number for the console to solve the arithmetic expression.

We get the equals variable. Attach an event listener, then set the result box to evaluate its contents.

const equals = document.getElementById("equals");

equals.addEventListener("click", (e) => { document.getElementById("result").innerText = eval(document.getElementById("result").innerText);
}

Hooray! you've just added evaluation logic to your calculator! :) .

Putting it all together -

Now that the logic of the calculator has been set, we need to add styles to give the calculator life-like button presses and not look like a bland document.

Of course, I used sass for this as you write CSS faster, for now, we'll go through the plain CSS (I'll add the sass documentation at a future point).

I usually start with removing the default margins and paddings the HTML document adds to the page and specify a border-box box-sizing which makes the padding and margin stay the size you want it without any calculations on the part of the browser.

* {
padding: 0;
margin: 0;
box-sizing: borderbox;
}

*::before, *::after {
padding: 0;
margin: 0;
box-sizing: borderbox;
}

The universal selector ('*') applies styling to all HTML elements, in this case, it sets the default paddings and margins to 0 and also sets the box-sizing to border-box.

The universal pseudoelements('*::before, *::after') also apply the same properties to replace the default of all pseudoelements.

We then give the body and html elements a min-height to allow content extend beyond the viewport height if they are required to.

body, html {
min-height: 100vh;
}

Now we can start styling the body separately, I chose to apply a linear-gradient of two colors.

We modify the main element that houses the calculator as.

main {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
max-width: 100%;
background: linear-gradient(3turn, hsla(205deg, 55%, 45%, 0.8), hsla(139deg, 45%, 55%, 0.5));
}

We give display 'flex' to the main element and set document flow to column using flex-direction.

Child elements are then centered with justify-content and align-items 'center'.

The calc-wrapper div also follows this document flow.

div.calc-wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
max-width: 425px;
padding: 16px 16px 24px 16px;
margin: auto;
border: 1px solid black;
border-radius: 15px;
z-index: 2;
overflow: hidden;
background-color: hsla(0deg, 45%, 45%, 0.3);
position: relative;
}

Our calc-wrapper serves as the main body of the calculator, we can set the inner child elements document flow, the inner padding, and other properties.

The properties position -> relative, z-index -> 2 and overflow -> hidden are set so as to contain the div.style that is used as additional styling within the body of the calculator.

Position relative for the calc-wrapper so we can use position absolute to life the div.style(and its pseudoelements) outside of the normal flow.

z-index -> 2 so the styled elements are below the main body, creating a streaking effect.

overflow -> hidden so it can extend beyond the body of the calculator and adjust to any viewport width.

Now to get to the main body.

div.calc-body {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 215px;
outline: 1px solid hsl(0deg, 0%, 100%);
outline-offset: 16px;
outline-width: 3.2px;
outline-color: hsla(50deg, 35%, 65%, 0.9);
gap: 24px;
justify-content: center;
align-items: center;
padding: 16px;
margin: 19.2px;
border-radius: 15px;
border: 1px solid-black; 
z-index: 2;
}

The main things to take note of in the app is that the document flow is set to row, this means any child element that cannot be contained within the same row would move to the next.

We then specify the position of the elements to be centered using a flexbox.

styling properties like borders and outlines, give the body a visible dimension.

Going to symbol.

div.symbol {
display: flex;
justify-self: space-around;
padding: 10px;
margin: 5px;
align-items: center;
border: 1px solid black;
border-radius: 5px;
color: hsl(0deg, 0%, 100%);
z-index: 2;
transition: all 0.4s;
background-color: hsla(40deg, 100%, 50%, 0.5);
}

We give padding to each div.symbol that represents the number pad of the calculator and apply a transition to all the keys to have the keys animate within 0.4 seconds.

We then add hover and active pseudoselectors to our symbol.

div.symbol:hover {
color: hsl(139deg, 45%, 55%);
background-color: hsla(55deg, 100%, 50%, 0.6);
cursor: pointer;
transform: scale(1.1);
}

div.symbol:active {
color: hsl(139deg, 70%, 80%);
background-color: hsl(55deg, 100%, 70%);
box-shadow: 0px 0px 10px 5px;
transform: scale(0.9);
cursor: grab;
}

On hover, we expand the element hovered(scale -> 1.1), add a pointer then bold the colors.

On click, the scale is dropped to create a click effect.

For the most part, the calculator is finished.

View here https://astonishing-choux-a439c4.netlify.app

Mistakes made and ways to improve -

I had assumed the best way to convert strings into numbers for arithmetic to be applied was to use the parseInt('', 10); in-built method, this did not work as expected.

This led to the use of the eval() method described in the above tutorial. Which is easier to use but introduces the possibility of client-side code execution by third parties different from the user.
The remedy to this would be to use a regex expression, to filter out any unsuspecting code that may have been applied on submission.

Future Updates -

We're done and even though the calculator works there will always be new things to add and learn. I recently added keypress event listeners that expanded the calculator's functionality. The wonderful thing about building a basic calculator is I learned a lot of cool tricks to converting strings to numbers to be evaluated.

As this was just a quick project to broaden DOM Scripting skills, the eval() function was used, a future update will move away from this method for security concerns, as of the moment a regex expression has been applied to prevent unwanted code execution.

UPDATED -

The Calculator no longer uses the eval() function to calculate arithmetic, instead it uses mathjs to do so. Delete button added. Please refer to my code on GitHub for updates.