Brian Holt's Calculator with Walk-through of '1 + 2 = 3'
Tags: javascript, calculator, beginners, frontendmasters
Published: 2022-07-15 16:07:17

```html
  <body>
    <div class="calc">
      <section class="screen">
        0
      </section>
      <section class="calc-buttons">
        <div class="calc-row">
          <button class="double calc-button">C</button>
          <button class="calc-button">←</button>
          <button class="calc-button">÷</button>
        </div>
        <div class="calc-row">
          <button class="calc-button">7</button>
          <button class="calc-button">8</button>
          <button class="calc-button">9</button>
          <button class="calc-button">×</button>
        </div>
        <div class="calc-row">
          <button class="calc-button">4</button>
          <button class="calc-button">5</button>
          <button class="calc-button">6</button>
          <button class="calc-button">-</button>
        </div>
        <div class="calc-row">
          <button class="calc-button">1</button>
          <button class="calc-button">2</button>
          <button class="calc-button">3</button>
          <button class="calc-button">+</button>
        </div>
        <div class="calc-row">
          <button class="triple calc-button">0</button>
          <button class="calc-button">=</button>
        </div>
      </section>
    </div>
    <script src="./assets/js/app.js"></script>
  </body>
```

```css
* {
    box-sizing: border-box;
}

body {
    padding: 0;
    margin: 0;
}

.calc {
    width: 400px;
    background-color: black;
    color: white;
}

.screen {
    font-size: 40px;
    font-family: 'Courier New', Courier, monospace;
    text-align: right;
    padding: 20px 5px;
}

.calc-button {
    background-color: #d8d9db;
    color: black;
    height: 100px;
    width: 24.5%;
    border: none;
    border-radius: 0;
    font-size: 40px;
    cursor: pointer;
}

.calc-button:hover {
    background-color: #ebebeb;
}

.calc-button:active {
    background-color: #bbbcbe;
}

.calc-button:last-child {
    background-color: #df974c;
    color: white;
}

.calc-button:last-child:hover {
    background-color: #dfb07e;
}

.calc-button:last-child:active {
    background-color: #dd8d37;
}

.double {
    width: 49.7%;
}

.triple {
    width: 74.8%;
}

.calc-row {
    display: flex;
    justify-content: space-between;
    align-content: stretch;
    margin-bottom: 0.5%;
}
```

```javascript
// The below code is from one of Brian Holt's Frontend Masters courses. The comments are mine, added to help me better understand the calculator.


// WALKING THROUGH '1 + 2 = 3':

  // BEFORE ANYTHING IS CLICKED:
    // runningTotal = 0
    // previousOperator = null
    // buffer = '0'

  // AFTER HAVING CLICKED '1':
    // runningTotal = 0
    // previousOperator = null
    // buffer = '1'

  // AFTER HAVING CLICKED '1 +':
    // runningTotal = 1
    // previousOperator = +
    // buffer = '0'
    
  // AFTER HAVING CLICKED '1 + 2':
    // runningTotal = 1
    // previousOperator = +
    // buffer = '2'

  // AFTER HAVING CLICKED '1 + 2 =':
    // runningTotal = 0
    // previousOperator = null
    // buffer = '3'

let runningTotal = 0 // numerical total (not seen in UI)
let buffer = '0' // what user is typing in ('buffer' gets stored below in 'screen.innerText' so is effectively what's seen in the UI)
let previousOperator = null // most recent operator pressed // note that 'null' is different than '0'; 'null' is the absence of anything
const screen = document.querySelector('.screen')

document.querySelector('.calc-buttons').addEventListener('click', function(event) { // using event delegation/bubbling here
  buttonClick(event.target.innerText)
})

function buttonClick(value) { 
  if (isNaN(parseInt(value))) { // MDN: 'isNaN()' determines if a value is NaN or not. Because coercion inside 'isNaN()' can be surprising, 'Number.isNaN()' may be preferable
    handleSymbol(value)
  } else {
    handleNumber(value)
  }
  rerender()
}

function handleNumber(value) {
  if (buffer === '0') {
    buffer = value
  } else {
    buffer += value
  }
}

function handleSymbol(value) {
  switch (value) { // using a 'switch' statement instead of an 'if...else' statement with a bunch of 'else if'
    case 'C': // i.e., if 'value' is 'C', do what's below
      buffer = '0'
      runningTotal = 0 // yes, this IS here for a reason. E.g., if user pressed '2' and then '+', runningTotal would have '2' stored in it, so if user decided at this point to press 'C' to clear, then runningTotal would need to be set back to 0
      previousOperator = null
      break
    case '=':
      if (previousOperator === null) {
        return // do nothing, and exit this function
      }
      flushOperation(parseInt(buffer))
      previousOperator = null
      buffer = '' + runningTotal // concatenates the empty string and 'runningTotal', thereby storing in 'buffer' 'runningTotal''s value but as a string
      runningTotal = 0
      break
    case '←':
      if (buffer.length === 1) {
        buffer = '0'
      } else {
        buffer = buffer.substring(0, buffer.length - 1)
      }
      break
    default:
      handleMath(value)
      break
  }
}

function handleMath(value) {
  const intBuffer = parseInt(buffer)
  if (runningTotal === 0) {
    runningTotal = intBuffer
  } else {
    flushOperation(intBuffer)
  }

  previousOperator = value

  buffer = '0' // this is the reason '0' appears each time '÷', '×', '-', or '+' is pressed
}

function flushOperation(intBuffer) { // this is a confusing name for this parameter, given that 'intBuffer' is the name of an actual variable and argument above.
  if (previousOperator === '+') {
    runningTotal += intBuffer
  } else if (previousOperator === '-') {
    runningTotal -= intBuffer
  } else if (previousOperator === '×') {
    runningTotal *= intBuffer
  } else {
    runningTotal /= intBuffer
  }
}

function rerender() {
  screen.innerText = buffer
}
```