-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[XGrid] Make Infinite loading support rowCount
#1715
Changes from all commits
ada4c4d
21196bd
ddc90de
4c0a5b1
1f94832
e520dbc
dd7af2c
b841f96
4ab7cd0
97c86a8
802ff36
9dd3579
8bbbe71
6222f1e
8fc923e
369cb68
60a7adc
11d936a
f9f2ab1
59aa187
658879f
f81a1cf
4290995
323f10a
02b02de
b481127
42f0830
c08eb4b
2007edd
cdcdfee
f96f635
828a186
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import * as React from 'react'; | ||
import { XGrid } from '@material-ui/x-grid'; | ||
import { | ||
useDemoData, | ||
getRealData, | ||
getCommodityColumns, | ||
} from '@material-ui/x-grid-data-generator'; | ||
|
||
async function sleep(duration) { | ||
return new Promise((resolve) => { | ||
setTimeout(() => { | ||
resolve(); | ||
}, duration); | ||
}); | ||
} | ||
|
||
const loadServerRows = async (newRowLength) => { | ||
const newData = await getRealData(newRowLength, getCommodityColumns()); | ||
// Simulate network throttle | ||
await sleep(Math.random() * 100 + 100); | ||
|
||
return newData.rows; | ||
}; | ||
|
||
export default function InfiniteLoadingGrid() { | ||
const { data } = useDemoData({ | ||
dataSet: 'Commodity', | ||
rowLength: 10, | ||
maxColumns: 6, | ||
}); | ||
|
||
const handleFetchRows = async (params) => { | ||
const newRowsBatch = await loadServerRows(params.viewportPageSize); | ||
|
||
params.api.current.insertRows({ | ||
startIndex: params.startIndex, | ||
pageSize: params.viewportPageSize, | ||
newRows: newRowsBatch, | ||
}); | ||
}; | ||
|
||
return ( | ||
<div style={{ height: 400, width: '100%' }}> | ||
<XGrid | ||
{...data} | ||
hideFooterPagination | ||
rowCount={50} | ||
sortingMode="server" | ||
filterMode="server" | ||
infniteLoadingMode="server" | ||
onFetchRows={handleFetchRows} | ||
/> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,57 @@ | ||||||||||||||||||||||
import * as React from 'react'; | ||||||||||||||||||||||
import { GridFetchRowsParams, GridRowData, XGrid } from '@material-ui/x-grid'; | ||||||||||||||||||||||
import { | ||||||||||||||||||||||
useDemoData, | ||||||||||||||||||||||
getRealData, | ||||||||||||||||||||||
getCommodityColumns, | ||||||||||||||||||||||
} from '@material-ui/x-grid-data-generator'; | ||||||||||||||||||||||
|
||||||||||||||||||||||
async function sleep(duration) { | ||||||||||||||||||||||
return new Promise<void>((resolve) => { | ||||||||||||||||||||||
setTimeout(() => { | ||||||||||||||||||||||
resolve(); | ||||||||||||||||||||||
}, duration); | ||||||||||||||||||||||
}); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
const loadServerRows = async (newRowLength: number): Promise<GridRowData[]> => { | ||||||||||||||||||||||
const newData = await getRealData(newRowLength, getCommodityColumns()); | ||||||||||||||||||||||
// Simulate network throttle | ||||||||||||||||||||||
await sleep(Math.random() * 100 + 100); | ||||||||||||||||||||||
|
||||||||||||||||||||||
return newData.rows; | ||||||||||||||||||||||
}; | ||||||||||||||||||||||
|
||||||||||||||||||||||
export default function InfiniteLoadingGrid() { | ||||||||||||||||||||||
const { data } = useDemoData({ | ||||||||||||||||||||||
dataSet: 'Commodity', | ||||||||||||||||||||||
rowLength: 10, | ||||||||||||||||||||||
maxColumns: 6, | ||||||||||||||||||||||
}); | ||||||||||||||||||||||
|
||||||||||||||||||||||
const handleFetchRows = async (params: GridFetchRowsParams) => { | ||||||||||||||||||||||
const newRowsBatch: GridRowData[] = await loadServerRows( | ||||||||||||||||||||||
params.viewportPageSize, | ||||||||||||||||||||||
); | ||||||||||||||||||||||
|
||||||||||||||||||||||
params.api.current.insertRows({ | ||||||||||||||||||||||
startIndex: params.startIndex, | ||||||||||||||||||||||
pageSize: params.viewportPageSize, | ||||||||||||||||||||||
newRows: newRowsBatch, | ||||||||||||||||||||||
}); | ||||||||||||||||||||||
Comment on lines
+37
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks wrong. The data grid is the best one to know if the resolved value is still relevant or not, not the developer at this level. I think that we should simply yield:
Suggested change
Or at least, if we really want to keep this API, we need to update this demo to force the requests to resolve in the right order (ignore outdated requests). Which we don't do here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The initial version was like the one you are suggesting but after discussing it with @dtassone I reworked it to match the rest of the grid's APIs. In that case the responsibility of loading the rows falls on to the developer. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case, I think that it's important we update the demo to showcase a sound implementation of data fetching. It will be a good forcing function to see the complexity we are pushing to developers, it will help us evaluate if we are happy with this or prefer to internalize it. If we decide to internalize the complexity in the future, the current API is not flying, we would need to add a "token" if we want to keep an api call, or to switch to a promise. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess this is what you mean #1715 (comment). The current demo does show how to use the feature in action, it just doesn't do the filtering and sorting part. |
||||||||||||||||||||||
}; | ||||||||||||||||||||||
|
||||||||||||||||||||||
return ( | ||||||||||||||||||||||
<div style={{ height: 400, width: '100%' }}> | ||||||||||||||||||||||
<XGrid | ||||||||||||||||||||||
{...data} | ||||||||||||||||||||||
hideFooterPagination | ||||||||||||||||||||||
rowCount={50} | ||||||||||||||||||||||
sortingMode="server" | ||||||||||||||||||||||
filterMode="server" | ||||||||||||||||||||||
infniteLoadingMode="server" | ||||||||||||||||||||||
onFetchRows={handleFetchRows} | ||||||||||||||||||||||
/> | ||||||||||||||||||||||
</div> | ||||||||||||||||||||||
); | ||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -17,6 +17,7 @@ import { GridEmptyCell } from './cell/GridEmptyCell'; | |||||||||||||||||||||||||
import { GridRenderingZone } from './GridRenderingZone'; | ||||||||||||||||||||||||||
import { GridRow } from './GridRow'; | ||||||||||||||||||||||||||
import { GridRowCells } from './cell/GridRowCells'; | ||||||||||||||||||||||||||
import { GridSkeletonRowCells } from './cell/GridSkeletonRowCells'; | ||||||||||||||||||||||||||
import { GridStickyContainer } from './GridStickyContainer'; | ||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||
gridContainerSizesSelector, | ||||||||||||||||||||||||||
|
@@ -52,6 +53,7 @@ export const GridViewport: ViewportType = React.forwardRef<HTMLDivElement, {}>( | |||||||||||||||||||||||||
renderState.renderContext.firstRowIdx, | ||||||||||||||||||||||||||
renderState.renderContext.lastRowIdx!, | ||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return renderedRows.map(([id, row], idx) => ( | ||||||||||||||||||||||||||
<GridRow | ||||||||||||||||||||||||||
className={ | ||||||||||||||||||||||||||
|
@@ -63,25 +65,38 @@ export const GridViewport: ViewportType = React.forwardRef<HTMLDivElement, {}>( | |||||||||||||||||||||||||
rowIndex={renderState.renderContext!.firstRowIdx! + idx} | ||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||
<GridEmptyCell width={renderState.renderContext!.leftEmptyWidth} height={rowHeight} /> | ||||||||||||||||||||||||||
<GridRowCells | ||||||||||||||||||||||||||
columns={visibleColumns} | ||||||||||||||||||||||||||
row={row} | ||||||||||||||||||||||||||
id={id} | ||||||||||||||||||||||||||
height={rowHeight} | ||||||||||||||||||||||||||
firstColIdx={renderState.renderContext!.firstColIdx!} | ||||||||||||||||||||||||||
lastColIdx={renderState.renderContext!.lastColIdx!} | ||||||||||||||||||||||||||
hasScrollX={scrollBarState.hasScrollX} | ||||||||||||||||||||||||||
hasScrollY={scrollBarState.hasScrollY} | ||||||||||||||||||||||||||
showCellRightBorder={!!options.showCellRightBorder} | ||||||||||||||||||||||||||
extendRowFullWidth={!options.disableExtendRowFullWidth} | ||||||||||||||||||||||||||
rowIndex={renderState.renderContext!.firstRowIdx! + idx} | ||||||||||||||||||||||||||
cellFocus={cellFocus} | ||||||||||||||||||||||||||
cellTabIndex={cellTabIndex} | ||||||||||||||||||||||||||
isSelected={selectionState[id] !== undefined} | ||||||||||||||||||||||||||
editRowState={editRowsState[id]} | ||||||||||||||||||||||||||
cellClassName={options.classes?.cell} | ||||||||||||||||||||||||||
getCellClassName={options.getCellClassName} | ||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||
{id.toString().indexOf('null-') === 0 ? ( | ||||||||||||||||||||||||||
<GridSkeletonRowCells | ||||||||||||||||||||||||||
columns={visibleColumns} | ||||||||||||||||||||||||||
firstColIdx={renderState.renderContext!.firstColIdx!} | ||||||||||||||||||||||||||
lastColIdx={renderState.renderContext!.lastColIdx!} | ||||||||||||||||||||||||||
hasScrollX={scrollBarState.hasScrollX} | ||||||||||||||||||||||||||
hasScrollY={scrollBarState.hasScrollY} | ||||||||||||||||||||||||||
showCellRightBorder={!!options.showCellRightBorder} | ||||||||||||||||||||||||||
extendRowFullWidth={!options.disableExtendRowFullWidth} | ||||||||||||||||||||||||||
rowIndex={renderState.renderContext!.firstRowIdx! + idx} | ||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||
) : ( | ||||||||||||||||||||||||||
Comment on lines
+68
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of hardcoding different types of rows. How about we add an API to turn the loading state programmatically on some cells?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would even argue that we could breakdown this effort once we get a good enough POC of the integration to have the skeleton standalone, and maybe use it for the infinite loading use case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that that is not the best solution but there is already a ticket for changing the loading visuals once this initial version is merged #1685 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem is that without skeleton rows this feature doesn't work at all because it will just display empty rows. The UX wouldn't be great. |
||||||||||||||||||||||||||
<GridRowCells | ||||||||||||||||||||||||||
columns={visibleColumns} | ||||||||||||||||||||||||||
row={row} | ||||||||||||||||||||||||||
id={id} | ||||||||||||||||||||||||||
height={rowHeight} | ||||||||||||||||||||||||||
firstColIdx={renderState.renderContext!.firstColIdx!} | ||||||||||||||||||||||||||
lastColIdx={renderState.renderContext!.lastColIdx!} | ||||||||||||||||||||||||||
hasScrollX={scrollBarState.hasScrollX} | ||||||||||||||||||||||||||
hasScrollY={scrollBarState.hasScrollY} | ||||||||||||||||||||||||||
showCellRightBorder={!!options.showCellRightBorder} | ||||||||||||||||||||||||||
extendRowFullWidth={!options.disableExtendRowFullWidth} | ||||||||||||||||||||||||||
rowIndex={renderState.renderContext!.firstRowIdx! + idx} | ||||||||||||||||||||||||||
cellFocus={cellFocus} | ||||||||||||||||||||||||||
cellTabIndex={cellTabIndex} | ||||||||||||||||||||||||||
isSelected={selectionState[id] !== undefined} | ||||||||||||||||||||||||||
editRowState={editRowsState[id]} | ||||||||||||||||||||||||||
cellClassName={options.classes?.cell} | ||||||||||||||||||||||||||
getCellClassName={options.getCellClassName} | ||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||
)} | ||||||||||||||||||||||||||
<GridEmptyCell width={renderState.renderContext!.rightEmptyWidth} height={rowHeight} /> | ||||||||||||||||||||||||||
</GridRow> | ||||||||||||||||||||||||||
)); | ||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import * as React from 'react'; | ||
import clsx from 'clsx'; | ||
import Skeleton from '@material-ui/lab/Skeleton'; | ||
oliviertassinari marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import { GRID_SKELETON_CELL_CSS_CLASS } from '../../constants/cssClassesConstants'; | ||
|
||
export interface GridSkeletonCellProps { | ||
colIndex: number; | ||
height: number; | ||
rowIndex: number; | ||
showRightBorder?: boolean; | ||
width: number; | ||
} | ||
|
||
export const GridSkeletonCell = React.memo(function GridSkeletonCell(props: GridSkeletonCellProps) { | ||
const { colIndex, height, rowIndex, showRightBorder, width } = props; | ||
|
||
const cellRef = React.useRef<HTMLDivElement>(null); | ||
const cssClasses = clsx(GRID_SKELETON_CELL_CSS_CLASS, { | ||
'MuiDataGrid-withBorder': showRightBorder, | ||
}); | ||
|
||
const style = { | ||
minWidth: width, | ||
maxWidth: width, | ||
lineHeight: `${height - 1}px`, | ||
minHeight: height, | ||
maxHeight: height, | ||
}; | ||
|
||
return ( | ||
<div | ||
ref={cellRef} | ||
className={cssClasses} | ||
role="cell" | ||
data-rowindex={rowIndex} | ||
aria-colindex={colIndex} | ||
style={style} | ||
tabIndex={-1} | ||
> | ||
<Skeleton animation="wave" /> | ||
</div> | ||
); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import * as React from 'react'; | ||
import { GridColumns } from '../../models/index'; | ||
import { GridApiContext } from '../GridApiContext'; | ||
import { gridDensityRowHeightSelector } from '../../hooks/features/density/densitySelector'; | ||
import { useGridSelector } from '../../hooks/features/core/useGridSelector'; | ||
import { GridSkeletonCell } from './GridSkeletonCell'; | ||
|
||
interface SkeletonRowCellsProps { | ||
columns: GridColumns; | ||
extendRowFullWidth: boolean; | ||
firstColIdx: number; | ||
hasScrollX: boolean; | ||
hasScrollY: boolean; | ||
lastColIdx: number; | ||
rowIndex: number; | ||
showCellRightBorder: boolean; | ||
} | ||
|
||
export const GridSkeletonRowCells = React.memo(function GridSkeletonRowCells( | ||
props: SkeletonRowCellsProps, | ||
) { | ||
const { | ||
columns, | ||
firstColIdx, | ||
hasScrollX, | ||
hasScrollY, | ||
lastColIdx, | ||
rowIndex, | ||
showCellRightBorder, | ||
} = props; | ||
const apiRef = React.useContext(GridApiContext); | ||
const rowHeight = useGridSelector(apiRef, gridDensityRowHeightSelector); | ||
|
||
const skeletonCellsProps = columns.slice(firstColIdx, lastColIdx + 1).map((column, colIdx) => { | ||
const colIndex = firstColIdx + colIdx; | ||
const isLastColumn = colIndex === columns.length - 1; | ||
const removeLastBorderRight = isLastColumn && hasScrollX && !hasScrollY; | ||
const showRightBorder = !isLastColumn | ||
? showCellRightBorder | ||
: !removeLastBorderRight && !props.extendRowFullWidth; | ||
|
||
const skeletonCellProps = { | ||
field: column.field, | ||
width: column.width!, | ||
height: rowHeight, | ||
showRightBorder, | ||
rowIndex, | ||
colIndex, | ||
}; | ||
|
||
return skeletonCellProps; | ||
}); | ||
|
||
return ( | ||
<React.Fragment> | ||
{skeletonCellsProps.map((skeletonCellProps) => ( | ||
<GridSkeletonCell key={skeletonCellProps.field} {...skeletonCellProps} /> | ||
))} | ||
</React.Fragment> | ||
); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.