How to keep popup of Quasar Select component open?

Viewed 468

I'm working to create a geocoding component that allows a user to search for their address, using Quasar's <q-select /> component. I'm running in to one issue with the popup however.

After a user enter's the search query, I fetch the results from an API and the results are set to a reactive local state (which populates the select's options). Instead of the popup displaying though, it closes, and I have to click on the chevron icon twice for the popup to display the results.

This first image is what it looks like when I first click in to the input.

Screenshot 1

The second image shows what happens after entering a query. The data is fetched, options are set, and the popup closes.

Screenshot 2

The third image shows the select after clicking on the chevron icon twice.

Screenshot 3

How do I programmatically show the popup, so that once the results are fetched, the popup is displayed correctly?

Edit: Created a working repro here.

<template>
  <q-select
    ref="geolocateRef"
    v-model="state.location"
    :options="state.locations"
    :loading="state.loadingResults"
    clear-icon="clear"
    dropdown-icon="expand_more"
    clearable
    outlined
    :use-input="!state.location"
    dense
    label="Location (optional)"
    @clear="state.locations = undefined"
    @input-value="fetchOptions">
      <template #prepend>
        <q-icon name="place " />
      </template>
      <template #no-option>
        <q-item>
          <q-item-section class="text-grey">
            No results
          </q-item-section>
        </q-item>
      </template>
    </q-select>
</template>

<script lang='ts' setup>
  import { reactive } from 'vue';
  import { debounce, QSelect } from 'quasar';
  import { fetchGeocodeResults } from '@/services';

  const state = reactive({
    location: undefined as string | undefined,
    locations: undefined,
    loadingResults: false,
    geolocateRef: null as QSelect | null,
  });

  const fetchOptions = debounce(async (value: string) => {
    if (value) {
      state.loadingResults = true;
      const results = await fetchGeocodeResults(value);

      state.locations = results.items.map(item => ({
        label: item.title,
        value: JSON.stringify(item.position),
      }));

      state.loadingResults = false;
      state.geolocateRef?.showPopup(); // doesn't work?
    }
  }, 500);
</script>
1 Answers

I'd also posted this question over in the Quasar Github discussions, and someone posted a brilliant solution.

<template>
  <q-select
    v-model="state.location"
    :use-input="!state.location"
    input-debounce="500"
    label="Location (optional)"
    :options="options"
    dense
    clear-icon="bi-x"
    dropdown-icon="bi-chevron-down"
    clearable
    outlined
    @filter="fetchOptions">
    <template #prepend>
      <q-icon name="bi-geo-alt" />
    </template>
    <template #no-option>
      <q-item>
        <q-item-section class="text-grey">
          No results
        </q-item-section>
      </q-item>
    </template>
  </q-select>
</template>

<script lang='ts' setup>
  import { reactive, ref } from 'vue';
  import { QSelect } from 'quasar';
  import { fetchGeocodeResults } from '@/services';

  interface Result {
    position: {
      lat: number;
      lng: number;
    }
    title: string;
  }

  const state = reactive({
    ...other unrelated state,
    location: undefined as string | undefined,
  });

  const options = ref([]);

  const fetchOptions = async (val: string, update) => {
    if (val === '') {
      update();
      return;
    }

    const needle = val.toLowerCase();
    const results = await fetchGeocodeResults(needle);

    options.value = results.items.map((item: Result) => ({
      label: item.title,
      value: JSON.stringify(item.position),
    }));

    update();
  };
</script>

Related