🛠️ 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!