Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DataGrid] Prepare work for a future public state api #533

Merged
merged 11 commits into from
Nov 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion packages/grid/_modules_/grid/GridComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ export const GridComponent = React.forwardRef<HTMLDivElement, GridComponentProps
const renderingZoneRef = React.useRef<HTMLDivElement>(null);

const apiRef = useApiRef(props.apiRef);
const [gridState] = useGridState(apiRef);
const [gridState, setGridState, forceUpdate] = useGridState(apiRef);

const internalOptions = useOptionsProp(apiRef, props);

useLoggerFactory(internalOptions.logger, internalOptions.logLevel);
Expand Down Expand Up @@ -87,6 +88,14 @@ export const GridComponent = React.forwardRef<HTMLDivElement, GridComponentProps
[gridState.options, gridState.containerSizes],
);

React.useEffect(() => {
if (props.state != null && apiRef.current.state !== props.state) {
logger.debug('Overriding state with props.state');
setGridState((previousState) => ({ ...previousState, ...props.state! }));
dtassone marked this conversation as resolved.
Show resolved Hide resolved
forceUpdate();
}
}, [apiRef, forceUpdate, logger, props.state, setGridState]);

logger.info(
`Rendering, page: ${renderCtx?.page}, col: ${renderCtx?.firstColIdx}-${renderCtx?.lastColIdx}, row: ${renderCtx?.firstRowIdx}-${renderCtx?.lastRowIdx}`,
apiRef.current.state,
Expand Down
16 changes: 15 additions & 1 deletion packages/grid/_modules_/grid/GridComponentProps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { Columns, ApiRef, GridComponentOverridesProp, GridOptions, RowsProp } from './models';
import { GridState } from './hooks/features/core/gridState';
import { ApiRef } from './models/api/apiRef';
import { Columns } from './models/colDef/colDef';
import { GridComponentOverridesProp } from './models/gridComponentOverridesProp';
import { GridOptions } from './models/gridOptions';
import { StateChangeParams } from './models/params/stateChangeParams';
import { RowsProp } from './models/rows';

/**
* Partial set of [[GridOptions]].
Expand Down Expand Up @@ -41,4 +47,12 @@ export interface GridComponentProps extends GridOptionsProp {
* An error that will turn the grid into its error state and display the error component.
*/
error?: any;
/**
* Set the whole state of the grid.
*/
state?: Partial<GridState>;
dtassone marked this conversation as resolved.
Show resolved Hide resolved
/**
* Set a callback fired when the state of the grid is updated.
*/
onStateChange?: (params: StateChangeParams) => void; // We are overriding the handler in GridOptions to fix the params type and avoid the cycle dependency
}
208 changes: 105 additions & 103 deletions packages/grid/_modules_/grid/components/column-header-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,114 +20,116 @@ interface ColumnHeaderItemProps {
separatorProps: React.HTMLAttributes<HTMLDivElement>;
}

export const ColumnHeaderItem = ({
column,
colIndex,
isDragging,
isResizing,
separatorProps,
sortDirection,
sortIndex,
options,
}: ColumnHeaderItemProps) => {
const apiRef = React.useContext(ApiContext);
const { disableColumnReorder, showColumnRightBorder, disableColumnResize } = options;
export const ColumnHeaderItem = React.memo(
({
column,
colIndex,
isDragging,
isResizing,
separatorProps,
sortDirection,
sortIndex,
options,
}: ColumnHeaderItemProps) => {
const apiRef = React.useContext(ApiContext);
const { disableColumnReorder, showColumnRightBorder, disableColumnResize } = options;

let headerComponent: React.ReactElement | null = null;
if (column.renderHeader) {
headerComponent = column.renderHeader({
api: apiRef!.current!,
colDef: column,
colIndex,
field: column.field,
});
}
let headerComponent: React.ReactElement | null = null;
if (column.renderHeader) {
headerComponent = column.renderHeader({
api: apiRef!.current!,
colDef: column,
colIndex,
field: column.field,
});
}

const onDragStart = React.useCallback(
(event) => apiRef!.current.onColItemDragStart(column, event.currentTarget),
[apiRef, column],
);
const onDragEnter = React.useCallback((event) => apiRef!.current.onColItemDragEnter(event), [
apiRef,
]);
const onDragOver = React.useCallback(
(event) =>
apiRef!.current.onColItemDragOver(column, {
x: event.clientX,
y: event.clientY,
}),
[apiRef, column],
);
const onDragStart = React.useCallback(
(event) => apiRef!.current.onColItemDragStart(column, event.currentTarget),
[apiRef, column],
);
const onDragEnter = React.useCallback((event) => apiRef!.current.onColItemDragEnter(event), [
apiRef,
]);
const onDragOver = React.useCallback(
(event) =>
apiRef!.current.onColItemDragOver(column, {
x: event.clientX,
y: event.clientY,
}),
[apiRef, column],
);

const dragConfig = {
draggable: !disableColumnReorder,
onDragStart,
onDragEnter,
onDragOver,
};
const width = column.width!;

let ariaSort: any;
if (sortDirection != null) {
ariaSort = {
'aria-sort': sortDirection === 'asc' ? 'ascending' : 'descending',
const dragConfig = {
draggable: !disableColumnReorder,
onDragStart,
onDragEnter,
onDragOver,
};
}
const width = column.width!;

return (
<div
className={classnames(
HEADER_CELL_CSS_CLASS,
showColumnRightBorder ? 'MuiDataGrid-withBorder' : '',
column.headerClassName,
column.headerAlign === 'center' && 'MuiDataGrid-colCellCenter',
column.headerAlign === 'right' && 'MuiDataGrid-colCellRight',
{
'MuiDataGrid-colCellSortable': column.sortable,
'MuiDataGrid-colCellMoving': isDragging,
},
)}
key={column.field}
data-field={column.field}
style={{
width,
minWidth: width,
maxWidth: width,
}}
role="columnheader"
tabIndex={-1}
aria-colindex={colIndex + 1}
{...ariaSort}
>
<div className="MuiDataGrid-colCell-draggable" {...dragConfig}>
{column.type === 'number' && (
<ColumnHeaderSortIcon
direction={sortDirection}
index={sortIndex}
hide={column.hideSortIcons}
/>
)}
{headerComponent || (
<ColumnHeaderTitle
label={column.headerName || column.field}
description={column.description}
columnWidth={width}
/>
)}
{column.type !== 'number' && (
<ColumnHeaderSortIcon
direction={sortDirection}
index={sortIndex}
hide={column.hideSortIcons}
/>
let ariaSort: any;
if (sortDirection != null) {
ariaSort = {
'aria-sort': sortDirection === 'asc' ? 'ascending' : 'descending',
};
}

return (
<div
className={classnames(
HEADER_CELL_CSS_CLASS,
showColumnRightBorder ? 'MuiDataGrid-withBorder' : '',
column.headerClassName,
column.headerAlign === 'center' && 'MuiDataGrid-colCellCenter',
column.headerAlign === 'right' && 'MuiDataGrid-colCellRight',
{
'MuiDataGrid-colCellSortable': column.sortable,
'MuiDataGrid-colCellMoving': isDragging,
},
)}
key={column.field}
data-field={column.field}
style={{
width,
minWidth: width,
maxWidth: width,
}}
role="columnheader"
tabIndex={-1}
aria-colindex={colIndex + 1}
{...ariaSort}
>
<div className="MuiDataGrid-colCell-draggable" {...dragConfig}>
{column.type === 'number' && (
<ColumnHeaderSortIcon
direction={sortDirection}
index={sortIndex}
hide={column.hideSortIcons}
/>
)}
{headerComponent || (
<ColumnHeaderTitle
label={column.headerName || column.field}
description={column.description}
columnWidth={width}
/>
)}
{column.type !== 'number' && (
<ColumnHeaderSortIcon
direction={sortDirection}
index={sortIndex}
hide={column.hideSortIcons}
/>
)}
</div>
<ColumnHeaderSeparator
resizable={!disableColumnResize && !!column.resizable}
resizing={isResizing}
{...separatorProps}
/>
</div>
<ColumnHeaderSeparator
resizable={!disableColumnResize && !!column.resizable}
resizing={isResizing}
{...separatorProps}
/>
</div>
);
};
);
},
);
ColumnHeaderItem.displayName = 'ColumnHeaderItem';
2 changes: 2 additions & 0 deletions packages/grid/_modules_/grid/constants/eventsConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,6 @@ export const COLUMNS_UPDATED = 'columnsUpdated';

export const SORT_MODEL_CHANGE = 'sortModelChange';

export const STATE_CHANGE = 'stateChange';

export const MULTIPLE_KEY_PRESS_CHANGED = 'multipleKeyPressChange';
39 changes: 36 additions & 3 deletions packages/grid/_modules_/grid/hooks/features/core/useGridApi.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import * as React from 'react';
import { STATE_CHANGE } from '../../../constants/eventsConstants';
import { ApiRef } from '../../../models/api/apiRef';
import { GridApi } from '../../../models/api/gridApi';
import { StateChangeParams } from '../../../models/params/stateChangeParams';
import { isFunction } from '../../../utils/utils';
import { useApiMethod } from '../../root/useApiMethod';
import { useLogger } from '../../utils/useLogger';
import { getInitialState } from './gridState';
import { getInitialState, GridState } from './gridState';

export const useGridApi = (apiRef: ApiRef): GridApi => {
const logger = useLogger('useGridApi');
Expand All @@ -11,9 +15,38 @@ export const useGridApi = (apiRef: ApiRef): GridApi => {
logger.info('Initialising state.');
apiRef.current.state = getInitialState();
apiRef.current.forceUpdate = forceUpdate;
apiRef.current.getState = <State>(stateId?: string) =>
(stateId ? apiRef.current.state[stateId] : apiRef.current.state) as State;
}

const getState = React.useCallback(
<State>(stateId?: string) =>
(stateId ? apiRef.current.state[stateId] : apiRef.current.state) as State,
[apiRef],
);

const onStateChange = React.useCallback(
(handler: (param: StateChangeParams) => void): (() => void) => {
return apiRef.current.subscribeEvent(STATE_CHANGE, handler);
},
[apiRef],
);

const setState = React.useCallback(
(stateOrFunc: GridState | ((oldState: GridState) => GridState)) => {
let state: GridState;
if (isFunction(stateOrFunc)) {
state = stateOrFunc(apiRef.current.state);
} else {
state = stateOrFunc;
}
apiRef.current.state = state;
forceUpdate(() => state);
const params: StateChangeParams = { api: apiRef.current, state };
apiRef.current.publishEvent(STATE_CHANGE, params);
},
[apiRef],
);

useApiMethod(apiRef, { getState, onStateChange, setState }, 'StateApi');

return apiRef.current;
};
19 changes: 15 additions & 4 deletions packages/grid/_modules_/grid/hooks/features/core/useGridState.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import * as React from 'react';
import { STATE_CHANGE } from '../../../constants/eventsConstants';
import { ApiRef } from '../../../models/api/apiRef';
import { StateChangeParams } from '../../../models/params/stateChangeParams';
import { GridState } from './gridState';
import { useGridApi } from './useGridApi';

export const useGridState = (
apiRef: ApiRef,
): [GridState, (stateUpdaterFn: (oldState: GridState) => GridState) => void, () => void] => {
const api = useGridApi(apiRef);
useGridApi(apiRef);
const forceUpdate = React.useCallback(
() => apiRef.current.forceUpdate(() => apiRef.current.state),
[apiRef],
);
const setGridState = React.useCallback(
(stateUpdaterFn: (oldState: GridState) => GridState) => {
api.state = stateUpdaterFn(api.state);
const newState = stateUpdaterFn(apiRef.current.state);
const hasChanged = apiRef.current.state !== newState;

// We always assign it as we mutate rows for perf reason.
apiRef.current.state = newState;

if (hasChanged && apiRef.current.publishEvent) {
const params: StateChangeParams = { api: apiRef.current, state: newState };
apiRef.current.publishEvent(STATE_CHANGE, params);
}
},
[api],
[apiRef],
);
return [api.state, setGridState, forceUpdate];
return [apiRef.current.state, setGridState, forceUpdate];
};
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ export const useSorting = (apiRef: ApiRef) => {
(sortModel: SortModel): FieldComparatorList => {
const comparators = sortModel.map((item) => {
const column = apiRef.current.getColumnFromField(item.field);
if (!column) {
throw new Error(`Error sorting: column with field '${item.field}' not found. `);
}
const comparator = isDesc(item.sort)
? (v1: CellValue, v2: CellValue, cellParams1: CellParams, cellParams2: CellParams) =>
-1 * column.sortComparator!(v1, v2, cellParams1, cellParams2)
Expand Down Expand Up @@ -204,7 +207,7 @@ export const useSorting = (apiRef: ApiRef) => {
if (gridState.sorting.sortModel.length > 0) {
apiRef.current.applySorting();
}
}, [gridState.sorting.sortModel.length, apiRef]);
}, [gridState.sorting.sortModel, apiRef]);

const getSortModel = React.useCallback(() => gridState.sorting.sortModel, [
gridState.sorting.sortModel,
Expand Down
Loading