Quasar: [Suggestion] Autocomplete with select options

Created on 29 Sep 2017  路  8Comments  路  Source: quasarframework/quasar

It would be good if there was an implementation of autocomplete with a limited group of options, a kind of q-select with autocomplete.
Is there a way to do this already?

Most helpful comment

If we get data like this from the server:

"sellers": [
  {
    "id": "e1e7519f-fe04-4cf8-96d9-fbbbccefe52f",
    "name": "Indonesia Test Seller"
  },
  {
    "id": "b8c240e7-6afe-4274-93ec-7d833657fe5b",
    "name": "Taiwan Test Seller 1"
  },
  {
    "id": "98bfa6a5-5558-4d21-9c6c-db5d6267acc4",
    "name": "Taiwan Test Seller 2"
  }
]

and if there are thousands Sellers, then how do we search them partially?
Let's say: fetch data by search term + limit by 10.

  • "Limit by 10" can be a constant.
  • But "search term" should be a user input.

If I'm using QAutocomplete, I can do server-side filter, e.g. by seller's name.
But I don't know how to keep the seller's id in the client-side.

If I'm using QSelect, I can only do client-side filter.
Should I use QSearch separately to do server-side filter?

All 8 comments

What do you mean "limited group of options"? You specify autocomplete options yourself. Also, q-select has filter option, take a look at it.

Hi,

Not sure what you mean. You can currently:

  • use a QSelect -- you got options to fill in
  • you can use a QInput/QSearch with QAutocomplete

Am I missing something?

I believe he means QSelect with filter option, but fetching options asynchronously... is it possible in any way? I'm kinda needing this too...

Example:
I have this "Client" field and i want to fetch them by their name, but i need to set it's value in the field... using autocomplete, after i select the option i want, instead showing (Client A, Client B), it will show their id (1, 2)...

If we get data like this from the server:

"sellers": [
  {
    "id": "e1e7519f-fe04-4cf8-96d9-fbbbccefe52f",
    "name": "Indonesia Test Seller"
  },
  {
    "id": "b8c240e7-6afe-4274-93ec-7d833657fe5b",
    "name": "Taiwan Test Seller 1"
  },
  {
    "id": "98bfa6a5-5558-4d21-9c6c-db5d6267acc4",
    "name": "Taiwan Test Seller 2"
  }
]

and if there are thousands Sellers, then how do we search them partially?
Let's say: fetch data by search term + limit by 10.

  • "Limit by 10" can be a constant.
  • But "search term" should be a user input.

If I'm using QAutocomplete, I can do server-side filter, e.g. by seller's name.
But I don't know how to keep the seller's id in the client-side.

If I'm using QSelect, I can only do client-side filter.
Should I use QSearch separately to do server-side filter?

That's a workaround I am using while this function is not supported out of the box...

1) Create a new component (mb-auto-complete) using QChipsInput and QAutocomplete:

<template>
  <div>
    <q-chips-input v-model="chipList"
                   :float-label="floatLabel"
                   :disable="disable"
                   @remove="remove"
                   @duplicate="duplicate"
                   @input="input">
      <q-autocomplete value-field="label"
                      :min-characters="3"
                      :max-results="30"
                      @search="handleSearch"
                      @selected="selected" />
    </q-chips-input>
  </div>

</template>

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

<script>
import { QChipsInput, QAutocomplete } from 'quasar'
import _ from 'lodash'

export default {
  components: {
    QChipsInput,
    QAutocomplete
  },

  props: {
    apiUrl: String,
    floatLabel: String,
    disable: Boolean,
    selectedList: {}
  },

  data: function () {
    return {
      chipList: [],
      isDuplicated: false,
      isArray: false,
      selectedListTemp: []
    }
  },

  watch: {
    selectedList: function () {
      // on change update chips (needed in case the initial kvp is comming from some api)
      this.selectedListTemp = _.cloneDeep(this.selectedList)
      this.updateChips()
    }
  },

  created () {
    this.selectedListTemp = _.cloneDeep(this.selectedList)
    if (Array.isArray(this.selectedList)) this.isArray = true
    else this.isArray = false
    this.updateChips()
  },

  methods: {
    selected (item, keyboard) {
      // Insert kvp
      if (!keyboard && !this.isDuplicated) {
        if (this.isArray) this.selectedListTemp.push(item)
        else this.selectedListTemp = item
        this.updateChips()
        this.$emit('update:selectedList', this.selectedListTemp)
      }
      this.isDuplicated = false
    },

    duplicate (label) {
      // flag chip already exist
      this.isDuplicated = true
    },

    remove (e) {
      // also remove kvp
      if (this.isArray) this.selectedListTemp.splice(e.index, 1)
      else this.selectedListTemp = {}
      this.$emit('update:selectedList', this.selectedListTemp)
    },

    input (e) {
      // remove chip after enter. The chips are inserted on updateChips method (that's for not accept chips that does not exist in filter)
      if (this.isArray) this.chipList.splice(this.selectedListTemp.length, 1)
      else this.selectedListTemp = {}
      this.updateChips()
    },

    handleSearch (term, done) {
      this.$axios
        .get(this.apiUrl + '/' + term)
        .then(response => {
          done(response.data)
        })
        .catch(() => {
          done([])
        })
    },

    updateChips () {
      this.chipList = []
      if (this.isArray) {
        for (let i = 0, size = this.selectedListTemp.length; i < size; i++) {
          this.chipList.push(this.selectedListTemp[i].label)
        }
      } else if (this.selectedListTemp.label) this.chipList.push(this.selectedListTemp.label)
    }
  }
}
</script>

2) Call the new component passing the api url and the result variable, here 'client'.

Note 1) 'client' can either be an object or an array. If it is declared as an object the user will be able to select only one chip. If it's an array the user can select as many chips he wants.

Note 2) The API returns an array of type: [ {value: 1, label: 'Client A'}, {value: 2, label: 'Client B'}, ... ]

Note 3) The component adds /blabla to the API call, where "blabla" is the text entered by the user in the autocomplete. So my backend API is using "blabla" to filter the results.

<template>
  <div>
    <mb-auto-complete apiUrl="client/kvp"
                      floatLabel="Client name"
                      :selectedList.sync="client"
                      :disable="false" />
  </div>
</template>

<script>
import mbAutoComplete from './../../components/mbAutoComplete'

export default {
  components: {
    mbAutoComplete
  },

  data: function () {
    return {
      client: {value: 164, label: 'Client 1'}
      // client: [{value: 164, label: 'Client 1'}]
    }
  }
}
</script>

Well then, QAutocomplete is the best thing I can use.

If we get data like this from the server:

"sellers": [
  {
    "id": "e1e7519f-fe04-4cf8-96d9-fbbbccefe52f",
    "name": "Indonesia Test Seller"
  },
  {
    "id": "b8c240e7-6afe-4274-93ec-7d833657fe5b",
    "name": "Taiwan Test Seller 1"
  },
  {
    "id": "98bfa6a5-5558-4d21-9c6c-db5d6267acc4",
    "name": "Taiwan Test Seller 2"
  }
]

and if there are thousands Sellers, then how do we search them partially?
Let's say: fetch data by search term + limit by 10.

* "Limit by 10" can be a constant.

* But "search term" should be a user input.

If I'm using QAutocomplete, I can do server-side filter, e.g. by seller's name.
But I don't know how to keep the seller's id in the client-side.

If I'm using QSelect, I can only do client-side filter.
Should I use QSearch separately to do server-side filter?

@yaliv i am searching for the same solution. q-select with server side data feteched asynchronously.
q-autocomplete does the thing but it is not possible to bind it to q-select.

Have you found a solution for this?

@NicoP-S
I practically don't use QSelect at all. I just keep the seller's id manually (or array of them for multi-select). For example, I created this component: SearchableMultiselect.vue.zip. For the server-side filtering, I just forward the search event to the parent component, so I can do anything there. The most important thing is the done function should be called with an array of objects containing id and value keys.

Usage:

  • template:
<SearchableMultiselect
  ref="recipients"
  v-model="form.recipients"
  :debounce="1000"
  :min-characters="2"
  :max-results="10"
  color="info"
  chips-bg-color="brown-5"
  @search="findSellers"
/>
  • method:
async findSellers(terms, done) {
  const queryResult = await this.$apollo.query({
    client: 'product',
    query: FIND_SELLERS,
    variables: {
      search: terms
    }
  })

  const sellers = queryResult.data.allSellers.sellers.map(seller => ({
    value: seller.name,
    label: seller.name,
    sublabel: seller.city + ', ' + seller.country,
    id: seller.id
  }))

  done(sellers)
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

adwidianjaya picture adwidianjaya  路  3Comments

nueko picture nueko  路  3Comments

Bangood picture Bangood  路  3Comments

victorborgaco picture victorborgaco  路  3Comments

danikane picture danikane  路  3Comments