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 24 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
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
88 changes: 88 additions & 0 deletions packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx
@@ -0,0 +1,88 @@
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 caretRafRef = React.useRef(0);
dtassone marked this conversation as resolved.
Show resolved Hide resolved
React.useEffect(() => {
return () => {
cancelAnimationFrame(caretRafRef.current);
};
}, []);

const keepCaretPosition = React.useCallback((event) => {
// Fix caret from jumping to the end of the input
const caret = event.target.selectionStart;
const element = event.target;
caretRafRef.current = window.requestAnimationFrame(() => {
element.selectionStart = caret;
element.selectionEnd = caret;
});
}, []);

const onValueChange = React.useCallback(
(event) => {
if (colDef.type === 'string') {
keepCaretPosition(event);
}
dtassone marked this conversation as resolved.
Show resolved Hide resolved
const newValue = event.target.value;
const update: GridEditRowUpdate = {};
update[field] = {
value: colDef.type === 'date' || colDef.type === 'dateTime' ? new Date(newValue) : newValue,
};
editRowApi.setEditCellProps(row.id, update);
},
[editRowApi, colDef.type, field, keepCaretPosition, 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 =
value && isDate(value)
? formatDateToLocalInputDate({ value, withTime: colDef.type === 'dateTime' })
: value;

return (
<InputBase
autoFocus
onKeyDown={onKeyDown}
value={formattedValue}
onChange={onValueChange}
type={inputType}
style={{ width: '100%' }}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid inline style when you can, it's not CSP friendly. mui/material-ui#19938 (comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On another note, the UI of the editable input can be improved by a non-negligible amount. We could:

  • keep the same font-size (body2 typography instead of body1)
  • show an outline to better visualize where the input is/ if it's still in editable mode

{...inputBaseProps}
/>
);
}
export const renderEditInputCell = (params) => <EditInputCell {...params} />;
7 changes: 7 additions & 0 deletions packages/grid/_modules_/grid/constants/eventsConstants.ts
@@ -1,6 +1,7 @@
// 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';
Expand All @@ -9,12 +10,18 @@ export const GRID_SCROLL = 'scroll';
export const GRID_DRAGEND = 'dragend';

// XGRID events
dtassone marked this conversation as resolved.
Show resolved Hide resolved
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
3 changes: 3 additions & 0 deletions packages/grid/_modules_/grid/hooks/features/core/gridState.ts
Expand Up @@ -4,6 +4,7 @@ import {
GridScrollBarState,
GridViewportSizeState,
} from '../../../models/gridContainerProps';
import { GridEditRowsModel } from '../../../models/gridEditRowModel';
import { DEFAULT_GRID_OPTIONS, GridOptions } from '../../../models/gridOptions';
import { ColumnMenuState } from '../columnMenu/columnMenuState';
import {
Expand Down Expand Up @@ -32,6 +33,7 @@ import {

export interface GridState {
rows: InternalGridRowsState;
editRows: GridEditRowsModel;
pagination: PaginationState;
options: GridOptions;
isScrolling: boolean;
Expand All @@ -53,6 +55,7 @@ export interface GridState {

export const getInitialGridState: () => GridState = () => ({
rows: getInitialGridRowState(),
editRows: {},
dtassone marked this conversation as resolved.
Show resolved Hide resolved
pagination: GRID_INITIAL_PAGINATION_STATE,
options: DEFAULT_GRID_OPTIONS,
isScrolling: false,
Expand Down
Expand Up @@ -12,6 +12,7 @@ import { GridCellIndexCoordinates } from '../../../models/gridCell';
import {
findParentElementFromClassName,
getIdFromRowElem,
getRowEl,
isGridCellRoot,
} from '../../../utils/domUtils';
import {
Expand Down Expand Up @@ -211,10 +212,7 @@ export const useGridKeyboard = (
);

const handleCopy = React.useCallback(() => {
const rowEl = findParentElementFromClassName(
document.activeElement as HTMLDivElement,
GRID_ROW_CSS_CLASS,
)! as HTMLElement;
const rowEl = getRowEl(document.activeElement)!;
const rowId = getIdFromRowElem(rowEl);
const isRowSelected = selectionState[rowId];

Expand Down
@@ -0,0 +1,3 @@
import { GridState } from '../core/gridState';

export const gridEditRowsStateSelector = (state: GridState) => state.editRows;
dtassone marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions packages/grid/_modules_/grid/hooks/features/rows/index.ts
@@ -1,3 +1,5 @@
export * from './gridRowsSelector';
export * from './gridRowsState';
export * from './useGridRows';
export * from './gridEditRowsSelector';
export * from './useGridEditRows';