Skip to content
This repository was archived by the owner on Nov 5, 2023. It is now read-only.

Commit b7e2058

Browse files
authored
feat: support debouncing calls to page provider (#175)
1 parent e3f8910 commit b7e2058

File tree

4 files changed

+53
-30
lines changed

4 files changed

+53
-30
lines changed

README.md

+24-23
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
This is a reusable component for Vue 3 that renders a list with a huge number of
44
items (e.g. 1000+ items) as a grid in a performant way.
55

6-
* [Demo][demo]
7-
* [NPM Package][npm]
6+
- [Demo][demo]
7+
- [NPM Package][npm]
88

99
## Features
1010

@@ -18,8 +18,8 @@ items (e.g. 1000+ items) as a grid in a performant way.
1818

1919
## Code Examples
2020

21-
* [As an ES module (with a bundler)][esm]
22-
* [As an Universal Module Definition (no bundler)][umd]
21+
- [As an ES module (with a bundler)][esm]
22+
- [As an Universal Module Definition (no bundler)][umd]
2323

2424
## Install
2525

@@ -29,20 +29,22 @@ npm install vue-virtual-scroll-grid
2929

3030
## Available Props
3131

32-
| Name | Description | Type | Validation |
33-
|----------------|---------------------------------------------------------------------------|----------------------------------------------------------------|---------------------------------------------------------------------------------|
34-
| `length` | The number of items in the list | `number` | Required, an integer greater than or equal to 0 |
35-
| `pageProvider` | The callback that returns a page of items as a promise | `(pageNumber: number, pageSize: number) => Promise<unknown[]>` | Required |
36-
| `pageSize` | The number of items in a page from the item provider (e.g. a backend API) | `number` | Required, an integer greater than or equal to 1 |
37-
| `scrollTo` | Scroll to a specific item by index | `number` | Optional, an integer from 0 to the `length` prop - 1 |
32+
| Name | Description | Type | Validation |
33+
| -------------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------------- | ---------------------------------------------------- |
34+
| `length` | The number of items in the list | `number` | Required, an integer greater than or equal to 0 |
35+
| `pageProvider` | The callback that returns a page of items as a promise | `(pageNumber: number, pageSize: number) => Promise<unknown[]>` | Required |
36+
| `pageProviderDebounceTime` | Debounce window in milliseconds on the calls to `pageProvider` | `number` | Optional, an integer greater than or equal to 0 |
37+
| `pageSize` | The number of items in a page from the item provider (e.g. a backend API) | `number` | Required, an integer greater than or equal to 1 |
38+
| `scrollTo` | Scroll to a specific item by index | `number` | Optional, an integer from 0 to the `length` prop - 1 |
3839

3940
Example:
4041

4142
```vue
42-
<Grid :length="1000"
43-
:pageProvider="async (pageNumber, pageSize) => Array(pageSize).fill('x')"
44-
:pageSize="40"
45-
:scrollTo="10"
43+
<Grid
44+
:length="1000"
45+
:pageProvider="async (pageNumber, pageSize) => Array(pageSize).fill('x')"
46+
:pageSize="40"
47+
:scrollTo="10"
4648
>
4749
<!-- ...slots -->
4850
</Grid>
@@ -121,16 +123,15 @@ items can be 200px x 200px when the view is under 768px and 300px x 500px above
121123

122124
Required environment variables:
123125

124-
* `VITE_APP_ID`: An Algolia app ID
125-
* `VITE_SEARCH_ONLY_API_KEY`: The search API key for the Algolia app above
126+
- `VITE_APP_ID`: An Algolia app ID
127+
- `VITE_SEARCH_ONLY_API_KEY`: The search API key for the Algolia app above
126128

127-
128-
- Setup: `npm install`
129-
- Run dev server: `npm run dev `
130-
- Lint (type check): `npm run lint `
131-
- Build the library: `npm run build `
132-
- Build the demo: `npm run build -- --mode=demo `
133-
- Preview the locally built demo: `npm run serve `
129+
* Setup: `npm install`
130+
* Run dev server: `npm run dev `
131+
* Lint (type check): `npm run lint `
132+
* Build the library: `npm run build `
133+
* Build the demo: `npm run build -- --mode=demo `
134+
* Preview the locally built demo: `npm run serve `
134135

135136
### How to Release a New Version
136137

src/Grid.vue

+9
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ export default defineComponent({
6565
type: Function as PropType<PageProvider>,
6666
required: true,
6767
},
68+
// Debounce window in milliseconds on the calls to `pageProvider`,
69+
// which is useful for avoiding network requests of skimmed pages.
70+
pageProviderDebounceTime: {
71+
type: Number as PropType<number>,
72+
required: false,
73+
default: 0,
74+
validator: (value: number) => Number.isInteger(value) && value >= 0,
75+
},
6876
// The number of items in a page from the item provider (e.g. a backend API).
6977
pageSize: {
7078
type: Number as PropType<number>,
@@ -92,6 +100,7 @@ export default defineComponent({
92100
// streams of prop
93101
length$: fromProp(props, "length"),
94102
pageProvider$: fromProp(props, "pageProvider"),
103+
pageProviderDebounceTime$: fromProp(props, "pageProviderDebounceTime"),
95104
pageSize$: fromProp(props, "pageSize"),
96105
// a stream of item size measurements when it is changed
97106
itemRect$: fromResizeObserver(probeRef, "contentRect"),

src/demo/App.vue

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
:length="length"
66
:pageSize="pageSize"
77
:pageProvider="pageProvider"
8+
:pageProviderDebounceTime="0"
89
:scrollTo="scrollTo"
910
:class="$style.grid"
1011
>

src/pipeline.ts

+19-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import {
22
combineLatest,
3+
debounceTime,
34
distinct,
45
distinctUntilChanged,
56
filter,
67
map,
78
merge,
9+
mergeAll,
810
mergeMap,
911
Observable,
1012
of,
@@ -218,6 +220,7 @@ export function getContentHeight(
218220
interface PipelineInput {
219221
length$: Observable<number>;
220222
pageProvider$: Observable<PageProvider>;
223+
pageProviderDebounceTime$: Observable<number>;
221224
pageSize$: Observable<number>;
222225
itemRect$: Observable<DOMRectReadOnly>;
223226
rootResize$: Observable<Element>;
@@ -234,6 +237,7 @@ interface PipelineOutput {
234237
export function pipeline({
235238
length$,
236239
pageProvider$,
240+
pageProviderDebounceTime$,
237241
pageSize$,
238242
itemRect$,
239243
rootResize$,
@@ -282,18 +286,26 @@ export function pipeline({
282286
getBufferMeta()
283287
).pipe(distinctUntilChanged<BufferMeta>(equals));
284288

285-
const itemsByPage$: Observable<ItemsByPage> = combineLatest([
289+
const visiblePageNumbers$ = combineLatest([
286290
bufferMeta$,
287291
length$,
288292
pageSize$,
289-
]).pipe(
290-
mergeMap(apply(getObservableOfVisiblePageNumbers)),
291-
distinct(identity, merge(pageSize$, pageProvider$)),
292-
withLatestFrom(pageSize$, pageProvider$),
293-
mergeMap(apply(callPageProvider)),
294-
shareReplay(1)
293+
]).pipe(map(apply(getObservableOfVisiblePageNumbers)));
294+
295+
const pageNumber$ = pageProviderDebounceTime$.pipe(
296+
switchMap((time) =>
297+
visiblePageNumbers$.pipe(time === 0 ? identity : debounceTime(time))
298+
),
299+
mergeAll(),
300+
distinct(identity, merge(pageSize$, pageProvider$))
295301
);
296302

303+
const itemsByPage$: Observable<ItemsByPage> = combineLatest([
304+
pageNumber$,
305+
pageSize$,
306+
pageProvider$,
307+
]).pipe(mergeMap(apply(callPageProvider)), shareReplay(1));
308+
297309
const replayLength$ = length$.pipe(shareReplay(1));
298310

299311
const allItems$: Observable<unknown[]> = pageProvider$.pipe(

0 commit comments

Comments
 (0)