React Data Grid - добавление настраиваемой функции копирования и вставки с несколькими таблицами - PullRequest
0 голосов
/ 03 августа 2020

Требуется помощь в выяснении того, как пользовательские функции копирования / вставки могут быть реализованы для нескольких таблиц React Data Grid на одной странице. Согласно приведенному ниже коду, копирование / вставка запускается для обеих таблиц.

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 раза для решения бизнес-задачи.

Несколько методов, которые я пробовал -

  1. Пытался создать ссылку - <div ref={el => this.wrapperRef = el}> и добавил слушателя событий в ссылку, например this.wrapperRef.addEventListener('copy', this.handleCopy), но тогда функция handleCopy не вызывается.
  2. Пробовали <div onCopy={this.handleCopy}, но функция handleCopy не вызывается.

1 Ответ

0 голосов
/ 06 августа 2020

Решено, добавив переменную в состоянии (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;

Если есть лучший способ решить эту проблему, дайте мне знать.

...