reactjs - 使用react-select作为弹出自定义单元格编辑器时,ag-grid中的键盘事件出现问题

标签 reactjs ag-grid react-select ag-grid-react

我想使用react-select作为ag-grid的自定义单元格编辑器

这是code sandbox link

您可以下载源码here

npm install
npm start

我已经删除了所有的 CSS,所以它看起来很简单,但它仍然可以完成工作。

这是我的 package.json

{
  "name": "Test",
  "version": "1.5.0",
  "private": true,
  "dependencies": {
    "react": "16.8.1",
    "react-dom": "16.8.1",
    "react-select": "^2.4.1",
    "react-scripts": "2.1.5",
    "ag-grid-community": "20.1.0",
    "ag-grid-react": "20.1.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject",
    "deploy": "npm run build",
    "lint:check": "eslint . --ext=js,jsx;  exit 0",
    "lint:fix": "eslint . --ext=js,jsx --fix;  exit 0",
    "install:clean": "rm -rf node_modules/ && rm -rf package-lock.json && npm install && npm start"
  },
  "optionalDependencies": {
    "@types/googlemaps": "3.30.16",
    "@types/markerclustererplus": "2.1.33",
    "ajv": "6.9.1",
    "prettier": "1.16.4"
  },
  "devDependencies": {
    "eslint-config-prettier": "4.0.0",
    "eslint-plugin-prettier": "3.0.1"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ]
}

index.js

import React from "react";
import ReactDOM from "react-dom";
import Grid from './app/AgGrid'

const colDefs = [
    {
        headerName              : "Test",
        field                   : "test",       
        editor                  : "manyToOne",
        editable                : true,
        suppressKeyboardEvent   : function suppressEnter(params) {
            let KEY_ENTER = 13;
            let KEY_LEFT = 37;
            let KEY_UP = 38;
            let KEY_RIGHT = 39;
            let KEY_DOWN = 40;
            var event = params.event;
            var key = event.which;
            var editingKeys = [KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_ENTER];
            var suppress = params.editing && editingKeys.indexOf(key) >= 0;
            console.log("suppress "+suppress);
            return suppress;
        },
        cellEditor              : "reactSelectCellEditor",
    },
]

ReactDOM.render(
<Grid
    readOnly={false}
    field={null}
    columnDefs={colDefs}
    editable={true}
    editingMode={null}
    rowData={[]}
/>
,
document.getElementById("root")
);

AgGrid.js

import React, { Component } from 'react'
import { AgGridReact } from 'ag-grid-react'
import 'ag-grid-community/dist/styles/ag-grid.css'
import ReactSelectCellEditor from './ReactSelectCellEditor'

class Grid extends Component {

    constructor(props) {
        super(props);
        this.state = {
            columnDefs  : props.columnDefs,
            rowData     : [],
            isFormOpen  : false,
            editedData  : {
                data        : [],
                rowIndex    : null,
                columns     : []
            },
            frameworkComponents:{
                'reactSelectCellEditor': ReactSelectCellEditor,
            },
        }
    }

    updateRowData = (newData,index) => {
        var unsavedData = this.state.rowData.map((row,i)=>{
            if (i === index) {
                return newData
            }
            else{
                return row
            }
        })
        this.setState({rowData:unsavedData})
    }

    onGridReady = params => {       
    }

    createData = () => {
        let fields = this.state.columnDefs.map(column=>{
            return column.field
        })
        let newData = {}
        fields.map(field=>{
            if (field !== "id") {
                newData[field] = ""
            }           
        })
        newData["delete"] = false
        this.setState({rowData:[...this.state.rowData,newData]})
    }

    toggleModal = () => {
        this.setState({isFormOpen:!this.state.isFormOpen})
    }

    setEditedData = (data) => {
        console.log("editedData",data)
        this.setState({editedData:data})
    }


    render() {
        var defaultColDef = {
            editable:this.state.editable,
            sortable:false
        }

        var initColDefs = this.state.columnDefs.map(colDef=>{
            return {
                ...colDef,
                cellEditorParams:{
                    ...colDef.cellEditorParams,
                }
            }
        })

        var renderedColumnDefs = []
        if (this.props.editingMode === "inline") {
            renderedColumnDefs = [
                ...initColDefs
            ]
        }
        else if (this.props.readOnly) {
            renderedColumnDefs = [...initColDefs]
        }
        else{
            defaultColDef = {
                editable:false,
                sortable:false
            }
            renderedColumnDefs = [
                ...initColDefs,
            ]
        }
        var renderedRowData = []
        this.state.rowData.map(row=>{
            if (row.delete !== true) {
                renderedRowData.push(row)
            }
        })
        return (
            <React.Fragment>
                <AgGridReact
                    defaultColDef                   = {defaultColDef}
                    columnDefs                      = {renderedColumnDefs}
                    rowData                         = {renderedRowData}
                    onGridReady                     = {this.onGridReady}
                    onCellValueChanged              = {this.handleChange}
                    frameworkComponents             = {this.state.frameworkComponents}
                    singleClickEdit                 = {true}
                    stopEditingWhenGridLosesFocus   = {true}
                    reactNext={true}
                />
                <div className="addButton" style={{padding:'5px', border: '1px solid black'}} onClick={()=>this.createData()}>Add</div>
            </React.Fragment>
        );
    } 
}

export default Grid

和ReactSelectCellEditor.js

import React, { Component } from 'react'
import CreatableSelect  from "react-select/lib/Creatable"
import { components } from 'react-select'
import ReactDOM from 'react-dom'

const colourOptions = [
    { value: 'red', label: 'Red' },
    { value: 'blue', label: 'Blue' },
    { value: 'yellow', label: 'Yellow' }
]

class ReactSelectCellEditor extends Component {

    constructor(props) {
        super(props);
        this.state = {
            value : null
        }
        this.handleChange = this.handleChange.bind(this)
    }

    componentDidMount() {
    }

    handleCreateOption = (inputValue:any) => {
    }

    handleChange(selected){
        this.setState({
            value : selected.value
        })
    }

    afterGuiAttached(param) {
        let self = this;
        this.Ref.addEventListener('keydown', function (event) {
            // let key = event.which || event.keyCode
            // if (key === 37 || key === 38 || key === 39 || key === 40 || key === 13) {
            //     event.stopPropagation();
            //     console.log("onKeyDown "+key);
            // }
        });
        this.SelectRef.focus()
    }

    formatCreate = (inputValue) => {
        return (<p> Add: {inputValue}</p>); 
    };

    isPopup(){
        return true
    }

    getValue() {
        return this.state.value
    }

    render() {
        return (
            <div ref = { ref => { this.Ref = ref }} style={{width: '200px'}}> 
                <CreatableSelect
                    onChange                = {this.handleChange}
                    options                 = {colourOptions}
                    formatCreateLabel       = {this.formatCreate}
                    createOptionPosition    = {"first"}
                    ref                     = { ref => { this.SelectRef = ref }}
                />
            </div>
        );
    }
}

export default ReactSelectCellEditor

正如您所看到的,ag-grid 和 弹出(不在单元格中)react-select 之间的简单组合实现。

它工作得很好,除了ag-grid的默认导航键事件阻碍了react-select完美地工作。

现在我已经在 ag-grid documentation of keyboard navigation while editing 中阅读了有关此问题的解决方案

事情是这样的:

  1. 首先,我尝试了解决方案 1,即停止自定义单元格编辑器的事件传播。它有效,但奇怪的是尽管我停止了react-select的容器中的事件冒泡,而不是react-select本身,正如您从注释掉的脚本中看到的那样ReactSelectCellEditor.js,不知何故,React Select Key Event 也停止执行。我不知道为什么当我停止父元素中的事件传播时,作为 div 子元素的 React select 事件会停止执行。难道这个事件不应该从 child 到 parent 而不是相反吗?所以第一个解决方案是不行的。

  2. 至于第二个解决方案,奇怪的是 ag-grid 本身。正如文档所说,ag-grid 允许我们通过从列定义中定义一个用于suppressKeyboardEvent 的函数来拦截按键事件。如果编辑模式是弹出,则它可以工作。但就我而言,我使用弹出模式,在弹出模式下我发现编辑时不会触发suppressKeyboardEvent,甚至根本不会调用它。

现在我陷入了僵局。在第一个解决方案中,react-select 的行为很奇怪,在第二个解决方案中,ag-grid 的行为很奇怪。我该如何解决这个问题?

其他查找

在互联网上搜索后,看起来 React Select 使用了 SyntheticKeyboardEvent ,如果我没记错的话,它会在事件已经经历了 DOM 树上的第一个捕获/冒泡循环之后执行。因此,如果我停止传播 SyntheticKeyboardEvent 将不会被触发。但是,如果我不停止使用 native 事件监听器的传播 ag-grid 将触发其默认导航功能并终止 react 选择组件。现在这是给我的吗?死胡同?

最佳答案

让我警告你这个解决方案是一种黑客和反模式。正如您所说,react select 使用 SyntheticKeyboardEvent 并且它会等待,直到 native 事件经历整个 DOM 树的循环。但是有一种方法可以让您通过修改代码来调用 React select 功能,这将允许您从其他组件访问 React select 组件的方法,这就是为什么这是一个反模式解决方案。

  1. 从 colDef 中删除 suppressKeyboardEvent

  2. 从 node_modules/react-select/src/Creatable.js 复制 CreatableSelect 组件

  3. 将这些行添加到复制的组件中以通过 onRef 进行访问

    componentDidMount() {
       this.props.onRef(this)
    }
    componentWillUnmount() {
       this.props.onRef(undefined)
    }
    
  4. 通过在副本中定义这些来传递react select的控制方法

    focusOption(param) {
       this.select.focusOption(param);
    }
    selectOption(param) {
       this.select.selectOption(param);
    }
    
  5. 然后在渲染组件时添加 onRef = { ref => { this.SelectRef = ref }}

  6. 现在您将能够在停止传播之前调用这些来模拟控件

    this.SelectRef.focusOption('up');
    this.SelectRef.focusOption('down');
    
  7. 您可以通过编写这样的方法来访问 react 选择的状态

    focusedOption() {
       return this.select.state.focusedOption;
    }
    
  8. 引用node_modules/react-select/src/Select.js第1142行完成剩余的模拟

关于reactjs - 使用react-select作为弹出自定义单元格编辑器时,ag-grid中的键盘事件出现问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56373994/

相关文章:

javascript - ReactJs 如何知道组件何时从 DOM 中删除

reactjs - React Grid System 相当于 Bootstrap 的 Grid 系统?

reactjs - 滚动时 React Native Flatlist 标题重新渲染

javascript - ag-grid:如何连接自定义主题?

javascript - 如何使搜索图标在 ag-grid 的列中永久可见?

javascript - react 选择多个选项

reactjs - React-Select - 带有自定义处理程序的可点击值

reactjs - 动态单元格值的行条件样式

javascript - React-Select 破坏 CoreUi 功能

reactjs - 如何为 'react-select' 实现字段验证