Categories
Quasar VueJS

How to create a Custom Select Component in Quasar V2 using <script setup> SFC

In this post we are going to wrap Quasar V2 q-select in a custom base component we can reuse. The q-select has many props and options and sometime we want some of these props to be available by default for all our select components. Same goes for the styling, sometimes we require our q-select’s to have the same look and feel or wrapped in some custom HTML that has a common look and feel across our website or App.

For such cases a custom Select Component is quite handy. In a similar manner we can wrap other components like q-input, q-uploader etc as well if required.

Below is the sample code for creating a custom Select Component wrapping the quasar’s q-select. Please note we have used Vue 3′ script setup SFC API in this and it will not work if you are using Vue 2 or Quasar V1.

<template>
  <div>
    <q-select
      :options="arrMatchedOptions"
      use-input
      outlined
      dense
      hide-selected
      hide-bottom-space
      fill-input
      map-options
      input-debounce="0"
      :option-value="strOptionValue"
      :option-label="strOptionLabel"
      option-disable="inactive"
      :label="strLabel"
      emit-value
      clearable
      :modelValue="modelValue"
      @update:model-value="fnEmit"
      :loading="blnLoading"
      :rules="arrRules"
      :disable="blnDisabled"
      @filter="fnFilter"
    ></q-select>
  </div>
</template>

<script setup>
import { computed, onMounted, ref } from "vue"
import { myUtils } from '@/boot/utils'

const props = defineProps({
  modelValue: { required: true },
  strLabel: {
    type: String,
    required: true
  },
  strOptionLabel: {
    type: String,
    required: false,
    default: 'name'
  },
  strOptionValue: {
    type: String,
    required: false,
    default: 'id'
  },
  arrOptions: {
    type: Array,
    required: true
  },
  blnLoading: {
    type: Boolean,
    default: false
  },
  blnRequired: {
    type: Boolean,
    default: false
  },
  blnDisabled: {
    type: Boolean,
    default: false
  }
})

const emit = defineEmits(['update:modelValue'])


const arrMatchedOptions = ref(JSON.parse(JSON.stringify(props.arrOptions)));

const arrRules = computed(() => {
  if (props.blnRequired) {
    return [myUtils.requiredSelectRule]
  }
  return []
}
);

onMounted(() => {
});


const fnFilter = (strVal, update) => {

  if (strVal === '') {
    update(() => {
      arrMatchedOptions.value = props.arrOptions
    })
    return
  }

  update(() => {
    const strNeedle = strVal.toLowerCase()
    arrMatchedOptions.value = props.arrOptions.filter(obj => {
      const strOptionLabel = obj[props.strOptionLabel]
      return strOptionLabel.toLowerCase().indexOf(strNeedle) > -1
    })
  })
};

const fnEmit = ($event) => {

  emit('update:modelValue', $event)
}

How to use in your parent component, below is an example code:

<template>
  <q-layout>
    <q-card>
      <q-card-section class="p20px">
        <div class="sectionTitle">
          <h2>Base Select Test</h2>
          <span class="after"></span>
        </div>

        <div class="mt20px row">
          <BaseSelect
            :arrOptions="arrGender"
            strLabel="Gender"
            strOptionLabel="name"
            strOptionValue="id"
            v-model="gender"
            hide-bottom-space
          ></BaseSelect>
        </div>
      </q-card-section>
    </q-card>
  </q-layout>
</template>

<script setup>
import { ref } from "vue"
import BaseSelect from "./components/BaseSelect.vue"

const arrGender = ref([
  { name: 'Male', id: 'm' },
  { name: 'Female', id: 'f' },
  { name: 'Other', id: 'o' },
]);

const gender = ref('')