The custom cell editor fails when setting cell focus on a newly added row.

1 Answer 23 Views
Grid
胡张
Top achievements
Rank 1
Iron
胡张 asked on 24 Jun 2024, 12:13 AM | edited on 24 Jun 2024, 12:15 AM

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>
    );
};


1 Answer, 1 is accepted

Sort by
0
胡张
Top achievements
Rank 1
Iron
answered on 24 Jun 2024, 03:48 AM

I have found a solution to this problem myself by modifying MyDropDownCell:


// MyDropDownCell.tsx

import * as React from 'react';
import { MultiColumnComboBox } from '@progress/kendo-react-dropdowns';
import { GridCellProps } from '@progress/kendo-react-grid';

const employees = [ /* Your employees data */ ];

const columns = [ /* Your columns data */ ];

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
            });
        }
    };

    // Using React ref and effect to handle focus
    const comboBoxRef = React.useRef<any>(null);

    React.useEffect(() => {
        if (dataItem.inEdit && comboBoxRef.current) {
            comboBoxRef.current.focus();
        }
    }, [dataItem.inEdit]);

    return (
        <td>
            {dataItem.inEdit ? (
                <MultiColumnComboBox
                    ref={comboBoxRef} // Setting the ref
                    data={employees}
                    columns={columns}
                    textField="name"
                    style={{ width: '300px' }}
                    placeholder="Please select ..."
                    value={employees.find(emp => emp.name === dataValue)}
                    onChange={handleChange}
                />
            ) : (
                dataValue
            )}
        </td>
    );
};

Tags
Grid
Asked by
胡张
Top achievements
Rank 1
Iron
Answers by
胡张
Top achievements
Rank 1
Iron
Share this question
or