# ag-Grid学习笔记:内置编辑器以及自定义React编辑器(三)

ag-Grid 是一款功能丰富的数据网格库,它不仅提供了内置的编辑器,还允许你轻松创建和使用自定义编辑器。在这篇文章中,我们将深入探讨如何在 React 中创建自定义 ag-Grid 编辑器,并展示一些最佳实践。

内置编辑器

  1. **文本框编辑器 ( agTextCellEditor**​ ):

    • 作用: 允许用户直接在单元格中输入文本。
    • 用法: 在列的 cellEditor 属性中设置为 'agTextCellEditor'
typescript 复制代码
columnDefs: [
  { headerName: '姓名', field: 'name', editable: true, cellEditor: 'agTextCellEditor' },
  // 其他列配置...
],
  1. 大文本编辑器 **( agLargeTextCellEditor**​​ ):

    • 作用: 允许用户直接在单元格中输入大文本。类似 textarea 元素。
    • 用法: 在列的 cellEditor 属性中设置为 'agTextCellEditor'
    typescript 复制代码
    columnDefs: [
      {
        headerName: '备注',
        field: 'notes',
        editable: true,
        cellEditor: 'agLargeTextCellEditor',
    	cellEditorParams: {
    		// 设置大文本编辑器的最大高度
      		maxHeight: 200,
    	},
      },
      // 其他列配置...
    ],

  2. **数字编辑器 ( agNumberCellEditor**​​ ):

    • 作用: 用于编辑数字类型的数据。
    • 用法: 在列的 cellEditor 属性中设置为 'agNumberCellEditor'
typescript 复制代码
columnDefs: [
  { headerName: '年龄', field: 'age', editable: true, cellEditor: 'agNumberCellEditor' },
  // 其他列配置...
],
  1. **日期编辑器 ( agDateCellEditor**​ ):

    • 作用: 用于编辑日期类型的数据。
    • 用法: 在列的 cellEditor 属性中设置为 'agDateCellEditor'
typescript 复制代码
columnDefs: [
  { headerName: '生日', field: 'birthday', editable: true, cellEditor: 'agDateCellEditor' },
  // 其他列配置...
],
  1. **下拉框编辑器 ( agSelectCellEditor**​ ):

    • 作用: 提供一个下拉框,允许用户从预定义的选项中选择。
    • 用法: 在列的 cellEditor 属性中设置为 'agSelectCellEditor',并使用 cellEditorParams 传递选项。
typescript 复制代码
const genderOptions = ['Male', 'Female'];

columnDefs: [
  {
    headerName: '性别',
    field: 'gender',
    editable: true,
    cellEditor: 'agSelectCellEditor',
    cellEditorParams: { values: genderOptions },
  },
  // 其他列配置...
],
  1. **富文本下拉编辑器 ( agRichSelectCellEditor**​ ):

    • 作用: 提供一个富文本下拉框,允许用户选择复杂的选项。
    • 用法: 在列的 cellEditor 属性中设置为 'agRichSelectCellEditor',并使用 cellEditorParams 传递选项。
typescript 复制代码
columnDefs: [
  {
    headerName: '城市',
    field: 'city',
    editable: true,
    cellEditor: 'agRichSelectCellEditor',
    cellEditorParams: {
      values: ['New York', 'London', 'Tokyo'],
    },
  },
  // 其他列配置...
],

这些是一些常见的内置编辑器。在配置 ag-Grid 列时,你可以根据数据类型和需求选择合适的编辑器。

agRichSelectCellEditor​​和 **agSelectCellEditor**​​的区别

agRichSelectCellEditor​ 提供了一个富文本样式的下拉框,允许用户从预定义的选项中选择。

基本用法:

agRichSelectCellEditor ​的基本用法和 agSelectCellEditor ​是一样的。

高级用法:

  1. **自定义样式:**​agRichSelectCellEditor​ 支持使用 CSS 来自定义下拉框的样式。你可以通过全局样式或者在列定义中使用 cellEditorParams​ 中的 cellStyle​ 属性来实现。

    typescript 复制代码
    cellEditorParams: {
      values: ['Option 1', 'Option 2', 'Option 3'],
      cellStyle: {
        backgroundColor: 'lightblue',
        color: 'darkblue',
      },
    },
  2. 异步加载选项: 有时,你可能需要在下拉框中异步加载选项。在这种情况下,可以在 cellEditorParams​ 中使用 values​ 属性的函数形式。

    typescript 复制代码
    cellEditorParams: {
      values: () => {
        return new Promise((resolve) => {
          // 异步加载选项的逻辑,例如从服务端获取数据
          setTimeout(() => {
            resolve(['Option 1', 'Option 2', 'Option 3']);
          }, 1000);
        });
      },
    },

    使用异步加载可以确保编辑器中的选项在需要时动态获取。

  3. 自定义模板: 你可以通过在 cellEditorParams​ 中使用 cellRenderer​ 属性来自定义下拉框中每个选项的模板。

    typescript 复制代码
    cellEditorParams: {
      values: ['Option 1', 'Option 2', 'Option 3'],
      cellRenderer: (params) => {
        // 自定义模板的逻辑,返回一个包含 HTML 的字符串
        return `<span style="font-style: italic;">${params.value}</span>`;
      },
    },

    使用自定义模板可以实现更灵活的选项显示。

注意事项:

  • agRichSelectCellEditor 通常在编辑模式下通过单击单元格进入。
  • cellEditorParams 中的 values 属性可以是一个静态数组,也可以是一个返回数组的函数,甚至是一个返回 Promise 的异步函数。
  • cellEditorParams 中的 cellStyle 属性用于设置下拉框的样式,可以根据需要进行定制。
  • agRichSelectCellEditor是企业版ag-Grid才有的功能,还有一些其他的功能,可以自行查询文档React Data Grid: Provided Cell Editors (ag-grid.com)

cellEditorParams​​参数

cellEditorParams​ 是用于配置 ag-Grid 内置编辑器的参数对象。以下是一些通用常见的属性,可以用于配置不同的内置编辑器组件:

  1. ​**values**​

    • **类型:**Array | Function | Promise
    • 描述: 用于提供下拉框选项的数组,可以是一个静态数组,一个返回数组的函数,或者一个返回 Promise 的异步函数。适用于 agRichSelectCellEditoragSelectCellEditor 等有下拉框的编辑器。
    typescript 复制代码
    cellEditorParams: {
      values: ['Option 1', 'Option 2', 'Option 3'],
    },
  2. ​**maxHeight**​

    • **类型:**number
    • 描述: 用于设置编辑器的最大高度,以防止过度增长。适用于 agLargeTextCellEditor
    typescript 复制代码
    cellEditorParams: {
      maxHeight: 200,
    },
  3. ​**cellStyle**​

    • **类型:**Object
    • 描述: 用于设置编辑器的样式。可以通过这个属性来自定义编辑器的外观。
    typescript 复制代码
    cellEditorParams: {
      cellStyle: {
        backgroundColor: 'lightyellow',
        color: 'darkblue',
      },
    },
  4. ​**cellRenderer**​

    • **类型:**Function
    • 描述: 用于自定义编辑器中每个选项的模板。可以通过这个属性来实现更灵活的选项显示。
    typescript 复制代码
    cellEditorParams: {
      cellRenderer: (params) => {
        // 自定义模板的逻辑,返回一个包含 HTML 的字符串
        return `<div style="font-style: italic;">${params.value}</div>`;
      },
    },

cellEditorParams​动态参数

cellEditorParams​属性还可以是一个函数,可以动态调整编辑器的参数,在自定义编辑器时,经常会用到。

typescript 复制代码
cellEditorParams: params => {
    const selectedCountry = params.data.country;
	// 根据单元格数据中的 'country' 属性,动态生成了不同的城市列表。
    if (selectedCountry === 'Ireland') {
        return {
            values: ['Dublin', 'Cork', 'Galway']
        };
    } else {
        return {
            values: ['New York', 'Los Angeles', 'Chicago', 'Houston']
        };
    }
}

使用自定义编辑器

在某些情况下,内置编辑器可能无法满足特定的编辑需求。例如,你可能需要一个具有复杂交互、自定义样式或特殊输入控件的编辑器。这时,自定义编辑器就派上用场了。通过自定义编辑器,你可以完全掌控编辑器的外观和行为,以满足项目的特定需求。比如说使用 antd 组件实现自定义编辑器。

步骤一:创建自定义编辑器组件

首先,我们将创建一个简单的自定义编辑器组件。在这个例子中,我们将创建一个带有日期选择器的编辑器。

typescript 复制代码
// CustomDateEditor.jsx

import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react';

const CustomDateEditor = forwardRef((props, ref) => {
  const [date, setDate] = useState(props.value);

  useEffect(() => {
    setDate(props.value);
  }, [props.value]);

  const handleDateChange = (e) => {
    setDate(e.target.value);
  };
  // 使用 useImperativeHandle 暴露给父组件的接口
  useImperativeHandle(ref, () => ({
    // 提供一个名为 getValue 的方法,用于获取编辑器的值,这个方法是必须实现的
    getValue: () => date,
    // isCancelBeforeStart 和 isCancelAfterEnd 用于控制编辑器状态的取消
	// 在初始化后被调用一次,如果返回true,则无法进入编辑状态。可以使用这个方法根据特定条件决定是否开始编辑,例如,仅在某些按键按下时才允许编辑。
	isCancelBeforeStart: () => false,
	// 如果返回true,则修改的值不会被真正使用
	isCancelAfterEnd: () => false,
	// 获得焦点时会调用这个方法
	focusIn: () => false;
	// 失去焦点时调用
	focusOut: () => false;

  }));

  return (
    <div>
      <input type="date" value={date} onChange={handleDateChange} />
    </div>
  );
});

export default CustomDateEditor;

在这个组件中,我们使用 forwardRef​ 和 useImperativeHandle​,以便在外部获取编辑器实例,并提供一个名为 getValue​ 的方法,用于获取编辑器的值。

步骤二:在 ag-Grid 中使用自定义编辑器

现在,我们将在 ag-Grid 中配置并使用我们的自定义编辑器。

typescript 复制代码
// YourGridComponent.jsx

import React, { useRef } from 'react';
import { AgGridReact } from 'ag-grid-react';
import CustomDateEditor from './CustomDateEditor';

const YourGridComponent = () => {
  const customEditorRef = useRef(null);

  const onGridReady = (params) => {
    // 获取 CustomDateEditor 的实例
    customEditorRef.current = params.api.getCellEditorInstances()[0];
  };

  const getValueFromCustomEditor = () => {
    // 调用 CustomDateEditor 的 getValue 方法
    const editorValue = customEditorRef.current.getValue();
    console.log('Value from CustomDateEditor:', editorValue);
  };

  const columnDefs = [
    {
      headerName: '日期',
      field: 'date',
      editable: true,
      cellEditor: 'CustomDateEditor',
    },
    // 其他列配置...
  ];

  return (
    <div>
      <button onClick={getValueFromCustomEditor}>获取自定义编辑器的值</button>
      <AgGridReact
        // 其他配置...
        onGridReady={onGridReady}
        columnDefs={columnDefs}
        components={{
          CustomDateEditor: CustomDateEditor,
        }}
      />
    </div>
  );
};

export default YourGridComponent;

在这个例子中,我们在 columnDefs​​ 中将编辑器配置为 'CustomDateEditor'​​,并通过 components​​ 注册我们的自定义编辑器组件。在 onGridReady​​ 中,我们使用 useRef​​ 获取自定义编辑器的实例,然后通过按钮调用 getValueFromCustomEditor​​ 方法获取编辑器的值。

进阶

在实际项目中,自定义编辑器很常见,也很复杂,因为需要考虑的地方太多了。再和其他的一些显示需求糅合在一起,更复杂。例如:

编辑:

实现一个工艺选择器,下拉框形式。

下拉列表数据从后台请求,根据每一行数据中的工厂code来请求不同的工艺列表数据,组件的入参需要动态生成;

每次进入编辑,都需要请求一次数据,是否需要缓存,如果缓存,累计下拉,是否会导致内存过多,影响性能;

当重新选择工艺后,同一行数据中的工艺路线和所属车间都需要一起修改,也就是说修改一个单元格的数据,其他单元格可能也会被修改;

有可能还会涉及到其他行的数据也需要修改;

显示:

后端数据和前端需要的数据可能不一致,比如前端需要的是字符串数组,后端返回的数据是字符串逗号拼接的;

后端返回的数据是拆分为id和name的,而前端可能需要一个对象{id: "xx", name: "name"},这样才能更好的回显。

总结来讲,涉及到编辑,编辑表单需要考虑的东西,这里都需要考虑,总所周知,表单是很复杂的东西。我们需要考虑:

  1. 异步加载数据:

    • 对于下拉框形式的选择器,可以通过 agRichSelectCellEditor 来实现,当然也可以使用自定义编辑器的方式实现;
    • 关于数据缓存,可以考虑使用缓存来减少不必要的请求,但需要注意缓存策略,确保不会导致内存过多。
  2. 数据的联动:

    • 当重新选择后,其他单元格甚至其他行的数据也需要发生变化。
    typescript 复制代码
    // 在onCellValueChanged监听单元格的变更,setDataValue设置值
    const onCellValueChanged = useCallback((params) => {
      const colId = params.column.getId();
      if (colId === 'country') {
        const selectedCountry = params.data.country;
        const selectedCity = params.data.city;
        const allowedCities = countyToCityMap(selectedCountry);
        const cityMismatch = allowedCities.indexOf(selectedCity) < 0;
        if (cityMismatch) {
    	 // 单独修改当前行其他数据
          params.node.setDataValue('city', null);
        }
    	// 动态批量更新其他的内容
    	params.api.applyTransaction({ update: [{...}] });
      }
    }, []);
  3. 前后端数据结构差异处理:

    • 合理的使用valueGettervalueSetter,对数据进行处理。
  4. 数据校验:

    • 需要根据具体的业务场景来设计编辑器的行为,考虑用户交互和数据流的整体流程。
    • 可以通过isCancelBeforeStartisCancelAfterEnd实现校验功能。

撤销/回退

在复杂的数据表格应用中,用户可能频繁进行编辑操作,但有时候需要撤销之前的更改或者重新应用已经取消的操作。Ag-Grid 提供了内建的撤销和回退功能。

启用编辑记录

在配置中设置 undoRedoCellEditing​ 和 undoRedoCellEditingLimit​ 属性,即可开启撤销和回退功能,并限制历史记录的数量。

typescript 复制代码
<AgGridReact
  // 其他配置...
  undoRedoCellEditing={true}
  // 可以撤销的次数
  undoRedoCellEditingLimit={20}
/>

撤销和回退按钮

撤销操作可以通过调用 api.undoCellEditing()​ 实现,而回退操作可以通过 api.redoCellEditing()​ 实现。

typescript 复制代码
const undo = () => {
  gridApi.current.api.undoCellEditing();
};

const redo = () => {
  gridApi.current.api.redoCellEditing();
};

撤销和回退记录

也可以获取到撤销和回退的次数,通过次数控制按钮是否可点击

typescript 复制代码
// 撤销的次数
const undoSize = gridApi.current.api.getCurrentUndoSize();

// 回退的次数
const redoSize = gridApi.current.api.getCurrentRedoSize();

复杂对象

如果单元格值包含复杂对象,则需要执行几个步骤才能进行撤消/重做。

typescript 复制代码
const [columnDefs, setColumnDefs] = useState([
    {
        field: 'a',
        editable: true,
        valueGetter: params => {
            // create complex object from data
            return {
                actualValue: params.data[params.colDef.field],
                anotherProperty: params.data.anotherProperty,
            }
        },
		// 
        valueSetter: params => {
            // update data from complex object
            params.data[params.colDef.field] = params.newValue.actualValue
            return true
        },
		// 自定义对象是否相同
        equals: (valueA, valueB) => {
            // compare complex objects
            return valueA.actualValue === valueB.actualValue
        }
    }
]);

结论

通过自定义 ag-Grid 编辑器,我们可以在 React 项目中轻松满足各种编辑需求。后续我会尝试使用formily来实现ag-Grid的编辑器,利用formily完成字段的校验、关联逻辑、异步请求等。

相关推荐
栈老师不回家4 分钟前
Vue 计算属性和监听器
前端·javascript·vue.js
前端啊龙10 分钟前
用vue3封装丶高仿element-plus里面的日期联级选择器,日期选择器
前端·javascript·vue.js
一颗松鼠14 分钟前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
小远yyds34 分钟前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
吕彬-前端1 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱2 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai2 小时前
uniapp
前端·javascript·vue.js·uni-app
bysking3 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓3 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4113 小时前
无网络安装ionic和运行
前端·npm