I saw an example of setting focus on a new row in the table: https://stackblitz.com/edit/react-dym3qj?file=app%2Fmain.jsx. I tried adding a custom cell editor and setting it to focus, but it failed.
page.tsx:
"use client";
import * as React from 'react';
import {
Grid,
GridColumn as Column,
GridToolbar,
GridCellProps,
GridRowClickEvent,
GridItemChangeEvent
} from '@progress/kendo-react-grid';
import { sampleProducts } from './sample-products';
import { DropDownCell } from './myDropDownCell';
interface Product {
ProductID: number;
ProductName?: string;
FirstOrderedOn?: Date;
UnitsInStock?: number;
Discontinued?: boolean;
}
const App: React.FC = () => {
const [data, setData] = React.useState<Product[]>(sampleProducts);
const [editID, setEditID] = React.useState<number | null>(null);
const [newRecordID, setNewRecordID] = React.useState<number | null>(null); // 新增状态变量
const rowClick = (event: GridRowClickEvent) => {
setEditID(event.dataItem.ProductID);
};
const itemChange = (event: GridItemChangeEvent) => {
const inEditID = event.dataItem.ProductID;
const field = event.field || '';
const newData = data.map((item) =>
item.ProductID === inEditID ? { ...item, [field]: event.value } : item
);
setData(newData);
};
const closeEdit = (event: React.MouseEvent<HTMLDivElement>) => {
if (event.target === event.currentTarget) {
setEditID(null);
}
};
const addRecord = () => {
const nextID = data.length + 1;
const newRecord: Product = {
ProductID: nextID,
};
setData([newRecord, ...data]);
setEditID(newRecord.ProductID);
setNewRecordID(newRecord.ProductID); // 设置新增行的 ID
};
const cellRender = (td: React.ReactElement, props: GridCellProps) => {
if (!newRecordID || newRecordID !== props.dataItem.ProductID || props.field !== 'ProductName') {
return td;
}
return (
<td
{...td.props}
ref={(tdElement) => {
const input = tdElement && tdElement.querySelector('input');
const activeElement = document.activeElement as HTMLElement;
const isButton = activeElement.className.indexOf('k-button') >= 0;
if (
!input ||
!activeElement ||
input === activeElement ||
(!activeElement.contains(input) && !isButton)
) {
return;
} else {
setTimeout(() => {
input.select();
});
}
}}
></td>
);
};
return (
<Grid
cellRender={cellRender}
style={{
height: '420px',
}}
data={data.map((item) => ({
...item,
inEdit: item.ProductID === editID,
}))}
editField="inEdit"
onRowClick={rowClick}
onItemChange={itemChange}
>
<GridToolbar>
<div onClick={closeEdit}>
<button
title="Add new"
className="k-button k-button-md k-rounded-md k-button-solid k-button-solid-primary"
onClick={addRecord}
>
Add new
</button>
</div>
</GridToolbar>
<Column field="ProductID" title="Id" width="50px" editable={false} />
<Column field="FirstOrderedOn" title="First Ordered" editor="date" format="{0:d}" />
<Column field="ProductName" title="Name" cell={DropDownCell} />
<Column field="UnitsInStock" title="Units" width="150px" editor="numeric" />
<Column field="Discontinued" title="Discontinued" editor="boolean" />
</Grid>
);
};
const TestPage6: React.FC = () => {
return <App />;
};
export default TestPage6;
sample-products.tsx:
exportconst sampleProducts = [{
"ProductID": 1,
"ProductName": "Chai",
"SupplierID": 1,
"CategoryID": 1,
"QuantityPerUnit": "10 boxes x 20 bags",
"UnitPrice": 18,
"UnitsInStock": 39,
"UnitsOnOrder": 0,
"ReorderLevel": 10,
"Discontinued": false,
"Category": {
"CategoryID": 1,
"CategoryName": "Beverages",
"Description": "Soft drinks, coffees, teas, beers, and ales"
},
"FirstOrderedOn": newDate(1996, 8, 20)
}, {
"ProductID": 2,
"ProductName": "Chang",
"SupplierID": 1,
"CategoryID": 1,
"QuantityPerUnit": "24 - 12 oz bottles",
"UnitPrice": 19,
"UnitsInStock": 17,
"UnitsOnOrder": 40,
"ReorderLevel": 25,
"Discontinued": false,
"Category": {
"CategoryID": 1,
"CategoryName": "Beverages",
"Description": "Soft drinks, coffees, teas, beers, and ales"
},
"FirstOrderedOn": newDate(1996, 7, 12)
}, {
"ProductID": 3,
"ProductName": "Aniseed Syrup",
"SupplierID": 1,
"CategoryID": 2,
"QuantityPerUnit": "12 - 550 ml bottles",
"UnitPrice": 10,
"UnitsInStock": 13,
"UnitsOnOrder": 70,
"ReorderLevel": 25,
"Discontinued": false,
"Category": {
"CategoryID": 2,
"CategoryName": "Condiments",
"Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
},
"FirstOrderedOn": newDate(1996, 8, 26)
}, {
"ProductID": 4,
"ProductName": "Chef Anton's Cajun Seasoning",
"SupplierID": 2,
"CategoryID": 2,
"QuantityPerUnit": "48 - 6 oz jars",
"UnitPrice": 22,
"UnitsInStock": 53,
"UnitsOnOrder": 0,
"ReorderLevel": 0,
"Discontinued": false,
"Category": {
"CategoryID": 2,
"CategoryName": "Condiments",
"Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
},
"FirstOrderedOn": newDate(1996, 9, 19)
}, {
"ProductID": 5,
"ProductName": "Chef Anton's Gumbo Mix",
"SupplierID": 2,
"CategoryID": 2,
"QuantityPerUnit": "36 boxes",
"UnitPrice": 21.35,
"UnitsInStock": 0,
"UnitsOnOrder": 0,
"ReorderLevel": 0,
"Discontinued": true,
"Category": {
"CategoryID": 2,
"CategoryName": "Condiments",
"Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
},
"FirstOrderedOn": newDate(1996, 7, 17)
}, {
"ProductID": 6,
"ProductName": "Grandma's Boysenberry Spread",
"SupplierID": 3,
"CategoryID": 2,
"QuantityPerUnit": "12 - 8 oz jars",
"UnitPrice": 25,
"UnitsInStock": 120,
"UnitsOnOrder": 0,
"ReorderLevel": 25,
"Discontinued": false,
"Category": {
"CategoryID": 2,
"CategoryName": "Condiments",
"Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
},
"FirstOrderedOn": newDate(1996, 9, 19)
}, {
"ProductID": 7,
"ProductName": "Uncle Bob's Organic Dried Pears",
"SupplierID": 3,
"CategoryID": 7,
"QuantityPerUnit": "12 - 1 lb pkgs.",
"UnitPrice": 30,
"UnitsInStock": 15,
"UnitsOnOrder": 0,
"ReorderLevel": 10,
"Discontinued": false,
"Category": {
"CategoryID": 7,
"CategoryName": "Produce",
"Description": "Dried fruit and bean curd"
},
"FirstOrderedOn": newDate(1996, 7, 22)
}, {
"ProductID": 8,
"ProductName": "Northwoods Cranberry Sauce",
"SupplierID": 3,
"CategoryID": 2,
"QuantityPerUnit": "12 - 12 oz jars",
"UnitPrice": 40,
"UnitsInStock": 6,
"UnitsOnOrder": 0,
"ReorderLevel": 0,
"Discontinued": false,
"Category": {
"CategoryID": 2,
"CategoryName": "Condiments",
"Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
},
"FirstOrderedOn": newDate(1996, 11, 1)
}, {
"ProductID": 9,
"ProductName": "Mishi Kobe Niku",
"SupplierID": 4,
"CategoryID": 6,
"QuantityPerUnit": "18 - 500 g pkgs.",
"UnitPrice": 97,
"UnitsInStock": 29,
"UnitsOnOrder": 0,
"ReorderLevel": 0,
"Discontinued": true,
"Category": {
"CategoryID": 6,
"CategoryName": "Meat/Poultry",
"Description": "Prepared meats"
},
"FirstOrderedOn": newDate(1997, 1, 21)
}, {
"ProductID": 10,
"ProductName": "Ikura",
"SupplierID": 4,
"CategoryID": 8,
"QuantityPerUnit": "12 - 200 ml jars",
"UnitPrice": 31,
"UnitsInStock": 31,
"UnitsOnOrder": 0,
"ReorderLevel": 0,
"Discontinued": false,
"Category": {
"CategoryID": 8,
"CategoryName": "Seafood",
"Description": "Seaweed and fish"
},
"FirstOrderedOn": newDate(1996, 8, 5)
}];
MyDropDownCell.tsx:
import * as React from 'react';
import { MultiColumnComboBox } from '@progress/kendo-react-dropdowns';
import { GridCellProps } from '@progress/kendo-react-grid';
// 定义员工数据和列
const employees = [
{
id: 1,
name: "Daryl Sweeney",
reportsTo: null,
phone: "(555) 924-9726",
extension: 8253,
hireDate: new Date("2012-02-07T20:00:00.000Z"),
fullTime: true,
position: "CEO",
timeInPosition: 2,
},
{
id: 2,
name: "Guy Wooten",
reportsTo: 1,
phone: "(438) 738-4935",
extension: 1155,
hireDate: new Date("2010-03-03T20:00:00.000Z"),
fullTime: true,
position: "Chief Technical Officer",
timeInPosition: 1,
}
];
const columns = [
{ field: 'id', header: 'ID', width: '100px' },
{ field: 'name', header: 'Name', width: '300px' },
{ field: 'position', header: 'Position', width: '300px' }
];
// 创建 DropDownCell 组件
export const DropDownCell = (props: GridCellProps) => {
const { dataItem, field, onChange } = props;
const dataValue = dataItem[field];
const handleChange = (e: any) => {
if (onChange) {
onChange({
dataItem,
field,
syntheticEvent: e.syntheticEvent,
value: e.target.value
});
}
};
return (
<td>
{dataItem.inEdit ? (
<MultiColumnComboBox
data={employees}
columns={columns}
textField="name"
style={{ width: '300px' }}
placeholder="Please select ..."
value={employees.find(emp => emp.name === dataValue)}
onChange={handleChange}
/>
) : (
dataValue
)}
</td>
);
};