Bugzilla – Attachment 156475 Details for
Bug 32474
Implement infinite scroll in vue-select
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 32474: Infinite scroll v-selects
Bug-32474-Infinite-scroll-v-selects.patch (text/plain), 9.84 KB, created by
Martin Renvoize
on 2023-10-03 07:14:00 UTC
(
hide
)
Description:
Bug 32474: Infinite scroll v-selects
Filename:
MIME Type:
Creator:
Martin Renvoize
Created:
2023-10-03 07:14:00 UTC
Size:
9.84 KB
patch
obsolete
>From 879ac69bbe018f7dd9b2052e82989e5aa99d553b Mon Sep 17 00:00:00 2001 >From: Matt Blenkinsop <matt.blenkinsop@ptfs-europe.com> >Date: Fri, 21 Apr 2023 10:57:43 +0000 >Subject: [PATCH] Bug 32474: Infinite scroll v-selects > >This patch is an example ajax based v-select. The v-select will load the first >20 items and then continue to load paginated sections of 20 items as the user >scrolls down. The v-select also offers ajax based searches (unpaginated) and >will return to 20 item pagination if the search is cleared. > >Currently the pagination just works with an Intersection Observer based on >scrolling - the main issue with this is that the size of the v-select window >changes every time new data is added to the list and this causes the scrollbar >to jump before resetting at the correct size. This can be a bit annoying, >especially when scrolling quickly. > >The only way round this will either be to paginate using buttons i.e. >(previous/next page) or to limit the data to 20 items at all times and >re-paginate when scrolling back up - interested to hear thoughts/suggestions >on this or whether anyone has a magic CSS fix that solves it ;) > >The new v-select is only in one location so far as a test - Agreement Licenses > >Test plan: >1) You will need to add multiple licenses in order to see the pagination, > attached is a script that will create 100 dummy licenses at a time if you > wish to use that >2) Once licenses are created, apply patch and run yarn build >3) Navigate to Agreements and click the New Agreement button >4) Scroll down to the Add new license option and click the button >5) The License select is the InfiniteScrollSelect and should display the > licenses you have added >6) Open the dropdown and 20 items will be listed >7) Scroll down and as you scroll, more items will be loaded (this can be seen > in the Network tab in developer tools) >8) Enter a search query and the results should reflect the search query >9) Delete the search query and the dropdown should return to the first 20 > paginated items and pagination will work again when scrolling >10) Try submitting the form with paginate/searched options and the form should > still work as intended > >Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org> >Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> >--- > .../vue/components/ERM/AgreementLicenses.vue | 22 +-- > .../vue/components/InfiniteScrollSelect.vue | 167 ++++++++++++++++++ > 2 files changed, 175 insertions(+), 14 deletions(-) > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/InfiniteScrollSelect.vue > >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/AgreementLicenses.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/AgreementLicenses.vue >index 665be1f2efa..a244ab14413 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/AgreementLicenses.vue >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/ERM/AgreementLicenses.vue >@@ -19,22 +19,12 @@ > <label :for="`license_id_${counter}`" class="required" > >{{ $__("License") }}:</label > > >- <v-select >+ <InfiniteScrollSelect > :id="`license_id_${counter}`" > v-model="agreement_license.license_id" >- label="name" >- :reduce="l => l.license_id" >- :options="licenses" >- > >- <template #search="{ attributes, events }"> >- <input >- :required="!agreement_license.license_id" >- class="vs__search" >- v-bind="attributes" >- v-on="events" >- /> >- </template> >- </v-select> >+ dataType="licenses" >+ :required="true" >+ /> > <span class="required">{{ $__("Required") }}</span> > </li> > <li> >@@ -102,6 +92,7 @@ > > <script> > import { APIClient } from "../../fetch/api-client.js" >+import InfiniteScrollSelect from "../InfiniteScrollSelect.vue" > > export default { > name: "AgreementLicenses", >@@ -139,5 +130,8 @@ export default { > this.agreement_licenses.splice(counter, 1) > }, > }, >+ components: { >+ InfiniteScrollSelect, >+ }, > } > </script> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/InfiniteScrollSelect.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/InfiniteScrollSelect.vue >new file mode 100644 >index 00000000000..83bd76cce51 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/InfiniteScrollSelect.vue >@@ -0,0 +1,167 @@ >+<template> >+ <v-select >+ v-bind:id="id" >+ v-model="model" >+ :label="queryProperty" >+ :options="search ? data : paginated" >+ :reduce="item => item[dataIdentifier]" >+ @open="onOpen" >+ @close="onClose" >+ @search="searchFilter($event)" >+ > >+ <template #list-footer> >+ <li v-show="hasNextPage && !this.search" ref="load"> >+ {{ $__("Loading more options...") }} >+ </li> >+ </template> >+ </v-select> >+</template> >+ >+<script> >+import { APIClient } from "../fetch/api-client.js" >+ >+export default { >+ created() { >+ this.fetchInitialData(this.dataType) >+ switch (this.dataType) { >+ case "vendors": >+ this.dataIdentifier = "id" >+ this.queryProperty = "name" >+ break >+ case "agreements": >+ this.dataIdentifier = "agreement_id" >+ this.queryProperty = "name" >+ break >+ case "licenses": >+ this.dataIdentifier = "license_id" >+ this.queryProperty = "name" >+ break >+ case "localPackages": >+ this.dataIdentifier = "package_id" >+ this.queryProperty = "name" >+ break >+ default: >+ break >+ } >+ }, >+ props: { >+ id: String, >+ dataType: String, >+ modelValue: Number, >+ required: Boolean, >+ }, >+ emits: ["update:modelValue"], >+ data() { >+ return { >+ observer: null, >+ dataIdentifier: null, >+ queryProperty: null, >+ limit: null, >+ search: "", >+ scrollPage: null, >+ data: [], >+ } >+ }, >+ computed: { >+ model: { >+ get() { >+ return this.modelValue >+ }, >+ set(value) { >+ this.$emit("update:modelValue", value) >+ }, >+ }, >+ filtered() { >+ return this.data.filter(item => >+ item[this.queryProperty].includes(this.search) >+ ) >+ }, >+ paginated() { >+ return this.filtered.slice(0, this.limit) >+ }, >+ hasNextPage() { >+ return this.paginated.length < this.filtered.length >+ }, >+ }, >+ mounted() { >+ this.observer = new IntersectionObserver(this.infiniteScroll) >+ }, >+ methods: { >+ async fetchInitialData(dataType) { >+ const client = APIClient.erm >+ await client[dataType] >+ .getAll("_page=1&_per_page=20&_match=contains") >+ .then( >+ items => { >+ this.data = items >+ this.search = "" >+ this.limit = 19 >+ this.scrollPage = 1 >+ }, >+ error => {} >+ ) >+ }, >+ async searchFilter(e) { >+ if (e) { >+ this.observer.disconnect() >+ this.data = [] >+ this.search = e >+ const client = APIClient.erm >+ await client[this.dataType] >+ .getAll( >+ `q={"me.${this.queryProperty}":{"like":"%${e}%"}}&_per_page=-1` >+ ) >+ .then( >+ items => { >+ this.data = items >+ }, >+ error => {} >+ ) >+ } else { >+ await this.fetchInitialData(this.dataType) >+ await this.resetSelect() >+ } >+ }, >+ async onOpen() { >+ await this.fetchInitialData(this.dataType) >+ if (this.hasNextPage) { >+ await this.$nextTick() >+ this.observer.observe(this.$refs.load) >+ } >+ }, >+ onClose() { >+ this.observer.disconnect() >+ this.search = "" >+ }, >+ async infiniteScroll([{ isIntersecting, target }]) { >+ if (isIntersecting) { >+ const ul = target.offsetParent >+ const scrollTop = target.offsetParent.scrollTop >+ this.limit += 20 >+ this.scrollPage++ >+ await this.$nextTick() >+ const client = APIClient.erm >+ await client[this.dataType] >+ .getAll( >+ `_page=${this.scrollPage}&_per_page=20&_match=contains` >+ ) >+ .then( >+ items => { >+ const existingData = [...this.data] >+ this.data = [...existingData, ...items] >+ }, >+ error => {} >+ ) >+ ul.scrollTop = scrollTop >+ } >+ }, >+ async resetSelect() { >+ if (this.hasNextPage) { >+ await this.$nextTick() >+ this.observer.observe(this.$refs.load) >+ } >+ }, >+ }, >+ name: "InfiniteScrollSelect", >+} >+</script> >-- >2.41.0
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 32474
:
144614
|
144719
|
144720
|
145143
|
145144
|
145154
|
150030
|
150031
|
150282
|
150283
|
150284
|
150287
|
150305
|
150308
|
150309
|
153110
|
153111
|
153112
|
153113
|
154094
|
156410
|
156470
|
156471
|
156472
|
156473
|
156474
|
156475
|
156476
|
156477
|
156478
|
156479
|
156481
|
156482
|
156483
|
156504
|
156505
|
159327
|
159328
|
159329
|
159330
|
159340
|
159343
|
159386
|
159389
|
159395
|
159397
|
159398
|
159399
|
159400
|
159401
|
159402
|
159403
|
159404
|
159405
|
159406
|
159407
|
161724