Skip to content

Commit

Permalink
[DataGrid] Add disableVirtualization prop (mui#2326)
Browse files Browse the repository at this point in the history
  • Loading branch information
m4theushw committed Sep 28, 2021
1 parent b4e72dc commit 50edf8d
Show file tree
Hide file tree
Showing 24 changed files with 408 additions and 180 deletions.
1 change: 1 addition & 0 deletions docs/pages/api-docs/data-grid/data-grid-pro.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ The name <code>MuiDataGridPro</code> can be used when providing [default props](
| <span class="prop-name">disableMultipleColumnsSorting</span> | <span class="prop-type">boolean</span> | false | If `true`, sorting with multiple columns is disabled. |
| <span class="prop-name">disableMultipleSelection</span> | <span class="prop-type">boolean</span> | false | If `true`, multiple selection using the CTRL or CMD key is disabled. |
| <span class="prop-name">disableSelectionOnClick</span> | <span class="prop-type">boolean</span> | false | If `true`, the selection on click on a row or cell is disabled. |
| <span class="prop-name">disableVirtualization</span> | <span class="prop-type">boolean</span> | false | If `true`, the virtualization is disabled. |
| <span class="prop-name">error</span> | <span class="prop-type">any</span> | | An error that will turn the grid into its error state and display the error component. |
| <span class="prop-name">editMode</span> | <span class="prop-type">GridEditMode</span> | 'cell' | Controls whether to use the cell or row editing. |
| <span class="prop-name">editRowsModel</span> | <span class="prop-type">GridEditRowsModel</span> | undefined | Set the edit rows model of the grid. |
Expand Down
1 change: 1 addition & 0 deletions docs/pages/api-docs/data-grid/data-grid.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ The name <code>MuiDataGrid</code> can be used when providing [default props](/cu
| <span class="prop-name">disableDensitySelector</span> | <span class="prop-type">boolean</span> | false | If `true`, the density selector is disabled. |
| <span class="prop-name">disableExtendRowFullWidth</span> | <span class="prop-type">boolean</span> | false | If `true`, rows will not be extended to fill the full width of the grid container. |
| <span class="prop-name">disableSelectionOnClick</span> | <span class="prop-type">boolean</span> | false | If `true`, the selection on click on a row or cell is disabled. |
| <span class="prop-name">disableVirtualization</span> | <span class="prop-type">boolean</span> | false | If `true`, the virtualization is disabled. |
| <span class="prop-name">error</span> | <span class="prop-type">any</span> | | An error that will turn the grid into its error state and display the error component. |
| <span class="prop-name">editMode</span> | <span class="prop-type">GridEditMode</span> | 'cell' | Controls whether to use the cell or row editing. |
| <span class="prop-name">editRowsModel</span> | <span class="prop-type">GridEditRowsModel</span> | undefined | Set the edit rows model of the grid. |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "GridVirtualizationApi",
"description": "The virtualization API interface that is available in the grid apiRef.",
"name": "GridScrollApi",
"description": "The scroll API interface that is available in the grid apiRef.",
"properties": [
{
"name": "getScrollPosition",
Expand Down
2 changes: 1 addition & 1 deletion docs/scripts/buildApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ function run(argv: { outputDirectory?: string }) {
'GridFilterApi',
'GridCsvExportApi',
'GridExportCsvOptions',
'GridVirtualizationApi',
'GridScrollApi',
'GridEditRowApi',
'GridEvents',
];
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ title: Data Grid - Virtualization
DOM virtualization is the feature that allows the grid to handle an unlimited\* number of rows and columns.
This is a built-in feature of the rendering engine and greatly improves rendering performance.

_unlimited\*: Browsers set a limit on the number of pixels a scroll container can host: 17.5 million pixels on Firefox, 33.5 million pixels on Chrome, Edge, and Safari. A [reproduction](https://codesandbox.io/s/beautiful-silence-1yifo?file=/src/App.js)._
_\*unlimited: Browsers set a limit on the number of pixels a scroll container can host: 17.5 million pixels on Firefox, 33.5 million pixels on Chrome, Edge, and Safari. A [reproduction](https://codesandbox.io/s/beautiful-silence-1yifo?file=/src/App.js)._

## Row virtualization [<span class="pro"></span>](https://material-ui.com/store/items/material-ui-pro/)

Expand All @@ -33,9 +33,16 @@ By default, 2 columns are rendered outside of the viewport. You can change this

You can disable column virtualization by setting the column buffer to a higher number than the number of rendered columns, e.g. with `columnBuffer={columns.length}` or `columnBuffer={Number.MAX_SAFE_INTEGER}`.

## apiRef [<span class="pro"></span>](https://material-ui.com/store/items/material-ui-pro/)
## Disable virtualization

{{"demo": "pages/components/data-grid/virtualization/VirtualizationApiNoSnap.js", "bg": "inline", "hideToolbar": true}}
The virtualization can be disabled completely using the `disableVirtualization` prop.
You may want to turn it off to be able to test the grid with a headless browser, like jsdom.

```tsx
<DataGrid {...data} disableVirtualization />
```

**Note**: Disabling the virtualization will increase the size of the DOM and drastically reduce the performance. Use it only for testing purposes or on small datasets.

## API

Expand Down
1 change: 1 addition & 0 deletions packages/grid/_modules_/grid/hooks/features/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from './rows';
export * from './selection';
export * from './sorting';
export * from './virtualization';
export * from './scroll';

export * from './useGridApiRef';
export * from './columnResize';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useGridScroll';
151 changes: 151 additions & 0 deletions packages/grid/_modules_/grid/hooks/features/scroll/useGridScroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import * as React from 'react';
import { GridCellIndexCoordinates } from '../../../models/gridCell';
import { GridApiRef } from '../../../models/api/gridApiRef';
import { useGridLogger } from '../../utils/useGridLogger';
import {
gridColumnsMetaSelector,
visibleGridColumnsSelector,
} from '../columns/gridColumnsSelector';
import { useGridSelector } from '../core/useGridSelector';
import { GridComponentProps } from '../../../GridComponentProps';
import { gridPaginationSelector } from '../pagination/gridPaginationSelector';
import { gridRowCountSelector } from '../rows/gridRowsSelector';
import { gridDensityRowHeightSelector } from '../density/densitySelector';
import { GridScrollParams } from '../../../models/params/gridScrollParams';
import { GridScrollApi } from '../../../models/api/gridScrollApi';
import { scrollStateSelector } from '../virtualization/renderingStateSelector';
import { useGridApiMethod } from '../../root/useGridApiMethod';
import { useNativeEventListener } from '../../root/useNativeEventListener';

// Logic copied from https://www.w3.org/TR/wai-aria-practices/examples/listbox/js/listbox.js
// Similar to https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
function scrollIntoView(dimensions) {
const { clientHeight, scrollTop, offsetHeight, offsetTop } = dimensions;

const elementBottom = offsetTop + offsetHeight;
if (elementBottom - clientHeight > scrollTop) {
return elementBottom - clientHeight;
}
if (offsetTop < scrollTop) {
return offsetTop;
}
return undefined;
}

export const useGridScroll = (
apiRef: GridApiRef,
props: Pick<GridComponentProps, 'pagination'>,
): void => {
const logger = useGridLogger(apiRef, 'useGridScroll');
const colRef = apiRef.current.columnHeadersElementRef!;
const windowRef = apiRef.current.windowRef!;

const rowHeight = useGridSelector(apiRef, gridDensityRowHeightSelector);
const paginationState = useGridSelector(apiRef, gridPaginationSelector);
const totalRowCount = useGridSelector(apiRef, gridRowCountSelector);
const visibleColumns = useGridSelector(apiRef, visibleGridColumnsSelector);
const columnsMeta = useGridSelector(apiRef, gridColumnsMetaSelector);

const scrollToIndexes = React.useCallback<GridScrollApi['scrollToIndexes']>(
(params: Partial<GridCellIndexCoordinates>) => {
if (totalRowCount === 0 || visibleColumns.length === 0) {
return false;
}

logger.debug(`Scrolling to cell at row ${params.rowIndex}, col: ${params.colIndex} `);

const scrollCoordinates: any = {};

if (params.colIndex != null) {
scrollCoordinates.left = scrollIntoView({
clientHeight: windowRef.current!.clientWidth,
scrollTop: windowRef.current!.scrollLeft,
offsetHeight: visibleColumns[params.colIndex].computedWidth,
offsetTop: columnsMeta.positions[params.colIndex],
});
}

if (params.rowIndex != null) {
const elementIndex = !props.pagination
? params.rowIndex
: params.rowIndex - paginationState.page * paginationState.pageSize;

scrollCoordinates.top = scrollIntoView({
clientHeight: windowRef.current!.clientHeight,
scrollTop: windowRef.current!.scrollTop,
offsetHeight: rowHeight,
offsetTop: rowHeight * elementIndex,
});
}

if (
typeof scrollCoordinates.left !== undefined ||
typeof scrollCoordinates.top !== undefined
) {
apiRef.current.scroll(scrollCoordinates);
return true;
}

return false;
},
[
totalRowCount,
visibleColumns,
logger,
apiRef,
props.pagination,
paginationState.page,
paginationState.pageSize,
windowRef,
columnsMeta.positions,
rowHeight,
],
);

const scroll = React.useCallback<GridScrollApi['scroll']>(
(params: Partial<GridScrollParams>) => {
if (windowRef.current && params.left != null && colRef.current) {
colRef.current.scrollLeft = params.left;
windowRef.current.scrollLeft = params.left;
logger.debug(`Scrolling left: ${params.left}`);
}
if (windowRef.current && params.top != null) {
windowRef.current.scrollTop = params.top;
logger.debug(`Scrolling top: ${params.top}`);
}
logger.debug(`Scrolling, updating container, and viewport`);
},
[windowRef, colRef, logger],
);

const getScrollPosition = React.useCallback<GridScrollApi['getScrollPosition']>(
() => scrollStateSelector(apiRef.current.getState()),
[apiRef],
);

const scrollApi: GridScrollApi = {
scroll,
scrollToIndexes,
getScrollPosition,
};
useGridApiMethod(apiRef, scrollApi, 'GridScrollApi');

const preventScroll = React.useCallback((event: any) => {
event.target.scrollLeft = 0;
event.target.scrollTop = 0;
}, []);

useNativeEventListener(
apiRef,
() => apiRef.current?.renderingZoneRef?.current?.parentElement,
'scroll',
preventScroll,
);

useNativeEventListener(
apiRef,
() => apiRef.current?.columnHeadersContainerElementRef?.current,
'scroll',
preventScroll,
);
};
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './useGridVirtualRows';
export * from './useGridVirtualization';
export * from './renderingState';
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as React from 'react';
import { GridComponentProps } from '../../../GridComponentProps';
import { GridApiRef } from '../../../models/api/gridApiRef';
import { useNativeEventListener } from '../../root/useNativeEventListener';
import { useGridScrollFn } from '../../utils/useGridScrollFn';
import { visibleGridColumnsSelector } from '../columns/gridColumnsSelector';
import { useGridSelector } from '../core';
import { useGridState } from '../core/useGridState';
import { gridPaginationSelector } from '../pagination/gridPaginationSelector';

export const useGridNoVirtualization = (
apiRef: GridApiRef,
props: Pick<GridComponentProps, 'disableVirtualization' | 'pagination' | 'paginationMode'>,
): void => {
const windowRef = apiRef.current.windowRef;
const columnsHeaderRef = apiRef.current.columnHeadersElementRef;
const renderingZoneRef = apiRef.current.renderingZoneRef;
const [gridState, setGridState, forceUpdate] = useGridState(apiRef);
const [scrollTo] = useGridScrollFn(apiRef, renderingZoneRef!, columnsHeaderRef!);
const paginationState = useGridSelector(apiRef, gridPaginationSelector);
const visibleColumns = useGridSelector(apiRef, visibleGridColumnsSelector);

const syncState = React.useCallback(() => {
if (!gridState.containerSizes || !windowRef?.current) {
return;
}

let firstRowIdx = 0;
const { page, pageSize } = paginationState;
if (props.pagination && props.paginationMode === 'client') {
firstRowIdx = pageSize * page;
}
const lastRowIdx = firstRowIdx + gridState.containerSizes.virtualRowsCount;
const lastColIdx = visibleColumns.length > 0 ? visibleColumns.length - 1 : 0;
const renderContext = { firstRowIdx, lastRowIdx, firstColIdx: 0, lastColIdx };

const scrollParams = {
top: windowRef.current!.scrollTop,
left: windowRef.current!.scrollLeft,
};

setGridState((state) => ({
...state,
rendering: {
...state.rendering,
virtualPage: 0,
renderContext,
realScroll: scrollParams,
renderingZoneScroll: scrollParams,
},
}));
forceUpdate();
}, [
gridState.containerSizes,
paginationState,
props.pagination,
props.paginationMode,
setGridState,
forceUpdate,
visibleColumns.length,
windowRef,
]);

React.useEffect(() => {
if (!props.disableVirtualization) {
return;
}
syncState();
}, [props.disableVirtualization, syncState]);

const handleScroll = React.useCallback(() => {
if (!props.disableVirtualization || !windowRef?.current) {
return;
}
const { scrollLeft, scrollTop } = windowRef.current;
scrollTo({ top: scrollTop, left: scrollLeft });
syncState();
}, [props.disableVirtualization, scrollTo, windowRef, syncState]);

useNativeEventListener(apiRef, windowRef!, 'scroll', handleScroll, { passive: true });
};
Loading

0 comments on commit 50edf8d

Please sign in to comment.