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] Implement base foundation for editing a cell #1025

Merged
merged 34 commits into from Mar 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3a38366
WIP implementation of edit cell with validation and serverside callback
dtassone Feb 10, 2021
b16062a
fix rebase issues
dtassone Feb 22, 2021
53d7c90
fix date edit and refactoring
dtassone Feb 23, 2021
9f066f7
fixes and refactoring
dtassone Feb 24, 2021
84dbf55
fix client edit rows
dtassone Feb 24, 2021
a0642b9
format
dtassone Feb 24, 2021
11e7da0
add todo
dtassone Feb 24, 2021
03debae
fix edit click
dtassone Feb 24, 2021
cabe172
add missing import
dtassone Feb 24, 2021
699e8d3
fix imports
dtassone Feb 24, 2021
1283d4e
Merge branch 'master' of github.com:mui-org/material-ui-x into editMode
dtassone Feb 25, 2021
c15dfc2
refactor edit row state, and api
dtassone Feb 25, 2021
67451b9
add gridEditCellParams
dtassone Feb 25, 2021
357f385
fix event publish and add api desc
dtassone Feb 25, 2021
72a9863
fix import order
dtassone Feb 25, 2021
7d7d361
add basic test to switch cell mode
dtassone Feb 26, 2021
77a17b3
sort asc
oliviertassinari Feb 27, 2021
d84a10a
sort asc
oliviertassinari Feb 27, 2021
5e7dd29
blank line convention
oliviertassinari Feb 27, 2021
142d693
blank line convention
oliviertassinari Feb 27, 2021
2b1acc3
prefer assertion with a potentially better DX
oliviertassinari Feb 27, 2021
0d03941
blank line convention
oliviertassinari Feb 27, 2021
d8e117b
blank line convention
oliviertassinari Feb 27, 2021
d9b461f
Api description improvements
dtassone Mar 1, 2021
df4ee43
small refactoring
dtassone Mar 1, 2021
629e2a5
more small refactoring
dtassone Mar 1, 2021
c067a32
cleanup and prettier
dtassone Mar 1, 2021
19ab929
fix edit input ux, rm caret
dtassone Mar 1, 2021
ee320e8
prettier lint
dtassone Mar 1, 2021
30eea3e
Merge branch 'master' into editMode
dtassone Mar 2, 2021
c27e5d1
fix package.json syntax
dtassone Mar 2, 2021
df0e557
small fixes
dtassone Mar 2, 2021
cf65266
add methods in api pages
dtassone Mar 2, 2021
6dca60e
fix update rows
dtassone Mar 2, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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%)',
dtassone marked this conversation as resolved.
Show resolved Hide resolved
},
},
// 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