React Data Grid - 为多个表格添加自定义复制粘贴功能
React Data Grid - Adding custom copy paste functionality with multiple tables
需要帮助弄清楚如何在单个页面中为多个 React 数据网格表实现自定义复制/粘贴功能。按照下面的代码,复制/粘贴被触发为两个表。
import { connect } from 'react-redux';
import ReactDataGrid from 'fixed-react-data-grid';
const defaultParsePaste = str => str.split(/\r\n|\n|\r/).map(row => row.split('\t'));
class DataGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: [],
topLeft: {},
botRight: {},
};
this.columns = [
{ key: 'col1', name: 'Col1', editable: true },
{ key: 'col2', name: 'Col2', editable: true },
{ key: 'col3', name: 'Col3', editable: true },
{ key: 'col4', name: 'Col4', editable: true },
];
}
componentDidMount() {
document.addEventListener('copy', this.handleCopy);
document.addEventListener('paste', this.handlePaste);
}
componentWillUnmount() {
document.removeEventListener('copy', this.handleCopy);
document.removeEventListener('paste', this.handlePaste);
}
rowGetter = i => {
const { rows } = this.state;
return rows[i];
};
handleCopy = e => {
e.preventDefault();
e.stopPropagation();
const { topLeft, botRight } = this.state;
// Loop through each row
const text = range(topLeft.rowIdx, botRight.rowIdx + 1)
.map(
// Loop through each column
rowIdx =>
this.columns
.slice(topLeft.colIdx, botRight.colIdx + 1)
.map(
// Grab the row values and make a text string
col => this.rowGetter(rowIdx)[col.key],
)
.join('\t'),
)
.join('\n');
e.clipboardData.setData('text/plain', text);
};
handlePaste = e => {
e.preventDefault();
e.stopPropagation();
const { topLeft } = this.state;
const newRows = [];
const pasteData = defaultParsePaste(e.clipboardData.getData('text/plain'));
pasteData.forEach(row => {
const rowData = {};
// Merge the values from pasting and the keys from the columns
this.columns.slice(topLeft.colIdx, topLeft.colIdx + row.length).forEach((col, j) => {
// Create the key-value pair for the row
rowData[col.key] = row[j];
});
// Push the new row to the changes
newRows.push(rowData);
});
this.updateRows(topLeft.rowIdx, newRows);
};
onGridRowsUpdated = ({ fromRow, toRow, updated }) => {
const { rows } = this.state;
this.setState(state => {
const rows1 = state.rows.slice();
for (let i = fromRow; i <= toRow; i += 1) {
rows[i] = { ...rows[i], ...updated };
}
return { rows1 };
});
};
setSelection = args => {
console.log(args, 'setSelection');
this.setState({
topLeft: {
rowIdx: args.topLeft.rowIdx,
colIdx: args.topLeft.idx,
},
botRight: {
rowIdx: args.bottomRight.rowIdx,
colIdx: args.bottomRight.idx,
},
});
};
render() {
return (
<div>
<ReactDataGrid
columns={this.columns}
rowGetter={i => this.state.rows[i]}
rowsCount={this.state.rows.length}
onGridRowsUpdated={this.onGridRowsUpdated}
enableCellSelect
minColumnWidth={40}
cellRangeSelection={{
onComplete: this.setSelection,
}}
onCellSelected={s => this.setSelection({ topLeft: s, bottomRight: s })}
/>
</div>
);
}
}
export default connect(
null,
null,
)(DataGrid);
为了解决一个业务案例,上面的DataGrid组件在父组件中导入了2次。
我试过的几种方法-
- 尝试创建一个引用 -
<div ref={el => this.wrapperRef = el}>
并将事件侦听器添加到引用 this.wrapperRef.addEventListener('copy', this.handleCopy)
但是 handleCopy
函数没有被调用。
- 已尝试
<div onCopy={this.handleCopy}
但未调用 handleCopy
函数。
通过在状态 (inFocus) 中添加一个变量并根据 mousedown 和 keydown 事件更改它来解决它。
import React from 'react';
import ReactDataGrid from 'fixed-react-data-grid';
import { range } from 'lodash';
const defaultParsePaste = str => str.split(/\r\n|\n|\r/).map(row => row.split('\t'));
// References -
// https://adazzle.github.io/react-data-grid/docs/examples/simple-grid
// https://gist.github.com/ZackKnopp/40fc0691feb03f0fba3e25e7353b73ae
// Props -
// columns = [
// { key: 'a', name: 'a', editable: true },
// { key: 'b', name: 'b', editable: true },
// { key: 'c', name: 'c', editable: true },
// { key: 'd', name: 'd', editable: true },
// ];
// rows = [{ a: 1, b: 2, c: 3, d: 4 }, { a: 5, b: 6, c: 7, d: 8 }];
// updateRows = rows => {
// this.setState({ rows });
// };
class CustomDataGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
topLeft: {},
botRight: {},
inFocus: false,
};
}
componentDidMount() {
document.addEventListener('mousedown', this.handleMousedown);
document.addEventListener('keydown', this.handleKeydown);
document.addEventListener('copy', this.handleCopy);
document.addEventListener('paste', this.handlePaste);
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.handleMouseDown);
document.removeEventListener('keydown', this.handleKeydown);
document.removeEventListener('copy', this.handleCopy);
document.removeEventListener('paste', this.handlePaste);
}
handleMouseDown = e => {
if (this.wrapperRef.contains(e.target) && !this.state.inFocus) {
this.setState({ inFocus: true });
e.stopPropagation();
e.preventDefault();
}
if (!this.wrapperRef.contains(e.target) && this.state.inFocus) {
this.setState({ inFocus: false });
}
};
handleKeydown = e => {
if (this.wrapperRef.contains(e.target) && !this.state.inFocus) {
this.setState({ inFocus: true });
e.stopPropagation();
e.preventDefault();
}
if (!this.wrapperRef.contains(e.target) && this.state.inFocus) {
this.setState({ inFocus: false });
}
};
rowGetter = i => this.props.rows[i];
handleCopy = e => {
if (this.state.inFocus) {
e.preventDefault();
e.stopPropagation();
const { topLeft, botRight } = this.state;
const text = range(topLeft.rowIdx, botRight.rowIdx + 1)
.map(rowIdx =>
this.props.columns
.slice(topLeft.colIdx, botRight.colIdx + 1)
.map(col => this.rowGetter(rowIdx)[col.key])
.join('\t'),
)
.join('\n');
e.clipboardData.setData('text/plain', text);
}
};
updateRows = (startIdx, newRows) => {
const rows = this.props.rows.slice();
for (let i = 0; i < newRows.length; i += 1) {
rows[startIdx + i] = { ...rows[startIdx + i], ...newRows[i] };
}
this.props.updateRows(rows);
};
handlePaste = e => {
if (this.state.inFocus) {
e.preventDefault();
e.stopPropagation();
const { topLeft, botRight } = this.state;
const pasteData = defaultParsePaste(e.clipboardData.getData('text/plain'));
const newRows = [];
if (pasteData.length === 1 && pasteData[0] && pasteData[0].length === 1) {
range(topLeft.rowIdx, botRight.rowIdx + 1).forEach(() => {
const rowData = {};
this.props.columns.slice(topLeft.colIdx, botRight.colIdx + 1).forEach(col => {
rowData[col.key] = pasteData[0][0];
});
newRows.push(rowData);
});
} else {
pasteData.forEach(row => {
const rowData = {};
this.props.columns.slice(topLeft.colIdx, topLeft.colIdx + row.length).forEach((col, j) => {
rowData[col.key] = row[j];
});
newRows.push(rowData);
});
}
this.updateRows(topLeft.rowIdx, newRows);
}
};
onGridRowsUpdated = ({ fromRow, toRow, updated }) => {
const rows = this.props.rows.slice();
for (let i = fromRow; i <= toRow; i += 1) {
rows[i] = { ...rows[i], ...updated };
}
this.props.updateRows(rows);
};
setSelection = async args => {
await this.setState({
topLeft: {
rowIdx: args.topLeft.rowIdx,
colIdx: args.topLeft.idx,
},
botRight: {
rowIdx: args.bottomRight.rowIdx,
colIdx: args.bottomRight.idx,
},
inFocus: true,
});
};
render() {
return (
<div ref={el => this.wrapperRef = el}>
<ReactDataGrid
columns={this.props.columns}
rowGetter={i => this.props.rows[i]}
rowsCount={this.props.rows.length}
onGridRowsUpdated={this.onGridRowsUpdated}
enableCellSelect
minColumnWidth={40}
cellRangeSelection={{
onComplete: this.setSelection,
}}
onCellSelected={s => this.setSelection({ topLeft: s, bottomRight: s })}
/>
</div>
);
}
}
export default CustomDataGrid;
如果有更好的方法解决这个问题,请告诉我。
需要帮助弄清楚如何在单个页面中为多个 React 数据网格表实现自定义复制/粘贴功能。按照下面的代码,复制/粘贴被触发为两个表。
import { connect } from 'react-redux';
import ReactDataGrid from 'fixed-react-data-grid';
const defaultParsePaste = str => str.split(/\r\n|\n|\r/).map(row => row.split('\t'));
class DataGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: [],
topLeft: {},
botRight: {},
};
this.columns = [
{ key: 'col1', name: 'Col1', editable: true },
{ key: 'col2', name: 'Col2', editable: true },
{ key: 'col3', name: 'Col3', editable: true },
{ key: 'col4', name: 'Col4', editable: true },
];
}
componentDidMount() {
document.addEventListener('copy', this.handleCopy);
document.addEventListener('paste', this.handlePaste);
}
componentWillUnmount() {
document.removeEventListener('copy', this.handleCopy);
document.removeEventListener('paste', this.handlePaste);
}
rowGetter = i => {
const { rows } = this.state;
return rows[i];
};
handleCopy = e => {
e.preventDefault();
e.stopPropagation();
const { topLeft, botRight } = this.state;
// Loop through each row
const text = range(topLeft.rowIdx, botRight.rowIdx + 1)
.map(
// Loop through each column
rowIdx =>
this.columns
.slice(topLeft.colIdx, botRight.colIdx + 1)
.map(
// Grab the row values and make a text string
col => this.rowGetter(rowIdx)[col.key],
)
.join('\t'),
)
.join('\n');
e.clipboardData.setData('text/plain', text);
};
handlePaste = e => {
e.preventDefault();
e.stopPropagation();
const { topLeft } = this.state;
const newRows = [];
const pasteData = defaultParsePaste(e.clipboardData.getData('text/plain'));
pasteData.forEach(row => {
const rowData = {};
// Merge the values from pasting and the keys from the columns
this.columns.slice(topLeft.colIdx, topLeft.colIdx + row.length).forEach((col, j) => {
// Create the key-value pair for the row
rowData[col.key] = row[j];
});
// Push the new row to the changes
newRows.push(rowData);
});
this.updateRows(topLeft.rowIdx, newRows);
};
onGridRowsUpdated = ({ fromRow, toRow, updated }) => {
const { rows } = this.state;
this.setState(state => {
const rows1 = state.rows.slice();
for (let i = fromRow; i <= toRow; i += 1) {
rows[i] = { ...rows[i], ...updated };
}
return { rows1 };
});
};
setSelection = args => {
console.log(args, 'setSelection');
this.setState({
topLeft: {
rowIdx: args.topLeft.rowIdx,
colIdx: args.topLeft.idx,
},
botRight: {
rowIdx: args.bottomRight.rowIdx,
colIdx: args.bottomRight.idx,
},
});
};
render() {
return (
<div>
<ReactDataGrid
columns={this.columns}
rowGetter={i => this.state.rows[i]}
rowsCount={this.state.rows.length}
onGridRowsUpdated={this.onGridRowsUpdated}
enableCellSelect
minColumnWidth={40}
cellRangeSelection={{
onComplete: this.setSelection,
}}
onCellSelected={s => this.setSelection({ topLeft: s, bottomRight: s })}
/>
</div>
);
}
}
export default connect(
null,
null,
)(DataGrid);
为了解决一个业务案例,上面的DataGrid组件在父组件中导入了2次。
我试过的几种方法-
- 尝试创建一个引用 -
<div ref={el => this.wrapperRef = el}>
并将事件侦听器添加到引用this.wrapperRef.addEventListener('copy', this.handleCopy)
但是handleCopy
函数没有被调用。 - 已尝试
<div onCopy={this.handleCopy}
但未调用handleCopy
函数。
通过在状态 (inFocus) 中添加一个变量并根据 mousedown 和 keydown 事件更改它来解决它。
import React from 'react';
import ReactDataGrid from 'fixed-react-data-grid';
import { range } from 'lodash';
const defaultParsePaste = str => str.split(/\r\n|\n|\r/).map(row => row.split('\t'));
// References -
// https://adazzle.github.io/react-data-grid/docs/examples/simple-grid
// https://gist.github.com/ZackKnopp/40fc0691feb03f0fba3e25e7353b73ae
// Props -
// columns = [
// { key: 'a', name: 'a', editable: true },
// { key: 'b', name: 'b', editable: true },
// { key: 'c', name: 'c', editable: true },
// { key: 'd', name: 'd', editable: true },
// ];
// rows = [{ a: 1, b: 2, c: 3, d: 4 }, { a: 5, b: 6, c: 7, d: 8 }];
// updateRows = rows => {
// this.setState({ rows });
// };
class CustomDataGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
topLeft: {},
botRight: {},
inFocus: false,
};
}
componentDidMount() {
document.addEventListener('mousedown', this.handleMousedown);
document.addEventListener('keydown', this.handleKeydown);
document.addEventListener('copy', this.handleCopy);
document.addEventListener('paste', this.handlePaste);
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.handleMouseDown);
document.removeEventListener('keydown', this.handleKeydown);
document.removeEventListener('copy', this.handleCopy);
document.removeEventListener('paste', this.handlePaste);
}
handleMouseDown = e => {
if (this.wrapperRef.contains(e.target) && !this.state.inFocus) {
this.setState({ inFocus: true });
e.stopPropagation();
e.preventDefault();
}
if (!this.wrapperRef.contains(e.target) && this.state.inFocus) {
this.setState({ inFocus: false });
}
};
handleKeydown = e => {
if (this.wrapperRef.contains(e.target) && !this.state.inFocus) {
this.setState({ inFocus: true });
e.stopPropagation();
e.preventDefault();
}
if (!this.wrapperRef.contains(e.target) && this.state.inFocus) {
this.setState({ inFocus: false });
}
};
rowGetter = i => this.props.rows[i];
handleCopy = e => {
if (this.state.inFocus) {
e.preventDefault();
e.stopPropagation();
const { topLeft, botRight } = this.state;
const text = range(topLeft.rowIdx, botRight.rowIdx + 1)
.map(rowIdx =>
this.props.columns
.slice(topLeft.colIdx, botRight.colIdx + 1)
.map(col => this.rowGetter(rowIdx)[col.key])
.join('\t'),
)
.join('\n');
e.clipboardData.setData('text/plain', text);
}
};
updateRows = (startIdx, newRows) => {
const rows = this.props.rows.slice();
for (let i = 0; i < newRows.length; i += 1) {
rows[startIdx + i] = { ...rows[startIdx + i], ...newRows[i] };
}
this.props.updateRows(rows);
};
handlePaste = e => {
if (this.state.inFocus) {
e.preventDefault();
e.stopPropagation();
const { topLeft, botRight } = this.state;
const pasteData = defaultParsePaste(e.clipboardData.getData('text/plain'));
const newRows = [];
if (pasteData.length === 1 && pasteData[0] && pasteData[0].length === 1) {
range(topLeft.rowIdx, botRight.rowIdx + 1).forEach(() => {
const rowData = {};
this.props.columns.slice(topLeft.colIdx, botRight.colIdx + 1).forEach(col => {
rowData[col.key] = pasteData[0][0];
});
newRows.push(rowData);
});
} else {
pasteData.forEach(row => {
const rowData = {};
this.props.columns.slice(topLeft.colIdx, topLeft.colIdx + row.length).forEach((col, j) => {
rowData[col.key] = row[j];
});
newRows.push(rowData);
});
}
this.updateRows(topLeft.rowIdx, newRows);
}
};
onGridRowsUpdated = ({ fromRow, toRow, updated }) => {
const rows = this.props.rows.slice();
for (let i = fromRow; i <= toRow; i += 1) {
rows[i] = { ...rows[i], ...updated };
}
this.props.updateRows(rows);
};
setSelection = async args => {
await this.setState({
topLeft: {
rowIdx: args.topLeft.rowIdx,
colIdx: args.topLeft.idx,
},
botRight: {
rowIdx: args.bottomRight.rowIdx,
colIdx: args.bottomRight.idx,
},
inFocus: true,
});
};
render() {
return (
<div ref={el => this.wrapperRef = el}>
<ReactDataGrid
columns={this.props.columns}
rowGetter={i => this.props.rows[i]}
rowsCount={this.props.rows.length}
onGridRowsUpdated={this.onGridRowsUpdated}
enableCellSelect
minColumnWidth={40}
cellRangeSelection={{
onComplete: this.setSelection,
}}
onCellSelected={s => this.setSelection({ topLeft: s, bottomRight: s })}
/>
</div>
);
}
}
export default CustomDataGrid;
如果有更好的方法解决这个问题,请告诉我。