🛠️ Calculator: Refactoring (Part 2)

Hey ,

I'm thrilled to help you learn JavaScript. Unfortunately, you've landed on a page where you cannot access with your current purchase.

Please upgrade (use this link) access this content.

I'm super eager to help you learn more!

🛠️ Calculator: Refactoring (Part 2)

Here’s what we wrote for the event listener so far:

calculatorButtonsDiv.addEventListener('click', event => {
  // ...

  // Release operator pressed state
  const operatorKeys = [...calculatorButtonsDiv.children]
    .filter(button => button.dataset.buttonType === 'operator')
  operatorKeys.forEach(button => button.classList.remove('is-pressed'))

  if (buttonType === 'clear') {
    // ...
  }

  if (buttonType !== 'clear') {
    // ...
  }

  if (buttonType === 'number') {
    // ...
  }

  if (buttonType === 'decimal') {
    // ...
  }

  if (buttonType === 'operator') {
    // ...
  }

  if (buttonType === 'equal') {
    // ...
  }

  calculator.dataset.previousButtonType = buttonType
})

We can make the event listener code easier to read by breaking each “button” part into a dedicated function.

calculatorButtonsDiv.addEventListener('click', event => {
  // ...
  if (buttonType !== 'clear') { /* ...*/ }

  if (buttonType === 'clear') handleClearkey()
  if (buttonType === 'number') handleNumberKeys()
  if (buttonType === 'decimal') handleDecimalKey()
  if (buttonType === 'operator') handleOperatorKey()
  if (buttonType === 'equal') handleEqualKey()

  calculator.dataset.previousButtonType = buttonType
})

Ideally, we want to use early returns if there are many if statements. An early return tells us the rest of the code won’t be executed. It makes code easier to understand.

// Ideal, but doesn't work.
calculatorButtonsDiv.addEventListener('click', event => {
  // ...
  if (buttonType !== 'clear') { /* ...*/ }

  if (buttonType === 'clear') return handleClearkey()
  if (buttonType === 'number') return handleNumberKeys()
  if (buttonType === 'decimal') return handleDecimalKey()
  if (buttonType === 'operator') return handleOperatorKey()
  if (buttonType === 'equal') return handleEqualKey()

  calculator.dataset.previousButtonType = buttonType
})

We cannot use early returns because we need to assign buttonType as previousButtonType at the end of the event listener.

This is where a switch statement is perfect. With switch, we can get the clarity we want, and still execute the final line of code.

calculatorButtonsDiv.addEventListener('click', event => {
  // ...

  if (buttonType !== 'clear') { /* ...*/ }

  switch (buttonType) {
    case 'clear':
      handleClearKey()
      break
    case 'number':
      handleNumberKeys()
      break
    case 'decimal':
      handleDecimalKey()
      break
    case 'operator':
      handleOperatorKeys()
      break
    case 'equal':
      handleEqualKey()
      break
  }

  calculator.dataset.previousButtonType = buttonType
})

We can make the code terser by combining case, the function we want to call, and break in a single line.

calculatorButtonsDiv.addEventListener('click', event => {
  // ...

  if (buttonType !== 'clear') { /* ...*/ }

  switch (buttonType) {
    case 'clear': handleClearKey(); break
    case 'number': handleNumberKeys(); break
    case 'decimal': handleDecimalKey(); break
    case 'operator': handleOperatorKeys(); break
    case 'equal': handleEqualKey(); break
  }

  calculator.dataset.previousButtonType = buttonType
})

Function to handle clear key

We’ll create this function by copying everything from the if (buttonType === 'clear').

function handleClearKey () {
  display.textContent = '0'
  button.textContent = 'AC'
  if (previousButtonType === 'clear') {
    delete calculator.dataset.firstValue
    delete calculator.dataset.operator
    delete calculator.dataset.modifierValue
  }
}

We can see we need calculator and button to run this function. We can pass these two variables into the function.

function handleClearKey (calculator, button) {
  const { previousButtonType } = calculator.dataset

  if (buttonType === 'clear') {
    display.textContent = '0'
    button.textContent = 'AC'
    if (previousButtonType === 'clear') {
      delete calculator.dataset.firstValue
      delete calculator.dataset.operator
      delete calculator.dataset.modifierValue
    }
  }
}

calculatorButtonsDiv.addEventListener('click', event => {
  // ...
  switch (buttonType) {
    case 'clear': handleClearKey(calculator, button); break
  }
  // ...
})

Function to handle number keys

We can create a function called handleNumberKeys. We’ll copy everything from if (buttonType === 'number') into this function.

function handleNumberKeys () {
 if (displayValue === '0') {
    display.textContent = key
  } else {
    display.textContent = displayValue + key
  }

  if (previousButtonType === 'operator') {
    display.textContent = key
  }

  if (previousButtonType === 'equal') {
    resetCalculator()
    display.textContent = key
  }
}

We need key, previousButtonType and displayValue in this function.

We can get key from dutton.dataset and previousButtonType from calculator. We’ll pass these two variables in.

function handleNumberKeys (calculator, button) {
  const { previousButtonType } = calculator.dataset
  const { key } = button.dataset
  // ...
}

We can get displayValue from getDisplayValue.

function handleNumberKeys (calculator, button) {
  const displayValue = getDisplayValue()
  // ...
}

We’ll use handleNumberKeys like this:

calculatorButtonsDiv.addEventListener('click', event => {
  // ...
  switch (buttonType) {
    // ...
    case 'number': handleNumberKeys(calculator, button); break
  }
  // ...
})

Function to handle decimal key

Again, we start by copying everything we wrote from if (buttonType === 'decimal') into a new function. We’ll call this function handleDecimalKey

function handleDecimalKey () {
  if (!displayValue.includes('.')) {
    display.textContent = displayValue + '.'
  }

  if (previousButtonType === 'equal') {
    resetCalculator()
    display.textContent = '0.'
  }

  if (previousButtonType === 'operator') {
    display.textContent = '0.'
  }
}

We can see handleDecimalKey needs displayValue and previousButtonType.

  • displayValue can be found from getDisplayValue
  • previousButtonType can be found in calculator.dataset.

So we need to pass calculator into handleDecimalKey.

function handleDecimalKey (calculator) {
  const displayValue = getDisplayValue()
  const { previousButtonType } = calculator.dataset

  if (!displayValue.includes('.')) {
    display.textContent = displayValue + '.'
  }

  if (previousButtonType === 'equal') {
    resetCalculator()
    display.textContent = '0.'
  }

  if (previousButtonType === 'operator') {
    display.textContent = '0.'
  }
}

Using handleDecimalKey:

calculatorButtonsDiv.addEventListener('click', event => {
  // ...
  switch (buttonType) {
    // ...
    case 'decimal': handleDecimalKey(calculator); break
  }
  // ...
})

Function to handle operator keys

Again, we will copy everything we wrote in if (buttonType === 'operator') into a new function. We’ll call it handleOperatorKeys.

function handleOperatorKeys () {
  button.classList.add('is-pressed')

  const firstValue = calculator.dataset.firstValue
  const operator = calculator.dataset.operator
  const secondValue = displayValue

  if (
    previousButtonType !== 'operator' &&
    previousButtonType !== 'equal' &&
    firstValue &&
    operator
  ) {
    const result = calculate(firstValue, operator, secondValue)
    display.textContent = result
    calculator.dataset.firstValue = result
  } else {
    calculator.dataset.firstValue = displayValue
  }

  calculator.dataset.operator = button.dataset.key
}

We can see we calculator, button, and displayValue in this function.

We will pass calculator and button into the function.

function handleOperatorKeys (calculator, button) {
  const { previousButtonType, firstvalue, operator } = calculator.dataset

  // ...
}

We will get displayValue from getDisplayValue.

function handleOperatorKeys (calculator, button) {
  const displayValue = getDisplayValue()
  // ...
}

Using handleOperatorKeys:

calculatorButtonsDiv.addEventListener('click', event => {
  // ...
  switch (buttonType) {
    // ...
    case 'operator': handleOperatorKeys(calculator, button); break
  }
  // ...
})

Function to handle Equal key

Finally, we will create a function to handle the equal key. We will also start by copying everything we wrote from if (buttonType === 'equal') into the new function.

function handleEqualKey () {
  const firstValue = calculator.dataset.firstValue
  const operator = calculator.dataset.operator
  const modifierValue = calculator.dataset.modifierValue
  const secondValue = modifierValue || displayValue

  if (firstValue && operator) {
    const result = calculate(firstValue, operator, secondValue)
    display.textContent = result
    calculator.dataset.firstValue = result
    calculator.dataset.modifierValue = secondValue
  } else {
    display.textContent = parseFloat(displayValue) * 1
  }
}

We can see we need calculator and displayValue in this function. You should know the drill by now :)

function handleEqualKey (calculator) {
  const displayValue = getDisplayValue()
  const { firstValue, operator, modifierValue } = calculator.dataset

  // ...
}

Using handleEqualKey

calculatorButtonsDiv.addEventListener('click', event => {
  // ...
  switch (buttonType) {
    // ...
    case 'equal': handleEqualKey(calculator, button); break
  }
  // ...
})

That’s it!