From 854934d2f018be817ab25c973c130c0f28346d16 Mon Sep 17 00:00:00 2001 From: Dan Popescu Date: Sat, 25 Nov 2023 21:32:49 +0200 Subject: [PATCH] feat(post 003): add keep open / loading --- package.json | 4 +- pages/posts/post_003.md | 95 ++++++++++++++++++----- pnpm-lock.yaml | 162 ++++++++++++++++++++-------------------- 3 files changed, 161 insertions(+), 100 deletions(-) diff --git a/package.json b/package.json index fa29f9e..6359e70 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "dependencies": { "@vueuse/core": "^10.6.1", - "vue": "^3.3.8" + "vue": "^3.3.9" }, "devDependencies": { "@vue/eslint-config-airbnb": "^7.0.0", @@ -20,7 +20,7 @@ "postcss": "^8.4.31", "sass": "^1.69.5", "vite-plugin-vitepress-demo": "^2.2.0", - "vitepress": "1.0.0-rc.30" + "vitepress": "1.0.0-rc.31" }, "pnpm": { "peerDependencyRules": { diff --git a/pages/posts/post_003.md b/pages/posts/post_003.md index 91c23ed..4dd176d 100644 --- a/pages/posts/post_003.md +++ b/pages/posts/post_003.md @@ -19,6 +19,8 @@ In order to make a Quasar QSelect filterable you need a `@filter` function that Most of the time this requires an original (complete) list of options, a filtering function and a filtered list of options. +Also you may want to keep the options list opened while filtering (when you are using async filtering). + ## Solution You can create a composable `useFilteredSelect` that encapsulates and hides all the complexity. @@ -34,6 +36,17 @@ This composable: - the optional `props` - an `options` key with the filtered list of options - an `onFilter` event handler for QSelect + - a `loading` key with the loading state of the filter (see below) + +::: info Keeping the list of options open while filtering +If you want to keep the list of options open while filtering then pass `loading: true` in the `props`. + +In that case the returned reactive object will also include a `loading` key that will control the loading indicator on QSelect. +::: + +::: warning +The filtered `options` and `loading` status are shared by all QSelects using the same returned reactive object. +::: This composable will be combined with a filtering function. There are three pre-defined functions: - one that filters the options as string, case insensitive, match anywhere @@ -48,7 +61,7 @@ The search function generator `createMappedFilterFn` gets a configuration object ::: code-group ```ts [useFilteredSelect.ts] -import { shallowRef, unref, reactive, type MaybeRef, type ShallowRef } from 'vue'; +import { shallowRef, ref, unref, reactive, type MaybeRef, type ShallowRef, type Ref } from 'vue'; import type { QSelect, QSelectProps } from 'quasar'; type MaybePromise = T | Promise | PromiseLike; @@ -90,8 +103,10 @@ export function useFilteredSelect( ) { const getOptions = typeof optionsOrFn === 'function' ? optionsOrFn : () => optionsOrFn; const filteredOpts = shallowRef(typeof optionsOrFn === 'function' ? [] : optionsOrFn); + const loading = ref(false); + const keepOpen = props && props['loading'] === true; - const useProps: Pick | { options: ShallowRef, onFilter: QSelectProps[ 'onFilter' ]; } = { + const useProps: Pick & { options: ShallowRef, onFilter: QSelectProps[ 'onFilter' ], loading?: Ref; } = { ...props, options: filteredOpts, onFilter( @@ -99,19 +114,30 @@ export function useFilteredSelect( doneFn: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void, abortFn: () => void, ) { + if (keepOpen === true) { + doneFn(() => {}); + } + loading.value = true; + Promise.resolve(getOptions(search)) .then((options) => { const newOpts = filterFn(search, unref(options)); doneFn(() => { filteredOpts.value = newOpts; + loading.value = false; }, afterFn); }) .catch(() => { abortFn(); + loading.value = false; }); }, }; + if (keepOpen === true) { + useProps.loading = loading; + } + return reactive(useProps); } ``` @@ -125,7 +151,7 @@ export function useFilteredSelect( label="String filter" v-bind="filteredSelectProps1" > -