-
Notifications
You must be signed in to change notification settings - Fork 424
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add new Infinite Scroll example (#1040)
* feat: add new Infinite Scroll example
- Loading branch information
1 parent
856286d
commit 060c8a0
Showing
4 changed files
with
334 additions
and
6 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
describe('Example - Infinite Scroll', () => { | ||
const GRID_ROW_HEIGHT = 25; | ||
const titles = ['#', 'Title', 'Duration', '% Complete', 'Start', 'Finish', 'Effort Driven']; | ||
|
||
it('should display Example title', () => { | ||
cy.visit(`${Cypress.config('baseUrl')}/examples/example-infinite-scroll-esm.html`); | ||
cy.get('h2').contains('Demonstrates'); | ||
cy.get('h2 + p').contains('This page demonstrates Infinite Scroll using DataView.'); | ||
|
||
cy.get('.grid-header > label') | ||
.should('contain', 'SlickGrid - Infinite Scroll'); | ||
}); | ||
|
||
it('should have exact Column Titles in the grid', () => { | ||
cy.get('#myGrid') | ||
.find('.slick-header-columns') | ||
.children() | ||
.each(($child, index) => expect($child.text()).to.eq(titles[index])); | ||
}); | ||
|
||
it('should expect first row to include "Task 0" and other specific properties', () => { | ||
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0'); | ||
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '5 days'); | ||
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3) .percent-complete-bar`).should('exist'); | ||
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/01/2009'); | ||
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/05/2009'); | ||
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).find('.sgi.sgi-check').should('have.length', 1); | ||
}); | ||
|
||
it('should scroll to bottom of the grid and expect next batch of 50 items appended to current dataset for a total of 100 items', () => { | ||
cy.get('[data-test="itemCount"]') | ||
.should('have.text', '50'); | ||
|
||
cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left') | ||
.scrollTo('bottom'); | ||
|
||
cy.get('[data-test="itemCount"]') | ||
.should('have.text', '100'); | ||
}); | ||
|
||
it('should scroll to bottom of the grid again and expect 50 more items for a total of now 150 items', () => { | ||
cy.get('[data-test="itemCount"]') | ||
.should('have.text', '100'); | ||
|
||
cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left') | ||
.scrollTo('bottom'); | ||
|
||
cy.get('[data-test="itemCount"]') | ||
.should('have.text', '150'); | ||
}); | ||
|
||
it('should disable onSort for data reset and expect same dataset length of 150 items after sorting by Title', () => { | ||
cy.get('[data-test="onsort-off"]').click(); | ||
|
||
cy.get('[data-id="title"]') | ||
.click(); | ||
|
||
cy.get('[data-test="itemCount"]') | ||
.should('have.text', '150'); | ||
|
||
cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left') | ||
.scrollTo('top'); | ||
|
||
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0'); | ||
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 1'); | ||
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 10'); | ||
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 100'); | ||
}); | ||
|
||
it('should enable onSort for data reset and expect dataset to be reset to 50 items after sorting by Title', () => { | ||
cy.get('[data-test="onsort-on"]').click(); | ||
|
||
cy.get('[data-id="title"]') | ||
.click(); | ||
|
||
cy.get('[data-test="itemCount"]') | ||
.should('have.text', '50'); | ||
|
||
cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left') | ||
.scrollTo('top'); | ||
|
||
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 9'); | ||
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 8'); | ||
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 7'); | ||
cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 6'); | ||
}); | ||
}); |
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,230 @@ | ||
<!DOCTYPE HTML> | ||
<html> | ||
<head> | ||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> | ||
<link rel="shortcut icon" type="image/ico" href="favicon.ico" /> | ||
<title>SlickGrid example: Infinite Scroll</title> | ||
<link rel="stylesheet" href="../dist/styles/css/slick-icons.css" type="text/css"/> | ||
<link rel="stylesheet" href="../dist/styles/css/example-demo.css" type="text/css"/> | ||
<link rel="stylesheet" href="../dist/styles/css/slick-alpine-theme.css" type="text/css"/> | ||
<style> | ||
.cell-title { | ||
font-weight: bold; | ||
} | ||
|
||
.cell-effort-driven { | ||
justify-content: center; | ||
} | ||
|
||
.cell-selection { | ||
border-right-color: silver; | ||
border-right-style: solid; | ||
background: #f5f5f5; | ||
color: gray; | ||
text-align: right; | ||
font-size: 10px; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div style="position:relative"> | ||
<div style="width:650px;"> | ||
<div class="grid-header" style="width:100%"> | ||
<label>SlickGrid - Infinite Scroll</label> | ||
</div> | ||
<div id="myGrid" class="slick-container" style="width:100%;height:500px;"></div> | ||
</div> | ||
|
||
<div class="options-panel"> | ||
<h2> | ||
<a href="/examples/index.html" style="text-decoration: none; font-size: 22px">⌂</a> | ||
Demonstrates: | ||
</h2> | ||
|
||
<p> | ||
This page demonstrates Infinite Scroll using DataView. | ||
<ul> | ||
<li>Infinite scrolling allows the grid to lazy-load rows from the server (or locally) when reaching the scroll bottom (end) position.</li> | ||
<li>In its simplest form, the more the user scrolls down, the more rows will get loaded and appended to the in-memory dataset.</li> | ||
<li>This demo will keep loading and adding data indifinitely, however in most cases you will eventually reach the end of your dataset and have everything loaded in memory.</li> | ||
<li>You can choose to reset (or not) the dataset after Sorting by any column.</li> | ||
<li>Note that instead of the DataView, we could also use just plain SlickGrid (without DataView) to add items</li> | ||
</ul> | ||
</p> | ||
<h2>View Source:</h2> | ||
<ul> | ||
<li><A href="https://github.com/6pac/SlickGrid/blob/master/examples/example-infinite-scroll-esm.html" target="_sourcewindow"> View the source for this example on Github</a></li> | ||
</ul> | ||
<h4>Reset Dataset onSort</h4> | ||
<button data-test="onsort-on">ON</button> | ||
<button data-test="onsort-off">OFF</button> | ||
|
||
<h4>Data Count</h4> | ||
<p> | ||
<label>Data length:</label> | ||
<span id="dataLength" data-test="itemCount">0</span> | ||
</p> | ||
</div> | ||
</div> | ||
|
||
<script src="https://cdn.jsdelivr.net/npm/sortablejs/Sortable.min.js"></script> | ||
<script src="sortable-cdn-fallback.js"></script> | ||
|
||
<script type="module"> | ||
import { | ||
Formatters, | ||
SlickDataView, | ||
SlickGrid, | ||
} from '../dist/esm/index.js'; | ||
|
||
const FETCH_SIZE = 50; | ||
let dataView; | ||
let grid; | ||
let data = []; | ||
let columns = [ | ||
{id: "sel", name: "#", field: "num", behavior: "select", cssClass: "cell-selection", width: 40, resizable: false, selectable: false }, | ||
{id: "title", name: "Title", field: "title", width: 120, cssClass: "cell-title", sortable: true }, | ||
{id: "duration", name: "Duration", field: "duration", sortable: true }, | ||
{id: "%", name: "% Complete", field: "percentComplete", width: 120, resizable: false, formatter: Formatters.PercentCompleteBar, sortable: true }, | ||
{id: "start", name: "Start", field: "start", minWidth: 60, sortable: true }, | ||
{id: "finish", name: "Finish", field: "finish", minWidth: 60, sortable: true }, | ||
{id: "effort-driven", name: "Effort Driven", width: 100, minWidth: 20, cssClass: "cell-effort-driven", field: "effortDriven", formatter: Formatters.Checkmark, sortable: true } | ||
]; | ||
let shouldResetOnSort = false; | ||
let scrollEndCalled = false; | ||
let sortcol = "title"; | ||
let sortdir = 1; | ||
|
||
let gridOptions = { | ||
editable: false, | ||
enableAddRow: false, | ||
enableCellNavigation: true | ||
}; | ||
|
||
let percentCompleteThreshold = 0; | ||
let prevPercentCompleteThreshold = 0; | ||
let h_runfilters = null; | ||
|
||
function onSortReset(shouldReset) { | ||
shouldResetOnSort = shouldReset; | ||
} | ||
|
||
function myFilter(item, args) { | ||
return item["percentComplete"] >= args; | ||
} | ||
|
||
function DataItem(i) { | ||
this.num = i; | ||
this.id = "id_" + i; | ||
this.percentComplete = Math.round(Math.random() * 100); | ||
this.effortDriven = (i % 5 == 0); | ||
this.start = "01/01/2009"; | ||
this.finish = "01/05/2009"; | ||
this.title = "Task " + i; | ||
this.duration = "5 days"; | ||
} | ||
|
||
function handleOnScrollEnd() { | ||
console.log('onScroll end reached, add more items'); | ||
const startIdx = dataView.getItemCount(); | ||
|
||
dataView.addItems(loadData(startIdx, FETCH_SIZE)); | ||
scrollEndCalled = false; | ||
} | ||
|
||
function loadData(startIdx, count) { | ||
let tmpData = []; | ||
for (let i = startIdx; i < startIdx + count; i++) { | ||
tmpData.push(new DataItem(i)); | ||
} | ||
return tmpData; | ||
} | ||
|
||
function comparer(a, b) { | ||
let x = a[sortcol], y = b[sortcol]; | ||
return (x == y ? 0 : (x > y ? 1 : -1)); | ||
} | ||
|
||
document.addEventListener("DOMContentLoaded", function() { | ||
// prepare the data | ||
data = loadData(0, FETCH_SIZE); | ||
|
||
dataView = new SlickDataView({ inlineFilters: true }); | ||
grid = new SlickGrid("#myGrid", dataView, columns, gridOptions); | ||
|
||
// wire up model events to drive the grid | ||
dataView.onRowCountChanged.subscribe((e, args) => { | ||
grid.updateRowCount(); | ||
grid.render(); | ||
|
||
let countElm = document.querySelector('#dataLength'); | ||
countElm.textContent = args.itemCount; | ||
}); | ||
|
||
dataView.onRowsChanged.subscribe((e, args) => { | ||
grid.invalidateRows(args.rows); | ||
grid.render(); | ||
}); | ||
|
||
grid.onSort.subscribe((e, args) => { | ||
sortdir = args.sortAsc ? 1 : -1; | ||
sortcol = args.sortCol.field; | ||
|
||
// using native sort with comparer | ||
dataView.sort(comparer, args.sortAsc); | ||
|
||
// reset data loaded | ||
if (shouldResetOnSort) { | ||
const newData = loadData(0, FETCH_SIZE); | ||
grid.scrollTo(0); // scroll back to top to avoid unwanted onScroll end triggered | ||
dataView.setItems(newData); | ||
dataView.reSort(); | ||
} | ||
}); | ||
|
||
// add onScroll listener to append items to the dataset whenever reaching the scroll bottom (scroll end) | ||
grid.onScroll.subscribe((e, args) => { | ||
const viewportElm = args.grid.getViewportNode(); | ||
if ( | ||
['mousewheel', 'scroll'].includes(args.triggeredBy || '') | ||
&& !scrollEndCalled | ||
&& viewportElm.scrollTop > 0 | ||
&& Math.ceil(viewportElm.offsetHeight + args.scrollTop) >= args.scrollHeight | ||
) { | ||
const vpLeft = document.querySelector('.slick-viewport-top.slick-viewport-left'); | ||
scrollEndCalled = true; | ||
handleOnScrollEnd(); | ||
} | ||
}); | ||
|
||
function filterAndUpdate() { | ||
let isNarrowing = percentCompleteThreshold > prevPercentCompleteThreshold; | ||
let isExpanding = percentCompleteThreshold < prevPercentCompleteThreshold; | ||
let renderedRange = grid.getRenderedRange(); | ||
|
||
dataView.setFilterArgs(percentCompleteThreshold); | ||
dataView.setRefreshHints({ | ||
ignoreDiffsBefore: renderedRange.top, | ||
ignoreDiffsAfter: renderedRange.bottom + 1, | ||
isFilterNarrowing: isNarrowing, | ||
isFilterExpanding: isExpanding | ||
}); | ||
dataView.refresh(); | ||
|
||
prevPercentCompleteThreshold = percentCompleteThreshold; | ||
} | ||
|
||
// initialize the model after all the events have been hooked up | ||
dataView.beginUpdate(); | ||
dataView.setItems(data); | ||
dataView.setFilter(myFilter); | ||
dataView.setFilterArgs(0); | ||
dataView.endUpdate(); | ||
|
||
// add DOM event listeners | ||
document.querySelector('[data-test="onsort-on"]').addEventListener('click', () => onSortReset(true)); | ||
document.querySelector('[data-test="onsort-off"]').addEventListener('click', () => onSortReset(false)); | ||
}); | ||
</script> | ||
</body> | ||
</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
Oops, something went wrong.