Skip to content

Updated Excel edit style sample to functional component (React v19) #777

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

Open
wants to merge 9 commits into
base: vnext
Choose a base branch
from
217 changes: 102 additions & 115 deletions samples/grids/grid/editing-excel-style/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,146 +1,133 @@
import React from 'react';
import React, { useRef, useEffect } from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';

import { IgrGridModule } from 'igniteui-react-grids';
import { IgrGrid, IgrColumn } from 'igniteui-react-grids';
import { ComponentRenderer, WebGridDescriptionModule } from 'igniteui-react-core';
import { IgrGrid, IgrColumn, IgrActiveNodeChangeEventArgs } from 'igniteui-react-grids';
import NwindData from './NwindData.json';
import { IgrGridKeydownEventArgs } from 'igniteui-react-grids';

import 'igniteui-react-grids/grids/combined';
import 'igniteui-react-grids/grids/themes/light/bootstrap.css';

const mods: any[] = [
IgrGridModule
];
mods.forEach((m) => m.register());

export default class Sample extends React.Component<any, any> {
private grid1: IgrGrid
private grid1Ref(r: IgrGrid) {
this.grid1 = r;
this.setState({});
}
const nwindData = NwindData;

constructor(props: any) {
super(props);

this.grid1Ref = this.grid1Ref.bind(this);
this.webGridEditingExcelStyle = this.webGridEditingExcelStyle.bind(this);
}

public render(): JSX.Element {
return (
<div className="container sample ig-typography">

<div className="container fill">
<IgrGrid
autoGenerate={false}
data={this.nwindData}
primaryKey="ProductID"
onGridKeydown={this.webGridEditingExcelStyle}
ref={this.grid1Ref}>
<IgrColumn
field="ProductID"
header="Product ID"
editable={true}
groupable={true}
hidden={true}>
</IgrColumn>
<IgrColumn
field="ProductName"
header="Product Name"
dataType="string"
editable={true}>
</IgrColumn>
<IgrColumn
field="UnitPrice"
header="Unit Price"
dataType="number"
editable={true}>
</IgrColumn>
<IgrColumn
field="QuantityPerUnit"
header="Quantity Per Unit"
groupable={true}
dataType="string"
editable={true}>
</IgrColumn>
<IgrColumn
field="ReorderLevel"
header="Reorder Level"
dataType="number"
groupable={true}
editable={true}>
</IgrColumn>
</IgrGrid>
</div>
</div>
);
}
const Sample = () => {
const gridRef = useRef<IgrGrid>(null);
const shouldAppendValue = useRef(false);

private _nwindData: any[] = NwindData;
public get nwindData(): any[] {
return this._nwindData;
}
useEffect(() => {
const gridElement = gridRef.current;

private _componentRenderer: ComponentRenderer = null;
public get renderer(): ComponentRenderer {
if (this._componentRenderer == null) {
this._componentRenderer = new ComponentRenderer();
var context = this._componentRenderer.context;
WebGridDescriptionModule.register(context);
if (!gridElement) {
return undefined;
}
return this._componentRenderer;
}

public webGridEditingExcelStyle(args: IgrGridKeydownEventArgs): void {
var key = (args.detail.event as any).keyCode;
var grid = args.detail.target.grid;
var activeElem = grid.navigation.activeNode;
const handleKeyDown = (event: KeyboardEvent) => {
var code = event.code;
var activeElem = gridRef.current.selectedCells[0];
Copy link
Member

Choose a reason for hiding this comment

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

Also const instead of var please :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed


if ((event.code >= 'Digit0' && event.code <= 'Digit9') ||
(event.code >= 'KeyA' && event.code <= 'KeyZ') ||
(event.code >= 'Numpad0' && event.code <= 'Numpad9') &&
event.code !== 'Enter' && event.code !== 'NumpadEnter') {

if (activeElem && activeElem.editMode === false) {
activeElem.editMode = true;
activeElem.editValue = event.key;
shouldAppendValue.current = true;
gridRef.current.markForCheck();
} else

if (activeElem && activeElem.editMode && shouldAppendValue.current) {
event.preventDefault();
activeElem.editValue = activeElem.editValue + event.key;
shouldAppendValue.current = false;
}
}

if ((key >= 48 && key <= 57) || (key >= 65 && key <= 90) || (key >= 97 && key <= 122)) {
var columnName = grid.getColumnByVisibleIndex(activeElem.column).field;
var cell = grid.getCellByColumn(activeElem.row, columnName);
if (code === 'Backspace') {
if(activeElem == null) {
return;
}
const rowIndex = activeElem.row.index;
const columnKey = activeElem.column.field;

gridRef.current.data[rowIndex][columnKey] = '';
gridRef.current.markForCheck();

if (cell && !grid.crudService.cellInEditMode) {
grid.crudService.enterEditMode(cell);
cell.editValue = key;
}
}

if (key == 13) {
var thisRow = activeElem.row;
var column = activeElem.column;
var rowInfo = grid.dataView;
if (code === 'Enter' || code === 'NumpadEnter') {

if(activeElem == null) {
return;
}

const thisRow = activeElem.row.index;
const dataView = gridRef.current.dataView;
const nextRowIndex = getNextEditableRowIndex(thisRow, dataView, event.shiftKey);

gridRef.current.navigateTo(nextRowIndex, activeElem.column.visibleIndex, (obj: any) => {

requestAnimationFrame(() => {
gridRef.current.endEdit(true, obj);
gridRef.current.clearCellSelection();
obj.target.activate();

});
});

var nextRow = this.getNextEditableRowIndex(thisRow, rowInfo, (args.detail.event as any).shiftKey);
}
};

grid.navigateTo(nextRow, column, (obj: any) => {
obj.target.activate();
grid.clearCellSelection();
});
}
}
gridElement.addEventListener("keydown", handleKeyDown);
Copy link
Member

Choose a reason for hiding this comment

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

Multiple points:

  • Why is this stuck in useEffect? The old sample used gridKeydown which should now be onGridKeydown and should be used instead
  • What's with the gridRef.current.markForCheck();, requestAnimationFrame and endEdit calls? Those aren't needed in the original source (ng) and shouldn't be here as well. If they are, we might want to investigate why. Especially the rAF since it's in the already delayed navigateTo callback

Copy link
Contributor Author

Choose a reason for hiding this comment

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

afaik onGridKeyDown does not trigger when using any key, just the ones that are in navigation and have grid functions. Have this changed in 19? Initially I had it with onGridkeyDown but I remember having issues with this.
For the second one, the RAF is needed as the navigate is async, so I get timing issues and the cell does not exit edit mode in time, so this was the solution I came up with. Open to discussion to improve it as it does not feel very smooth

Copy link
Member

Choose a reason for hiding this comment

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

Oh cool, just noticed that in https://www.infragistics.com/products/ignite-ui-angular/angular/components/grid/cell-editing#angular-grid-excel-style-editing-sample
Great, we made a sample that modifies navigation without our own dedicated event for that. Cool, cool 😶

Fine onKeyDown then, at the very least still remove the useEffect and use the default way to attach a handler:
image
That works just fine and is equivalent w/ less code. Also note, the correct arg type here is React.KeyboardEvent<IgrGrid> instead of the lib.dom one and that means your grid ref is also:

const grid = event.currentTarget;

And that's both typed correctly and can replace the ref entirely. Same goes for the gridEndEdit, though you'll need to assert here. I'll leave a note there too and you can remove the ref entirely after.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

assigned keydown and added handler outside of useEffect


return () => {
gridElement.removeEventListener("keydown", handleKeyDown);
};
}, []);

public getNextEditableRowIndex(currentRowIndex: number, dataView: any, previous: boolean) {

const getNextEditableRowIndex = (currentRowIndex: number, dataView: any, previous: boolean) => {
if (currentRowIndex < 0 || (currentRowIndex === 0 && previous) || (currentRowIndex >= dataView.length - 1 && !previous)) {
return currentRowIndex;
}
if (previous) {
return dataView.findLastIndex((rec: any, index: number) => index < currentRowIndex && this.isEditableDataRecordAtIndex(index, dataView));
return dataView.findLastIndex((rec: any, index: number) => index < currentRowIndex && isEditableDataRecordAtIndex(index, dataView));
}
return dataView.findIndex((rec: any, index: number) => index > currentRowIndex && this.isEditableDataRecordAtIndex(index, dataView));
}
return dataView.findIndex((rec: any, index: number) => index > currentRowIndex && isEditableDataRecordAtIndex(index, dataView));
};

public isEditableDataRecordAtIndex(dataViewIndex: number, dataView: any) {
const isEditableDataRecordAtIndex = (dataViewIndex: number, dataView: any) => {
const rec = dataView[dataViewIndex];
return !rec.expression && !rec.summaries && !rec.childGridsData && !rec.detailsData;
};

function gridEndEdit(event: IgrActiveNodeChangeEventArgs): void {
gridRef.current.endEdit(true, event.detail);
}
Copy link
Member

Choose a reason for hiding this comment

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

Note endEdit is not correctly called with event.detail, the second arg is for actual event and errors out. Also, no real public use should need it, so just replicate the original

Suggested change
function gridEndEdit(event: IgrActiveNodeChangeEventArgs): void {
gridRef.current.endEdit(true, event.detail);
}
function gridEndEdit(event: IgrActiveNodeChangeEventArgs): void {
const grid = event.currentTarget as IgrGrid;
grid.clearCellSelection();
grid.endEdit();
}

Copy link
Member

Choose a reason for hiding this comment

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

Interestingly, I see an error with the endEdit, so there might be an issue to log with the product bcuz...
image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not really sure how to proceed with this one, advise please


}
return (
<div className="container sample ig-typography">
<div className="container fill">
<IgrGrid
autoGenerate={false}
data={nwindData}
primaryKey="ProductID"
ref={gridRef}
onActiveNodeChange={gridEndEdit}
>
<IgrColumn field="ProductID" header="Product ID" editable={true} groupable={true} hidden={true} />
<IgrColumn field="ProductName" header="Product Name" dataType="string" editable={true} />
<IgrColumn field="UnitPrice" header="Unit Price" dataType="number" editable={true} />
<IgrColumn field="QuantityPerUnit" header="Quantity Per Unit" groupable={true} dataType="string" editable={true} />
<IgrColumn field="ReorderLevel" header="Reorder Level" dataType="number" groupable={true} editable={true} />
</IgrGrid>
</div>
</div>
);
};

export default Sample;

// rendering above component in the React DOM
// Render the component
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Sample/>);
root.render(<Sample />);