From ada4c4dfcf6d22c6768c42ef73b32890b5c78a37 Mon Sep 17 00:00:00 2001 From: Danail H Date: Wed, 19 May 2021 17:20:14 +0300 Subject: [PATCH 01/27] Add support for detecting when virtual page changes --- .../_modules_/grid/components/GridRow.tsx | 5 +- .../grid/components/GridViewport.tsx | 37 +++++++------ .../grid/constants/eventsConstants.ts | 6 ++ .../infiniteLoader/useGridInfiniteLoader.ts | 49 ++++++++++++++++- .../grid/hooks/features/rows/gridRowsState.ts | 4 +- .../grid/hooks/features/rows/useGridRows.ts | 39 ++++++++++++- .../virtualization/useGridVirtualRows.ts | 7 +++ .../_modules_/grid/models/api/gridRowApi.ts | 7 +++ .../_modules_/grid/models/gridOptions.tsx | 4 ++ .../params/gridVirtualPageChangeParas.ts | 17 ++++++ .../src/stories/grid-rows.stories.tsx | 55 +++++++++++++++++++ 11 files changed, 204 insertions(+), 26 deletions(-) create mode 100644 packages/grid/_modules_/grid/models/params/gridVirtualPageChangeParas.ts diff --git a/packages/grid/_modules_/grid/components/GridRow.tsx b/packages/grid/_modules_/grid/components/GridRow.tsx index 51f6775e9046b..8b564f3e40250 100644 --- a/packages/grid/_modules_/grid/components/GridRow.tsx +++ b/packages/grid/_modules_/grid/components/GridRow.tsx @@ -30,6 +30,7 @@ export const GridRow = (props: GridRowProps) => { const apiRef = React.useContext(GridApiContext); const rowHeight = useGridSelector(apiRef, gridDensityRowHeightSelector); const options = useGridSelector(apiRef, optionsSelector); + const rowId = id || `key-${Math.floor(Math.random() * 1000)}`; const publish = React.useCallback( (eventName: string) => (event: React.MouseEvent) => { @@ -69,8 +70,8 @@ export const GridRow = (props: GridRowProps) => { return (
( renderState.renderContext.firstRowIdx, renderState.renderContext.lastRowIdx!, ); + return renderedRows.map(([id, row], idx) => ( ( rowIndex={renderState.renderContext!.firstRowIdx! + idx} > - + {id === null ? ( +
EMPTY
+ ) : ( + + )}
)); diff --git a/packages/grid/_modules_/grid/constants/eventsConstants.ts b/packages/grid/_modules_/grid/constants/eventsConstants.ts index b25cdaeda7cce..872ac1d51c692 100644 --- a/packages/grid/_modules_/grid/constants/eventsConstants.ts +++ b/packages/grid/_modules_/grid/constants/eventsConstants.ts @@ -89,3 +89,9 @@ export const GRID_STATE_CHANGE = 'stateChange'; * @event */ export const GRID_COLUMN_VISIBILITY_CHANGE = 'columnVisibilityChange'; + +/** + * Fired when the virtual page changes. Called with a [[GridVirtualPageChangeParams]] object. + * @event + */ +export const GRID_VIRTUAL_PAGE_CHANGE = 'virtualPageChange'; diff --git a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts index 6e1d217f46b13..bb749848cea23 100644 --- a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts +++ b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts @@ -2,24 +2,39 @@ import * as React from 'react'; import { optionsSelector } from '../../utils/optionsSelector'; import { GridApiRef } from '../../../models/api/gridApiRef'; import { useGridSelector } from '../core/useGridSelector'; -import { GRID_ROWS_SCROLL, GRID_ROWS_SCROLL_END } from '../../../constants/eventsConstants'; +import { GRID_ROWS_SCROLL, GRID_ROWS_SCROLL_END, GRID_VIRTUAL_PAGE_CHANGE } from '../../../constants/eventsConstants'; import { gridContainerSizesSelector } from '../../root/gridContainerSizesSelector'; import { useGridApiEventHandler, useGridApiOptionHandler } from '../../root/useGridApiEventHandler'; import { GridRowScrollEndParams } from '../../../models/params/gridRowScrollEndParams'; import { visibleGridColumnsSelector } from '../columns/gridColumnsSelector'; +import { GridVirtualPageChangeParams } from '../../../models/params/gridvirtualPageChangeParas'; +import { useLogger } from '../../utils/useLogger'; export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { + const logger = useLogger('useGridInfiniteLoader'); const options = useGridSelector(apiRef, optionsSelector); const containerSizes = useGridSelector(apiRef, gridContainerSizesSelector); const visibleColumns = useGridSelector(apiRef, visibleGridColumnsSelector); const isInScrollBottomArea = React.useRef(false); const handleGridScroll = React.useCallback(() => { + logger.debug('Checking if scroll position reached bottom'); + if (!containerSizes) { return; } const scrollPosition = apiRef.current.getScrollPosition(); + + // console.log(containerSizes) + // console.log(scrollPosition.top) + + // const maxScrollHeight = containerSizes!.renderingZoneScrollHeight; + + // const page = lastState.rendering.virtualPage; + // const nextPage = maxScrollHeight > 0 ? Math.floor(scrollPosition.top / maxScrollHeight) : 0; + // console.log(`${page} - ${nextPage}`) + const scrollPositionBottom = scrollPosition.top + containerSizes.windowSizes.height + options.scrollEndThreshold; @@ -29,7 +44,8 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { if ( scrollPositionBottom >= containerSizes.dataContainerSizes.height && - !isInScrollBottomArea.current + !isInScrollBottomArea.current && + !options.rowCount ) { const rowScrollEndParam: GridRowScrollEndParams = { api: apiRef, @@ -40,8 +56,35 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { apiRef.current.publishEvent(GRID_ROWS_SCROLL_END, rowScrollEndParam); isInScrollBottomArea.current = true; } - }, [options, containerSizes, apiRef, visibleColumns]); + }, [logger, options, containerSizes, apiRef, visibleColumns]); + + const handleGridVirtualPageChange = React.useCallback( + (params: GridVirtualPageChangeParams) => { + logger.debug('Virtual page changed'); + + if(!containerSizes || !options.loadRows) { + return; + } + + const state = apiRef.current.getState(); + const loadedRowsCount = Object.keys(state.rows.idRowsLookup).length; + const newRowsBatchStartIndex = (params.nextPage + 1) * containerSizes.viewportPageSize; + + if (loadedRowsCount > newRowsBatchStartIndex) { + return; + } + + const newRowsBatch = options.loadRows({ + startIndex: newRowsBatchStartIndex, + viewportPageSize: containerSizes.viewportPageSize, + }); + + if (newRowsBatch.length) { + apiRef.current.loadRows(newRowsBatchStartIndex, containerSizes.viewportPageSize ,newRowsBatch); + } + }, [logger, options, containerSizes, apiRef]); useGridApiEventHandler(apiRef, GRID_ROWS_SCROLL, handleGridScroll); + useGridApiEventHandler(apiRef, GRID_VIRTUAL_PAGE_CHANGE, handleGridVirtualPageChange); useGridApiOptionHandler(apiRef, GRID_ROWS_SCROLL_END, options.onRowsScrollEnd); }; diff --git a/packages/grid/_modules_/grid/hooks/features/rows/gridRowsState.ts b/packages/grid/_modules_/grid/hooks/features/rows/gridRowsState.ts index 2509273373dbc..700612961289f 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/gridRowsState.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/gridRowsState.ts @@ -6,8 +6,8 @@ export interface InternalGridRowsState { totalRowCount: number; } -export const getInitialGridRowState: () => InternalGridRowsState = () => ({ +export const getInitialGridRowState: (rowCount?: number) => InternalGridRowsState = (rowsCount = 0) => ({ idRowsLookup: {}, - allRows: [], + allRows: new Array(rowsCount).fill(null), totalRowCount: 0, }); diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts index 12d11f5f6a7d1..8365b4edd0d5e 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts @@ -36,13 +36,13 @@ export function convertGridRowsPropToState( rowIdGetter?: GridRowIdGetter, ): InternalGridRowsState { const state: InternalGridRowsState = { - ...getInitialGridRowState(), + ...getInitialGridRowState(totalRowCount), totalRowCount: totalRowCount && totalRowCount > rows.length ? totalRowCount : rows.length, }; - rows.forEach((rowData) => { + rows.forEach((rowData, index) => { const id = getGridRowId(rowData, rowIdGetter); - state.allRows.push(id); + state.allRows[index] = id; state.idRowsLookup[id] = rowData; }); @@ -114,6 +114,38 @@ export const useGridRows = ( [apiRef], ); + const loadRows = React.useCallback( + (startIndex: number, pageSize: number, newRows: GridRowModel[]) => { + logger.debug(`Loading rows from index:${startIndex} to index:${startIndex + pageSize}`); + + const newRowsToState = convertGridRowsPropToState( + newRows, + newRows.length, + getRowIdProp, + ); + + setGridState((state) => { + const allRowsUpdated = state.rows.allRows.map((row, index) => { + if (index >= startIndex && index < startIndex + pageSize) { + return newRowsToState.allRows[index - startIndex]; + } + return row; + }); + + internalRowsState.current = { + allRows: allRowsUpdated, + idRowsLookup: { ...state.rows.idRowsLookup, ...newRowsToState.idRowsLookup }, + totalRowCount: state.rows.totalRowCount, + }; + + return { ...state, rows: internalRowsState.current}; + }); + + forceUpdate(); + apiRef.current.updateViewport(); + apiRef.current.applySorting(); + }, [logger, apiRef, getRowIdProp, setGridState, forceUpdate]); + const setRows = React.useCallback( (allNewRows: GridRowModel[]) => { logger.debug(`updating all rows, new length ${allNewRows.length}`); @@ -222,6 +254,7 @@ export const useGridRows = ( getAllRowIds, setRows, updateRows, + loadRows, }; useGridApiMethod(apiRef, rowApi, 'GridRowApi'); }; diff --git a/packages/grid/_modules_/grid/hooks/features/virtualization/useGridVirtualRows.ts b/packages/grid/_modules_/grid/hooks/features/virtualization/useGridVirtualRows.ts index 4b1822cdeed64..22c0652d22928 100644 --- a/packages/grid/_modules_/grid/hooks/features/virtualization/useGridVirtualRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/virtualization/useGridVirtualRows.ts @@ -3,6 +3,7 @@ import { GRID_RESIZE, GRID_NATIVE_SCROLL, GRID_ROWS_SCROLL, + GRID_VIRTUAL_PAGE_CHANGE, } from '../../../constants/eventsConstants'; import { GridApiRef } from '../../../models/api/gridApiRef'; import { GridVirtualizationApi } from '../../../models/api/gridVirtualizationApi'; @@ -154,6 +155,12 @@ export const useGridVirtualRows = ( if (containerProps.isVirtualized && page !== nextPage) { setRenderingState({ virtualPage: nextPage }); logger.debug(`Changing page from ${page} to ${nextPage}`); + + apiRef.current.publishEvent(GRID_VIRTUAL_PAGE_CHANGE, { + currentPage: page, + nextPage, + api: apiRef + }); requireRerender = true; } else { if (!containerProps.isVirtualized && page > 0) { diff --git a/packages/grid/_modules_/grid/models/api/gridRowApi.ts b/packages/grid/_modules_/grid/models/api/gridRowApi.ts index 93cce25d8889e..4012d694dd05d 100644 --- a/packages/grid/_modules_/grid/models/api/gridRowApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridRowApi.ts @@ -42,4 +42,11 @@ export interface GridRowApi { * @param id */ getRowFromId: (id: GridRowId) => GridRowModel; + /** + * Loads a new subset of Rows. + * @param startIndex + * @param pageSize + * @param rows + */ + loadRows: (startIndex: number, pageSize: number, rows: GridRowModel[]) => void; } diff --git a/packages/grid/_modules_/grid/models/gridOptions.tsx b/packages/grid/_modules_/grid/models/gridOptions.tsx index 0a831fcd5bdf9..bfb656a53c107 100644 --- a/packages/grid/_modules_/grid/models/gridOptions.tsx +++ b/packages/grid/_modules_/grid/models/gridOptions.tsx @@ -171,6 +171,10 @@ export interface GridOptions { * Callback fired when a cell is rendered, returns true if the cell is editable. */ isCellEditable?: (params: GridCellParams) => boolean; + /** + * + */ + loadRows?: (params) => any; /** * Set the locale text of the grid. * You can find all the translation keys supported in [the source](https://github.com/mui-org/material-ui-x/blob/HEAD/packages/grid/_modules_/grid/constants/localeTextConstants.ts) in the GitHub repository. diff --git a/packages/grid/_modules_/grid/models/params/gridVirtualPageChangeParas.ts b/packages/grid/_modules_/grid/models/params/gridVirtualPageChangeParas.ts new file mode 100644 index 0000000000000..52bd9f23d0489 --- /dev/null +++ b/packages/grid/_modules_/grid/models/params/gridVirtualPageChangeParas.ts @@ -0,0 +1,17 @@ +/** + * Object passed as parameter of the virtual page change event. + */ +export interface GridVirtualPageChangeParams { + /** + * The current page. + */ + currentPage: number; + /** + * The next page. + */ + nextPage: number; + /** + * Api that let you manipulate the grid. + */ + api: any; +} diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 409753bfc4848..c087a333c3a61 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -4,6 +4,7 @@ import Typography from '@material-ui/core/Typography'; import Button from '@material-ui/core/Button'; import Popper from '@material-ui/core/Popper'; import Paper from '@material-ui/core/Paper'; + import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; import { GridCellValue, @@ -914,3 +915,57 @@ export function SwitchVirtualization() {
); } + +export function InfiniteLoader() { + const data = { + rows: [ + { + id: 0, + brand: 'Nike', + }, + { + id: 1, + brand: 'Adidas', + }, + { + id: 2, + brand: 'Puma', + }, + { + id: 3, + brand: 'SB', + }, + { + id: 4, + brand: 'New Balance', + }, + { + id: 5, + brand: 'Reebok', + }, + ], + columns: [{ field: 'brand', flex: 1 }], + }; + + return ( +
+ { + const newRowsBatch: any = []; + while(newRowsBatch.length < params.viewportPageSize) { + const id = Math.floor(Math.random() * 1000); + newRowsBatch.push({ + id, + brand: `Jordan ${id}`, + }); + }; + + return newRowsBatch; + }} + /> +
+ ); +} From 21196bdf783f51acf48b9e55550404c2ec08a5a4 Mon Sep 17 00:00:00 2001 From: Danail H Date: Thu, 20 May 2021 15:34:55 +0300 Subject: [PATCH 02/27] Initial version --- .../grid/components/GridViewport.tsx | 2 +- .../export/seralizers/csvSeraliser.ts | 2 +- .../hooks/features/filter/useGridFilter.ts | 4 +++ .../infiniteLoader/useGridInfiniteLoader.ts | 27 ++++++++++++++----- .../grid/hooks/features/rows/gridRowsState.ts | 4 ++- .../grid/hooks/features/rows/useGridRows.ts | 12 ++++----- .../features/sorting/gridSortingSelector.ts | 3 ++- .../hooks/features/sorting/useGridSorting.ts | 7 +++++ .../virtualization/useGridVirtualRows.ts | 2 +- ...aras.ts => gridVirtualPageChangeParams.ts} | 0 .../src/stories/grid-rows.stories.tsx | 8 ++++-- 11 files changed, 50 insertions(+), 21 deletions(-) rename packages/grid/_modules_/grid/models/params/{gridVirtualPageChangeParas.ts => gridVirtualPageChangeParams.ts} (100%) diff --git a/packages/grid/_modules_/grid/components/GridViewport.tsx b/packages/grid/_modules_/grid/components/GridViewport.tsx index c9a5e4f94addb..9d1b072c4206a 100644 --- a/packages/grid/_modules_/grid/components/GridViewport.tsx +++ b/packages/grid/_modules_/grid/components/GridViewport.tsx @@ -64,7 +64,7 @@ export const GridViewport: ViewportType = React.forwardRef( rowIndex={renderState.renderContext!.firstRowIdx! + idx} > - {id === null ? ( + {id.toString().indexOf('null-') === 0 ? (
EMPTY
) : ( , getCellValue: (id: GridRowId, field: string) => GridCellValue, ): string { - let rowIds = [...rows.keys()]; + let rowIds = [...rows.keys()].filter((id) => id.toString().indexOf('null-') !== 0); const selectedRowIds = Object.keys(selectedRows); if (selectedRowIds.length) { diff --git a/packages/grid/_modules_/grid/hooks/features/filter/useGridFilter.ts b/packages/grid/_modules_/grid/hooks/features/filter/useGridFilter.ts index eb5b7c966c2b2..25b6e012898b1 100644 --- a/packages/grid/_modules_/grid/hooks/features/filter/useGridFilter.ts +++ b/packages/grid/_modules_/grid/hooks/features/filter/useGridFilter.ts @@ -92,6 +92,10 @@ export const useGridFilter = (apiRef: GridApiRef, rowsProp: GridRowsProp): void const rows = sortedGridRowsSelector(state); rows.forEach((row: GridRowModel, id: GridRowId) => { + if (id.toString().indexOf('null-') === 0) { + return; + } + const params = apiRef.current.getCellParams(id, filterItem.columnField!); const isShown = applyFilterOnRow(params); diff --git a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts index bb749848cea23..d1362ca5e6584 100644 --- a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts +++ b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts @@ -2,12 +2,16 @@ import * as React from 'react'; import { optionsSelector } from '../../utils/optionsSelector'; import { GridApiRef } from '../../../models/api/gridApiRef'; import { useGridSelector } from '../core/useGridSelector'; -import { GRID_ROWS_SCROLL, GRID_ROWS_SCROLL_END, GRID_VIRTUAL_PAGE_CHANGE } from '../../../constants/eventsConstants'; +import { + GRID_ROWS_SCROLL, + GRID_ROWS_SCROLL_END, + GRID_VIRTUAL_PAGE_CHANGE, +} from '../../../constants/eventsConstants'; import { gridContainerSizesSelector } from '../../root/gridContainerSizesSelector'; import { useGridApiEventHandler, useGridApiOptionHandler } from '../../root/useGridApiEventHandler'; import { GridRowScrollEndParams } from '../../../models/params/gridRowScrollEndParams'; import { visibleGridColumnsSelector } from '../columns/gridColumnsSelector'; -import { GridVirtualPageChangeParams } from '../../../models/params/gridvirtualPageChangeParas'; +import { GridVirtualPageChangeParams } from '../../../models/params/gridVirtualPageChangeParams'; import { useLogger } from '../../utils/useLogger'; export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { @@ -62,15 +66,18 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { (params: GridVirtualPageChangeParams) => { logger.debug('Virtual page changed'); - if(!containerSizes || !options.loadRows) { + if (!containerSizes || !options.loadRows) { return; } const state = apiRef.current.getState(); - const loadedRowsCount = Object.keys(state.rows.idRowsLookup).length; const newRowsBatchStartIndex = (params.nextPage + 1) * containerSizes.viewportPageSize; + const toBeLoadedRange: any = [...state.rows.allRows].splice( + newRowsBatchStartIndex, + containerSizes.viewportPageSize, + ); - if (loadedRowsCount > newRowsBatchStartIndex) { + if (!toBeLoadedRange.includes(null)) { return; } @@ -80,9 +87,15 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { }); if (newRowsBatch.length) { - apiRef.current.loadRows(newRowsBatchStartIndex, containerSizes.viewportPageSize ,newRowsBatch); + apiRef.current.loadRows( + newRowsBatchStartIndex, + containerSizes.viewportPageSize, + newRowsBatch, + ); } - }, [logger, options, containerSizes, apiRef]); + }, + [logger, options, containerSizes, apiRef], + ); useGridApiEventHandler(apiRef, GRID_ROWS_SCROLL, handleGridScroll); useGridApiEventHandler(apiRef, GRID_VIRTUAL_PAGE_CHANGE, handleGridVirtualPageChange); diff --git a/packages/grid/_modules_/grid/hooks/features/rows/gridRowsState.ts b/packages/grid/_modules_/grid/hooks/features/rows/gridRowsState.ts index 700612961289f..ae145ae643e85 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/gridRowsState.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/gridRowsState.ts @@ -6,7 +6,9 @@ export interface InternalGridRowsState { totalRowCount: number; } -export const getInitialGridRowState: (rowCount?: number) => InternalGridRowsState = (rowsCount = 0) => ({ +export const getInitialGridRowState: (rowCount?: number) => InternalGridRowsState = ( + rowsCount = 0, +) => ({ idRowsLookup: {}, allRows: new Array(rowsCount).fill(null), totalRowCount: 0, diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts index 8365b4edd0d5e..d24d124589ecb 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts @@ -118,11 +118,7 @@ export const useGridRows = ( (startIndex: number, pageSize: number, newRows: GridRowModel[]) => { logger.debug(`Loading rows from index:${startIndex} to index:${startIndex + pageSize}`); - const newRowsToState = convertGridRowsPropToState( - newRows, - newRows.length, - getRowIdProp, - ); + const newRowsToState = convertGridRowsPropToState(newRows, newRows.length, getRowIdProp); setGridState((state) => { const allRowsUpdated = state.rows.allRows.map((row, index) => { @@ -138,13 +134,15 @@ export const useGridRows = ( totalRowCount: state.rows.totalRowCount, }; - return { ...state, rows: internalRowsState.current}; + return { ...state, rows: internalRowsState.current }; }); forceUpdate(); apiRef.current.updateViewport(); apiRef.current.applySorting(); - }, [logger, apiRef, getRowIdProp, setGridState, forceUpdate]); + }, + [logger, apiRef, getRowIdProp, setGridState, forceUpdate], + ); const setRows = React.useCallback( (allNewRows: GridRowModel[]) => { diff --git a/packages/grid/_modules_/grid/hooks/features/sorting/gridSortingSelector.ts b/packages/grid/_modules_/grid/hooks/features/sorting/gridSortingSelector.ts index a9f44a9b9e35b..da4ff9031fa17 100644 --- a/packages/grid/_modules_/grid/hooks/features/sorting/gridSortingSelector.ts +++ b/packages/grid/_modules_/grid/hooks/features/sorting/gridSortingSelector.ts @@ -33,7 +33,8 @@ export const sortedGridRowsSelector = createSelector< (sortedIds: GridRowId[], idRowsLookup: GridRowsLookup) => { const map = new Map(); sortedIds.forEach((id) => { - map.set(id, idRowsLookup[id]); + const normalizeId = id !== null ? id : `null-${Math.floor(Math.random() * 1000)}`; + map.set(normalizeId, idRowsLookup[id]); }); return map; }, diff --git a/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts b/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts index a993bda486a09..1a6e6e19245ee 100644 --- a/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts +++ b/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts @@ -174,7 +174,9 @@ export const useGridSorting = (apiRef: GridApiRef, rowsProp: GridRowsProp) => { if (sortModel.length > 0) { const comparatorList = buildComparatorList(sortModel); logger.debug('Sorting rows with ', sortModel); + const rowIdsLength = rowIds.length; sorted = rowIds + .filter((id) => id !== null) .map((id) => { return comparatorList.map((colComparator) => { return getSortCellParams(id, colComparator.field); @@ -182,6 +184,11 @@ export const useGridSorting = (apiRef: GridApiRef, rowsProp: GridRowsProp) => { }) .sort(comparatorListAggregate(comparatorList)) .map((field) => field[0].id); + + if (rowIdsLength !== sorted.length) { + const NullArray = new Array(rowIdsLength - sorted.length).fill(null); + sorted = sorted.concat(NullArray); + } } setGridState((oldState) => { diff --git a/packages/grid/_modules_/grid/hooks/features/virtualization/useGridVirtualRows.ts b/packages/grid/_modules_/grid/hooks/features/virtualization/useGridVirtualRows.ts index 22c0652d22928..e84cb283d4c19 100644 --- a/packages/grid/_modules_/grid/hooks/features/virtualization/useGridVirtualRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/virtualization/useGridVirtualRows.ts @@ -159,7 +159,7 @@ export const useGridVirtualRows = ( apiRef.current.publishEvent(GRID_VIRTUAL_PAGE_CHANGE, { currentPage: page, nextPage, - api: apiRef + api: apiRef, }); requireRerender = true; } else { diff --git a/packages/grid/_modules_/grid/models/params/gridVirtualPageChangeParas.ts b/packages/grid/_modules_/grid/models/params/gridVirtualPageChangeParams.ts similarity index 100% rename from packages/grid/_modules_/grid/models/params/gridVirtualPageChangeParas.ts rename to packages/grid/_modules_/grid/models/params/gridVirtualPageChangeParams.ts diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index c087a333c3a61..c7249c1d9808c 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -20,6 +20,7 @@ import { GridEditCellPropsParams, GridEditRowModelParams, GRID_CELL_EDIT_ENTER, + GridToolbar, } from '@material-ui/x-grid'; import { useDemoData } from '@material-ui/x-grid-data-generator'; import { action } from '@storybook/addon-actions'; @@ -953,15 +954,18 @@ export function InfiniteLoader() { {...data} hideFooterPagination rowCount={20} + components={{ + Toolbar: GridToolbar, + }} loadRows={(params) => { const newRowsBatch: any = []; - while(newRowsBatch.length < params.viewportPageSize) { + while (newRowsBatch.length < params.viewportPageSize) { const id = Math.floor(Math.random() * 1000); newRowsBatch.push({ id, brand: `Jordan ${id}`, }); - }; + } return newRowsBatch; }} From ddc90de55df8ba80b692181eec8a06f2db766645 Mon Sep 17 00:00:00 2001 From: Danail H Date: Thu, 20 May 2021 16:54:50 +0300 Subject: [PATCH 03/27] Remove commented out code --- .../features/infiniteLoader/useGridInfiniteLoader.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts index d1362ca5e6584..0289df75ace7d 100644 --- a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts +++ b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts @@ -30,15 +30,6 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const scrollPosition = apiRef.current.getScrollPosition(); - // console.log(containerSizes) - // console.log(scrollPosition.top) - - // const maxScrollHeight = containerSizes!.renderingZoneScrollHeight; - - // const page = lastState.rendering.virtualPage; - // const nextPage = maxScrollHeight > 0 ? Math.floor(scrollPosition.top / maxScrollHeight) : 0; - // console.log(`${page} - ${nextPage}`) - const scrollPositionBottom = scrollPosition.top + containerSizes.windowSizes.height + options.scrollEndThreshold; From 4c0a5b1f23735c5f9a39bc1c460d454acca811df Mon Sep 17 00:00:00 2001 From: Danail H Date: Mon, 24 May 2021 14:27:24 +0300 Subject: [PATCH 04/27] Add base skeleton row logic and styles --- .../_modules_/grid/components/GridRow.tsx | 5 +- .../grid/components/GridViewport.tsx | 15 ++++- .../grid/components/cell/GridRowCells.tsx | 2 - .../grid/components/cell/GridSkeletonCell.tsx | 42 ++++++++++++ .../components/cell/GridSkeletonRowCells.tsx | 60 +++++++++++++++++ .../components/containers/GridRootStyles.ts | 66 ++++++++++++++++++- .../grid/constants/cssClassesConstants.ts | 1 + .../features/sorting/gridSortingSelector.ts | 1 + 8 files changed, 182 insertions(+), 10 deletions(-) create mode 100644 packages/grid/_modules_/grid/components/cell/GridSkeletonCell.tsx create mode 100644 packages/grid/_modules_/grid/components/cell/GridSkeletonRowCells.tsx diff --git a/packages/grid/_modules_/grid/components/GridRow.tsx b/packages/grid/_modules_/grid/components/GridRow.tsx index 8b564f3e40250..51f6775e9046b 100644 --- a/packages/grid/_modules_/grid/components/GridRow.tsx +++ b/packages/grid/_modules_/grid/components/GridRow.tsx @@ -30,7 +30,6 @@ export const GridRow = (props: GridRowProps) => { const apiRef = React.useContext(GridApiContext); const rowHeight = useGridSelector(apiRef, gridDensityRowHeightSelector); const options = useGridSelector(apiRef, optionsSelector); - const rowId = id || `key-${Math.floor(Math.random() * 1000)}`; const publish = React.useCallback( (eventName: string) => (event: React.MouseEvent) => { @@ -70,8 +69,8 @@ export const GridRow = (props: GridRowProps) => { return (
( renderState.renderContext.lastRowIdx!, ); - return renderedRows.map(([id, row], idx) => ( + return renderedRows.map(([id], idx) => ( ( > {id.toString().indexOf('null-') === 0 ? ( -
EMPTY
+ ) : ( { + const { colIndex, height, rowIndex, showRightBorder, width } = props; + + const cellRef = React.useRef(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 ( +
+ ); +}); + +GridSkeletonCell.displayName = 'GridSkeletonCell'; diff --git a/packages/grid/_modules_/grid/components/cell/GridSkeletonRowCells.tsx b/packages/grid/_modules_/grid/components/cell/GridSkeletonRowCells.tsx new file mode 100644 index 0000000000000..ef3e9c60504fb --- /dev/null +++ b/packages/grid/_modules_/grid/components/cell/GridSkeletonRowCells.tsx @@ -0,0 +1,60 @@ +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((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 ( + + {skeletonCellsProps.map((skeletonCellProps) => ( + + ))} + + ); +}); +GridSkeletonRowCells.displayName = 'GridSkeletonRowCells'; diff --git a/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts b/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts index 42a6068fda7b2..fb5c3b9e38138 100644 --- a/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts +++ b/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts @@ -82,7 +82,7 @@ export const useStyles = makeStyles( alignItems: 'center', overflow: 'hidden', }, - '& .MuiDataGrid-colCell, & .MuiDataGrid-cell': { + '& .MuiDataGrid-colCell, & .MuiDataGrid-cell, & .MuiDataGrid-skeletonCell': { WebkitTapHighlightColor: 'transparent', lineHeight: null, padding: '0 10px', @@ -238,6 +238,25 @@ export const useStyles = makeStyles( }, }, }, + '& .MuiDataGrid-skeletonCell': { + display: 'block', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + borderBottom: `1px solid ${borderColor}`, + paddingTop: theme.spacing(1), + paddingBottom: theme.spacing(1), + '&::before': { + content: '""', + display: 'block', + width: '100%', + height: '100%', + '-webkit-animation': `3000ms ${theme.transitions.easing.easeInOut} $skeleton infinite`, + animation: `3000ms ${theme.transitions.easing.easeInOut} $skeleton infinite`, + background: theme.palette.grey[300], + willChange: 'transform-origin, transform, opacity', + }, + }, '& .MuiDataGrid-cell': { display: 'block', overflow: 'hidden', @@ -375,7 +394,50 @@ export const useStyles = makeStyles( }, }; } - return gridStyle; + return { + ...gridStyle, + '@keyframes skeleton': { + '0%': { + opacity: 0.3, + transform: 'scaleX(0)', + transformOrigin: 'left', + }, + '20%': { + opacity: 1, + transform: 'scaleX(1)', + transformOrigin: 'left', + }, + '28%': { + transform: 'scaleX(1)', + transformOrigin: 'right', + }, + '51%': { + transform: 'scaleX(0)', + transformOrigin: 'right', + }, + '58%': { + transform: 'scaleX(0)', + transformOrigin: 'right', + }, + '82%': { + transform: 'scaleX(1)', + transformOrigin: 'right', + }, + '83%': { + transform: 'scaleX(1)', + transformOrigin: 'left', + }, + '96%': { + transform: 'scaleX(0)', + transformOrigin: 'left', + }, + '100%': { + opacity: 0.3, + transform: 'scaleX(0)', + transformOrigin: 'left', + }, + }, + }; }, { name: 'MuiDataGrid' }, ); diff --git a/packages/grid/_modules_/grid/constants/cssClassesConstants.ts b/packages/grid/_modules_/grid/constants/cssClassesConstants.ts index d40426fcb540f..5e70204f2d43e 100644 --- a/packages/grid/_modules_/grid/constants/cssClassesConstants.ts +++ b/packages/grid/_modules_/grid/constants/cssClassesConstants.ts @@ -1,5 +1,6 @@ // CSS_CLASSES_CONSTANTS export const GRID_CELL_CSS_CLASS = 'MuiDataGrid-cell'; +export const GRID_SKELETON_CELL_CSS_CLASS = 'MuiDataGrid-skeletonCell'; export const GRID_ROW_CSS_CLASS = 'MuiDataGrid-row'; export const GRID_HEADER_CELL_CSS_CLASS = 'MuiDataGrid-colCell'; export const GRID_HEADER_CELL_SEPARATOR_RESIZABLE_CSS_CLASS = diff --git a/packages/grid/_modules_/grid/hooks/features/sorting/gridSortingSelector.ts b/packages/grid/_modules_/grid/hooks/features/sorting/gridSortingSelector.ts index da4ff9031fa17..02c7c3072de3e 100644 --- a/packages/grid/_modules_/grid/hooks/features/sorting/gridSortingSelector.ts +++ b/packages/grid/_modules_/grid/hooks/features/sorting/gridSortingSelector.ts @@ -33,6 +33,7 @@ export const sortedGridRowsSelector = createSelector< (sortedIds: GridRowId[], idRowsLookup: GridRowsLookup) => { const map = new Map(); sortedIds.forEach((id) => { + // TODO: think of a better null class name const normalizeId = id !== null ? id : `null-${Math.floor(Math.random() * 1000)}`; map.set(normalizeId, idRowsLookup[id]); }); From 1f94832fb7374277fe3b67fdef729ef27024ab37 Mon Sep 17 00:00:00 2001 From: Danail H Date: Mon, 24 May 2021 14:28:55 +0300 Subject: [PATCH 05/27] Make new skeleton components public --- packages/grid/_modules_/grid/components/cell/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/grid/_modules_/grid/components/cell/index.ts b/packages/grid/_modules_/grid/components/cell/index.ts index 8d5f70eb20a5c..0b4303bb6c6ad 100644 --- a/packages/grid/_modules_/grid/components/cell/index.ts +++ b/packages/grid/_modules_/grid/components/cell/index.ts @@ -2,3 +2,5 @@ export * from './GridCell'; export * from './GridEditInputCell'; export * from './GridEmptyCell'; export * from './GridRowCells'; +export * from './GridSkeletonRowCells'; +export * from './GridSkeletonCell'; From e520dbcbe6d43e907039eb3365e139e43c65fea7 Mon Sep 17 00:00:00 2001 From: Danail H Date: Tue, 25 May 2021 17:32:29 +0300 Subject: [PATCH 06/27] Sort out loading on scroll and sorting --- .../infiniteLoader/useGridInfiniteLoader.ts | 83 +++++++++++++++++-- .../grid/hooks/features/rows/useGridRows.ts | 14 +++- .../features/sorting/gridSortingSelector.ts | 4 +- .../_modules_/grid/models/api/gridRowApi.ts | 7 +- .../src/stories/grid-rows.stories.tsx | 1 + 5 files changed, 97 insertions(+), 12 deletions(-) diff --git a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts index 0289df75ace7d..89bb0e66d9e42 100644 --- a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts +++ b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts @@ -3,8 +3,10 @@ import { optionsSelector } from '../../utils/optionsSelector'; import { GridApiRef } from '../../../models/api/gridApiRef'; import { useGridSelector } from '../core/useGridSelector'; import { + GRID_FILTER_MODEL_CHANGE, GRID_ROWS_SCROLL, GRID_ROWS_SCROLL_END, + GRID_SORT_MODEL_CHANGE, GRID_VIRTUAL_PAGE_CHANGE, } from '../../../constants/eventsConstants'; import { gridContainerSizesSelector } from '../../root/gridContainerSizesSelector'; @@ -12,13 +14,23 @@ import { useGridApiEventHandler, useGridApiOptionHandler } from '../../root/useG import { GridRowScrollEndParams } from '../../../models/params/gridRowScrollEndParams'; import { visibleGridColumnsSelector } from '../columns/gridColumnsSelector'; import { GridVirtualPageChangeParams } from '../../../models/params/gridVirtualPageChangeParams'; +import { GridSortModelParams } from '../../../models/params/gridSortModelParams'; +import { GridFilterModelParams } from '../../../models/params/gridFilterModelParams'; import { useLogger } from '../../utils/useLogger'; +import { renderStateSelector } from '../virtualization/renderingStateSelector'; +import { unorderedGridRowIdsSelector } from '../rows/gridRowsSelector'; +import { gridSortModelSelector } from '../sorting/gridSortingSelector'; +import { filterGridStateSelector } from '../filter/gridFilterSelector'; export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const logger = useLogger('useGridInfiniteLoader'); const options = useGridSelector(apiRef, optionsSelector); const containerSizes = useGridSelector(apiRef, gridContainerSizesSelector); const visibleColumns = useGridSelector(apiRef, visibleGridColumnsSelector); + const renderState = useGridSelector(apiRef, renderStateSelector); + const allRows = useGridSelector(apiRef, unorderedGridRowIdsSelector); + const sortModel = useGridSelector(apiRef, gridSortModelSelector); + const filterState = useGridSelector(apiRef, filterGridStateSelector); const isInScrollBottomArea = React.useRef(false); const handleGridScroll = React.useCallback(() => { @@ -56,14 +68,21 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const handleGridVirtualPageChange = React.useCallback( (params: GridVirtualPageChangeParams) => { logger.debug('Virtual page changed'); - if (!containerSizes || !options.loadRows) { return; } - const state = apiRef.current.getState(); - const newRowsBatchStartIndex = (params.nextPage + 1) * containerSizes.viewportPageSize; - const toBeLoadedRange: any = [...state.rows.allRows].splice( + let nextPage = params.nextPage; + + if ( + params.nextPage > params.currentPage && + params.nextPage !== renderState.renderedSizes!.lastPage + ) { + nextPage = params.nextPage + 1; + } + + const newRowsBatchStartIndex = nextPage * containerSizes.viewportPageSize; + const toBeLoadedRange: any = [...allRows].splice( newRowsBatchStartIndex, containerSizes.viewportPageSize, ); @@ -75,6 +94,8 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const newRowsBatch = options.loadRows({ startIndex: newRowsBatchStartIndex, viewportPageSize: containerSizes.viewportPageSize, + sortingModel: sortModel, + filter: filterState, }); if (newRowsBatch.length) { @@ -85,10 +106,62 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { ); } }, - [logger, options, containerSizes, apiRef], + [logger, options, renderState, allRows, sortModel, containerSizes, filterState, apiRef], + ); + + const handleGridSortModelChange = React.useCallback( + (params: GridSortModelParams) => { + logger.debug('Sort model changed'); + + if (!containerSizes || !options.loadRows) { + return; + } + + const newRowsBatchStartIndex = renderState.virtualPage * containerSizes.viewportPageSize; + const newRowsBatch = options.loadRows({ + startIndex: newRowsBatchStartIndex, + viewportPageSize: containerSizes.viewportPageSize, + sortingModel: params.sortModel, + filter: filterState, + }); + + if (newRowsBatch.length) { + apiRef.current.loadRows( + newRowsBatchStartIndex, + containerSizes.viewportPageSize, + newRowsBatch, + true, + ); + } + }, + [logger, options, renderState, containerSizes, filterState, apiRef], + ); + + // TODO: Iron out the infite loader filter combination + const handleGridFilterModelChange = React.useCallback( + (params: GridFilterModelParams) => { + logger.debug('Filter model changed'); + if (!containerSizes || !options.loadRows) { + return; + } + + const newRowsBatch = options.loadRows({ + startIndex: 0, + viewportPageSize: containerSizes.viewportPageSize, + sortingModel: sortModel, + filter: params.filterModel, + }); + + if (newRowsBatch.length) { + apiRef.current.loadRows(0, containerSizes.viewportPageSize, newRowsBatch); + } + }, + [logger, options, containerSizes, sortModel, apiRef], ); useGridApiEventHandler(apiRef, GRID_ROWS_SCROLL, handleGridScroll); useGridApiEventHandler(apiRef, GRID_VIRTUAL_PAGE_CHANGE, handleGridVirtualPageChange); + useGridApiEventHandler(apiRef, GRID_SORT_MODEL_CHANGE, handleGridSortModelChange); + useGridApiEventHandler(apiRef, GRID_FILTER_MODEL_CHANGE, handleGridFilterModelChange); useGridApiOptionHandler(apiRef, GRID_ROWS_SCROLL_END, options.onRowsScrollEnd); }; diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts index d24d124589ecb..fe90a9d050c45 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts @@ -115,25 +115,31 @@ export const useGridRows = ( ); const loadRows = React.useCallback( - (startIndex: number, pageSize: number, newRows: GridRowModel[]) => { + (startIndex: number, pageSize: number, newRows: GridRowModel[], resetRows = false) => { logger.debug(`Loading rows from index:${startIndex} to index:${startIndex + pageSize}`); const newRowsToState = convertGridRowsPropToState(newRows, newRows.length, getRowIdProp); setGridState((state) => { - const allRowsUpdated = state.rows.allRows.map((row, index) => { + const allRows = resetRows + ? new Array(state.rows.totalRowCount).fill(null) + : state.rows.allRows; + const allRowsUpdated = allRows.map((row, index) => { if (index >= startIndex && index < startIndex + pageSize) { return newRowsToState.allRows[index - startIndex]; } return row; }); + const idRowsLookupUpdated = resetRows + ? newRowsToState.idRowsLookup + : { ...state.rows.idRowsLookup, ...newRowsToState.idRowsLookup }; + internalRowsState.current = { allRows: allRowsUpdated, - idRowsLookup: { ...state.rows.idRowsLookup, ...newRowsToState.idRowsLookup }, + idRowsLookup: idRowsLookupUpdated, totalRowCount: state.rows.totalRowCount, }; - return { ...state, rows: internalRowsState.current }; }); diff --git a/packages/grid/_modules_/grid/hooks/features/sorting/gridSortingSelector.ts b/packages/grid/_modules_/grid/hooks/features/sorting/gridSortingSelector.ts index 02c7c3072de3e..ddf15ba6ebc04 100644 --- a/packages/grid/_modules_/grid/hooks/features/sorting/gridSortingSelector.ts +++ b/packages/grid/_modules_/grid/hooks/features/sorting/gridSortingSelector.ts @@ -33,8 +33,8 @@ export const sortedGridRowsSelector = createSelector< (sortedIds: GridRowId[], idRowsLookup: GridRowsLookup) => { const map = new Map(); sortedIds.forEach((id) => { - // TODO: think of a better null class name - const normalizeId = id !== null ? id : `null-${Math.floor(Math.random() * 1000)}`; + // This was taken from the useId in the core + const normalizeId = id !== null ? id : `null-${Math.round(Math.random() * 1e5)}`; map.set(normalizeId, idRowsLookup[id]); }); return map; diff --git a/packages/grid/_modules_/grid/models/api/gridRowApi.ts b/packages/grid/_modules_/grid/models/api/gridRowApi.ts index 4012d694dd05d..be704559a5817 100644 --- a/packages/grid/_modules_/grid/models/api/gridRowApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridRowApi.ts @@ -48,5 +48,10 @@ export interface GridRowApi { * @param pageSize * @param rows */ - loadRows: (startIndex: number, pageSize: number, rows: GridRowModel[]) => void; + loadRows: ( + startIndex: number, + pageSize: number, + rows: GridRowModel[], + resetRows?: boolean, + ) => void; } diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index c7249c1d9808c..6d6a6b752c983 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -954,6 +954,7 @@ export function InfiniteLoader() { {...data} hideFooterPagination rowCount={20} + sortingMode="server" components={{ Toolbar: GridToolbar, }} From dd7af2c4351b9ebbbd93ea53d4fee9f144038a09 Mon Sep 17 00:00:00 2001 From: Danail H Date: Wed, 26 May 2021 16:49:24 +0300 Subject: [PATCH 07/27] Fix filter functionality --- .../infiniteLoader/useGridInfiniteLoader.ts | 51 ++++++++++--------- .../grid/hooks/features/rows/useGridRows.ts | 18 +++---- .../_modules_/grid/models/api/gridRowApi.ts | 7 +-- .../_modules_/grid/models/gridOptions.tsx | 3 +- .../grid/models/params/gridLoadRowsParams.ts | 43 ++++++++++++++++ .../_modules_/grid/models/params/index.ts | 1 + .../src/stories/grid-rows.stories.tsx | 26 +++++++++- 7 files changed, 108 insertions(+), 41 deletions(-) create mode 100644 packages/grid/_modules_/grid/models/params/gridLoadRowsParams.ts diff --git a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts index 89bb0e66d9e42..7a476b64c2735 100644 --- a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts +++ b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts @@ -21,6 +21,7 @@ import { renderStateSelector } from '../virtualization/renderingStateSelector'; import { unorderedGridRowIdsSelector } from '../rows/gridRowsSelector'; import { gridSortModelSelector } from '../sorting/gridSortingSelector'; import { filterGridStateSelector } from '../filter/gridFilterSelector'; +import { GridLoadRowsReturnValue } from '../../../models/params/gridLoadRowsParams'; export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const logger = useLogger('useGridInfiniteLoader'); @@ -91,19 +92,16 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { return; } - const newRowsBatch = options.loadRows({ + const { rows }: GridLoadRowsReturnValue = options.loadRows({ startIndex: newRowsBatchStartIndex, viewportPageSize: containerSizes.viewportPageSize, - sortingModel: sortModel, - filter: filterState, + sortModel, + filterModel: filterState, + api: apiRef, }); - if (newRowsBatch.length) { - apiRef.current.loadRows( - newRowsBatchStartIndex, - containerSizes.viewportPageSize, - newRowsBatch, - ); + if (rows.length) { + apiRef.current.loadRows(newRowsBatchStartIndex, containerSizes.viewportPageSize, rows); } }, [logger, options, renderState, allRows, sortModel, containerSizes, filterState, apiRef], @@ -118,26 +116,26 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { } const newRowsBatchStartIndex = renderState.virtualPage * containerSizes.viewportPageSize; - const newRowsBatch = options.loadRows({ + const { rows, rowCount }: GridLoadRowsReturnValue = options.loadRows({ startIndex: newRowsBatchStartIndex, viewportPageSize: containerSizes.viewportPageSize, - sortingModel: params.sortModel, - filter: filterState, + sortModel: params.sortModel, + filterModel: filterState, + api: apiRef, }); - if (newRowsBatch.length) { + if (rows.length) { apiRef.current.loadRows( newRowsBatchStartIndex, containerSizes.viewportPageSize, - newRowsBatch, - true, + rows, + rowCount, ); } }, [logger, options, renderState, containerSizes, filterState, apiRef], ); - // TODO: Iron out the infite loader filter combination const handleGridFilterModelChange = React.useCallback( (params: GridFilterModelParams) => { logger.debug('Filter model changed'); @@ -145,18 +143,25 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { return; } - const newRowsBatch = options.loadRows({ - startIndex: 0, + const newRowsBatchStartIndex = renderState.virtualPage * containerSizes.viewportPageSize; + const { rows, rowCount }: GridLoadRowsReturnValue = options.loadRows({ + startIndex: newRowsBatchStartIndex, viewportPageSize: containerSizes.viewportPageSize, - sortingModel: sortModel, - filter: params.filterModel, + sortModel, + filterModel: params.filterModel, + api: apiRef, }); - if (newRowsBatch.length) { - apiRef.current.loadRows(0, containerSizes.viewportPageSize, newRowsBatch); + if (rows.length) { + apiRef.current.loadRows( + newRowsBatchStartIndex, + containerSizes.viewportPageSize, + rows, + rowCount, + ); } }, - [logger, options, containerSizes, sortModel, apiRef], + [logger, options, containerSizes, sortModel, renderState, apiRef], ); useGridApiEventHandler(apiRef, GRID_ROWS_SCROLL, handleGridScroll); diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts index fe90a9d050c45..0da354b1969fe 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts @@ -115,15 +115,15 @@ export const useGridRows = ( ); const loadRows = React.useCallback( - (startIndex: number, pageSize: number, newRows: GridRowModel[], resetRows = false) => { + (startIndex: number, pageSize: number, newRows: GridRowModel[], rowCount?: number) => { logger.debug(`Loading rows from index:${startIndex} to index:${startIndex + pageSize}`); const newRowsToState = convertGridRowsPropToState(newRows, newRows.length, getRowIdProp); setGridState((state) => { - const allRows = resetRows - ? new Array(state.rows.totalRowCount).fill(null) - : state.rows.allRows; + // rowCount can be 0 + const allRows = + rowCount !== undefined ? new Array(rowCount).fill(null) : state.rows.allRows; const allRowsUpdated = allRows.map((row, index) => { if (index >= startIndex && index < startIndex + pageSize) { return newRowsToState.allRows[index - startIndex]; @@ -131,20 +131,20 @@ export const useGridRows = ( return row; }); - const idRowsLookupUpdated = resetRows - ? newRowsToState.idRowsLookup - : { ...state.rows.idRowsLookup, ...newRowsToState.idRowsLookup }; + const idRowsLookupUpdated = + rowCount !== undefined + ? newRowsToState.idRowsLookup + : { ...state.rows.idRowsLookup, ...newRowsToState.idRowsLookup }; internalRowsState.current = { allRows: allRowsUpdated, idRowsLookup: idRowsLookupUpdated, - totalRowCount: state.rows.totalRowCount, + totalRowCount: rowCount !== undefined ? rowCount : state.rows.totalRowCount, }; return { ...state, rows: internalRowsState.current }; }); forceUpdate(); - apiRef.current.updateViewport(); apiRef.current.applySorting(); }, [logger, apiRef, getRowIdProp, setGridState, forceUpdate], diff --git a/packages/grid/_modules_/grid/models/api/gridRowApi.ts b/packages/grid/_modules_/grid/models/api/gridRowApi.ts index be704559a5817..c3e85628a0fbf 100644 --- a/packages/grid/_modules_/grid/models/api/gridRowApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridRowApi.ts @@ -48,10 +48,5 @@ export interface GridRowApi { * @param pageSize * @param rows */ - loadRows: ( - startIndex: number, - pageSize: number, - rows: GridRowModel[], - resetRows?: boolean, - ) => void; + loadRows: (startIndex: number, pageSize: number, rows: GridRowModel[], rowCount?: number) => void; } diff --git a/packages/grid/_modules_/grid/models/gridOptions.tsx b/packages/grid/_modules_/grid/models/gridOptions.tsx index bfb656a53c107..a56609678525e 100644 --- a/packages/grid/_modules_/grid/models/gridOptions.tsx +++ b/packages/grid/_modules_/grid/models/gridOptions.tsx @@ -29,6 +29,7 @@ import { GridColumnOrderChangeParams } from './params/gridColumnOrderChangeParam import { GridResizeParams } from './params/gridResizeParams'; import { GridColumnResizeParams } from './params/gridColumnResizeParams'; import { GridColumnVisibilityChangeParams } from './params/gridColumnVisibilityChangeParams'; +import { GridLoadRowsParams, GridLoadRowsReturnValue } from './params/gridLoadRowsParams'; // TODO add multiSortKey /** @@ -174,7 +175,7 @@ export interface GridOptions { /** * */ - loadRows?: (params) => any; + loadRows?: (params: GridLoadRowsParams) => GridLoadRowsReturnValue; /** * Set the locale text of the grid. * You can find all the translation keys supported in [the source](https://github.com/mui-org/material-ui-x/blob/HEAD/packages/grid/_modules_/grid/constants/localeTextConstants.ts) in the GitHub repository. diff --git a/packages/grid/_modules_/grid/models/params/gridLoadRowsParams.ts b/packages/grid/_modules_/grid/models/params/gridLoadRowsParams.ts new file mode 100644 index 0000000000000..6a90778673298 --- /dev/null +++ b/packages/grid/_modules_/grid/models/params/gridLoadRowsParams.ts @@ -0,0 +1,43 @@ +import { GridFilterModel } from '../../hooks/features/filter/gridFilterModelState'; +import { GridRowData } from '../gridRows'; +import { GridSortModel } from '../gridSortModel'; + +/** + * Object passed as parameter to the [[loadRows]] option. + */ +export interface GridLoadRowsParams { + /** + * The start index from which rows needs to be loaded. + */ + startIndex: number; + /** + * The viewport page size. + */ + viewportPageSize: number; + /** + * The sort model used to sort the grid. + */ + sortModel: GridSortModel; + /** + * The filter model. + */ + filterModel: GridFilterModel; + /** + * GridApiRef that let you manipulate the grid. + */ + api: any; +} + +/** + * Object expected to be returned from the [[loadRows]] option. + */ +export interface GridLoadRowsReturnValue { + /** + * The start index from which rows needs to be loaded. + */ + rows: GridRowData[]; + /** + * The viewport page size. + */ + rowCount?: number; +} diff --git a/packages/grid/_modules_/grid/models/params/index.ts b/packages/grid/_modules_/grid/models/params/index.ts index 1a46417fabdd4..244b015905b0e 100644 --- a/packages/grid/_modules_/grid/models/params/index.ts +++ b/packages/grid/_modules_/grid/models/params/index.ts @@ -12,5 +12,6 @@ export * from './gridScrollParams'; export * from './gridSelectionModelChangeParams'; export * from './gridSortModelParams'; export * from './gridStateChangeParams'; +export * from './gridLoadRowsParams'; export * from './gridRowScrollEndParams'; export * from './gridResizeParams'; diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 6d6a6b752c983..434bbeb165723 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -944,17 +944,34 @@ export function InfiniteLoader() { id: 5, brand: 'Reebok', }, + { + id: 6, + brand: 'Test 6', + }, + { + id: 7, + brand: 'Test 7', + }, + { + id: 8, + brand: 'Test 8', + }, + { + id: 9, + brand: 'Test 8', + }, ], columns: [{ field: 'brand', flex: 1 }], }; return ( -
+
From b841f961b00d44ad700f137171b9a78667235be3 Mon Sep 17 00:00:00 2001 From: Danail H Date: Thu, 27 May 2021 11:19:13 +0300 Subject: [PATCH 08/27] rename loadRows option to getRows --- .../infiniteLoader/useGridInfiniteLoader.ts | 14 +++++++------- .../grid/_modules_/grid/models/gridOptions.tsx | 6 +++--- ...{gridLoadRowsParams.ts => gridGetRowsParams.ts} | 8 ++++---- .../grid/_modules_/grid/models/params/index.ts | 2 +- .../storybook/src/stories/grid-rows.stories.tsx | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) rename packages/grid/_modules_/grid/models/params/{gridLoadRowsParams.ts => gridGetRowsParams.ts} (78%) diff --git a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts index 7a476b64c2735..141223301e751 100644 --- a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts +++ b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts @@ -21,7 +21,7 @@ import { renderStateSelector } from '../virtualization/renderingStateSelector'; import { unorderedGridRowIdsSelector } from '../rows/gridRowsSelector'; import { gridSortModelSelector } from '../sorting/gridSortingSelector'; import { filterGridStateSelector } from '../filter/gridFilterSelector'; -import { GridLoadRowsReturnValue } from '../../../models/params/gridLoadRowsParams'; +import { GridGetRowsReturnValue } from '../../../models/params/gridGetRowsParams'; export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const logger = useLogger('useGridInfiniteLoader'); @@ -69,7 +69,7 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const handleGridVirtualPageChange = React.useCallback( (params: GridVirtualPageChangeParams) => { logger.debug('Virtual page changed'); - if (!containerSizes || !options.loadRows) { + if (!containerSizes || !options.getRows) { return; } @@ -92,7 +92,7 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { return; } - const { rows }: GridLoadRowsReturnValue = options.loadRows({ + const { rows }: GridGetRowsReturnValue = options.getRows({ startIndex: newRowsBatchStartIndex, viewportPageSize: containerSizes.viewportPageSize, sortModel, @@ -111,12 +111,12 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { (params: GridSortModelParams) => { logger.debug('Sort model changed'); - if (!containerSizes || !options.loadRows) { + if (!containerSizes || !options.getRows) { return; } const newRowsBatchStartIndex = renderState.virtualPage * containerSizes.viewportPageSize; - const { rows, rowCount }: GridLoadRowsReturnValue = options.loadRows({ + const { rows, rowCount }: GridGetRowsReturnValue = options.getRows({ startIndex: newRowsBatchStartIndex, viewportPageSize: containerSizes.viewportPageSize, sortModel: params.sortModel, @@ -139,12 +139,12 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const handleGridFilterModelChange = React.useCallback( (params: GridFilterModelParams) => { logger.debug('Filter model changed'); - if (!containerSizes || !options.loadRows) { + if (!containerSizes || !options.getRows) { return; } const newRowsBatchStartIndex = renderState.virtualPage * containerSizes.viewportPageSize; - const { rows, rowCount }: GridLoadRowsReturnValue = options.loadRows({ + const { rows, rowCount }: GridGetRowsReturnValue = options.getRows({ startIndex: newRowsBatchStartIndex, viewportPageSize: containerSizes.viewportPageSize, sortModel, diff --git a/packages/grid/_modules_/grid/models/gridOptions.tsx b/packages/grid/_modules_/grid/models/gridOptions.tsx index a56609678525e..b7ae609c08e35 100644 --- a/packages/grid/_modules_/grid/models/gridOptions.tsx +++ b/packages/grid/_modules_/grid/models/gridOptions.tsx @@ -29,7 +29,7 @@ import { GridColumnOrderChangeParams } from './params/gridColumnOrderChangeParam import { GridResizeParams } from './params/gridResizeParams'; import { GridColumnResizeParams } from './params/gridColumnResizeParams'; import { GridColumnVisibilityChangeParams } from './params/gridColumnVisibilityChangeParams'; -import { GridLoadRowsParams, GridLoadRowsReturnValue } from './params/gridLoadRowsParams'; +import { GridGetRowsParams, GridGetRowsReturnValue } from './params/gridGetRowsParams'; // TODO add multiSortKey /** @@ -173,9 +173,9 @@ export interface GridOptions { */ isCellEditable?: (params: GridCellParams) => boolean; /** - * + * Callback fired when rowCount is set and virtual pages change. */ - loadRows?: (params: GridLoadRowsParams) => GridLoadRowsReturnValue; + getRows?: (params: GridGetRowsParams) => GridGetRowsReturnValue; /** * Set the locale text of the grid. * You can find all the translation keys supported in [the source](https://github.com/mui-org/material-ui-x/blob/HEAD/packages/grid/_modules_/grid/constants/localeTextConstants.ts) in the GitHub repository. diff --git a/packages/grid/_modules_/grid/models/params/gridLoadRowsParams.ts b/packages/grid/_modules_/grid/models/params/gridGetRowsParams.ts similarity index 78% rename from packages/grid/_modules_/grid/models/params/gridLoadRowsParams.ts rename to packages/grid/_modules_/grid/models/params/gridGetRowsParams.ts index 6a90778673298..c298579eeb289 100644 --- a/packages/grid/_modules_/grid/models/params/gridLoadRowsParams.ts +++ b/packages/grid/_modules_/grid/models/params/gridGetRowsParams.ts @@ -3,9 +3,9 @@ import { GridRowData } from '../gridRows'; import { GridSortModel } from '../gridSortModel'; /** - * Object passed as parameter to the [[loadRows]] option. + * Object passed as parameter to the [[getRows]] option. */ -export interface GridLoadRowsParams { +export interface GridGetRowsParams { /** * The start index from which rows needs to be loaded. */ @@ -29,9 +29,9 @@ export interface GridLoadRowsParams { } /** - * Object expected to be returned from the [[loadRows]] option. + * Object expected to be returned from the [[getRows]] option. */ -export interface GridLoadRowsReturnValue { +export interface GridGetRowsReturnValue { /** * The start index from which rows needs to be loaded. */ diff --git a/packages/grid/_modules_/grid/models/params/index.ts b/packages/grid/_modules_/grid/models/params/index.ts index 244b015905b0e..b7c5272cc6d5a 100644 --- a/packages/grid/_modules_/grid/models/params/index.ts +++ b/packages/grid/_modules_/grid/models/params/index.ts @@ -1,5 +1,6 @@ export * from './gridCellParams'; export * from './gridEditCellParams'; +export * from './gridGetRowsParams'; export * from './gridColumnHeaderParams'; export * from './gridColumnOrderChangeParams'; export * from './gridColumnResizeParams'; @@ -12,6 +13,5 @@ export * from './gridScrollParams'; export * from './gridSelectionModelChangeParams'; export * from './gridSortModelParams'; export * from './gridStateChangeParams'; -export * from './gridLoadRowsParams'; export * from './gridRowScrollEndParams'; export * from './gridResizeParams'; diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 434bbeb165723..6d01d3dde97f7 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -975,7 +975,7 @@ export function InfiniteLoader() { components={{ Toolbar: GridToolbar, }} - loadRows={(params) => { + getRows={(params) => { const newRowsBatch: any = []; while (newRowsBatch.length < params.viewportPageSize) { const id = Math.floor(Math.random() * 1000); From 97c86a8ca7063d3099f5d86d1563088b9782e2ab Mon Sep 17 00:00:00 2001 From: Danail H Date: Thu, 27 May 2021 14:57:08 +0300 Subject: [PATCH 09/27] Remove dead code and fix tests --- packages/grid/_modules_/grid/components/GridViewport.tsx | 3 ++- .../grid/_modules_/grid/components/cell/GridRowCells.tsx | 2 ++ .../_modules_/grid/hooks/features/rows/gridRowsState.ts | 6 ++---- .../_modules_/grid/hooks/features/rows/useGridRows.ts | 5 +++-- .../grid/hooks/features/sorting/useGridSorting.ts | 8 +------- 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/grid/_modules_/grid/components/GridViewport.tsx b/packages/grid/_modules_/grid/components/GridViewport.tsx index 936420326aa2b..fda29acb841e2 100644 --- a/packages/grid/_modules_/grid/components/GridViewport.tsx +++ b/packages/grid/_modules_/grid/components/GridViewport.tsx @@ -54,7 +54,7 @@ export const GridViewport: ViewportType = React.forwardRef( renderState.renderContext.lastRowIdx!, ); - return renderedRows.map(([id], idx) => ( + return renderedRows.map(([id, row], idx) => ( ( ) : ( string; lastColIdx: number; + row: GridRowModel; rowIndex: number; showCellRightBorder: boolean; cellFocus: GridCellIdentifier | null; diff --git a/packages/grid/_modules_/grid/hooks/features/rows/gridRowsState.ts b/packages/grid/_modules_/grid/hooks/features/rows/gridRowsState.ts index ae145ae643e85..3d041836bdd4c 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/gridRowsState.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/gridRowsState.ts @@ -6,10 +6,8 @@ export interface InternalGridRowsState { totalRowCount: number; } -export const getInitialGridRowState: (rowCount?: number) => InternalGridRowsState = ( - rowsCount = 0, -) => ({ +export const getInitialGridRowState: (rowCount?: number) => InternalGridRowsState = (rowCount) => ({ idRowsLookup: {}, - allRows: new Array(rowsCount).fill(null), + allRows: rowCount !== undefined ? new Array(rowCount).fill(null) : [], totalRowCount: 0, }); diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts index f03ad53560689..5a46ce9fcacd3 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts @@ -35,9 +35,10 @@ export function convertGridRowsPropToState( totalRowCount?: number, rowIdGetter?: GridRowIdGetter, ): InternalGridRowsState { + const numberOfRows = totalRowCount && totalRowCount > rows.length ? totalRowCount : rows.length; const state: InternalGridRowsState = { - ...getInitialGridRowState(totalRowCount), - totalRowCount: totalRowCount && totalRowCount > rows.length ? totalRowCount : rows.length, + ...getInitialGridRowState(numberOfRows), + totalRowCount: numberOfRows, }; rows.forEach((rowData, index) => { diff --git a/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts b/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts index fac93d80964f3..c614ece925133 100644 --- a/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts +++ b/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts @@ -174,9 +174,8 @@ export const useGridSorting = (apiRef: GridApiRef, rowsProp: GridRowsProp) => { if (sortModel.length > 0) { const comparatorList = buildComparatorList(sortModel); logger.debug('Sorting rows with ', sortModel); - const rowIdsLength = rowIds.length; + sorted = rowIds - .filter((id) => id !== null) .map((id) => { return comparatorList.map((colComparator) => { return getSortCellParams(id, colComparator.field); @@ -184,11 +183,6 @@ export const useGridSorting = (apiRef: GridApiRef, rowsProp: GridRowsProp) => { }) .sort(comparatorListAggregate(comparatorList)) .map((field) => field[0].id); - - if (rowIdsLength !== sorted.length) { - const NullArray = new Array(rowIdsLength - sorted.length).fill(null); - sorted = sorted.concat(NullArray); - } } setGridState((oldState) => { From 802ff36be1b61f572132cc1981a629fd6d3dca9b Mon Sep 17 00:00:00 2001 From: Danail H Date: Thu, 27 May 2021 15:08:30 +0300 Subject: [PATCH 10/27] Fix styles --- .../grid/_modules_/grid/components/containers/GridRootStyles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts b/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts index 0a89c1153feeb..a01d8039d02b8 100644 --- a/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts +++ b/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts @@ -84,7 +84,7 @@ export const useStyles = makeStyles( alignItems: 'center', overflow: 'hidden', }, - '& .MuiDataGrid-columnHeader, & .MuiDataGrid-cell & .MuiDataGrid-skeletonCell': { + '& .MuiDataGrid-columnHeader, & .MuiDataGrid-cell, & .MuiDataGrid-skeletonCell': { WebkitTapHighlightColor: 'transparent', lineHeight: null, padding: '0 10px', From 9dd3579eeae350b6aff5fa80ee27cf7e36e9dfa3 Mon Sep 17 00:00:00 2001 From: Danail H Date: Thu, 27 May 2021 16:31:19 +0300 Subject: [PATCH 11/27] Fix server pagination --- .../grid/_modules_/grid/hooks/features/rows/useGridRows.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts index 5a46ce9fcacd3..0d845acca0468 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts @@ -34,10 +34,14 @@ export function convertGridRowsPropToState( rows: GridRowsProp, totalRowCount?: number, rowIdGetter?: GridRowIdGetter, + pagination?: boolean, ): InternalGridRowsState { const numberOfRows = totalRowCount && totalRowCount > rows.length ? totalRowCount : rows.length; + const initialRowState = pagination + ? getInitialGridRowState() + : getInitialGridRowState(numberOfRows); const state: InternalGridRowsState = { - ...getInitialGridRowState(numberOfRows), + ...initialRowState, totalRowCount: numberOfRows, }; @@ -87,6 +91,7 @@ export const useGridRows = ( rows, state.options.rowCount, getRowIdProp, + state.options.pagination, ); return { ...state, rows: internalRowsState.current }; }); From 8bbbe71a612791360f3bca4bca54f1990caaf689 Mon Sep 17 00:00:00 2001 From: Danail H Date: Mon, 31 May 2021 12:26:47 +0300 Subject: [PATCH 12/27] Rename methods and decouple api method from grid option --- .../grid/constants/eventsConstants.ts | 6 +++ .../infiniteLoader/useGridInfiniteLoader.ts | 51 +++++++------------ .../grid/hooks/features/rows/useGridRows.ts | 20 +++++--- .../_modules_/grid/models/api/gridRowApi.ts | 10 ++-- .../_modules_/grid/models/gridOptions.tsx | 10 ++-- .../grid/_modules_/grid/models/gridRows.ts | 18 +++++++ ...etRowsParams.ts => gridFetchRowsParams.ts} | 19 +------ .../_modules_/grid/models/params/index.ts | 2 +- .../src/stories/grid-rows.stories.tsx | 16 ++++-- 9 files changed, 78 insertions(+), 74 deletions(-) rename packages/grid/_modules_/grid/models/params/{gridGetRowsParams.ts => gridFetchRowsParams.ts} (56%) diff --git a/packages/grid/_modules_/grid/constants/eventsConstants.ts b/packages/grid/_modules_/grid/constants/eventsConstants.ts index 7b1955e70246c..02395eb4c6ec8 100644 --- a/packages/grid/_modules_/grid/constants/eventsConstants.ts +++ b/packages/grid/_modules_/grid/constants/eventsConstants.ts @@ -465,3 +465,9 @@ export const GRID_COLUMN_VISIBILITY_CHANGE = 'columnVisibilityChange'; * @event */ export const GRID_VIRTUAL_PAGE_CHANGE = 'virtualPageChange'; + +/** + * Fired when the virtual page changes. Called with a [[GridFetchRowsParams]] object. + * @event + */ +export const GRID_FETCH_ROWS = 'fetchRows'; diff --git a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts index 141223301e751..51ceaf8874157 100644 --- a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts +++ b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts @@ -3,6 +3,7 @@ import { optionsSelector } from '../../utils/optionsSelector'; import { GridApiRef } from '../../../models/api/gridApiRef'; import { useGridSelector } from '../core/useGridSelector'; import { + GRID_FETCH_ROWS, GRID_FILTER_MODEL_CHANGE, GRID_ROWS_SCROLL, GRID_ROWS_SCROLL_END, @@ -21,7 +22,7 @@ import { renderStateSelector } from '../virtualization/renderingStateSelector'; import { unorderedGridRowIdsSelector } from '../rows/gridRowsSelector'; import { gridSortModelSelector } from '../sorting/gridSortingSelector'; import { filterGridStateSelector } from '../filter/gridFilterSelector'; -import { GridGetRowsReturnValue } from '../../../models/params/gridGetRowsParams'; +import { GridFetchRowsParams } from '../../../models/params/gridFetchRowsParams'; export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const logger = useLogger('useGridInfiniteLoader'); @@ -55,13 +56,13 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { !isInScrollBottomArea.current && !options.rowCount ) { - const rowScrollEndParam: GridRowScrollEndParams = { + const rowScrollEndParams: GridRowScrollEndParams = { api: apiRef, visibleColumns, viewportPageSize: containerSizes.viewportPageSize, virtualRowsCount: containerSizes.virtualRowsCount, }; - apiRef.current.publishEvent(GRID_ROWS_SCROLL_END, rowScrollEndParam); + apiRef.current.publishEvent(GRID_ROWS_SCROLL_END, rowScrollEndParams); isInScrollBottomArea.current = true; } }, [logger, options, containerSizes, apiRef, visibleColumns]); @@ -69,7 +70,7 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const handleGridVirtualPageChange = React.useCallback( (params: GridVirtualPageChangeParams) => { logger.debug('Virtual page changed'); - if (!containerSizes || !options.getRows) { + if (!containerSizes || !options.onFetchRows) { return; } @@ -92,17 +93,14 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { return; } - const { rows }: GridGetRowsReturnValue = options.getRows({ + const fetchRowsParams: GridFetchRowsParams = { startIndex: newRowsBatchStartIndex, viewportPageSize: containerSizes.viewportPageSize, sortModel, filterModel: filterState, api: apiRef, - }); - - if (rows.length) { - apiRef.current.loadRows(newRowsBatchStartIndex, containerSizes.viewportPageSize, rows); - } + }; + apiRef.current.publishEvent(GRID_FETCH_ROWS, fetchRowsParams); }, [logger, options, renderState, allRows, sortModel, containerSizes, filterState, apiRef], ); @@ -111,27 +109,19 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { (params: GridSortModelParams) => { logger.debug('Sort model changed'); - if (!containerSizes || !options.getRows) { + if (!containerSizes || !options.onFetchRows) { return; } const newRowsBatchStartIndex = renderState.virtualPage * containerSizes.viewportPageSize; - const { rows, rowCount }: GridGetRowsReturnValue = options.getRows({ + const fetchRowsParams: GridFetchRowsParams = { startIndex: newRowsBatchStartIndex, viewportPageSize: containerSizes.viewportPageSize, sortModel: params.sortModel, filterModel: filterState, api: apiRef, - }); - - if (rows.length) { - apiRef.current.loadRows( - newRowsBatchStartIndex, - containerSizes.viewportPageSize, - rows, - rowCount, - ); - } + }; + apiRef.current.publishEvent(GRID_FETCH_ROWS, fetchRowsParams); }, [logger, options, renderState, containerSizes, filterState, apiRef], ); @@ -139,27 +129,19 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const handleGridFilterModelChange = React.useCallback( (params: GridFilterModelParams) => { logger.debug('Filter model changed'); - if (!containerSizes || !options.getRows) { + if (!containerSizes || !options.onFetchRows) { return; } const newRowsBatchStartIndex = renderState.virtualPage * containerSizes.viewportPageSize; - const { rows, rowCount }: GridGetRowsReturnValue = options.getRows({ + const fetchRowsParams: GridFetchRowsParams = { startIndex: newRowsBatchStartIndex, viewportPageSize: containerSizes.viewportPageSize, sortModel, filterModel: params.filterModel, api: apiRef, - }); - - if (rows.length) { - apiRef.current.loadRows( - newRowsBatchStartIndex, - containerSizes.viewportPageSize, - rows, - rowCount, - ); - } + }; + apiRef.current.publishEvent(GRID_FETCH_ROWS, fetchRowsParams); }, [logger, options, containerSizes, sortModel, renderState, apiRef], ); @@ -169,4 +151,5 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { useGridApiEventHandler(apiRef, GRID_SORT_MODEL_CHANGE, handleGridSortModelChange); useGridApiEventHandler(apiRef, GRID_FILTER_MODEL_CHANGE, handleGridFilterModelChange); useGridApiOptionHandler(apiRef, GRID_ROWS_SCROLL_END, options.onRowsScrollEnd); + useGridApiOptionHandler(apiRef, GRID_FETCH_ROWS, options.onFetchRows); }; diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts index 0d845acca0468..5ee8b84d36d24 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts @@ -14,9 +14,12 @@ import { GridRowsProp, GridRowIdGetter, GridRowData, + GridInsertRowParams, } from '../../../models/gridRows'; +import { gridContainerSizesSelector } from '../../root/gridContainerSizesSelector'; import { useGridApiMethod } from '../../root/useGridApiMethod'; import { useLogger } from '../../utils/useLogger'; +import { useGridSelector } from '../core/useGridSelector'; import { useGridState } from '../core/useGridState'; import { getInitialGridRowState, InternalGridRowsState } from './gridRowsState'; @@ -61,6 +64,7 @@ export const useGridRows = ( ): void => { const logger = useLogger('useGridRows'); const [gridState, setGridState, updateComponent] = useGridState(apiRef); + const containerSizes = useGridSelector(apiRef, gridContainerSizesSelector); const updateTimeout = React.useRef(); const forceUpdate = React.useCallback( @@ -120,9 +124,13 @@ export const useGridRows = ( [apiRef], ); - const loadRows = React.useCallback( - (startIndex: number, pageSize: number, newRows: GridRowModel[], rowCount?: number) => { - logger.debug(`Loading rows from index:${startIndex} to index:${startIndex + pageSize}`); + const insertRows = React.useCallback( + ({ startIndex, newRows, rowCount }: GridInsertRowParams) => { + logger.debug( + `Loading rows from index:${startIndex} to index:${ + startIndex + containerSizes!.viewportPageSize + }`, + ); const newRowsToState = convertGridRowsPropToState(newRows, newRows.length, getRowIdProp); @@ -131,7 +139,7 @@ export const useGridRows = ( const allRows = rowCount !== undefined ? new Array(rowCount).fill(null) : state.rows.allRows; const allRowsUpdated = allRows.map((row, index) => { - if (index >= startIndex && index < startIndex + pageSize) { + if (index >= startIndex && index < startIndex + containerSizes!.viewportPageSize) { return newRowsToState.allRows[index - startIndex]; } return row; @@ -153,7 +161,7 @@ export const useGridRows = ( forceUpdate(); apiRef.current.applySorting(); }, - [logger, apiRef, getRowIdProp, setGridState, forceUpdate], + [logger, apiRef, containerSizes, getRowIdProp, setGridState, forceUpdate], ); const setRows = React.useCallback( @@ -264,7 +272,7 @@ export const useGridRows = ( getAllRowIds, setRows, updateRows, - loadRows, + insertRows, }; useGridApiMethod(apiRef, rowApi, 'GridRowApi'); }; diff --git a/packages/grid/_modules_/grid/models/api/gridRowApi.ts b/packages/grid/_modules_/grid/models/api/gridRowApi.ts index 0206c01f12838..52870cb8c1e0e 100644 --- a/packages/grid/_modules_/grid/models/api/gridRowApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridRowApi.ts @@ -1,4 +1,4 @@ -import { GridRowModel, GridRowId, GridRowModelUpdate } from '../gridRows'; +import { GridRowModel, GridRowId, GridRowModelUpdate, GridInsertRowParams } from '../gridRows'; /** * The Row API interface that is available in the grid `apiRef`. @@ -48,10 +48,8 @@ export interface GridRowApi { */ getRow: (id: GridRowId) => GridRowModel; /** - * Loads a new subset of Rows. - * @param startIndex - * @param pageSize - * @param rows + * Inserts a new subset of Rows. + * @param {GridInsertRowParams} params The new rows. */ - loadRows: (startIndex: number, pageSize: number, rows: GridRowModel[], rowCount?: number) => void; + insertRows: (params: GridInsertRowParams) => void; } diff --git a/packages/grid/_modules_/grid/models/gridOptions.tsx b/packages/grid/_modules_/grid/models/gridOptions.tsx index dac0cc11d0e04..6928d1a980518 100644 --- a/packages/grid/_modules_/grid/models/gridOptions.tsx +++ b/packages/grid/_modules_/grid/models/gridOptions.tsx @@ -29,7 +29,7 @@ import { GridColumnOrderChangeParams } from './params/gridColumnOrderChangeParam import { GridResizeParams } from './params/gridResizeParams'; import { GridColumnResizeParams } from './params/gridColumnResizeParams'; import { GridColumnVisibilityChangeParams } from './params/gridColumnVisibilityChangeParams'; -import { GridGetRowsParams, GridGetRowsReturnValue } from './params/gridGetRowsParams'; +import { GridFetchRowsParams } from './params/gridFetchRowsParams'; import { GridClasses } from './gridClasses'; // TODO add multiSortKey @@ -185,10 +185,6 @@ export interface GridOptions { * Determines if a row can be selected. */ isRowSelectable?: (params: GridRowParams) => boolean; - /** - * Callback fired when rowCount is set and virtual pages change. - */ - getRows?: (params: GridGetRowsParams) => GridGetRowsReturnValue; /** * Set the locale text of the grid. * You can find all the translation keys supported in [the source](https://github.com/mui-org/material-ui-x/blob/HEAD/packages/grid/_modules_/grid/constants/localeTextConstants.ts) in the GitHub repository. @@ -346,6 +342,10 @@ export interface GridOptions { * @param param With all properties from [[GridFilterModelParams]]. */ onFilterModelChange?: (params: GridFilterModelParams) => void; + /** + * Callback fired when rowCount is set and virtual pages change. + */ + onFetchRows?: (params: GridFetchRowsParams) => void; /** * Callback fired when the current page has changed. * @param param With all properties from [[GridPageChangeParams]]. diff --git a/packages/grid/_modules_/grid/models/gridRows.ts b/packages/grid/_modules_/grid/models/gridRows.ts index 03c91268c4fb1..02c654c73b243 100644 --- a/packages/grid/_modules_/grid/models/gridRows.ts +++ b/packages/grid/_modules_/grid/models/gridRows.ts @@ -46,3 +46,21 @@ export function checkGridRowIdIsValid( return true; } + +/** + * The type of api.current.insertRows params. + */ +export interface GridInsertRowParams { + /** + * The start index from which rows needs to be loaded. + */ + startIndex: number; + /** + * Rows to be inserted. + */ + newRows: GridRowModel[]; + /** + * The updated row count. + */ + rowCount?: number; +} diff --git a/packages/grid/_modules_/grid/models/params/gridGetRowsParams.ts b/packages/grid/_modules_/grid/models/params/gridFetchRowsParams.ts similarity index 56% rename from packages/grid/_modules_/grid/models/params/gridGetRowsParams.ts rename to packages/grid/_modules_/grid/models/params/gridFetchRowsParams.ts index c298579eeb289..c8b1a22eb2a7f 100644 --- a/packages/grid/_modules_/grid/models/params/gridGetRowsParams.ts +++ b/packages/grid/_modules_/grid/models/params/gridFetchRowsParams.ts @@ -1,11 +1,10 @@ import { GridFilterModel } from '../../hooks/features/filter/gridFilterModelState'; -import { GridRowData } from '../gridRows'; import { GridSortModel } from '../gridSortModel'; /** - * Object passed as parameter to the [[getRows]] option. + * Object passed as parameter to the [[onFetchRows]] option. */ -export interface GridGetRowsParams { +export interface GridFetchRowsParams { /** * The start index from which rows needs to be loaded. */ @@ -27,17 +26,3 @@ export interface GridGetRowsParams { */ api: any; } - -/** - * Object expected to be returned from the [[getRows]] option. - */ -export interface GridGetRowsReturnValue { - /** - * The start index from which rows needs to be loaded. - */ - rows: GridRowData[]; - /** - * The viewport page size. - */ - rowCount?: number; -} diff --git a/packages/grid/_modules_/grid/models/params/index.ts b/packages/grid/_modules_/grid/models/params/index.ts index b7c5272cc6d5a..6fd7d543e6592 100644 --- a/packages/grid/_modules_/grid/models/params/index.ts +++ b/packages/grid/_modules_/grid/models/params/index.ts @@ -1,6 +1,6 @@ export * from './gridCellParams'; export * from './gridEditCellParams'; -export * from './gridGetRowsParams'; +export * from './gridFetchRowsParams'; export * from './gridColumnHeaderParams'; export * from './gridColumnOrderChangeParams'; export * from './gridColumnResizeParams'; diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 715d02a750a6d..8445c5bbd898c 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -978,7 +978,7 @@ export function InfiniteLoader() { components={{ Toolbar: GridToolbar, }} - getRows={(params) => { + onFetchRows={(params) => { const newRowsBatch: any = []; while (newRowsBatch.length < params.viewportPageSize) { const id = Math.floor(Math.random() * 1000); @@ -988,12 +988,18 @@ export function InfiniteLoader() { }); } - let rowCount = 20; if (params.filterModel.items.length) { - rowCount = 10; + params.api.current.insertRows({ + startIndex: params.startIndex, + newRows: newRowsBatch, + rowCount: 10, + }); + } else { + params.api.current.insertRows({ + startIndex: params.startIndex, + newRows: newRowsBatch, + }); } - - return { rows: newRowsBatch, rowCount }; }} />
From 6222f1e9310b893000a91b46a68d6b973578aed6 Mon Sep 17 00:00:00 2001 From: Danail H Date: Mon, 31 May 2021 14:04:56 +0300 Subject: [PATCH 13/27] PR comments --- .../features/infiniteLoader/useGridInfiniteLoader.ts | 12 ++++++------ .../grid/hooks/features/rows/useGridRows.ts | 6 +----- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts index 51ceaf8874157..8e1425eec8e8a 100644 --- a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts +++ b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts @@ -70,7 +70,7 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const handleGridVirtualPageChange = React.useCallback( (params: GridVirtualPageChangeParams) => { logger.debug('Virtual page changed'); - if (!containerSizes || !options.onFetchRows) { + if (!containerSizes) { return; } @@ -102,14 +102,14 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { }; apiRef.current.publishEvent(GRID_FETCH_ROWS, fetchRowsParams); }, - [logger, options, renderState, allRows, sortModel, containerSizes, filterState, apiRef], + [logger, renderState, allRows, sortModel, containerSizes, filterState, apiRef], ); const handleGridSortModelChange = React.useCallback( (params: GridSortModelParams) => { logger.debug('Sort model changed'); - if (!containerSizes || !options.onFetchRows) { + if (!containerSizes) { return; } @@ -123,13 +123,13 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { }; apiRef.current.publishEvent(GRID_FETCH_ROWS, fetchRowsParams); }, - [logger, options, renderState, containerSizes, filterState, apiRef], + [logger, renderState, containerSizes, filterState, apiRef], ); const handleGridFilterModelChange = React.useCallback( (params: GridFilterModelParams) => { logger.debug('Filter model changed'); - if (!containerSizes || !options.onFetchRows) { + if (!containerSizes) { return; } @@ -143,7 +143,7 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { }; apiRef.current.publishEvent(GRID_FETCH_ROWS, fetchRowsParams); }, - [logger, options, containerSizes, sortModel, renderState, apiRef], + [logger, containerSizes, sortModel, renderState, apiRef], ); useGridApiEventHandler(apiRef, GRID_ROWS_SCROLL, handleGridScroll); diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts index 5ee8b84d36d24..00069d100aa2c 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts @@ -126,11 +126,7 @@ export const useGridRows = ( const insertRows = React.useCallback( ({ startIndex, newRows, rowCount }: GridInsertRowParams) => { - logger.debug( - `Loading rows from index:${startIndex} to index:${ - startIndex + containerSizes!.viewportPageSize - }`, - ); + logger.debug(`Insert rows from index:${startIndex}`); const newRowsToState = convertGridRowsPropToState(newRows, newRows.length, getRowIdProp); From 8fc923ed56bbd28560fe74d12eaad32162ff0550 Mon Sep 17 00:00:00 2001 From: Danail H Date: Mon, 31 May 2021 15:37:10 +0300 Subject: [PATCH 14/27] Fix trailing rows issue --- .../infiniteLoader/useGridInfiniteLoader.ts | 25 ++++++++----------- .../grid/hooks/features/rows/useGridRows.ts | 10 +++----- .../grid/_modules_/grid/models/gridRows.ts | 4 +++ .../src/stories/grid-rows.stories.tsx | 9 +++++++ 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts index 8e1425eec8e8a..71e4f50641ce1 100644 --- a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts +++ b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts @@ -23,6 +23,7 @@ import { unorderedGridRowIdsSelector } from '../rows/gridRowsSelector'; import { gridSortModelSelector } from '../sorting/gridSortingSelector'; import { filterGridStateSelector } from '../filter/gridFilterSelector'; import { GridFetchRowsParams } from '../../../models/params/gridFetchRowsParams'; +import { GridRowId } from '../../../models/gridRows'; export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const logger = useLogger('useGridInfiniteLoader'); @@ -37,13 +38,11 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const handleGridScroll = React.useCallback(() => { logger.debug('Checking if scroll position reached bottom'); - if (!containerSizes) { return; } const scrollPosition = apiRef.current.getScrollPosition(); - const scrollPositionBottom = scrollPosition.top + containerSizes.windowSizes.height + options.scrollEndThreshold; @@ -74,19 +73,18 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { return; } - let nextPage = params.nextPage; + const newRowsBatchStartIndex = params.nextPage * containerSizes.viewportPageSize; + const remainingIndexes = + allRows.length - (newRowsBatchStartIndex + containerSizes.viewportPageSize); + let newRowsBatchLength = containerSizes.viewportPageSize; - if ( - params.nextPage > params.currentPage && - params.nextPage !== renderState.renderedSizes!.lastPage - ) { - nextPage = params.nextPage + 1; + if (remainingIndexes < containerSizes.renderingZonePageSize) { + newRowsBatchLength = containerSizes.renderingZonePageSize; } - const newRowsBatchStartIndex = nextPage * containerSizes.viewportPageSize; - const toBeLoadedRange: any = [...allRows].splice( + const toBeLoadedRange: Array = [...allRows].splice( newRowsBatchStartIndex, - containerSizes.viewportPageSize, + newRowsBatchLength, ); if (!toBeLoadedRange.includes(null)) { @@ -95,20 +93,19 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const fetchRowsParams: GridFetchRowsParams = { startIndex: newRowsBatchStartIndex, - viewportPageSize: containerSizes.viewportPageSize, + viewportPageSize: newRowsBatchLength, sortModel, filterModel: filterState, api: apiRef, }; apiRef.current.publishEvent(GRID_FETCH_ROWS, fetchRowsParams); }, - [logger, renderState, allRows, sortModel, containerSizes, filterState, apiRef], + [logger, allRows, sortModel, containerSizes, filterState, apiRef], ); const handleGridSortModelChange = React.useCallback( (params: GridSortModelParams) => { logger.debug('Sort model changed'); - if (!containerSizes) { return; } diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts index 00069d100aa2c..fe45c2659a83f 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts @@ -16,10 +16,8 @@ import { GridRowData, GridInsertRowParams, } from '../../../models/gridRows'; -import { gridContainerSizesSelector } from '../../root/gridContainerSizesSelector'; import { useGridApiMethod } from '../../root/useGridApiMethod'; import { useLogger } from '../../utils/useLogger'; -import { useGridSelector } from '../core/useGridSelector'; import { useGridState } from '../core/useGridState'; import { getInitialGridRowState, InternalGridRowsState } from './gridRowsState'; @@ -64,7 +62,6 @@ export const useGridRows = ( ): void => { const logger = useLogger('useGridRows'); const [gridState, setGridState, updateComponent] = useGridState(apiRef); - const containerSizes = useGridSelector(apiRef, gridContainerSizesSelector); const updateTimeout = React.useRef(); const forceUpdate = React.useCallback( @@ -125,7 +122,7 @@ export const useGridRows = ( ); const insertRows = React.useCallback( - ({ startIndex, newRows, rowCount }: GridInsertRowParams) => { + ({ startIndex, pageSize, newRows, rowCount }: GridInsertRowParams) => { logger.debug(`Insert rows from index:${startIndex}`); const newRowsToState = convertGridRowsPropToState(newRows, newRows.length, getRowIdProp); @@ -135,7 +132,7 @@ export const useGridRows = ( const allRows = rowCount !== undefined ? new Array(rowCount).fill(null) : state.rows.allRows; const allRowsUpdated = allRows.map((row, index) => { - if (index >= startIndex && index < startIndex + containerSizes!.viewportPageSize) { + if (index >= startIndex && index < startIndex + pageSize) { return newRowsToState.allRows[index - startIndex]; } return row; @@ -155,9 +152,10 @@ export const useGridRows = ( }); forceUpdate(); + apiRef.current.updateViewport(); apiRef.current.applySorting(); }, - [logger, apiRef, containerSizes, getRowIdProp, setGridState, forceUpdate], + [logger, apiRef, getRowIdProp, setGridState, forceUpdate], ); const setRows = React.useCallback( diff --git a/packages/grid/_modules_/grid/models/gridRows.ts b/packages/grid/_modules_/grid/models/gridRows.ts index 02c654c73b243..419aae7ab271f 100644 --- a/packages/grid/_modules_/grid/models/gridRows.ts +++ b/packages/grid/_modules_/grid/models/gridRows.ts @@ -55,6 +55,10 @@ export interface GridInsertRowParams { * The start index from which rows needs to be loaded. */ startIndex: number; + /** + * The page size. + */ + pageSize: number; /** * Rows to be inserted. */ diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 8445c5bbd898c..5cdfb5ecef359 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -991,12 +991,21 @@ export function InfiniteLoader() { if (params.filterModel.items.length) { params.api.current.insertRows({ startIndex: params.startIndex, + pageSize: params.viewportPageSize, newRows: newRowsBatch, rowCount: 10, }); + } else if (params.sortModel.length) { + params.api.current.insertRows({ + startIndex: params.startIndex, + pageSize: params.viewportPageSize, + newRows: newRowsBatch, + rowCount: 20, + }); } else { params.api.current.insertRows({ startIndex: params.startIndex, + pageSize: params.viewportPageSize, newRows: newRowsBatch, }); } From 369cb6846a1f38c36f86337629dd0601fb9fa29d Mon Sep 17 00:00:00 2001 From: Danail H Date: Tue, 1 Jun 2021 17:19:48 +0300 Subject: [PATCH 15/27] Add tests --- .../grid/x-grid/src/tests/rows.XGrid.test.tsx | 88 ++++++++++++++++++- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx b/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx index d232d884effa2..d5963104f9603 100644 --- a/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx +++ b/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx @@ -1,11 +1,18 @@ import * as React from 'react'; -import { createClientRenderStrictMode } from 'test/utils'; -import { useFakeTimers } from 'sinon'; +import { + createClientRenderStrictMode, + // @ts-expect-error need to migrate helpers to TypeScript + fireEvent, + // @ts-expect-error need to migrate helpers to TypeScript + screen, +} from 'test/utils'; +import { useFakeTimers, spy } from 'sinon'; import { expect } from 'chai'; -import { getCell, getColumnValues } from 'test/utils/helperFn'; +import { getCell, getColumnHeaderCell, getColumnValues } from 'test/utils/helperFn'; import { GridApiRef, GridComponentProps, + GridPreferencePanelsValue, GridRowData, useGridApiRef, XGrid, @@ -439,4 +446,79 @@ describe(' - Rows', () => { }); }); }); + + describe('infinite loader', () => { + before(function beforeHook() { + if (isJSDOM) { + // Need layouting + this.skip(); + } + }); + + let apiRef: GridApiRef; + const TestCaseInfiniteLoading = ( + props: Partial & { nbRows?: number; nbCols?: number; height?: number }, + ) => { + apiRef = useGridApiRef(); + const data = useData(props.nbRows || 100, props.nbCols || 10); + + return ( +
+ +
+ ); + }; + + it('should call onRowsScrollEnd when scroll reaches the bottom of the grid', () => { + const handleRowsScrollEnd = spy(); + render(); + + const gridWindow = document.querySelector('.MuiDataGrid-window')!; + gridWindow.scrollTop = 10e6; // scroll to the bottom + gridWindow.dispatchEvent(new Event('scroll')); + + expect(handleRowsScrollEnd.callCount).to.equal(1); + }); + + it('should call onFetchRows when scroll reaches the bottom of the grid', () => { + const handleFetchRows = spy(); + render( + , + ); + + const gridWindow = document.querySelector('.MuiDataGrid-window')!; + gridWindow.scrollTop = 10e6; // scroll to the bottom + gridWindow.dispatchEvent(new Event('scroll')); + + expect(handleFetchRows.callCount).to.equal(1); + }); + + it('should call onFetchRows when sorting is applied', () => { + const handleFetchRows = spy(); + render( + , + ); + + fireEvent.click(getColumnHeaderCell(0)); + expect(handleFetchRows.callCount).to.equal(1); + }); + }); }); From 60a7adc54035c4170f420151f48bfcabba9083d5 Mon Sep 17 00:00:00 2001 From: Danail H Date: Tue, 1 Jun 2021 17:20:33 +0300 Subject: [PATCH 16/27] Remove code --- packages/grid/x-grid/src/tests/rows.XGrid.test.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx b/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx index d5963104f9603..6891ccacf3881 100644 --- a/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx +++ b/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx @@ -3,8 +3,6 @@ import { createClientRenderStrictMode, // @ts-expect-error need to migrate helpers to TypeScript fireEvent, - // @ts-expect-error need to migrate helpers to TypeScript - screen, } from 'test/utils'; import { useFakeTimers, spy } from 'sinon'; import { expect } from 'chai'; @@ -12,7 +10,6 @@ import { getCell, getColumnHeaderCell, getColumnValues } from 'test/utils/helper import { GridApiRef, GridComponentProps, - GridPreferencePanelsValue, GridRowData, useGridApiRef, XGrid, From 11d936adf8d8bc6a2641e6371e261c897b249d68 Mon Sep 17 00:00:00 2001 From: Danail H Date: Wed, 2 Jun 2021 10:03:02 +0300 Subject: [PATCH 17/27] Finished tests --- .../grid/x-grid/src/tests/rows.XGrid.test.tsx | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx b/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx index 6891ccacf3881..8eaf0600de43e 100644 --- a/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx +++ b/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx @@ -11,6 +11,7 @@ import { GridApiRef, GridComponentProps, GridRowData, + GRID_SKELETON_CELL_CSS_CLASS, useGridApiRef, XGrid, XGridProps, @@ -444,7 +445,7 @@ describe(' - Rows', () => { }); }); - describe('infinite loader', () => { + describe('Infinite loader', () => { before(function beforeHook() { if (isJSDOM) { // Need layouting @@ -517,5 +518,53 @@ describe(' - Rows', () => { fireEvent.click(getColumnHeaderCell(0)); expect(handleFetchRows.callCount).to.equal(1); }); + + it('should render skeleton cell if rowCount is bigger than the number of rows', () => { + render( + , + ); + + const gridWindow = document.querySelector('.MuiDataGrid-window')!; + gridWindow.scrollTop = 10e6; // scroll to the bottom + gridWindow.dispatchEvent(new Event('scroll')); + + const lastCell = document.querySelector('[role="row"]:last-child [role="cell"]:first-child')!; + + expect(lastCell.classList.contains(GRID_SKELETON_CELL_CSS_CLASS)).to.equal(true); + }); + + it('should update allRows accordingly when apiRef.current.insertRows is called', () => { + render( + , + ); + + const pageSize = 3; + const startIndex = 7; + const endIndex = startIndex + pageSize; + const newRows: GridRowData[] = [ + { id: 'new-1', currencyPair: '' }, + { id: 'new-2', currencyPair: '' }, + { id: 'new-3', currencyPair: '' }, + ]; + + const initialAllRows = apiRef!.current!.getState().rows!.allRows; + expect(initialAllRows.slice(startIndex, endIndex)).to.deep.equal([null, null, null]); + + apiRef!.current!.insertRows({ startIndex, pageSize, newRows }); + + const updatedAllRows = apiRef!.current!.getState().rows!.allRows; + expect(updatedAllRows.slice(startIndex, endIndex)).to.deep.equal(['new-1', 'new-2', 'new-3']); + }); }); }); From f9f2ab1659bb76b0d4179ab2b0b7171aeaf278fe Mon Sep 17 00:00:00 2001 From: Danail H Date: Wed, 2 Jun 2021 12:03:25 +0300 Subject: [PATCH 18/27] Add docs --- .../rows/ServerInfiniteLoadingGrid.js | 54 +++++++++++ .../rows/ServerInfiniteLoadingGrid.tsx | 56 +++++++++++ .../pages/components/data-grid/rows/rows.md | 9 ++ .../src/stories/grid-rows.stories.tsx | 96 ------------------- 4 files changed, 119 insertions(+), 96 deletions(-) create mode 100644 docs/src/pages/components/data-grid/rows/ServerInfiniteLoadingGrid.js create mode 100644 docs/src/pages/components/data-grid/rows/ServerInfiniteLoadingGrid.tsx diff --git a/docs/src/pages/components/data-grid/rows/ServerInfiniteLoadingGrid.js b/docs/src/pages/components/data-grid/rows/ServerInfiniteLoadingGrid.js new file mode 100644 index 0000000000000..d4c4843cf9ace --- /dev/null +++ b/docs/src/pages/components/data-grid/rows/ServerInfiniteLoadingGrid.js @@ -0,0 +1,54 @@ +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 ( +
+ +
+ ); +} diff --git a/docs/src/pages/components/data-grid/rows/ServerInfiniteLoadingGrid.tsx b/docs/src/pages/components/data-grid/rows/ServerInfiniteLoadingGrid.tsx new file mode 100644 index 0000000000000..c70e9ecf434fc --- /dev/null +++ b/docs/src/pages/components/data-grid/rows/ServerInfiniteLoadingGrid.tsx @@ -0,0 +1,56 @@ +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((resolve) => { + setTimeout(() => { + resolve(); + }, duration); + }); +} + +const loadServerRows = async (newRowLength: number): Promise => { + 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, + }); + }; + + return ( +
+ +
+ ); +} diff --git a/docs/src/pages/components/data-grid/rows/rows.md b/docs/src/pages/components/data-grid/rows/rows.md index f6435e9330178..db67e6a2dcc49 100644 --- a/docs/src/pages/components/data-grid/rows/rows.md +++ b/docs/src/pages/components/data-grid/rows/rows.md @@ -36,6 +36,15 @@ In addition, the area in which the callback provided to the `onRowsScrollEnd` is {{"demo": "pages/components/data-grid/rows/InfiniteLoadingGrid.js", "bg": "inline", "disableAd": true}} +### Server-side infinite loading [](https://material-ui.com/store/items/material-ui-pro/) + +By default, infinite loading works on the client-side. +To switch it to server-side, the `rowCount` needs to be set and the number of initially loaded rows needs to be less than the `rowCount` value. In addition, you need to handle the `onFetchRows` callback to fetch the rows for the corresponding index. Finally, you need to use the `apiRef.current.insertRows()` to tell the DataGrid where to insert the newly fetched rows. + +Lastly, in order for the filtering and sorting to work you need to set their modes to `server`. You can find out more information about how to do that [here](https://material-ui.com/components/data-grid/filtering/#server-side-filter) and [here](https://material-ui.com/components/data-grid/sorting/#server-side-sorting). + +{{"demo": "pages/components/data-grid/rows/ServerInfiniteLoadingGrid.js", "bg": "inline", "disableAd": true}} + ### apiRef [](https://material-ui.com/store/items/material-ui-pro/) The second way to update rows is to use the apiRef. diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 5cdfb5ecef359..8fa8ca4754598 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -20,7 +20,6 @@ import { GridEditCellPropsParams, GridEditRowModelParams, GRID_CELL_EDIT_ENTER, - GridToolbar, } from '@material-ui/x-grid'; import { useDemoData } from '@material-ui/x-grid-data-generator'; import { action } from '@storybook/addon-actions'; @@ -919,98 +918,3 @@ export function SwitchVirtualization() {
); } - -export function InfiniteLoader() { - const data = { - rows: [ - { - id: 0, - brand: 'Nike', - }, - { - id: 1, - brand: 'Adidas', - }, - { - id: 2, - brand: 'Puma', - }, - { - id: 3, - brand: 'SB', - }, - { - id: 4, - brand: 'New Balance', - }, - { - id: 5, - brand: 'Reebok', - }, - { - id: 6, - brand: 'Test 6', - }, - { - id: 7, - brand: 'Test 7', - }, - { - id: 8, - brand: 'Test 8', - }, - { - id: 9, - brand: 'Test 8', - }, - ], - columns: [{ field: 'brand', flex: 1 }], - }; - - return ( -
- { - const newRowsBatch: any = []; - while (newRowsBatch.length < params.viewportPageSize) { - const id = Math.floor(Math.random() * 1000); - newRowsBatch.push({ - id, - brand: `Jordan ${id}`, - }); - } - - if (params.filterModel.items.length) { - params.api.current.insertRows({ - startIndex: params.startIndex, - pageSize: params.viewportPageSize, - newRows: newRowsBatch, - rowCount: 10, - }); - } else if (params.sortModel.length) { - params.api.current.insertRows({ - startIndex: params.startIndex, - pageSize: params.viewportPageSize, - newRows: newRowsBatch, - rowCount: 20, - }); - } else { - params.api.current.insertRows({ - startIndex: params.startIndex, - pageSize: params.viewportPageSize, - newRows: newRowsBatch, - }); - } - }} - /> -
- ); -} From 59aa18741d303a515d8a7d05ba42a5e72a93da9b Mon Sep 17 00:00:00 2001 From: Danail H Date: Wed, 9 Jun 2021 14:02:01 +0300 Subject: [PATCH 19/27] Load one page more --- .../grid/components/cell/GridSkeletonCell.tsx | 5 +- .../components/containers/GridRootStyles.ts | 66 +------------------ .../infiniteLoader/useGridInfiniteLoader.ts | 7 +- 3 files changed, 12 insertions(+), 66 deletions(-) diff --git a/packages/grid/_modules_/grid/components/cell/GridSkeletonCell.tsx b/packages/grid/_modules_/grid/components/cell/GridSkeletonCell.tsx index e5dc5b655a5a1..f0dafe6f4b349 100644 --- a/packages/grid/_modules_/grid/components/cell/GridSkeletonCell.tsx +++ b/packages/grid/_modules_/grid/components/cell/GridSkeletonCell.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import clsx from 'clsx'; +import Skeleton from '@material-ui/lab/Skeleton'; import { GRID_SKELETON_CELL_CSS_CLASS } from '../../constants/cssClassesConstants'; export interface GridSkeletonCellProps { @@ -35,7 +36,9 @@ export const GridSkeletonCell = React.memo((props: GridSkeletonCellProps) => { aria-colindex={colIndex} style={style} tabIndex={-1} - /> + > + +
); }); diff --git a/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts b/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts index a01d8039d02b8..def187970d2f1 100644 --- a/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts +++ b/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts @@ -240,26 +240,7 @@ export const useStyles = makeStyles( }, }, }, - '& .MuiDataGrid-skeletonCell': { - display: 'block', - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - borderBottom: `1px solid ${borderColor}`, - paddingTop: theme.spacing(1), - paddingBottom: theme.spacing(1), - '&::before': { - content: '""', - display: 'block', - width: '100%', - height: '100%', - '-webkit-animation': `3000ms ${theme.transitions.easing.easeInOut} $skeleton infinite`, - animation: `3000ms ${theme.transitions.easing.easeInOut} $skeleton infinite`, - background: theme.palette.grey[300], - willChange: 'transform-origin, transform, opacity', - }, - }, - '& .MuiDataGrid-cell': { + '& .MuiDataGrid-cell, & .MuiDataGrid-skeletonCell': { display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', @@ -396,50 +377,7 @@ export const useStyles = makeStyles( }, }; } - return { - ...gridStyle, - '@keyframes skeleton': { - '0%': { - opacity: 0.3, - transform: 'scaleX(0)', - transformOrigin: 'left', - }, - '20%': { - opacity: 1, - transform: 'scaleX(1)', - transformOrigin: 'left', - }, - '28%': { - transform: 'scaleX(1)', - transformOrigin: 'right', - }, - '51%': { - transform: 'scaleX(0)', - transformOrigin: 'right', - }, - '58%': { - transform: 'scaleX(0)', - transformOrigin: 'right', - }, - '82%': { - transform: 'scaleX(1)', - transformOrigin: 'right', - }, - '83%': { - transform: 'scaleX(1)', - transformOrigin: 'left', - }, - '96%': { - transform: 'scaleX(0)', - transformOrigin: 'left', - }, - '100%': { - opacity: 0.3, - transform: 'scaleX(0)', - transformOrigin: 'left', - }, - }, - }; + return gridStyle; }, { name: 'MuiDataGrid', defaultTheme }, ); diff --git a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts index 71e4f50641ce1..b81bc9e00535d 100644 --- a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts +++ b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts @@ -73,7 +73,12 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { return; } - const newRowsBatchStartIndex = params.nextPage * containerSizes.viewportPageSize; + let nextPage: number = params.nextPage; + if (params.currentPage < params.nextPage && params.nextPage !== containerSizes.lastPage) { + nextPage += 1; + } + + const newRowsBatchStartIndex = nextPage * containerSizes.viewportPageSize; const remainingIndexes = allRows.length - (newRowsBatchStartIndex + containerSizes.viewportPageSize); let newRowsBatchLength = containerSizes.viewportPageSize; From 658879fb1feaa09130ebf48a917a84c01dc9283c Mon Sep 17 00:00:00 2001 From: Danail H Date: Wed, 9 Jun 2021 14:22:18 +0300 Subject: [PATCH 20/27] add story --- .../src/stories/grid-rows.stories.tsx | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 8fa8ca4754598..9e321c763cc1f 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -20,8 +20,9 @@ import { GridEditCellPropsParams, GridEditRowModelParams, GRID_CELL_EDIT_ENTER, + GridFetchRowsParams, } from '@material-ui/x-grid'; -import { useDemoData } from '@material-ui/x-grid-data-generator'; +import { getCommodityColumns, getRealData, useDemoData } from '@material-ui/x-grid-data-generator'; import { action } from '@storybook/addon-actions'; import { randomInt } from '../data/random-generator'; @@ -918,3 +919,50 @@ export function SwitchVirtualization() { ); } + +async function sleep(duration) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, duration); + }); +} + +const loadServerRows = async (newRowLength: number): Promise => { + const newData = await getRealData(newRowLength, getCommodityColumns()); + // Simulate network throttle + await sleep(Math.random() * 100 + 100); + + return newData.rows; +}; + +export function ServerSideInfiniteLoading() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 20, + maxColumns: 20, + }); + + const handleFetchRows = async (params: GridFetchRowsParams) => { + const newRowsBatch: GridRowData[] = await loadServerRows(params.viewportPageSize); + + params.api.current.insertRows({ + startIndex: params.startIndex, + pageSize: params.viewportPageSize, + newRows: newRowsBatch, + }); + }; + + return ( +
+ +
+ ); +} From f81a1cf7bcc00676d4d7fe53bb97c03f38360087 Mon Sep 17 00:00:00 2001 From: Danail Hadjiatanasov Date: Tue, 15 Jun 2021 14:52:12 +0300 Subject: [PATCH 21/27] Update docs/src/pages/components/data-grid/rows/rows.md Co-authored-by: Olivier Tassinari --- docs/src/pages/components/data-grid/rows/rows.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/src/pages/components/data-grid/rows/rows.md b/docs/src/pages/components/data-grid/rows/rows.md index db67e6a2dcc49..c36bca2ace54a 100644 --- a/docs/src/pages/components/data-grid/rows/rows.md +++ b/docs/src/pages/components/data-grid/rows/rows.md @@ -39,7 +39,9 @@ In addition, the area in which the callback provided to the `onRowsScrollEnd` is ### Server-side infinite loading [](https://material-ui.com/store/items/material-ui-pro/) By default, infinite loading works on the client-side. -To switch it to server-side, the `rowCount` needs to be set and the number of initially loaded rows needs to be less than the `rowCount` value. In addition, you need to handle the `onFetchRows` callback to fetch the rows for the corresponding index. Finally, you need to use the `apiRef.current.insertRows()` to tell the DataGrid where to insert the newly fetched rows. +To switch it to server-side, the `rowCount` needs to be set and the number of initially loaded rows needs to be less than the `rowCount` value. +In addition, you need to handle the `onFetchRows` callback to fetch the rows for the corresponding index. +Finally, you need to use the `apiRef.current.insertRows()` to tell the DataGrid where to insert the newly fetched rows. Lastly, in order for the filtering and sorting to work you need to set their modes to `server`. You can find out more information about how to do that [here](https://material-ui.com/components/data-grid/filtering/#server-side-filter) and [here](https://material-ui.com/components/data-grid/sorting/#server-side-sorting). From 4290995e562afdff0b70eb829a03861b530831b0 Mon Sep 17 00:00:00 2001 From: Danail H Date: Tue, 15 Jun 2021 15:37:57 +0300 Subject: [PATCH 22/27] PR comments --- docs/src/pages/components/data-grid/rows/rows.md | 3 ++- .../grid/_modules_/grid/components/cell/GridSkeletonCell.tsx | 4 +--- .../_modules_/grid/components/cell/GridSkeletonRowCells.tsx | 5 +++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/pages/components/data-grid/rows/rows.md b/docs/src/pages/components/data-grid/rows/rows.md index db67e6a2dcc49..a53ec2192aecd 100644 --- a/docs/src/pages/components/data-grid/rows/rows.md +++ b/docs/src/pages/components/data-grid/rows/rows.md @@ -41,7 +41,8 @@ In addition, the area in which the callback provided to the `onRowsScrollEnd` is By default, infinite loading works on the client-side. To switch it to server-side, the `rowCount` needs to be set and the number of initially loaded rows needs to be less than the `rowCount` value. In addition, you need to handle the `onFetchRows` callback to fetch the rows for the corresponding index. Finally, you need to use the `apiRef.current.insertRows()` to tell the DataGrid where to insert the newly fetched rows. -Lastly, in order for the filtering and sorting to work you need to set their modes to `server`. You can find out more information about how to do that [here](https://material-ui.com/components/data-grid/filtering/#server-side-filter) and [here](https://material-ui.com/components/data-grid/sorting/#server-side-sorting). +Lastly, in order for the filtering and sorting to work you need to set their modes to `server`. +You can find out more information about how to do that on the [server side filter page](/components/data-grid/filtering/#server-side-filter) and on the [server side sorting page](/components/data-grid/sorting/#server-side-sorting). {{"demo": "pages/components/data-grid/rows/ServerInfiniteLoadingGrid.js", "bg": "inline", "disableAd": true}} diff --git a/packages/grid/_modules_/grid/components/cell/GridSkeletonCell.tsx b/packages/grid/_modules_/grid/components/cell/GridSkeletonCell.tsx index f0dafe6f4b349..8b9af01d50197 100644 --- a/packages/grid/_modules_/grid/components/cell/GridSkeletonCell.tsx +++ b/packages/grid/_modules_/grid/components/cell/GridSkeletonCell.tsx @@ -11,7 +11,7 @@ export interface GridSkeletonCellProps { width: number; } -export const GridSkeletonCell = React.memo((props: GridSkeletonCellProps) => { +export const GridSkeletonCell = React.memo(function GridSkeletonCell(props: GridSkeletonCellProps) { const { colIndex, height, rowIndex, showRightBorder, width } = props; const cellRef = React.useRef(null); @@ -41,5 +41,3 @@ export const GridSkeletonCell = React.memo((props: GridSkeletonCellProps) => { ); }); - -GridSkeletonCell.displayName = 'GridSkeletonCell'; diff --git a/packages/grid/_modules_/grid/components/cell/GridSkeletonRowCells.tsx b/packages/grid/_modules_/grid/components/cell/GridSkeletonRowCells.tsx index ef3e9c60504fb..847a196662a19 100644 --- a/packages/grid/_modules_/grid/components/cell/GridSkeletonRowCells.tsx +++ b/packages/grid/_modules_/grid/components/cell/GridSkeletonRowCells.tsx @@ -16,7 +16,9 @@ interface SkeletonRowCellsProps { showCellRightBorder: boolean; } -export const GridSkeletonRowCells = React.memo((props: SkeletonRowCellsProps) => { +export const GridSkeletonRowCells = React.memo(function GridSkeletonRowCells( + props: SkeletonRowCellsProps, +) { const { columns, firstColIdx, @@ -57,4 +59,3 @@ export const GridSkeletonRowCells = React.memo((props: SkeletonRowCellsProps) => ); }); -GridSkeletonRowCells.displayName = 'GridSkeletonRowCells'; From b481127bfb2aadbd867dc5125666cd2013c857b3 Mon Sep 17 00:00:00 2001 From: Danail H Date: Tue, 15 Jun 2021 16:11:33 +0300 Subject: [PATCH 23/27] run prettier --- .../components/containers/GridRootStyles.ts | 44 ++++++++++-------- .../panel/filterPanel/GridFilterPanel.tsx | 7 +-- .../hooks/features/sorting/useGridSorting.ts | 45 +++++++++---------- .../virtualization/useGridVirtualRows.ts | 7 +-- .../src/stories/grid-rows.stories.tsx | 15 +++---- 5 files changed, 61 insertions(+), 57 deletions(-) diff --git a/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts b/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts index def187970d2f1..b5d6d76c5f74b 100644 --- a/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts +++ b/packages/grid/_modules_/grid/components/containers/GridRootStyles.ts @@ -113,9 +113,10 @@ export const useStyles = makeStyles( duration: theme.transitions.duration.shorter, }), }, - '& .MuiDataGrid-columnHeader:not(.MuiDataGrid-columnHeaderSorted):hover .MuiDataGrid-sortIcon': { - opacity: 0.5, - }, + '& .MuiDataGrid-columnHeader:not(.MuiDataGrid-columnHeaderSorted):hover .MuiDataGrid-sortIcon': + { + opacity: 0.5, + }, '& .MuiDataGrid-columnHeaderTitleContainer': { display: 'flex', alignItems: 'center', @@ -134,13 +135,15 @@ export const useStyles = makeStyles( '& .MuiDataGrid-columnHeaderCenter .MuiDataGrid-columnHeaderTitleContainer': { justifyContent: 'center', }, - '& .MuiDataGrid-columnHeaderRight .MuiDataGrid-columnHeader-draggable, & .MuiDataGrid-columnHeaderRight .MuiDataGrid-columnHeaderTitleContainer': { - flexDirection: 'row-reverse', - }, - '& .MuiDataGrid-columnHeaderCenter .MuiDataGrid-menuIcon, & .MuiDataGrid-columnHeaderRight .MuiDataGrid-menuIcon': { - marginRight: 'auto', - marginLeft: -6, - }, + '& .MuiDataGrid-columnHeaderRight .MuiDataGrid-columnHeader-draggable, & .MuiDataGrid-columnHeaderRight .MuiDataGrid-columnHeaderTitleContainer': + { + flexDirection: 'row-reverse', + }, + '& .MuiDataGrid-columnHeaderCenter .MuiDataGrid-menuIcon, & .MuiDataGrid-columnHeaderRight .MuiDataGrid-menuIcon': + { + marginRight: 'auto', + marginLeft: -6, + }, '& .MuiDataGrid-columnHeaderTitle': { textOverflow: 'ellipsis', overflow: 'hidden', @@ -292,21 +295,24 @@ export const useStyles = makeStyles( '& .MuiDataGrid-cellLeft': { textAlign: 'left', }, - '& .MuiDataGrid-cellLeft.MuiDataGrid-cellWithRenderer, & .MuiDataGrid-cellLeft.MuiDataGrid-cellEditing': { - justifyContent: 'flex-start', - }, + '& .MuiDataGrid-cellLeft.MuiDataGrid-cellWithRenderer, & .MuiDataGrid-cellLeft.MuiDataGrid-cellEditing': + { + justifyContent: 'flex-start', + }, '& .MuiDataGrid-cellRight': { textAlign: 'right', }, - '& .MuiDataGrid-cellRight.MuiDataGrid-cellWithRenderer, & .MuiDataGrid-cellRight.MuiDataGrid-cellEditing': { - justifyContent: 'flex-end', - }, + '& .MuiDataGrid-cellRight.MuiDataGrid-cellWithRenderer, & .MuiDataGrid-cellRight.MuiDataGrid-cellEditing': + { + justifyContent: 'flex-end', + }, '& .MuiDataGrid-cellCenter': { textAlign: 'center', }, - '& .MuiDataGrid-cellCenter.MuiDataGrid-cellWithRenderer, & .MuiDataGrid-cellCenter.MuiDataGrid-cellEditing': { - justifyContent: 'center', - }, + '& .MuiDataGrid-cellCenter.MuiDataGrid-cellWithRenderer, & .MuiDataGrid-cellCenter.MuiDataGrid-cellEditing': + { + justifyContent: 'center', + }, '& .MuiDataGrid-rowCount, & .MuiDataGrid-selectedRowCount': { alignItems: 'center', display: 'flex', diff --git a/packages/grid/_modules_/grid/components/panel/filterPanel/GridFilterPanel.tsx b/packages/grid/_modules_/grid/components/panel/filterPanel/GridFilterPanel.tsx index 909e738d23513..dd7fc047e0bc5 100644 --- a/packages/grid/_modules_/grid/components/panel/filterPanel/GridFilterPanel.tsx +++ b/packages/grid/_modules_/grid/components/panel/filterPanel/GridFilterPanel.tsx @@ -16,9 +16,10 @@ export function GridFilterPanel() { const [gridState] = useGridState(apiRef!); const { disableMultipleColumnsFiltering } = useGridSelector(apiRef, optionsSelector); - const hasMultipleFilters = React.useMemo(() => gridState.filter.items.length > 1, [ - gridState.filter.items.length, - ]); + const hasMultipleFilters = React.useMemo( + () => gridState.filter.items.length > 1, + [gridState.filter.items.length], + ); const applyFilter = React.useCallback( (item: GridFilterItem) => { diff --git a/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts b/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts index bedc70963d1e3..4f8aeb53d722f 100644 --- a/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts +++ b/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts @@ -109,27 +109,25 @@ export const useGridSorting = (apiRef: GridApiRef, { rows }: { rows: GridRowsPro ); const comparatorListAggregate = React.useCallback( - (comparatorList: GridFieldComparatorList) => ( - row1: GridSortCellParams[], - row2: GridSortCellParams[], - ) => { - return comparatorList.reduce((res, colComparator, index) => { - if (res !== 0) { - return res; - } + (comparatorList: GridFieldComparatorList) => + (row1: GridSortCellParams[], row2: GridSortCellParams[]) => { + return comparatorList.reduce((res, colComparator, index) => { + if (res !== 0) { + return res; + } - const { comparator } = colComparator; - const sortCellParams1 = row1[index]; - const sortCellParams2 = row2[index]; - res = comparator( - sortCellParams1.value, - sortCellParams2.value, - sortCellParams1, - sortCellParams2, - ); - return res; - }, 0); - }, + const { comparator } = colComparator; + const sortCellParams1 = row1[index]; + const sortCellParams2 = row2[index]; + res = comparator( + sortCellParams1.value, + sortCellParams2.value, + sortCellParams1, + sortCellParams2, + ); + return res; + }, 0); + }, [], ); @@ -261,9 +259,10 @@ export const useGridSorting = (apiRef: GridApiRef, { rows }: { rows: GridRowsPro }); }, [setGridState]); - const getSortModel = React.useCallback(() => gridState.sorting.sortModel, [ - gridState.sorting.sortModel, - ]); + const getSortModel = React.useCallback( + () => gridState.sorting.sortModel, + [gridState.sorting.sortModel], + ); const getSortedRows = React.useCallback( (): GridRowModel[] => Object.values(sortedGridRowsSelector(apiRef.current.state)), diff --git a/packages/grid/_modules_/grid/hooks/features/virtualization/useGridVirtualRows.ts b/packages/grid/_modules_/grid/hooks/features/virtualization/useGridVirtualRows.ts index 47371d3bf7783..c95e169c54e68 100644 --- a/packages/grid/_modules_/grid/hooks/features/virtualization/useGridVirtualRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/virtualization/useGridVirtualRows.ts @@ -328,9 +328,10 @@ export const useGridVirtualRows = (apiRef: GridApiRef): void => { [apiRef], ); - const getContainerPropsState = React.useCallback(() => gridState.containerSizes, [ - gridState.containerSizes, - ]); + const getContainerPropsState = React.useCallback( + () => gridState.containerSizes, + [gridState.containerSizes], + ); const getRenderContextState = React.useCallback(() => { return gridState.rendering.renderContext || undefined; diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 9709906a74017..6cafe1ebb6e6a 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -315,29 +315,25 @@ const rows: any = [ id: 2, col1: 'XGrid', col2: 'is Awesome', - col3: - 'In publishing and graphic design, Lorem ipsum is a placeholder text or a typeface without relying on meaningful content. Lorem ipsum may be used as a placeholder before final copy is available.', + col3: 'In publishing and graphic design, Lorem ipsum is a placeholder text or a typeface without relying on meaningful content. Lorem ipsum may be used as a placeholder before final copy is available.', }, { id: 3, col1: 'Material-UI', col2: 'is Amazing', - col3: - 'Lorem ipsum is a placeholder text commonly used to demonstrate the visual form of a document or a typeface without relying on meaningful content. Lorem ipsum may be used as a placeholder before final copy is available.', + col3: 'Lorem ipsum is a placeholder text commonly used to demonstrate the visual form of a document or a typeface without relying on meaningful content. Lorem ipsum may be used as a placeholder before final copy is available.', }, { id: 4, col1: 'Hello', col2: 'World', - col3: - 'In publishing and graphic design, Lorem ipsum is a placeholder text commonly used to demonstrate the visual form.', + col3: 'In publishing and graphic design, Lorem ipsum is a placeholder text commonly used to demonstrate the visual form.', }, { id: 5, col1: 'XGrid', col2: 'is Awesome', - col3: - 'Typeface without relying on meaningful content. Lorem ipsum may be used as a placeholder before final copy is available.', + col3: 'Typeface without relying on meaningful content. Lorem ipsum may be used as a placeholder before final copy is available.', }, { id: 6, @@ -427,7 +423,8 @@ const baselineEditProps = { ], }; function validateEmail(email) { - const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + const re = + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test(String(email).toLowerCase()); } From 42f08300aa462b36e1dece8b02cb871bf4d73494 Mon Sep 17 00:00:00 2001 From: Danail H Date: Tue, 15 Jun 2021 16:38:18 +0300 Subject: [PATCH 24/27] format docs api --- docs/pages/api-docs/data-grid/grid-api.md | 1 + docs/src/pages/components/data-grid/events/events.json | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/docs/pages/api-docs/data-grid/grid-api.md b/docs/pages/api-docs/data-grid/grid-api.md index c1cca1dda7e28..4acc5e52d2556 100644 --- a/docs/pages/api-docs/data-grid/grid-api.md +++ b/docs/pages/api-docs/data-grid/grid-api.md @@ -57,6 +57,7 @@ import { GridApi } from '@material-ui/x-grid'; | hideColumnMenu | () => void | Hides the column menu that is open. | | hideFilterPanel | () => void | Hides the filter panel. | | hidePreferences | () => void | Hides the preferences panel. | +| insertRows | (params: GridInsertRowParams) => void | Inserts a new subset of Rows. | | isCellEditable | (params: GridCellParams) => boolean | Controls if a cell is editable. | | isColumnVisibleInWindow | (colIndex: number) => boolean | Checks if a column at the index given by `colIndex` is currently visible in the viewport. | | publishEvent | (name: string, ...args: any[]) => void | Emits an event. | diff --git a/docs/src/pages/components/data-grid/events/events.json b/docs/src/pages/components/data-grid/events/events.json index d9f31f0c55280..26c2ff008c821 100644 --- a/docs/src/pages/components/data-grid/events/events.json +++ b/docs/src/pages/components/data-grid/events/events.json @@ -174,5 +174,13 @@ { "name": "columnVisibilityChange", "description": "Fired when a column visibility changes. Called with a GridColumnVisibilityChangeParams object." + }, + { + "name": "virtualPageChange", + "description": "Fired when the virtual page changes. Called with a GridVirtualPageChangeParams object." + }, + { + "name": "fetchRows", + "description": "Fired when the virtual page changes. Called with a GridFetchRowsParams object." } ] From c08eb4b5970e8fbda963ddd7ac7b6696ec9c3716 Mon Sep 17 00:00:00 2001 From: Danail H Date: Wed, 16 Jun 2021 15:29:36 +0300 Subject: [PATCH 25/27] Fix docs --- docs/src/pages/components/data-grid/rows/rows.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/pages/components/data-grid/rows/rows.md b/docs/src/pages/components/data-grid/rows/rows.md index f0eef9f38a7b0..6cada540277d9 100644 --- a/docs/src/pages/components/data-grid/rows/rows.md +++ b/docs/src/pages/components/data-grid/rows/rows.md @@ -42,8 +42,8 @@ To switch it to server-side, the `rowCount` needs to be set and the number of in In addition, you need to handle the `onFetchRows` callback to fetch the rows for the corresponding index. Finally, you need to use the `apiRef.current.insertRows()` to tell the DataGrid where to insert the newly fetched rows. -Lastly, in order for the filtering and sorting to work you need to set their modes to `server`. -You can find out more information about how to do that on the [server side filter page](/components/data-grid/filtering/#server-side-filter) and on the [server side sorting page](/components/data-grid/sorting/#server-side-sorting). +**Note**: in order for the filtering and sorting to work you need to set their modes to `server`. +You can find out more information about how to do that on the [server-side filter page](/components/data-grid/filtering/#server-side-filter) and on the [server-side sorting page](/components/data-grid/sorting/#server-side-sorting). {{"demo": "pages/components/data-grid/rows/ServerInfiniteLoadingGrid.js", "bg": "inline", "disableAd": true}} From f96f635690811792a11def07a9b27e6cadba11c3 Mon Sep 17 00:00:00 2001 From: Danail H Date: Tue, 22 Jun 2021 17:13:40 +0300 Subject: [PATCH 26/27] Add infniteLoaderMode option --- docs/pages/api-docs/data-grid/data-grid.md | 1 + docs/pages/api-docs/data-grid/x-grid.md | 1 + .../rows/ServerInfiniteLoadingGrid.js | 1 + .../rows/ServerInfiniteLoadingGrid.tsx | 1 + .../pages/components/data-grid/rows/rows.md | 5 ++-- .../infiniteLoader/useGridInfiniteLoader.ts | 15 +++++------ .../grid/hooks/features/rows/useGridRows.ts | 25 +++++++++++++------ .../_modules_/grid/models/gridOptions.tsx | 7 ++++++ 8 files changed, 40 insertions(+), 16 deletions(-) diff --git a/docs/pages/api-docs/data-grid/data-grid.md b/docs/pages/api-docs/data-grid/data-grid.md index 1926f22659ca6..93630d4f604a6 100644 --- a/docs/pages/api-docs/data-grid/data-grid.md +++ b/docs/pages/api-docs/data-grid/data-grid.md @@ -44,6 +44,7 @@ import { DataGrid } from '@material-ui/data-grid'; | hideFooterRowCount | boolean | false | If `true`, the row count in the footer is hidden. | | hideFooterSelectedRowCount | boolean | false | If `true`, the selected row count in the footer is hidden. | | icons | IconsOptions | | Set of icons used in the grid. | +| infniteLoadingMode | GridFeatureMode | 'client' | Infnite loading can be processed on the server or client-side. Set it to 'client' if you would like to handle the infnite loading on the client-side. Set it to 'server' if you would like to handle the infnite loading on the server-side. | | isCellEditable | (params: GridCellParams) => boolean | | Callback fired when a cell is rendered, returns true if the cell is editable. | | isRowSelectable | (params: GridRowParams) => boolean | | Determines if a row can be selected. | | loading | boolean | false | If `true`, a loading overlay is displayed. | diff --git a/docs/pages/api-docs/data-grid/x-grid.md b/docs/pages/api-docs/data-grid/x-grid.md index 86773eeba8518..cf03d3d6bc241 100644 --- a/docs/pages/api-docs/data-grid/x-grid.md +++ b/docs/pages/api-docs/data-grid/x-grid.md @@ -48,6 +48,7 @@ import { XGrid } from '@material-ui/x-grid'; | hideFooterRowCount | boolean | false | If `true`, the row count in the footer is hidden. | | hideFooterSelectedRowCount | boolean | false | If `true`, the selected row count in the footer is hidden. | | icons | IconsOptions | | Set of icons used in the grid. | +| infniteLoadingMode | GridFeatureMode | 'client' | Infnite loading can be processed on the server or client-side. Set it to 'client' if you would like to handle the infnite loading on the client-side. Set it to 'server' if you would like to handle the infnite loading on the server-side. | | isCellEditable | (params: GridCellParams) => boolean | | Callback fired when a cell is rendered, returns true if the cell is editable. | | isRowSelectable | (params: GridRowParams) => boolean | | Determines if a row can be selected. | | loading | boolean | false | If `true`, a loading overlay is displayed.. | diff --git a/docs/src/pages/components/data-grid/rows/ServerInfiniteLoadingGrid.js b/docs/src/pages/components/data-grid/rows/ServerInfiniteLoadingGrid.js index d4c4843cf9ace..bf5ab5c14af2d 100644 --- a/docs/src/pages/components/data-grid/rows/ServerInfiniteLoadingGrid.js +++ b/docs/src/pages/components/data-grid/rows/ServerInfiniteLoadingGrid.js @@ -47,6 +47,7 @@ export default function InfiniteLoadingGrid() { rowCount={50} sortingMode="server" filterMode="server" + infniteLoadingMode="server" onFetchRows={handleFetchRows} /> diff --git a/docs/src/pages/components/data-grid/rows/ServerInfiniteLoadingGrid.tsx b/docs/src/pages/components/data-grid/rows/ServerInfiniteLoadingGrid.tsx index c70e9ecf434fc..425c0dcf93b92 100644 --- a/docs/src/pages/components/data-grid/rows/ServerInfiniteLoadingGrid.tsx +++ b/docs/src/pages/components/data-grid/rows/ServerInfiniteLoadingGrid.tsx @@ -49,6 +49,7 @@ export default function InfiniteLoadingGrid() { rowCount={50} sortingMode="server" filterMode="server" + infniteLoadingMode="server" onFetchRows={handleFetchRows} /> diff --git a/docs/src/pages/components/data-grid/rows/rows.md b/docs/src/pages/components/data-grid/rows/rows.md index 6cada540277d9..5c63e275e5f16 100644 --- a/docs/src/pages/components/data-grid/rows/rows.md +++ b/docs/src/pages/components/data-grid/rows/rows.md @@ -35,10 +35,11 @@ In addition, the area in which the callback provided to the `onRowsScrollEnd` is {{"demo": "pages/components/data-grid/rows/InfiniteLoadingGrid.js", "bg": "inline", "disableAd": true}} -### Server-side infinite loading [](https://material-ui.com/store/items/material-ui-pro/) +### Unstable Server-side infinite loading [](https://material-ui.com/store/items/material-ui-pro/) By default, infinite loading works on the client-side. -To switch it to server-side, the `rowCount` needs to be set and the number of initially loaded rows needs to be less than the `rowCount` value. +To switch it to server-side, set `infniteLoadingMode="server"`. +Then the `rowCount` needs to be set and the number of initially loaded rows needs to be less than the `rowCount` value. In addition, you need to handle the `onFetchRows` callback to fetch the rows for the corresponding index. Finally, you need to use the `apiRef.current.insertRows()` to tell the DataGrid where to insert the newly fetched rows. diff --git a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts index b81bc9e00535d..1e948c1688e8b 100644 --- a/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts +++ b/packages/grid/_modules_/grid/hooks/features/infiniteLoader/useGridInfiniteLoader.ts @@ -24,6 +24,7 @@ import { gridSortModelSelector } from '../sorting/gridSortingSelector'; import { filterGridStateSelector } from '../filter/gridFilterSelector'; import { GridFetchRowsParams } from '../../../models/params/gridFetchRowsParams'; import { GridRowId } from '../../../models/gridRows'; +import { GridFeatureModeConstant } from '../../../models/gridFeatureMode'; export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const logger = useLogger('useGridInfiniteLoader'); @@ -38,7 +39,7 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const handleGridScroll = React.useCallback(() => { logger.debug('Checking if scroll position reached bottom'); - if (!containerSizes) { + if (!containerSizes || options.infniteLoadingMode !== GridFeatureModeConstant.client) { return; } @@ -69,7 +70,7 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { const handleGridVirtualPageChange = React.useCallback( (params: GridVirtualPageChangeParams) => { logger.debug('Virtual page changed'); - if (!containerSizes) { + if (!containerSizes || options.infniteLoadingMode !== GridFeatureModeConstant.server) { return; } @@ -105,13 +106,13 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { }; apiRef.current.publishEvent(GRID_FETCH_ROWS, fetchRowsParams); }, - [logger, allRows, sortModel, containerSizes, filterState, apiRef], + [logger, options, allRows, sortModel, containerSizes, filterState, apiRef], ); const handleGridSortModelChange = React.useCallback( (params: GridSortModelParams) => { logger.debug('Sort model changed'); - if (!containerSizes) { + if (!containerSizes || options.infniteLoadingMode !== GridFeatureModeConstant.server) { return; } @@ -125,13 +126,13 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { }; apiRef.current.publishEvent(GRID_FETCH_ROWS, fetchRowsParams); }, - [logger, renderState, containerSizes, filterState, apiRef], + [logger, options, renderState, containerSizes, filterState, apiRef], ); const handleGridFilterModelChange = React.useCallback( (params: GridFilterModelParams) => { logger.debug('Filter model changed'); - if (!containerSizes) { + if (!containerSizes || options.infniteLoadingMode !== GridFeatureModeConstant.server) { return; } @@ -145,7 +146,7 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => { }; apiRef.current.publishEvent(GRID_FETCH_ROWS, fetchRowsParams); }, - [logger, containerSizes, sortModel, renderState, apiRef], + [logger, options, containerSizes, sortModel, renderState, apiRef], ); useGridApiEventHandler(apiRef, GRID_ROWS_SCROLL, handleGridScroll); diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts index a989558b9ebae..a41a1f4ef6033 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts @@ -7,6 +7,7 @@ import { import { GridComponentProps } from '../../../GridComponentProps'; import { GridApiRef } from '../../../models/api/gridApiRef'; import { GridRowApi } from '../../../models/api/gridRowApi'; +import { GridFeatureMode, GridFeatureModeConstant } from '../../../models/gridFeatureMode'; import { checkGridRowIdIsValid, GridRowModel, @@ -18,7 +19,9 @@ import { GridInsertRowParams, } from '../../../models/gridRows'; import { useGridApiMethod } from '../../root/useGridApiMethod'; +import { optionsSelector } from '../../utils/optionsSelector'; import { useLogger } from '../../utils/useLogger'; +import { useGridSelector } from '../core/useGridSelector'; import { useGridState } from '../core/useGridState'; import { getInitialGridRowState, InternalGridRowsState } from './gridRowsState'; @@ -36,12 +39,13 @@ export function convertGridRowsPropToState( rows: GridRowsProp, totalRowCount?: number, rowIdGetter?: GridRowIdGetter, - pagination?: boolean, + infniteLoadingMode?: GridFeatureMode, ): InternalGridRowsState { const numberOfRows = totalRowCount && totalRowCount > rows.length ? totalRowCount : rows.length; - const initialRowState = pagination - ? getInitialGridRowState() - : getInitialGridRowState(numberOfRows); + const initialRowState = + infniteLoadingMode === GridFeatureModeConstant.server + ? getInitialGridRowState(numberOfRows) + : getInitialGridRowState(); const state: InternalGridRowsState = { ...initialRowState, totalRowCount: numberOfRows, @@ -61,6 +65,7 @@ export const useGridRows = ( { rows, getRowId }: Pick, ): void => { const logger = useLogger('useGridRows'); + const { infniteLoadingMode } = useGridSelector(apiRef, optionsSelector); const [gridState, setGridState, updateComponent] = useGridState(apiRef); const updateTimeout = React.useRef(); @@ -92,10 +97,11 @@ export const useGridRows = ( rows, state.options.rowCount, getRowId, + infniteLoadingMode, ); return { ...state, rows: internalRowsState.current }; }); - }, [getRowId, rows, setGridState]); + }, [getRowId, rows, setGridState, infniteLoadingMode]); const getRowIndexFromId = React.useCallback( (id: GridRowId): number => { @@ -124,7 +130,12 @@ export const useGridRows = ( ({ startIndex, pageSize, newRows, rowCount }: GridInsertRowParams) => { logger.debug(`Insert rows from index:${startIndex}`); - const newRowsToState = convertGridRowsPropToState(newRows, newRows.length, getRowId); + const newRowsToState = convertGridRowsPropToState( + newRows, + newRows.length, + getRowId, + infniteLoadingMode, + ); setGridState((state) => { // rowCount can be 0 @@ -154,7 +165,7 @@ export const useGridRows = ( apiRef.current.updateViewport(); apiRef.current.applySorting(); }, - [logger, apiRef, getRowId, setGridState, forceUpdate], + [logger, apiRef, getRowId, setGridState, forceUpdate, infniteLoadingMode], ); const setRows = React.useCallback( diff --git a/packages/grid/_modules_/grid/models/gridOptions.tsx b/packages/grid/_modules_/grid/models/gridOptions.tsx index 43012e07ea98e..17d7161fbe11a 100644 --- a/packages/grid/_modules_/grid/models/gridOptions.tsx +++ b/packages/grid/_modules_/grid/models/gridOptions.tsx @@ -181,6 +181,12 @@ export interface GridOptions { * @default false */ hideFooterSelectedRowCount?: boolean; + /** + * Infnite loading can be processed on the server or client-side. + * Set it to 'client' if you would like to handle the infnite loading on the client-side. + * Set it to 'server' if you would like to handle the infnite loading on the server-side. + */ + infniteLoadingMode?: GridFeatureMode; /** * Callback fired when a cell is rendered, returns true if the cell is editable. */ @@ -514,6 +520,7 @@ export const DEFAULT_GRID_OPTIONS: GridOptions = { density: GridDensityTypes.Standard, filterMode: GridFeatureModeConstant.client, headerHeight: 56, + infniteLoadingMode: GridFeatureModeConstant.client, localeText: GRID_DEFAULT_LOCALE_TEXT, pageSize: 100, paginationMode: GridFeatureModeConstant.client, From 828a186575584ffcd8f02ca54c720de11277af18 Mon Sep 17 00:00:00 2001 From: Danail H Date: Tue, 22 Jun 2021 17:26:59 +0300 Subject: [PATCH 27/27] Fix tests --- packages/grid/x-grid/src/tests/rows.XGrid.test.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx b/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx index ad6ebe17ef00f..e357ea1116591 100644 --- a/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx +++ b/packages/grid/x-grid/src/tests/rows.XGrid.test.tsx @@ -499,6 +499,7 @@ describe(' - Rows', () => { - Rows', () => { - Rows', () => { , @@ -552,6 +555,7 @@ describe(' - Rows', () => {