DEV Community

loading...
Cover image for Simple Calculator using Vue and Bootstrap 5
Codeply

Simple Calculator using Vue and Bootstrap 5

Carol Skelly
Top girl dev on StackOverflow. Creator of Codeply.com. Love frontend, but full-stack too! #vue #vuetify #bootstrap #css #javascript #react #net
・3 min read

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>
Enter fullscreen mode Exit fullscreen mode

Bootstrap 5 Vue Calculator


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>
Enter fullscreen mode Exit fullscreen mode
...
  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: '/'},
        ],
  },
...
Enter fullscreen mode Exit fullscreen mode

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
    },
  },
...
Enter fullscreen mode Exit fullscreen mode

Demo | Source


As always, you can play with the latest Bootstrap 5, and find more handy Vue snippets and examples on Codeply!

Thanks for reading!

Discussion (0)