🙋🏻♀️ 编者按:产品突然指过来一个需求,需要在 S2 大数据的表格里面,添加可编辑功能。这可咋整呀?S2 的主打的是数据展示,而不是数据编辑,看来只能自己试试黑魔法了😏。本文中,AntV 团队的亚德将为大家演示,如何把 S2 数据展示表格变成编辑表格,欢迎享用~
怎么快速搭建一个S2表格?
让我们看下S2的官网 s2.antv.vision/,然后快速搭建一个明细表
import React, { useState, useRef } from 'react';
import ReactDOM from 'react-dom';
import { SheetComponent } from '@antv/s2-react';
import '@antv/s2-react/dist/style.min.css';
import 'antd/es/checkbox/style/index.css';
import { S2DataConfig, S2Options, SpreadSheet } from '@antv/s2';
const initOptions = {
width: 600,
height: 400,
showSeriesNumber: true,
tooltip: { showTooltip: false },
interaction: { enableCopy: true, },
showDefaultHeaderActionIcon: false,
}
const initData = {
fields: { columns: ['province', 'city', 'type', 'price'], },
sortParams: [],
}
const App = ({ data }) => {
const S2Ref = useRef<SpreadSheet>(null);
const [options, setOptions] = useState<S2Options>(initOptions);
const [dataCfg, setDataCfg] = useState<S2DataConfig>({ ...initData, data });
return (
<div>
<SheetComponent
ref={S2Ref}
dataCfg={dataCfg}
options={options}
sheetType="table"
/>
</div>
);
};
fetch('../data/basic-table-mode.json')
.then((res) => res.json())
.then((res) => {
ReactDOM.render(<App data={res} />, document.getElementById('container'));
});
然后让我们看看这个表格长什么样?
那怎么把数据展示表格变成编辑表格呢?
1. 先来一个受控的编辑元素
<input value={value} onChange={e => setValue(e.target.value)} />
那要如何把这个编辑元素和表格内的展示元素绑定在一起呢? 诶,S2不是有个事件机制么?我们是不是可以把click事件获取到,然后将我们的编辑元素盖到展示元素的上面? 找了下API,果然有一个可以用来监听数据格的点击事件(S2Event.DATA_CELL_CLICK)。
2. 注册一个DATA_CELL_CLICK事件,触发后保存当前点击Cell的信息
S2Ref.current.on(S2Event.DATA_CELL_CLICK, (e) => {
// 保存当前cell
setCell(e.target.cfg.parent)
})
为什么需要保存Cell的信息呢?因为我们接下来要使用这个信息去排版我们的编辑元素。 那怎么获取通过cell获取到当前cell的坐标以及宽高信息呢? cell的这些信息都挂载在cellMeta上面,使用cell.getMeta()即可快速获取到这些信息,然后我们将这些信息拼装一下,变成我们需要的style数据。同时还得把原始数据赋值给这个编辑元素,使得用户无缝切换编辑和展示模式。
3. 通过当前Cell信息将受控编辑元素覆盖到当前Cell上方,同时赋初始值
useEffect(() => {
const spreadsheet = S2Ref.current
if (spreadsheet && cell) {
const cellMeta = pick(cell.getMeta(), ['x', 'y', 'width', 'height', 'fieldValue']);
const colCellHeight = (spreadsheet.getColumnNodes()[0] || { height: 0 }).height
cellMeta.x -= scroll.scrollX || 0;
cellMeta.y -= (scroll.scrollY || 0) - colCellHeight;
setPosition({
left: cellMeta.x,
top: cellMeta.y,
width: cellMeta.width,
height: cellMeta.height,
})
setShow(true)
setValue(cellMeta.fieldValue)
}
}, [cell])
另外,还发现一个不是很舒服的体验,点击之后没有自动focus到编辑元素上,这个问题怎么优化呢? 也很简单,只要在show的时候自动focus就好。
// show的时候自动focus
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus({ preventScroll: true })
}
}, [show])
已经有了一个覆盖在S2上方的编辑元素,那么接下来我们就要考虑如何将修改后的值同步到S2内部。 再来看看现在我们的表格长什么样子?
4. 使用回车键触发保存功能
4.1 save函数
// 改变S2实际渲染内容
const onSave = (inputVal: string) => {
const spreadsheet = S2Ref.current
if (spreadsheet && cell) {
const { rowIndex, valueField } = cell.getMeta();
spreadsheet.dataSet.originData[rowIndex][valueField] = inputVal;
spreadsheet.render(true);
setShow(false)
}
}
通过修改originData然后再调用render函数即可做到更新S2数据的功能,是不是很简单? 渲染完成之后再把我们用来控制渲染元素的show置为false来隐藏编辑元素。
4.2 添加keydown事件,回车的时候触发保存功能
// 绑定回车键 回车的时候触发保存
useEffect(() => {
const onKeyDown = (e) => {
if (e.keyCode === 13) {
e.preventDefault();
onSave(value);
}
};
if (inputRef.current) {
inputRef.current.addEventListener('keydown', onKeyDown)
}
return () => {
inputRef.current?.removeEventListener('keydown', onKeyDown)
}
}, [value])
记得解绑事件哦,防止内存泄漏。 来看看效果 但是用户不一定非得使用回车保存内容,和Excel类似,编辑元素失去焦点的时候,应该也是需要触发保存的,这个功能我们要怎么去实现呢?
5. 切换Cell之前,先保存当前编辑的值
// 绑定 S2Event.DATA_CELL_CLICK 事件,触发时先将上一个cell的值保存,然后设置当前cell
useEffect(() => {
const spreadsheet = S2Ref.current
const handleClick = (e) => {
onSave(value)
setCell(e.target.cfg.parent)
}
if (spreadsheet) {
spreadsheet.on(S2Event.DATA_CELL_CLICK, handleClick)
}
return () => {
spreadsheet?.off(S2Event.DATA_CELL_CLICK, handleClick)
}
}, [value])
让我们在看看效果 正当我沾沾自喜要把代码push上去的时候,不小心碰到了鼠标滚轮,然后他变成这样了。😱😱😱 这可咋办,明天就要上线了!这个编辑元素居然不会跟着滚动! 又翻阅了S2的事件列表,找到了一个事件S2Eevnt.LAYOUT_CELL_SCROLL,可以用来监听滚动事件。 完美!😼😼😼
6. 监听滚动事件,使得编辑元素动态滚动
useEffect(() => {
const spreadsheet = S2Ref.current
const handleScroll = (e) => {
if (spreadsheet) {
const newScroll = spreadsheet.facet.getScrollOffset()
if (!isEqual(newScroll, scroll)) {
setScroll(spreadsheet.facet.getScrollOffset())
}
}
}
if (spreadsheet) {
spreadsheet.on(S2Event.LAYOUT_CELL_SCROLL, handleScroll)
}
return () => {
spreadsheet?.off(S2Event.LAYOUT_CELL_SCROLL, handleScroll)
}
}, [])
同时监听scroll的改动
// 监听滚动
useEffect(() => {
const spreadsheet = S2Ref.current
if (spreadsheet && cell) {
const cellMeta = pick(cell.getMeta(), ['x', 'y', 'width', 'height', 'fieldValue']);
cellMeta.x -= scroll.scrollX || 0;
cellMeta.y -=
(scroll.scrollY || 0) -
(spreadsheet.getColumnNodes()[0] || { height: 0 }).height;
setPosition({
left: cellMeta.x,
top: cellMeta.y,
width: cellMeta.width,
height: cellMeta.height,
})
}
}, [scroll])
我们再来看看效果
好了,一个简单的可编辑表格就在S2灵活的底层基础上实现了,是不是忍不住自己也想来尝试来一把? 在线预览地址 s2.antv.vision/zh/examples… 欢迎star以及fork!让我们一起DIY起来~ github.com/antvis/S2 🌟🌟🌟