Skip to content

Commit

Permalink
[DataGrid] Implement base foundation for editing a cell (#1025)
Browse files Browse the repository at this point in the history
  • Loading branch information
dtassone committed Mar 3, 2021
1 parent 025d2f3 commit 387d6e8
Show file tree
Hide file tree
Showing 39 changed files with 1,024 additions and 45 deletions.
6 changes: 6 additions & 0 deletions docs/pages/api-docs/data-grid.md
Expand Up @@ -28,22 +28,28 @@ import { DataGrid } from '@material-ui/data-grid';
| <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">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">editRowsModel</span> | <span class="prop-type">GridEditRowsModel</span> | undefined | Set the edit rows model of the grid. |
| <span class="prop-name">getRowId</span> | <span class="prop-type">GridRowIdGetter</span> | (row)=> row.id | A function that allows the grid to retrieve the row id. |
| <span class="prop-name">headerHeight</span> | <span class="prop-type">number</span> | 56 | Set the height in pixel of the column headers in the grid. |
| <span class="prop-name">hideFooter</span> | <span class="prop-type">boolean</span> | false | If `true`, the footer component is hidden. |
| <span class="prop-name">hideFooterPagination</span> | <span class="prop-type">boolean</span> | false | If `true`, the pagination component in the footer is hidden. |
| <span class="prop-name">hideFooterRowCount</span> | <span class="prop-type">boolean</span> | false | If `true`, the row count in the footer is hidden. |
| <span class="prop-name">hideFooterSelectedRowCount</span> | <span class="prop-type">boolean</span> | false | If `true`, the selected row count in the footer is hidden. |
| <span class="prop-name">icons</span> | <span class="prop-type">IconsOptions</span> | | Set of icons used in the grid. |
| <span class="prop-name">isCellEditable</span> | <span class="prop-type">(params: GridCellParams) => boolean; </span> | | Callback fired when a cell is rendered, returns true if the cell is editable. |
| <span class="prop-name">loading</span> | <span class="prop-type">boolean</span> | false | If `true`, a loading overlay is displayed. |
| <span class="prop-name">localeText</span> | <span class="prop-type">GridLocaleText</span> | | Set of text labels used in 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. |
| <span class="prop-name">logger</span> | <span class="prop-type">Logger</span> | null | Pass a custom logger in the components that implements the 'Logger' interface. |
| <span class="prop-name">logLevel</span> | <span class="prop-type">string | false</span> | false | Allows to pass the logging level or false to turn off logging. |
| <span class="prop-name">nonce</span> | <span class="prop-type">string</span> | | Nonce of the inline styles for [Content Security Policy](https://www.w3.org/TR/2016/REC-CSP2-20161215/#script-src-the-nonce-attribute). |
| <span class="prop-name">onCellClick</span> | <span class="prop-type">(param: GridCellParams) => void</span> | | Callback fired when a click event comes from a cell element. |
| <span class="prop-name">onCellHover</span> | <span class="prop-type">(param: GridCellParams) => void</span> | | Callback fired when a hover event comes from a cell element. |
| <span class="prop-name">onCellModeChange</span> | <span class="prop-type">(params: GridCellModeChangeParams) => void | | Callback fired when the cell mode changed. |
| <span class="prop-name">onColumnHeaderClick</span> | <span class="prop-type">(param: GridColParams) => void</span> | | Callback fired when a click event comes from a column header element. |
| <span class="prop-name">onError</span> | <span class="prop-type">(args: any) => void</span> | | Callback fired when an exception is thrown in the grid, or when the `showError` API method is called. |
| <span class="prop-name">onEditCellChange</span> | <span class="prop-type">(params: GridEditCellParams) => void</span> | | Callback fired when the edit cell value changed. |
| <span class="prop-name">onEditCellChangeCommitted</span> | <span class="prop-type">(params: GridEditCellParams) => void</span> | | Callback fired when the cell changes are committed. |
| <span class="prop-name">onEditRowModelChange</span> | <span class="prop-type">(params: GridEditRowModelParams) => void</span> | | Callback fired when the EditRowModel changed. |
| <span class="prop-name">onFilterModelChange</span> | <span class="prop-type">(params: GridFilterModelParams) => void</span> | | Callback fired when the Filter model changes before the filters are applied. |
| <span class="prop-name">onPageChange</span> | <span class="prop-type">(param: GridPageChangeParams) => void</span> | | Callback fired when the current page has changed. |
| <span class="prop-name">onPageSizeChange</span> | <span class="prop-type">(param: GridPageChangeParams) => void</span> | | Callback fired when the page size has changed. |
Expand Down
7 changes: 6 additions & 1 deletion docs/pages/api-docs/x-grid.md
Expand Up @@ -32,22 +32,27 @@ import { XGrid } from '@material-ui/x-grid';
| <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">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">getRowId</span> | <span class="prop-type">GridRowIdGetter</span> | (row)=> row.id | A function that allows the grid to retrieve the row id. |
| <span class="prop-name">editRowsModel</span> | <span class="prop-type">GridEditRowsModel</span> | undefined | Set the edit rows model of the grid. || <span class="prop-name">getRowId</span> | <span class="prop-type">GridRowIdGetter</span> | (row)=> row.id | A function that allows the grid to retrieve the row id. |
| <span class="prop-name">headerHeight</span> | <span class="prop-type">number</span> | 56 | Set the height in pixel of the column headers in the grid. |
| <span class="prop-name">hideFooter</span> | <span class="prop-type">boolean</span> | false | If `true`, the footer component is hidden. |
| <span class="prop-name">hideFooterPagination</span> | <span class="prop-type">boolean</span> | false | If `true`, the pagination component in the footer is hidden. |
| <span class="prop-name">hideFooterRowCount</span> | <span class="prop-type">boolean</span> | false | If `true`, the row count in the footer is hidden. |
| <span class="prop-name">hideFooterSelectedRowCount</span> | <span class="prop-type">boolean</span> | false | If `true`, the selected row count in the footer is hidden. |
| <span class="prop-name">icons</span> | <span class="prop-type">IconsOptions</span> | | Set of icons used in the grid. |
| <span class="prop-name">isCellEditable</span> | <span class="prop-type">(params: GridCellParams) => boolean; </span> | | Callback fired when a cell is rendered, returns true if the cell is editable. |
| <span class="prop-name">loading</span> | <span class="prop-type">boolean</span> | false | If `true`, a loading overlay is displayed.. |
| <span class="prop-name">localeText</span> | <span class="prop-type">GridLocaleText</span> | | Set of text labels used in 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. |
| <span class="prop-name">logger</span> | <span class="prop-type">Logger</span> | null | Pass a custom logger in the components that implements the 'Logger' interface. |
| <span class="prop-name">logLevel</span> | <span class="prop-type">string | false</span> | false | Allows to pass the logging level or false to turn off logging. |
| <span class="prop-name">nonce</span> | <span class="prop-type">string</span> | | Nonce of the inline styles for [Content Security Policy](https://www.w3.org/TR/2016/REC-CSP2-20161215/#script-src-the-nonce-attribute). |
| <span class="prop-name">onCellClick</span> | <span class="prop-type">(param: GridCellParams) => void</span> | | Callback fired when a click event comes from a cell element. |
| <span class="prop-name">onCellHover</span> | <span class="prop-type">(param: GridCellParams) => void</span> | | Callback fired when a hover event comes from a cell element. |
| <span class="prop-name">onCellModeChange</span> | <span class="prop-type">(params: GridCellModeChangeParams) => void | | Callback fired when the cell mode changed. |
| <span class="prop-name">onColumnHeaderClick</span> | <span class="prop-type">(param: GridColParams) => void</span> | | Callback fired when a click event comes from a column header element. |
| <span class="prop-name">onError</span> | <span class="prop-type">(args: any) => void</span> | | Callback fired when an exception is thrown in the grid, or when the `showError` API method is called. |
| <span class="prop-name">onEditCellChange</span> | <span class="prop-type">(params: GridEditCellParams) => void</span> | | Callback fired when the edit cell value changed. |
| <span class="prop-name">onEditCellChangeCommitted</span> | <span class="prop-type">(params: GridEditCellParams) => void</span> | | Callback fired when the cell changes are committed. |
| <span class="prop-name">onEditRowModelChange</span> | <span class="prop-type">(params: GridEditRowModelParams) => void</span> | | Callback fired when the EditRowModel changed. |
| <span class="prop-name">onFilterModelChange</span> | <span class="prop-type">(params: GridFilterModelParams) => void</span> | | Callback fired when the Filter model changes before the filters are applied. |
| <span class="prop-name">onPageChange</span> | <span class="prop-type">(param: GridPageChangeParams) => void</span> | | Callback fired when the current page has changed. |
| <span class="prop-name">onPageSizeChange</span> | <span class="prop-type">(param: GridPageChangeParams) => void</span> | | Callback fired when the page size has changed. |
Expand Down
2 changes: 2 additions & 0 deletions packages/grid/_modules_/grid/GridComponent.tsx
Expand Up @@ -22,6 +22,7 @@ import { useGridState } from './hooks/features/core/useGridState';
import { useGridPagination } from './hooks/features/pagination/useGridPagination';
import { useGridPreferencesPanel } from './hooks/features/preferencesPanel/useGridPreferencesPanel';
import { useGridRows } from './hooks/features/rows/useGridRows';
import { useGridEditRows } from './hooks/features/rows/useGridEditRows';
import { useGridSorting } from './hooks/features/sorting/useGridSorting';
import { useGridApiRef } from './hooks/features/useGridApiRef';
import { useGridColumnReorder } from './hooks/features/columnReorder';
Expand Down Expand Up @@ -75,6 +76,7 @@ export const GridComponent = React.forwardRef<HTMLDivElement, GridComponentProps

useGridColumns(props.columns, apiRef);
useGridRows(apiRef, props.rows, props.getRowId);
useGridEditRows(apiRef);
useGridKeyboard(rootContainerRef, apiRef);
useGridSelection(apiRef);
useGridSorting(apiRef, props.rows);
Expand Down
22 changes: 13 additions & 9 deletions packages/grid/_modules_/grid/components/GridCell.tsx
Expand Up @@ -5,18 +5,19 @@ import { GRID_CELL_CSS_CLASS } from '../constants/cssClassesConstants';
import { classnames } from '../utils';

export interface GridCellProps {
align: GridAlignment;
colIndex?: number;
cssClass?: string;
field?: string;
value?: GridCellValue;
formattedValue?: GridCellValue;
width: number;
hasFocus?: boolean;
height: number;
isEditable?: boolean;
rowIndex?: number;
showRightBorder?: boolean;
hasFocus?: boolean;
align: GridAlignment;
cssClass?: string;
tabIndex?: number;
colIndex?: number;
rowIndex?: number;
value?: GridCellValue;
width: number;
}

export const GridCell: React.FC<GridCellProps> = React.memo((props) => {
Expand All @@ -25,15 +26,16 @@ export const GridCell: React.FC<GridCellProps> = React.memo((props) => {
children,
colIndex,
cssClass,
hasFocus,
field,
formattedValue,
hasFocus,
height,
isEditable,
rowIndex,
showRightBorder,
tabIndex,
value,
width,
height,
} = props;

const valueToRender = formattedValue || value;
Expand All @@ -50,11 +52,13 @@ export const GridCell: React.FC<GridCellProps> = React.memo((props) => {
ref={cellRef}
className={classnames(GRID_CELL_CSS_CLASS, cssClass, `MuiDataGrid-cell${capitalize(align)}`, {
'MuiDataGrid-withBorder': showRightBorder,
'MuiDataGrid-cellEditable': isEditable,
})}
role="cell"
data-value={value}
data-field={field}
data-rowindex={rowIndex}
data-editable={isEditable}
aria-colindex={colIndex}
style={{
minWidth: width,
Expand Down
21 changes: 17 additions & 4 deletions packages/grid/_modules_/grid/components/GridRowCells.tsx
@@ -1,4 +1,5 @@
import * as React from 'react';
import { gridEditRowsStateSelector } from '../hooks/features/rows/gridEditRowsSelector';
import {
GridCellClassParams,
GridColumns,
Expand Down Expand Up @@ -51,6 +52,7 @@ export const GridRowCells: React.FC<RowCellsProps> = React.memo((props) => {
} = props;
const api = React.useContext(GridApiContext);
const rowHeight = useGridSelector(api, gridDensityRowHeightSelector);
const editRowsState = useGridSelector(api, gridEditRowsStateSelector);

const cellsProps = columns.slice(firstColIdx, lastColIdx + 1).map((column, colIdx) => {
const isLastColumn = firstColIdx + colIdx === columns.length - 1;
Expand All @@ -66,6 +68,7 @@ export const GridRowCells: React.FC<RowCellsProps> = React.memo((props) => {
rowModel: row,
colDef: column,
rowIndex,
colIndex: colIdx,
value,
api: api!.current!,
});
Expand All @@ -84,11 +87,8 @@ export const GridRowCells: React.FC<RowCellsProps> = React.memo((props) => {
cssClassProp = { cssClass: `${cssClassProp.cssClass} ${cssClass}` };
}

const editCellState = editRowsState[row.id] && editRowsState[row.id][column.field];
let cellComponent: React.ReactElement | null = null;
if (column.renderCell) {
cellComponent = column.renderCell(cellParams);
cssClassProp = { cssClass: `${cssClassProp.cssClass} MuiDataGrid-cellWithRenderer` };
}

if (column.valueGetter) {
// Value getter override the original value
Expand All @@ -98,9 +98,21 @@ export const GridRowCells: React.FC<RowCellsProps> = React.memo((props) => {

let formattedValueProp = {};
if (column.valueFormatter) {
// TODO add formatted value to cellParams?
formattedValueProp = { formattedValue: column.valueFormatter(cellParams) };
}

if (editCellState == null && column.renderCell) {
cellComponent = column.renderCell(cellParams);
cssClassProp = { cssClass: `${cssClassProp.cssClass} MuiDataGrid-cellWithRenderer` };
}

if (editCellState != null && column.renderEditCell) {
const params = { ...cellParams, ...editCellState };
cellComponent = column.renderEditCell(params);
cssClassProp = { cssClass: `${cssClassProp.cssClass} MuiDataGrid-cellEditing` };
}

const cellProps: GridCellProps & { children: any } = {
value,
field: column.field,
Expand All @@ -114,6 +126,7 @@ export const GridRowCells: React.FC<RowCellsProps> = React.memo((props) => {
rowIndex,
colIndex: colIdx + firstColIdx,
children: cellComponent,
isEditable: cellParams.isEditable,
hasFocus:
cellFocus !== null &&
cellFocus.rowIndex === rowIndex &&
Expand Down
Expand Up @@ -230,6 +230,17 @@ export const useStyles = makeStyles(
whiteSpace: 'nowrap',
borderBottom: `1px solid ${borderColor}`,
},
'& .MuiDataGrid-editCellInputBase': {
...theme.typography.body2,
border: `1px solid ${borderColor}`,
borderRadius: theme.shape.borderRadius,
'& :focus': {
borderColor: theme.palette.text.primary,
outline: 0,
// boxShadow: getThemePaletteMode(theme.palette) === 'light' ? null : '0 0 0 100px #266798 inset',
boxShadow: 'inset 0 1px 1px rgb(0 0 0 / 8%), 0 0 8px rgb(102 175 233 / 60%)',
},
},
// The very last cell
'& .MuiDataGrid-colCellWrapper .MuiDataGrid-cell': {
borderBottom: 'none',
Expand Down
76 changes: 76 additions & 0 deletions packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx
@@ -0,0 +1,76 @@
import * as React from 'react';
import InputBase, { InputBaseProps } from '@material-ui/core/InputBase';
import { GridCellParams } from '../../models/params/gridCellParams';
import { formatDateToLocalInputDate, isDate, mapColDefTypeToInputType } from '../../utils/utils';
import { GridEditRowUpdate } from '../../models/gridEditRowModel';
import { GridEditRowApi } from '../../models/api/gridEditRowApi';

export function EditInputCell(props: GridCellParams & InputBaseProps) {
const {
value,
api,
field,
row,
colDef,
getValue,
rowIndex,
colIndex,
isEditable,
...inputBaseProps
} = props;

const editRowApi = api as GridEditRowApi;
const [valueState, setValueState] = React.useState(value);

const onValueChange = React.useCallback(
(event) => {
const newValue = event.target.value;
const update: GridEditRowUpdate = {};
update[field] = {
value: colDef.type === 'date' || colDef.type === 'dateTime' ? new Date(newValue) : newValue,
};
setValueState(newValue);
editRowApi.setEditCellProps(row.id, update);
},
[editRowApi, colDef.type, field, row.id],
);

const onKeyDown = React.useCallback(
(event: React.KeyboardEvent) => {
if (!inputBaseProps.error && event.key === 'Enter') {
const update: GridEditRowUpdate = {};
update[field] = { value };
editRowApi.commitCellChange(row.id, update);
}

if (event.key === 'Escape') {
editRowApi.setCellMode(row.id, field, 'view');
}
},
[inputBaseProps.error, row.id, field, value, editRowApi],
);

const inputType = mapColDefTypeToInputType(colDef.type);
const formattedValue =
valueState && isDate(valueState)
? formatDateToLocalInputDate({ value: valueState, withTime: colDef.type === 'dateTime' })
: valueState;

React.useEffect(() => {
setValueState(value);
}, [value]);

return (
<InputBase
autoFocus
fullWidth
className="MuiDataGrid-editCellInputBase"
onKeyDown={onKeyDown}
value={formattedValue}
onChange={onValueChange}
type={inputType}
{...inputBaseProps}
/>
);
}
export const renderEditInputCell = (params) => <EditInputCell {...params} />;
9 changes: 8 additions & 1 deletion packages/grid/_modules_/grid/constants/eventsConstants.ts
@@ -1,20 +1,27 @@
// Web standard events
export const GRID_RESIZE = 'resize';
export const GRID_CLICK = 'click';
export const GRID_DOUBLE_CLICK = 'dblclick';
export const GRID_MOUSE_HOVER = 'mouseover';
export const GRID_FOCUS_OUT = 'focusout';
export const GRID_KEYDOWN = 'keydown';
export const GRID_KEYUP = 'keyup';
export const GRID_SCROLL = 'scroll';
export const GRID_DRAGEND = 'dragend';

// XGRID events
// GRID events
export const GRID_CELL_CHANGE = 'cellChange';
export const GRID_CELL_CHANGE_COMMITTED = 'cellChangeCommitted';
export const GRID_CELL_MODE_CHANGE = 'cellModeChange';
export const GRID_EDIT_ROW_MODEL_CHANGE = 'editRowModelChange';
export const GRID_COMPONENT_ERROR = 'componentError';
export const GRID_UNMOUNT = 'unmount';
export const GRID_ELEMENT_FOCUS_OUT = 'gridFocusOut';
export const GRID_CELL_CLICK = 'cellClick';
export const GRID_DOUBLE_CELL_CLICK = 'doubleCellClick';
export const GRID_CELL_HOVER = 'cellHover';
export const GRID_ROW_CLICK = 'rowClick';
export const GRID_DOUBLE_ROW_CLICK = 'doubleRowClick';
export const GRID_ROW_HOVER = 'rowHover';
export const GRID_ROW_SELECTED = 'rowSelected';
export const GRID_SELECTION_CHANGED = 'selectionChange';
Expand Down

0 comments on commit 387d6e8

Please sign in to comment.