Beginner JavaScript Project - Basic Calculator Using Flexbox and Vanilla JavaScript

Before we begin

Beginner JavaScript Project tutorials are a great way to learn and apply your knowledge. Rather than just show you how I do things, I'll explain WHY I do things. I recommend you try this yourself first, and see how I've done it by following along with the guide. There is a completed CodePen at the end. I will use CodePen.io throughout the tutorial but you can use your IDE if you like. Enjoy!

What we're building

This is the amazing calculator we will be building. We can input numbers, add, subtract, multiply and divide, as well as reset the display. Isn't it marvellous?

Build the UI

Let's start by adding the UI. This helps us visualise things as we go, such as user interactions and state changes.

Add a Container

In your HTML section add the following:

<div class="container">

</div>

and in your CSS section add the following:

.container{
    background: lightGrey;
    width: max-content;
    margin: auto;
}

You won't see anything yet but all we're doing here is adding a container, which gives us better control and structure of the calculator elements which we'll add later. We're also giving it a width (we want it to expand to fit the content), a background color, and centering everything in the view.

The display

When building the UI, can you build it however you like - For something like this I like to work "top down", so I'll start with the display. This displays the calculations and results to the user, and goes at the top of our container.

It makes sense for the display to be it's own element (so we can style it, and identify it clearly in the code/browser inspector). We'll create a seperate div for this and also hard code a dummy value (989), so we can visually see what the display will look like, which should make it easier to style.

Add the following to your HTML, just inside the container:

  <div class="display">989</div>

The completed HTML so far should look like this;

<div class="container">
  <div class="display">989</div>
</div>

And add the following to your CSS:

.display{
  width: 100%;
  background-color: darkGrey;
  height: 50px;
}

All we're doing here is making sure the display isn't too big (height: 50px), takes up the width of the container (width: 100%) and has a background color that differs from the container. Again, I might change this later but right now this will do.

So far, the our project should look like this:

js projects simple calculator step1

We still have some work to do. The number appears at the top left, we want it to appear towards the right hand side of the display, and have a bigger font-size.

When we need to arrange items, in a single direction (e.g horizontally or vertically) Flexbox is a good option, so we'll use that here to style our display input.

Amend the .display style in your CSS with the following:

.display{
    width: 100%;
    background-color: darkGrey;
    height: 50px;
    font-size: 48px;
    display: flex;
    justify-content: flex-end;

}

Note the bottom 2 lines - display:flex and justify-content: flex-end;. This means that the display is a flex container and that it's children (in this case, the text content, i.e the number) should align to the END (the right side) of the container. Lastly we're making the font bigger.

With our fancy flex stuff added, the display should look like this:

js projects simple calculator step2

If you add more numbers to the dummy text, the inputted number stays justified to the right of the container (this is thanks to our flex-end)

Add the buttons

Now that we have our display in place and formatted, it's time to add the buttons - this would be a pretty useless calculator otherwise!

We'll add a container div to hold our buttons - remember, adding a container div around related items makes it easier to manage/style related elements, and makes our code easier to read. We'll also add our buttons, in order of left-to-right (like we were reading a book) to match our design. We'll use flexbox to arrange the buttons later.

Go ahead and update your HTML so that it looks like this:

<div class="container">
    <div class="display">989</div>
    <div class="buttons">
        <button class="btn-number">1</button>
        <button class="btn-number">2</button>
        <button class="btn-number">3</button>
        <button class="btn-operator">+</button>
        <button class="btn-number">4</button>
        <button class="btn-number">5</button>
        <button class="btn-number">6</button>
        <button class="btn-operator">-</button>
        <button class="btn-number">7</button>
        <button class="btn-number">8</button>
        <button class="btn-number">9</button>
        <button class="btn-operator">X</button>
        <button class="btn-clear">C</button>
        <button class="btn-number">0</button>
        <button class="btn-equals">=</button>
        <button class="btn-operator">/</button>
    </div>
</div>

Note that the number buttons have class of btn-number, operator buttons have class of btn-operator, the equals button has a class of btn-equals and clear has a class of btn-clear. We'll need these classes to style the buttons later.

So our buttons are in, but the layout is wrong and they look a bit crappy.

js projects simple calculator step 3

The reason why we arranged our buttons in a single row like this is because we can use flexbox to control the layout. Remember, our buttons are wrapped in a buttons div, so will make this our flex container. Update the .buttons CSS to include the following:

.buttons{
    max-width: 400px;
    display:flex;
    flex-wrap: wrap;
}

Ok so what's happening here?

  • We're setting our buttons div to be a flex container
  • We're setting the width to be 400px;
  • We're saying that we want the items of the flex container (i.e, our buttons) to wrap (i.e take a new line). This will happen when the length of the row, goes over 400px (our width).

You should now see this:

js projects simple calculator step4

Now I know what you're thinking: "Chris, WTF? I'm setting up all this stuff and nothings working". Yes, but bare with me, the magic is about to happen. We've merely set up our flex container layout, now we need to add some properties to our flex items i.e buttons.

Add the following to your CSS:

button{
  flex: 1 25%;
} 

Our calculator should now look like this:

js projects simple calculator step5

If you were WTF'ing before, you still might be now, but in a more curious tone. Let's explain what just happened when we added flex: 1 25% to each button:

This is shorthand syntax for flex-grow: 1 and flex-basis: 25%, e.g, our button CSS above could be rewritten like:

button{
  flex-grow: 1; 
  flex-basis: 25%;
} 

This means "each item (i.e button) should take up 25% of the space available, and the item itself should grow to fill said space". Let's do some maths to explain further. Remember, our container has a max-width of 400px. We've told the container any overflowing items should break onto the next line by using flex-wrap: wrap.

This means we have 400px of space per row, before a line break happens. Since we've said that each button should take up 25% of the space available, each button should have a width of 100px (since 25% of 400px is 100px) giving us 4 buttons per row. By inspecting the dev tools, we can see this is true:

js projects simple calculator step6

If we wanted 2 buttons per row, we should change the percentage in our CSS:

button{
  flex: 1 50%;
} 

js projects simple calculator step7

Why do we need to do this?!

This makes it easier to align/arrange items in a few lines of code. If you look at our HTML and CSS, there isn't very much there. Should our design change in future (e.g change of button layouts, more responsive design etc) it will be less complex to change.

I encourage you to stop here and play about with some of the flex properties we talked about, so you really understand what's going on. Have a look at This Guide To Flexbox on CSS-Tricks for reference. It's amazing. I'm going to get a coffee while you do that, then we'll get into JavaScript mode!

Let's wrap up our UI by adding some styling to the buttons. Add the following to your CSS:

button{
  flex: 1 25%;
  padding: 15px;
  font-size: 26px;
} 

.btn-number {
  background-color:grey;
}

.btn-operator {
  background-color:orange;
}

.btn-equals {
  background-color:green;
}

.btn-clear {
   background-color:crimson;
}

And let's remove the dummy data in the display. In your HTML, remove the 989 value from the div:

  <div class="display"></div>

Our finished UI should look like this:

js projects simple calculator step8 ui finished

Look at our amazing calculator! It looks nice but does nothing yet. So let's get everything wired up.

NOTE: See the CodePen at the bottom to see the completed HTML/CSS if you're having issues.

Add the JavaScript

When you're starting the JavaScript-y parts of a project, it can be hard to know where to start. I like to begin by thinking about what functionality I need, and making a list (this is why I built the UI first, so it's easier to visualise the functionality).

Why not take this time to come up with a list of functionality, and compare it to my list below?

Ready? Ok good, here's my list

  • When the user clicks a number or operator button, the display should update with the value of the button
  • When the clear button is clicked, the display should reset/clear
  • When the equals button is clicked, the display should update with the result of the equation that is displayed

There's probably other stuff I missed that you think is important - you can always expand on this code later to add your own stuff!

Update the display when a number or operator button click

This is a good place to start, we'll worry about the other stuff later. Let's think more about how our JavaScript is going to look before jumping into the code.

"when a button is clicked, the display should update with the value of the button"

Some things that jump out when re-reading that functionality:

  • We'll need to get all the buttons and their values from the DOM
  • We'll need to add an event listener to handle the clicks
  • We're updating the display, so we'll need to get the display from the DOM as well

That should be enough to get us going! Let's start by updating the display when an operator button or number button is clicked. We will handle the clear and equals later.

Why are the equals and clear buttons code seperate? Can I not get all the buttons at once and add all the event listeners at once?

The answer is yes, you can do whatever you want as there is no "right" way to write code. However splitting the code out in this way has a few advantages:

  • It keeps similar logic together, enforcing good seperation of concerns for our functions
  • If we have to add more buttons & logic, we don't touch existing code
  • It makes the code easier to read & reason about; it's easier to see which buttons do what

Get the number & operator buttons from the DOM

Add the following to your JS section in CodePen (or to your JavaScript script if you're using an IDE):

const buttons = document.querySelectorAll('.btn-number, .btn-operator')
const display = document.querySelector('.display')

All we're doing here is using querySelectorAll to get our buttons (number buttons and operator buttons), and get our display.

In this case it's a good idea to assign DOM elements to top level variables, as it makes it easier for us to access these elements without having to repeatedly use document.querySelectorAll() throughout the code. It also helps if the querySelector changed for some reason (e.g from .display to .calculator-input-display), as we only have to change the querySelector in one place!

Next, we're going to add a data-num attribute to the HTML of our buttons, which will hold the value of that button. Update your HTML to match the following:

<div class="container">
  <div class="display"></div>
  <div class="buttons">
    <button class="btn-number" data-num="1">1</button>
    <button class="btn-number" data-num="2">2</button>
    <button class="btn-number" data-num="3">3</button>
    <button class="btn-operator" data-num="+">+</button>
    <button class="btn-number" data-num="4">4</button>
    <button class="btn-number" data-num="5">5</button>
    <button class="btn-number" data-num="6">6</button>
    <button class="btn-operator" data-num="-">-</button>
    <button class="btn-number" data-num="7">7</button>
    <button class="btn-number" data-num="8">8</button>
    <button class="btn-number" data-num="9">9</button>
    <button class="btn-operator" data-num="*">X</button>
    <button class="btn-clear">C</button>
    <button class="btn-number" data-num="0">0</button>
    <button class="btn-equals" data-num="=">=</button>
    <button class="btn-operator" data-num="/">/</button>
  </div>
</div>

"Why do we need to do this?! Why can I not get the value from the button text?"

Good question! This technique is used to seperate data from presentation. Take the example of the multiply sign:

    <button class="btn-operator" data-num="*">X</button>

The text of the button is X but the value is actually *, since this is how we multiply in JavaScript. Using this approach means the text can be whatever we want, but the value never changes. e.g:

<!-- If we ever change the text of any of the buttons, the value stays the same  -->
<button class="btn-operator" data-num="*">X</button>
<button class="btn-operator" data-num="*">Multiply</button>
<button class="btn-operator" data-num="*"><img src="link-to-multiply-image"/></button>

OK! So we've stored the data for each button and got the DOM elements. Next, let's handle what happens when a button is clicked.

Add the event listeners

When we use querySelectorAll(), this returns a NodeList (this is basically a list of matching elements, which we can loop over). This means we can loop over our buttons variable and attach an onclick listener.

Add the following to your JavaScript:

const buttons = document.querySelectorAll('.btn-number, .btn-operator')
const display = document.querySelector('.display')

//add an eventListener to each of the buttons
buttons.forEach(button => {
  button.addEventListener('click', () => { 
      // logic that run when the button is "clicked"
      alert("button was clicked!")
  })
})

As you can hopefully see, we're using the forEach() higher order function to loop over the buttons that were returned from our querySelectorAll() function.

For each button we are adding a click eventListener. When a button is clicked, it should show an alert box:

js projects simple calculator step9

Hurray! Success!

(If you're having issues and not seeing the alert box, check out the CodePen at the end of the article to see the finished solution).

Get the value of the button that was clicked

Next, we need to get the value of the clicked button from the data-num attribute we added earlier. When we want to get attributes from DOM elements, we can use the getAttribute(name) function. Add the following to the event listener logic in your JavaScript:

const buttonValue = button.getAttribute('data-num');  
alert(buttonValue + " was clicked!")

The JavaScript so far looks like this:

const buttons = document.querySelectorAll('.btn-number, .btn-operator')
const display = document.querySelector('.display')

//add an eventListener to each of the buttons
buttons.forEach(button => {
  button.addEventListener('click', () => { 
    // logic that run when the button is "clicked"
    const buttonValue = button.getAttribute('data-num');  
    alert(buttonValue + " was clicked!")
  })
})

When you click a button, the alert should show the value of the button that was clicked.

Update the display

Now that we have working event handlers, and can get the value of the button that was clicked, we can start updating our display.

Remember, it's good practice to seperate data from presentation whenever possible. So let's create a variable called displayData to hold the data for our display. In your JavaScript add the following:

let displayData = "";

and update the eventListener logic by replacing the alert with this line:

displayData += buttonValue;
alert("Display is now: " + displayData)

The completed JavaScript so far looks like this:

//get our buttons from the DOM
const buttons = document.querySelectorAll('.btn-number, .btn-operator')
const display = document.querySelector('.display')

let displayData = "";

//loop over the buttons
buttons.forEach(button => {

    //for each button, we want to add a "click" event listener
    button.addEventListener('click', () => { 

        //get the value of the clicked button from the attribute
        const buttonValue = button.getAttribute('data-num');

        //update our displayData variable with the value of the clicked button
        displayData += buttonValue;
        alert("Display is now: " + displayData)
    })
})

Now when you click a button, you will see an alert with the equation displayed!

js projects simple calculator step10

Now we know what our displayData successfully updates when a button is clicked, we can go ahead and update the actual display with this data. Remove the alert in your event listener and add the following:

display.textContent = displayData;

Now, our display should update when a number or operator button is clicked:

js projects simple calculator step 11

Woohoo! Looking good so far, but you will have noticed the equals and clear buttons do not work yet. So let's wire those up.

Update the display with the result

If we take some time to think about what we need to do here, it'll make our life easier when writing the code.

  • We need to get the equals button from the DOM
  • When the button is clicked, we want to evaluate the expression that is displayed
  • We want to show the result of the expression in the display

Why not try this next part yourself first before following on? HINT: We've used similar patterns already, referencing other code for help is a good way to solve problems!

Get the Equals button from the DOM

Just like before, we will get the equals button from the DOM. Add the following to your JavaScript:

const equalsButton = document.querySelector('.btn-equals')

Notice we use querySelector here instead of querySelectorAll as we know there is only one element with this selector. querySelector returns a single element instead of a NodeList which means we don't need to use a loop!

Next we'll add an eventListener, and use the eval() function to evaluate the displayData expression. We'll set this evaluated expression to our displayData variable, and update our display:

equalsButton.addEventListener('click', () => { 
    displayData=eval(displayData)
    display.textContent = displayData
})

The complete JavaScript so far looks like this:

//get our buttons from the DOM
const buttons = document.querySelectorAll('.btn-number, .btn-operator')
const equalsButton = document.querySelector('.btn-equals')
const display = document.querySelector('.display')

let displayData = "";

//loop over the buttons
buttons.forEach(button => {

    //for each button, we want to add a "click" event listener
    button.addEventListener('click', () => { 

        //get the value of the clicked button from the attribute
        const buttonValue = button.getAttribute('data-num');

        //update our displayData variable with the value of the clicked button
        displayData += buttonValue;

        //output the displayData value to the display element
        display.textContent = displayData;
    })
})

// add an event listener to the equals button
equalsButton.addEventListener('click', () => { 

    // use the eval() function to evaluate the expression and output it to the display
    displayData=eval(displayData)
    display.textContent = displayData
})

And that's it - if you perform an equation e.g "2+2", you should see the result in the display.

Clear the display

The last thing we need to do is clear the display when the C button is clicked. Just like before, we will get the clear button from the DOM. Add the following to our JavaScript:

const clearButton = document.querySelector('.btn-clear')

And add our eventListener:

clearButton.addEventListener('click', () => { 
  displayData = "";
  display.textContent = displayData;
})

Here, we are resetting our displayData variable to empty string, which effectively clears the display. Finally, we're updating our display in the usual way.

If all went well, you should have a fully working calculator! If not, check out the CodePen at the bottom so see the finished example.

Thing's to Try

Before we wrap up, here are some thing's you can try:

  • The numbers will overflow if the user types in too many numbers, how would you fix this?
  • The calculator won't work correctly if the user enters too many operators, e.g 2++2. How can you prevent the user from doing this?
  • Why not try adding an undo feature?

Finished Example