Need help figuring out how custom copy / paste functionality can be implemented for multiple React Data Grid tables in a single page. As per below code, copy / paste is being triggered for both the tables.
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);
The above DataGrid component is being imported in the parent component 2 times to solve a business case.
Few methods that I tried -
<div ref={el => this.wrapperRef = el}>
and added the event listener to the ref like this.wrapperRef.addEventListener('copy', this.handleCopy)
but then the handleCopy
function is not being called.<div onCopy={this.handleCopy}
but the handleCopy
function is not being called.Solved it by adding a variable in state (inFocus) and changing it based on mousedown and keydown events.
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;
If there is any better way to solve this, let me know.