Skip to content

How to make 2 Quasar toggle button groups mutually exclusive?

We have Vue.js app that uses Quasar component framework.

The screen shots look as following:

Incorrect case:

enter image description here

Correct case:

enter image description here

I need the only one toggle button group should be active, either percents or standard amounts. Pay attention there’s an array of toggle button groups.

The code I wrote produces the incorrect case. It looks as following:

<template>
  <q-dialog v-model="show" no-backdrop-dismiss full-width>
    <q-card>
      <q-card-section class="row items-center">
        <div class="text-h6 tip-color">Tip</div>
        <q-space />
        <q-btn icon="close" flat round dense v-close-popup @click="cancel" />
      </q-card-section>
      <q-card-section style="max-height: 50vh" class="scroll set-border">
        <div class="q-gutter-md">
          <q-card v-for="({ master }, index) in items" :key="master.id">
            <div class="row">
              <div class="col-3 flex justify-center items-center">
                <Avatar
                  :src="master.employer_avatar"
                  :size="50"
                  no-default-spinner
                />
                <span class="q-ml-md text-caption text-secondary">
                  {{ getEmployerName(master) }}
                </span>
              </div>
              <div class="col">
                <q-card-section>
                  <q-btn-toggle
                    v-model="togglePercentPayments[index]"
                    toggle-color="primary"
                    :options="percentPayments"
                    spread
                    @input="getFullTip"
                  />
                </q-card-section>
                <q-card-section>
                  <q-btn-toggle
                    v-model="toggleStandardPayments[index]"
                    toggle-color="primary"
                    :options="standardPayments"
                    spread
                    @input="getFullTip"
                  />
                </q-card-section>
              </div>
            </div>
          </q-card>
        </div>
      </q-card-section>
      <q-card-section>
        <div><strong>Summary:</strong> {{ total }}</div>
        <div>
          <strong>Tip:</strong> {{ getFullTip() ? getFullTip() : 0 }}
        </div>
      </q-card-section>
      <q-card-actions align="right" class="text-primary q-pt-none">
        <q-btn flat label="Cancel" @click="cancel" />
        <q-btn flat label="Pay" @click="pay" />
      </q-card-actions>
    </q-card>
  </q-dialog>
</template>

<script>
const percentPayments = [
  { label: '5%', value: 5 },
  { label: '10%', value: 10 },
  { label: '15%', value: 15 },
]

const standardPayments = [
  { label: '100', value: 100 },
  { label: '200', value: 200 },
  { label: '500', value: 500 },
]

export default {
  props: {
    showModal: {
      type: Boolean,
      default: false,
    },
    items: {
      type: Array,
      default: () => [],
    },
    total: {
      type: Number,
      default: 0,
    },
  },
  data: function() {
    return {
      show: false,
      togglePercentPayments: new Array(this.items.length).fill(null),
      toggleStandardPayments: new Array(this.items.length).fill(null),
      percentPayments,
      standardPayments,
    }
  },
  watch: {
    showModal(newVal) {
      this.show = newVal
    },
  },
  methods: {
    getEmployerName(master) {
      return `${master?.first_name ?? ''} ${master?.last_name[0] ?? ''}.`
    },
    getPercentage(total, percent) {
      return (total / 100) * percent
    },
    getFullTip() {
      let standardSum = 0
      for (const standardPayment of this.toggleStandardPayments) {
        standardSum += standardPayment
      }

      let percentageSum = 0
      for (const percentagePayment of this.togglePercentPayments) {
        const percent = this.getPercentage(this.total, percentagePayment)
        percentageSum += percent
      }

      return standardSum + percentageSum
    },
    pay() {
      this.$emit('pay', this.getFullTip())
      this.clear()
    },
    cancel() {
      this.$emit('cancel')
      this.clear()
    },
    clear() {
      this.togglePercentPayments = new Array(this.items.length).fill(null)
      this.toggleStandardPayments = new Array(this.items.length).fill(null)
    },
  },
}
</script>

<style scoped>
.set-border {
  border: 1px solid gainsboro;
}
.tip-color {
  color: rgb(4, 171, 171);
}
</style>

How to make 2 Quasar toggle button groups mutually exclusive?

Answer

Here’s the solution if anyone needs. The guys from Quasar tech support helped me.

<template>
  <q-dialog v-model="show" no-backdrop-dismiss full-width>
    <q-card>
      <q-card-section class="row items-center">
        <div class="text-h6 tip-color">Tip</div>
        <q-space />
        <q-btn icon="close" flat round dense v-close-popup @click="cancel" />
      </q-card-section>
      <q-card-section style="max-height: 50vh" class="scroll set-border">
        <div class="q-gutter-md">
          <q-card v-for="({ master }, index) in items" :key="master.id">
            <div class="row">
              <div class="col-3 flex justify-center items-center">
                <Avatar
                  :src="master.employer_avatar"
                  :size="50"
                  no-default-spinner
                />
                <span class="q-ml-md text-caption text-secondary">
                  {{ getEmployerName(master) }}
                </span>
              </div>
              <div class="col">
                <q-card-section>
                  <q-btn-toggle
                    v-model="togglePayments[index]"
                    toggle-color="primary"
                    :options="percentPayments"
                    spread
                    @input="getTotalTip"
                  />
                </q-card-section>
                <q-card-section>
                  <q-btn-toggle
                    v-model="togglePayments[index]"
                    toggle-color="primary"
                    :options="standardPayments"
                    spread
                    @input="getTotalTip"
                  />
                </q-card-section>
              </div>
            </div>
          </q-card>
        </div>
      </q-card-section>
      <q-card-section>
        <div><strong>Summary:</strong> {{ total }}</div>
        <div>
          <strong>Tip:</strong> {{ getTotalTip() ? getTotalTip() : 0 }}
        </div>
      </q-card-section>
      <q-card-actions align="right" class="text-primary q-pt-none">
        <q-btn flat label="Cancel" @click="cancel" />
        <q-btn flat label="Pay" @click="pay" />
      </q-card-actions>
    </q-card>
  </q-dialog>
</template>

<script>
import { axiosInstance } from 'src/boot/axios'
import { api } from 'src/api'

const percentPayments = [
  { label: '5%', value: 0.05 },
  { label: '10%', value: 0.1 },
  { label: '15%', value: 0.2 },
]

const standardPayments = [
  { label: '100', value: 100 },
  { label: '200', value: 200 },
  { label: '500', value: 500 },
]

export default {
  props: {
    showModal: {
      type: Boolean,
      default: false,
    },
    items: {
      type: Array,
      default: () => [],
    },
    total: {
      type: Number,
      default: 0,
    },
    orderId: {
      type: Number,
      required: true,
    },
  },
  data: function() {
    return {
      show: false,
      togglePayments: new Array(this.items.length).fill(0.0),
      percentPayments,
      standardPayments,
    }
  },
  watch: {
    showModal(newVal) {
      this.show = newVal
    },
  },
  methods: {
    getEmployerName(master) {
      return `${master?.first_name ?? ''} ${master?.last_name[0] ?? ''}.`
    },
    getValue(value, amount) {
      const floatValue = Number.parseFloat(value)
      if (floatValue > 1) {
        return floatValue
      }
      return floatValue * amount
    },
    getTotalTip() {
      let totalTip = 0
      for (const tip of this.togglePayments) {
        totalTip += this.getValue(tip, this.total)
      }

      return totalTip
    },
    async pay() {
      try {
        const response = await axiosInstance.post(api.payments.all, {
          reason_type: 'order',
          reason_id: this.orderId,
          amount: this.total + this.getTotalTip(),
        })
        this.$store.commit('booking/setCreatedPayment', response.data.data)
        this.$router.push({ name: 'Payment' })
      } catch (e) {
        console.info(e)
      }
    },
    cancel() {
      this.$emit('cancel')
      this.clear()
    },
    clear() {
      this.togglePercentPayments = new Array(this.items.length).fill(0.0)
      this.toggleStandardPayments = new Array(this.items.length).fill(0.0)
    },
  },
}
</script>

<style scoped>
.set-border {
  border: 1px solid gainsboro;
}
.tip-color {
  color: rgb(4, 171, 171);
}
</style>