The HTML Markup
Bootstrap 5 provides all the UI styling and responsive behavior. It's a great fit with Vue now that it's no longer dependent on jQuery. The Bootstrap grid row
& col-*
are used to responsively change the calculator width as screen size changes.
<div class="row">
<div class="col-xxl-2 col-lg-3 col-md-4 col-sm-6 mx-auto bg-dark rounded-3 shadow-sm p-3">
<input class="form-control form-control-lg text-success" v-model="calculator.displayValue" />
<!-- calculator number pad using grid -->
<div class="row g-0 text-center mt-2">
<div class="col-auto text-white">
<div class="row g-1 g-lg-1">
<div v-for="(key,i) in keypad" :key="i" class="ms-auto col-3 py-2">
<button class="btn btn-dark text-warning w-100" @click="processKey(key.value)">{{ key.label }}</button>
</div>
<div class="col-12 pt-2">
<button class="btn btn-dark border-secondary btn-lg text-warning w-100 fw-bold lead" @click="processKey('=')">=</button>
</div>
<div class="col-12">
<div v-if="errValue" class="alert alert-warning p-2 text-truncate small" role="alert">
{{ errValue }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
The Vue App Data
Calculator state is kept in it's own data calculator
object to track the displayValue
, firstOperand
and operator
. waitingForSecondOperand
let's us know when it's ready to perform a calculation.
The keypad
array is ordered by how I wanted the keys to render as columns inside the Bootstrap row
...
<div v-for="(key,i) in keypad" :key="i" class="ms-auto col-3 py-2">
<button class="btn btn-dark w-100" @click="processKey(key.value)">{{ key.label }}</button>
</div>
...
data () {
return {
calculator: {
displayValue: '0',
firstOperand: null,
waitingForSecondOperand: false,
operator: null,
},
errValue: null,
keypad: [
{label:'7', value: 7},
{label:'8', value: 8},
{label:'9', value: 9},
{label:'x', value: '*'},
{label:'4', value: 4},
{label:'5', value: 5},
{label:'6', value: 6},
{label:'+', value: '+'},
{label:'1', value: 1},
{label:'2', value: 2},
{label:'3', value: 3},
{label:'-', value: '-'},
{label:'AC', value: 'AC'},
{label:'.', value: '.'},
{label:'0', value: 0},
{label:'/', value: '/'},
],
},
...
The Vue App Methods
Of course the calculator actually needs to calculate so I define a series of methods to watch the keystrokes and perform basic math functions. Most of them should be self-explanatory. You will see that errValue
is used to store any errors, and resets upon keypress or clear.
When a key is pressed, processKey()
is called to determine which key and then call the appropriate function...
-
inputDigit()
- when any digit is clicked -
handleOperator()
- when any operator is clicked -
equalPressed()
- when the equal sign is clicked -
inputDecimal()
- to handle the decimal point (.) -
resetCalculator()
- to clear the calculator ('AC')
...
methods: {
processKey: function(val) {
this.errValue = null
switch (val){
case "AC": this.resetCalculator()
break;
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9: this.inputDigit(val)
break;
case "+": this.handleOperator("+")
break;
case "-": this.handleOperator("-")
break;
case "/": this.handleOperator("/")
break;
case "*": this.handleOperator("*")
break;
case "=": this.equalPressed();
break;
case ".": this.inputDecimal(".")
break;
default:
this.errValue = 'KEY ERROR: in default'
}
},
equalPressed() {
const { firstOperand, displayValue, operator } = this.calculator
try{
this.calculator.displayValue = this.calculate(firstOperand, displayValue, operator)
}
catch (e){
this.errValue = e
}
},
inputDigit(digit) {
const { displayValue, waitingForSecondOperand } = this.calculator
console.log(waitingForSecondOperand)
if (waitingForSecondOperand === true) {
this.calculator.displayValue = digit
this.calculator.waitingForSecondOperand = false
} else {
console.log(displayValue)
this.calculator.displayValue =
displayValue === '0' ? digit : displayValue + '' + digit
}
},
inputDecimal(dot) {
const { displayValue, waitingForSecondOperand } = this.calculator
if (waitingForSecondOperand === true) {
this.calculator.displayValue = '0.'
this.calculator.waitingForSecondOperand = false
return
}
// check for existing decimal
if (displayValue % 1 === 0) {
this.calculator.displayValue += dot
}
},
handleOperator(nextOperator) {
const { firstOperand, displayValue, operator, waitingForSecondOperand } = this.calculator
const inputValue = parseFloat(displayValue)
if (operator && waitingForSecondOperand) {
this.calculator.operator = nextOperator
return
}
if (firstOperand == null && !isNaN(inputValue)) {
this.calculator.firstOperand = inputValue
} else if (operator) {
const currentValue = firstOperand || 0
const result = this.calculate(currentValue, inputValue, operator)
this.calculator.displayValue = String(result)
this.calculator.firstOperand = result
}
this.calculator.waitingForSecondOperand = true
this.calculator.operator = nextOperator
},
calculate(firstOperand, secondOperand, operator) {
if (operator === '+') {
return firstOperand + secondOperand
} else if (operator === '-') {
return firstOperand - secondOperand
} else if (operator === '*') {
return firstOperand * secondOperand
} else if (operator === '/') {
if (secondOperand == 0){
this.errValue = 'ERROR: Cannot divide by 0'
}
else {
return firstOperand / secondOperand
}
}
return secondOperand
},
resetCalculator() {
this.calculator.displayValue = '0'
this.calculator.firstOperand = null
this.calculator.waitingForSecondOperand = false
this.calculator.operator = null
},
},
...
As always, you can play with the latest Bootstrap 5, and find more handy Vue snippets and examples on Codeply!
Thanks for reading!
Top comments (0)