Last active
June 13, 2025 17:00
-
-
Save blackcoper/17c9717e925fc76f3b565c0f7c135cfc to your computer and use it in GitHub Desktop.
vue-select feature Remote infinity scroll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <template> | |
| <vSelect | |
| ref="v_select_remote" | |
| :options="config.data" | |
| @open="onOpen" | |
| @close="onClose" | |
| append-to-body | |
| :searchable="true" | |
| @input="onSearch"> | |
| <template #list-footer class="vue-select-loader"> | |
| <div class="d-flex justify-content-center"> | |
| <li v-if="loading" class="vue-select-no-options"> | |
| <i class="fas fa-circle-notch fa-spin"></i> | |
| </li> | |
| <li v-show="hasNextPage" ref="load" class="loader"> | |
| | |
| </li> | |
| </div> | |
| </template> | |
| <template #no-options> | |
| <!-- check if still get data from remote dont show no data. --> | |
| <li v-if="loading" class="vue-select-no-options"></li> | |
| </template> | |
| </vSelect> | |
| </template> | |
| <script setup lang="ts"> | |
| import { computed, nextTick, onMounted, ref, useTemplateRef, watch } from 'vue' | |
| import vSelect from 'vue-select' | |
| const vSelectRemoteRef: any = useTemplateRef('v_select_remote') | |
| const { remote, excludeIds = [], filterKey, extraFilters } = defineProps(['remote','excludeIds','filterKey', 'extraFilters']) | |
| const observer = ref < any > (null) | |
| const initial = () => ({ | |
| currentPage: 0, | |
| perPage: 20, | |
| data: <Array<any>>[], | |
| search: '', | |
| total: 1, | |
| }) | |
| const config = ref(initial()) | |
| const load = ref(null) | |
| const loading = ref(false) | |
| watch(() => excludeIds, () => { | |
| if(vSelectRemoteRef && vSelectRemoteRef.value.open) { | |
| config.value = initial() | |
| onSearch(undefined); | |
| } | |
| }) | |
| watch(() => extraFilters, (id) => { | |
| if(vSelectRemoteRef && vSelectRemoteRef.value.open) { | |
| config.value = initial() | |
| onSearch(undefined); | |
| } | |
| }) | |
| const debounceQueryTimer = ref<number | null>(null) | |
| const onSearch = (query: any) => { | |
| loading.value = true | |
| if (debounceQueryTimer.value) { | |
| clearTimeout(debounceQueryTimer.value) | |
| } | |
| debounceQueryTimer.value = setTimeout(async () => { | |
| config.value = initial() | |
| config.value.search = (query?.target as HTMLInputElement)?.value || '' | |
| await getData(); | |
| }, 300) | |
| return; | |
| } | |
| const getData = async () => { | |
| const response = await remote({ | |
| page: config.value.currentPage + 1, | |
| count: config.value.perPage, | |
| excludeIds: excludeIds, | |
| filters: config.value.search && filterKey | |
| ? { | |
| [filterKey]: { | |
| op: 'like', | |
| value: config.value.search | |
| }, | |
| ...(extraFilters ? extraFilters : {}) | |
| } | |
| : { | |
| ...(extraFilters ? extraFilters : {}) | |
| }, | |
| }) | |
| const { items, page, total } = response.result | |
| config.value.currentPage = page | |
| config.value.total = total | |
| config.value.data = config.value.data.concat(items); | |
| loading.value = false | |
| } | |
| const onOpen = async () => { | |
| if (hasNextPage) { | |
| await nextTick() | |
| if(load.value) { | |
| observer.value.observe(load.value) | |
| } | |
| } | |
| } | |
| const onClose = async () => { | |
| observer.value.disconnect() | |
| } | |
| const infiniteScroll = async ([{ isIntersecting, target }]: any) => { | |
| if (isIntersecting) { | |
| const ul = target.offsetParent | |
| const scrollTop = target.offsetParent.scrollTop | |
| await nextTick() | |
| if(!loading.value) await getData(); | |
| ul.scrollTop = scrollTop + (config.value.currentPage > 1 ? 12 : 0) | |
| } | |
| }; | |
| const hasNextPage = computed(() => { | |
| return config.value.data.length < config.value.total | |
| }) | |
| onMounted(() => { | |
| observer.value = new IntersectionObserver(infiniteScroll) | |
| }) | |
| </script> |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage in your view:
in your script:
const selectedRole = ref<any>(null)in service axios:
in your return api must like this format: