-
-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add Infinite Scroll with local JSON data
- Loading branch information
1 parent
b2ff957
commit ef52d3f
Showing
8 changed files
with
388 additions
and
5 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
60 changes: 60 additions & 0 deletions
60
examples/vite-demo-vanilla-bundle/src/examples/example28.html
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<div class="demo26"> | ||
<h3 class="title is-3"> | ||
Example 28 - Infinite Scroll from JSON data | ||
<div class="subtitle code-link"> | ||
<span class="is-size-6">see</span> | ||
<a class="is-size-5" | ||
target="_blank" | ||
href="https://github.com/ghiscoding/slickgrid-universal/blob/master/examples/vite-demo-vanilla-bundle/src/examples/example26.ts"> | ||
<span class="mdi mdi-link-variant"></span> code | ||
</a> | ||
</div> | ||
</h3> | ||
|
||
<h6 class="title is-6 italic content"> | ||
<ul> | ||
<li> | ||
Infinite scrolling allows the grid to lazy-load rows from the server when reaching the scroll bottom (end) position. | ||
In its simplest form, the more the user scrolls down, the more rows get loaded. | ||
</li> | ||
<li>NOTES: <code>presets.pagination</code> is not supported with Infinite Scroll and will revert to the first page, | ||
simply because since we keep appending data, we always have to start from index zero (no offset). | ||
</li> | ||
</ul> | ||
</h6> | ||
|
||
<div class="row"> | ||
<button class="button is-small" data-test="clear-filters-sorting" | ||
onclick.delegate="clearAllFiltersAndSorts()" title="Clear all Filters & Sorts"> | ||
<span class="mdi mdi-close"></span> | ||
<span>Clear all Filter & Sorts</span> | ||
</button> | ||
<button class="button is-small" data-test="set-dynamic-filter" onclick.delegate="setFiltersDynamically()"> | ||
Set Filters Dynamically | ||
</button> | ||
<button class="button is-small" data-test="set-dynamic-sorting" onclick.delegate="setSortingDynamically()"> | ||
Set Sorting Dynamically | ||
</button> | ||
<button class="button is-small" data-test="group-by-duration" onclick.delegate="groupByDuration()"> | ||
Group by Duration | ||
</button> | ||
|
||
<label class="ml-4">Reset Dataset <code>onSort</code>:</label> | ||
<button class="button is-small" data-test="onsort-on" onclick.delegate="onSortReset(true)"> | ||
ON | ||
</button> | ||
<button class="button is-small" data-test="onsort-off" onclick.delegate="onSortReset(false)"> | ||
OFF | ||
</button> | ||
</div> | ||
|
||
<div class="mt-3 mb-2"> | ||
<b>Metrics:</b> | ||
<span textcontent.bind="metricsEndTime"></span> — | ||
<span textcontent.bind="metricsItemCount" data-test="itemCount"></span> of | ||
<span textcontent.bind="metricsTotalItemCount" data-test="totalItemCount"></span> items | ||
</div> | ||
|
||
<div class="grid28"> | ||
</div> | ||
</div> |
197 changes: 197 additions & 0 deletions
197
examples/vite-demo-vanilla-bundle/src/examples/example28.ts
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
import { BindingEventService } from '@slickgrid-universal/binding'; | ||
import { Aggregators, type Column, FieldType, Formatters, type GridOption, type Grouping, type OnRowCountChangedEventArgs, SortComparers, SortDirectionNumber, } from '@slickgrid-universal/common'; | ||
import { Slicker, type SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bundle'; | ||
|
||
import { ExampleGridOptions } from './example-grid-options'; | ||
import './example26.scss'; | ||
|
||
const FETCH_SIZE = 50; | ||
|
||
export default class Example28 { | ||
private _bindingEventService: BindingEventService; | ||
columnDefinitions: Column[]; | ||
gridOptions: GridOption; | ||
scrollEndCalled = false; | ||
shouldResetOnSort = false; | ||
metricsEndTime = ''; | ||
metricsItemCount = 0; | ||
metricsTotalItemCount = 0; | ||
sgb: SlickVanillaGridBundle; | ||
|
||
odataQuery = ''; | ||
processing = false; | ||
errorStatus = ''; | ||
errorStatusClass = 'hidden'; | ||
status = ''; | ||
statusClass = 'is-success'; | ||
isPageErrorTest = false; | ||
|
||
constructor() { | ||
this._bindingEventService = new BindingEventService(); | ||
this.resetAllStatus(); | ||
} | ||
|
||
attached() { | ||
this.defineGrid(); | ||
const gridContainerElm = document.querySelector(`.grid28`) as HTMLDivElement; | ||
const dataset = this.loadData(0, FETCH_SIZE); | ||
|
||
this.sgb = new Slicker.GridBundle(gridContainerElm, this.columnDefinitions, { ...ExampleGridOptions, ...this.gridOptions }, dataset); | ||
this.metricsItemCount = FETCH_SIZE; | ||
this.metricsTotalItemCount = FETCH_SIZE; | ||
|
||
|
||
// bind any of the grid events | ||
this._bindingEventService.bind(gridContainerElm, 'onrowcountchanged', this.refreshMetrics.bind(this) as EventListener); | ||
this._bindingEventService.bind(gridContainerElm, 'onsort', this.handleOnSort.bind(this)); | ||
this._bindingEventService.bind(gridContainerElm, 'onscroll', this.handleOnScroll.bind(this)); | ||
} | ||
|
||
dispose() { | ||
if (this.sgb) { | ||
this.sgb?.dispose(); | ||
} | ||
this._bindingEventService.unbindAll(); | ||
this.resetAllStatus(); | ||
} | ||
|
||
resetAllStatus() { | ||
this.status = ''; | ||
this.errorStatus = ''; | ||
this.statusClass = 'is-success'; | ||
this.errorStatusClass = 'hidden'; | ||
} | ||
|
||
defineGrid() { | ||
this.columnDefinitions = [ | ||
{ id: 'title', name: 'Title', field: 'title', sortable: true, minWidth: 100, filterable: true }, | ||
{ id: 'duration', name: 'Duration (days)', field: 'duration', sortable: true, minWidth: 100, filterable: true, type: FieldType.number }, | ||
{ id: '%', name: '% Complete', field: 'percentComplete', sortable: true, minWidth: 100, filterable: true, type: FieldType.number }, | ||
{ id: 'start', name: 'Start', field: 'start', formatter: Formatters.dateIso, exportWithFormatter: true, filterable: true }, | ||
{ id: 'finish', name: 'Finish', field: 'finish', formatter: Formatters.dateIso, exportWithFormatter: true, filterable: true }, | ||
{ id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', sortable: true, minWidth: 100, filterable: true, formatter: Formatters.checkmarkMaterial } | ||
]; | ||
|
||
this.gridOptions = { | ||
autoResize: { | ||
container: '.demo-container', | ||
}, | ||
enableAutoResize: true, | ||
enableFiltering: true, | ||
editable: false, | ||
rowHeight: 33, | ||
presets: { | ||
// NOTE: pagination preset is NOT supported with infinite scroll | ||
// filters: [{ columnId: 'gender', searchTerms: ['female'] }] | ||
}, | ||
}; | ||
} | ||
|
||
handleOnSort() { | ||
// reset data loaded | ||
if (this.shouldResetOnSort) { | ||
const newData = this.loadData(0, FETCH_SIZE); | ||
this.sgb.slickGrid?.scrollTo(0); // scroll back to top to avoid unwanted onScroll end triggered | ||
this.sgb.dataView?.setItems(newData); | ||
this.sgb.dataView?.reSort(); | ||
} | ||
} | ||
|
||
// add onScroll listener to append items to the dataset whenever reaching the scroll bottom (scroll end) | ||
handleOnScroll(event) { | ||
const args = event.detail?.args; | ||
const viewportElm = args.grid.getViewportNode(); | ||
if ( | ||
['mousewheel', 'scroll'].includes(args.triggeredBy || '') | ||
&& !this.scrollEndCalled | ||
&& viewportElm.scrollTop > 0 | ||
&& Math.ceil(viewportElm.offsetHeight + args.scrollTop) >= args.scrollHeight | ||
) { | ||
this.scrollEndCalled = true; | ||
this.handleOnScrollEnd(); | ||
} | ||
} | ||
|
||
handleOnScrollEnd() { | ||
console.log('onScroll end reached, add more items'); | ||
const startIdx = this.sgb.dataView?.getItemCount() || 0; | ||
const newItems = this.loadData(startIdx, FETCH_SIZE); | ||
this.sgb.dataView?.addItems(newItems); | ||
this.scrollEndCalled = false; | ||
} | ||
|
||
groupByDuration() { | ||
this.sgb?.dataView?.setGrouping({ | ||
getter: 'duration', | ||
formatter: (g) => `Duration: ${g.value} <span class="text-green">(${g.count} items)</span>`, | ||
comparer: (a, b) => SortComparers.numeric(a.value, b.value, SortDirectionNumber.asc), | ||
aggregators: [ | ||
new Aggregators.Avg('percentComplete'), | ||
new Aggregators.Sum('cost') | ||
], | ||
aggregateCollapsed: false, | ||
lazyTotalsCalculation: true | ||
} as Grouping); | ||
|
||
// you need to manually add the sort icon(s) in UI | ||
this.sgb?.slickGrid?.setSortColumns([{ columnId: 'duration', sortAsc: true }]); | ||
this.sgb?.slickGrid?.invalidate(); // invalidate all rows and re-render | ||
} | ||
|
||
loadData(startIdx: number, count: number) { | ||
const tmpData: any[] = []; | ||
for (let i = startIdx; i < startIdx + count; i++) { | ||
tmpData.push(this.newItem(i)); | ||
} | ||
|
||
return tmpData; | ||
} | ||
|
||
newItem(idx: number) { | ||
const randomYear = 2000 + Math.floor(Math.random() * 10); | ||
const randomMonth = Math.floor(Math.random() * 11); | ||
const randomDay = Math.floor((Math.random() * 29)); | ||
const randomPercent = Math.round(Math.random() * 100); | ||
|
||
return { | ||
id: idx, | ||
title: 'Task ' + idx, | ||
duration: Math.round(Math.random() * 100) + '', | ||
percentComplete: randomPercent, | ||
start: new Date(randomYear, randomMonth + 1, randomDay), | ||
finish: new Date(randomYear + 1, randomMonth + 1, randomDay), | ||
effortDriven: (idx % 5 === 0) | ||
}; | ||
} | ||
|
||
onSortReset(shouldReset) { | ||
this.shouldResetOnSort = shouldReset; | ||
} | ||
|
||
clearAllFiltersAndSorts() { | ||
if (this.sgb?.gridService) { | ||
this.sgb.gridService.clearAllFiltersAndSorts(); | ||
} | ||
} | ||
|
||
setFiltersDynamically() { | ||
// we can Set Filters Dynamically (or different filters) afterward through the FilterService | ||
this.sgb?.filterService.updateFilters([ | ||
{ columnId: 'percentComplete', searchTerms: ['>=50'] }, | ||
]); | ||
} | ||
|
||
refreshMetrics(event: CustomEvent<{ args: OnRowCountChangedEventArgs; }>) { | ||
const args = event?.detail?.args; | ||
if (args?.current >= 0) { | ||
this.metricsItemCount = this.sgb.dataView?.getFilteredItemCount() || 0; | ||
this.metricsTotalItemCount = args.itemCount || 0; | ||
} | ||
} | ||
|
||
setSortingDynamically() { | ||
this.sgb?.sortService.updateSorting([ | ||
{ columnId: 'title', direction: 'DESC' }, | ||
]); | ||
} | ||
} |
This file contains 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
Oops, something went wrong.