본문 바로가기
개발/React

React Handsontable로 csv 편집기 만들기 (19)

by 피로물든딸기 2021. 6. 29.
반응형

프로젝트 전체 링크

 

이전 - (18) FileUpload에 대한 이벤트 보완

현재 - (19) 불러오기 버튼 추가하기

 

깃허브에서 코드 확인하기


파일이 업로드 된 후, drag & drop 영역이 사라졌기 때문에, 다시 파일을 불러오려면 새로고침을 해야한다.

따라서 불러오기 버튼을 추가하자.

 

불러오기 버튼은 FileUpload에 추가하도록 하자. 

따라서 App.js에서 MyTable과 FileUpload 위치를 바꾼다.

<div className="App">
  <FileUpload setCsvObject={setCsvObject} />
  <MyTable csvFile={csvObject}/>
</div>

 

input의 파일 선택 버튼을 일반 버튼에 연결하려면, input 파일을 hidden으로 만들어서 event로 연결한다.

자세한 설명은 링크를 참고하자.

 

fileUploadFlag가 true가 되면 ( ) 안의 input이 사라지기 때문에 getElementById로 찾을 수 없다.

따라서 input을 하나 더 만들고 hidden으로 숨겨둔다.

const onClickFileLoad = () => {
    let my_input = document.getElementById("my-input");
    my_input.click();
}

  return (
    <div>
      <button onClick={onClickFileLoad}>불러오기</button>
      <input
        id="my-input"
        style={{visibility : "hidden"}}
        type="file"
        accept=".csv"
        onChange={(e) => lib.handleUpload(e, setCsvObject, flagOn)}
      />
      
      ...
      

 

아래와 같이 불러오기 버튼이 생겼다. 

그리고 input에 handleUpload를 그대로 연결하였기 때문에 정상적으로 동작한다.

 

 

그러나 불러오기는 file이 업로드 된 후 나타나면 되므로 아래처럼 고친다.

{fileUploadFlag && <button onClick={onClickFileLoad}>불러오기</button>}

 

이제 파일을 불러온 후 불러오기 버튼이 나타난다.

하지만 이전 table이 계속 남아 있게 된다.

 

myTable을 만들 때, 이미 table이 있는 경우에 기존 myTable을 destory하는 것으로 위의 버그를 해결할 수 있다.

const init = (csvFile) => {
  if (csvFile === undefined || csvFile.HEIGHT === 0) return;

  const container = document.getElementById("hot-app");

  if(myTable !== undefined) myTable.destroy(); 

  myTable = new Handsontable(container, {
    ...
  });
};

 

최종 코드는 아래와 같다.

//App.js
import React, { useState } from "react";

//import * as lib from "./components/library";
import FileUpload from "./components/FileUpload";
import MyTable from "./components/MyTable";

//import ReduxTest from "./components/ReduxTest";
//import AnotherReduxTest from "./components/AnotherReduxTest";

const csvObjectDefault = {
  HEIGHT: 0,
  WIDTH: 0,
  csv: [],
};

const App = () => {
  const [csvObject, setCsvObject] = useState(csvObjectDefault);
  return (
    <div>
      {/* <ReduxTest/>
      <AnotherReduxTest/> */}
      <button onClick={() => console.log(csvObject)}>print csv</button>
      <div className="App">
        <FileUpload setCsvObject={setCsvObject} />
        <MyTable csvFile={csvObject}/>
      </div>
    </div>
  );
};

export default App;

 

// FileUpload.js
import React, { useState } from "react";
import { connect } from "react-redux";
import { actionCreators } from "./store";
import * as lib from "./library.js";
import "../css/FileUpload.scss";

const FileUpload = ({ setCsvObject, fileUploadFlag, flagOn, flagOff }) => {
  const [dropFlag, setDropFlag] = useState(false);

  const onClickFileLoad = () => {
    let my_input = document.getElementById("my-input");
    my_input.click();
  };

  return (
    <div>
      {fileUploadFlag && <button onClick={onClickFileLoad}>불러오기</button>}
      <input
        id="my-input"
        style={{ visibility: "hidden" }}
        type="file"
        accept=".csv"
        onChange={(e) => lib.handleUpload(e, setCsvObject, flagOn)}
      />
      {!fileUploadFlag && (
        <div
          id="drag-drop-field"
          className={dropFlag ? "in" : ""}
          onDrop={(e) => lib.handleOnDrop(e, setDropFlag, setCsvObject, flagOn)}
          onDragOver={(e) => lib.handleDragOver(e, setDropFlag)}
          onDragLeave={(e) => lib.handleOnDragLeave(e, setDropFlag)}
        >
          <p>drag & drop</p>
          <input
            type="file"
            accept=".csv"
            onChange={(e) => lib.handleUpload(e, setCsvObject, flagOn)}
          />
        </div>
      )}
    </div>
  );
};

function mapStateToProps(state, ownProps) {
  //console.log(state);
  return { fileUploadFlag: state };
}

function mapDispatchToProps(dispatch, ownProps) {
  return {
    flagOn: () => dispatch(actionCreators.flagOn()),
    flagOff: () => dispatch(actionCreators.flagOff()),
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(FileUpload);

 

//MyTable.js
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import * as lib from "./library.js";

import "handsontable/dist/handsontable.full.css";
import Handsontable from "handsontable";

let myTable;
let currentRow, currentColumn;

const getCell = (cell) => {
  if(cell === null) return ``;
  return cell.includes(",") ? `"${cell}"` : `${cell}`;
}

const csvDownLoad = () => {
  let rows = myTable.countRows();
  let cols = myTable.countCols();
  let tmpTables = myTable.getData(0, 0, rows - 1, cols - 1);
  let maxRow, maxCol;
  
  maxCol = 0;
  for(let r = 0; r < rows; r++) {
    for(let c = cols - 1; c >=0; c--) {
      if(!(tmpTables[r][c] === "" || tmpTables[r][c] === null)) {
        maxCol = (maxCol < c) ? c : maxCol;
        break;
      }
    }
  }

  maxRow = 0;
  for(let c = 0; c < cols; c++) {
    for(let r = rows - 1; r >=0; r--) {
      if(!(tmpTables[r][c] === "" || tmpTables[r][c] === null)) {
        maxRow = (maxRow < r) ? r : maxRow;
        break;
      }
    }
  }

  let parsing = myTable.getData(0, 0, maxRow, maxCol)
                .map((item) => item.map((cell) => getCell(cell)));
  let realTable = parsing.map((item) => item.join(",")).join("\n");

  lib.downLoadCsv(realTable);

  return;
};

const MyTable = ({ csvFile, fileUploadFlag }) => {
  const [displayIndex, setDisplayIndex] = useState("");
  const [displayCell, setDisplayCell] = useState("");
  
  const selectCell = () => {
    let selected = myTable.getSelectedLast();

    currentRow = selected[0];
    currentColumn = selected[1];

    if(currentRow < 0 || currentColumn < 0) return;

    setDisplayCell(myTable.getValue());
    setDisplayIndex(`${lib.rowToAlpha(currentColumn + 1)}${currentRow + 1}`);
  }

  const setValueCell = (e) => {
    if(currentRow < 0 || currentColumn < 0) return;

    setDisplayCell(e.target.value); 
    myTable.setDataAtCell(currentRow, currentColumn, e.target.value);
  }

  const init = (csvFile) => {
    if (csvFile === undefined || csvFile.HEIGHT === 0) return;
  
    const container = document.getElementById("hot-app");
  
    if(myTable !== undefined) myTable.destroy(); 

    myTable = new Handsontable(container, {
      data: lib.makeTable(csvFile, 2, 3),
      colHeaders: true,         /* column header는 보이게 설정 */
      rowHeaders: true,         /* row header 보이게 설정 */
      colWidths: [60, 60, 60, 60, 60, 60, 60],
      wordWrap: false,          /* 줄 바꿈 x */
      width: "50%",
      manualColumnResize: true, /* column 사이즈 조절 */
      manualRowResize: true,    /* row 사이즈 조절 */
      manualColumnMove: true,   /* column move 허용 */
      manualRowMove: true,      /* row move 허용 */
      dropdownMenu: true,       /* dropdown 메뉴 설정 */
      filters: true,            /* 필터 기능 on */
      contextMenu: true,        /* cell 클릭 시 메뉴 설정 */
      licenseKey: "non-commercial-and-evaluation",
      afterSelection: selectCell,
    });
  };
  
  useEffect(() => {
    init(csvFile);
  }, [csvFile]);

  return (
    <div>
      {fileUploadFlag && (
        <div>
          <button onClick={csvDownLoad}>DOWNLOAD</button>
          <div>
            <span>{displayIndex}</span>
            <input value={displayCell} onChange={setValueCell} />
          </div>
          <div id="hot-app"></div>
        </div>
      )}
    </div>
  );
};

function mapStateToProps(state, ownProps) {
  //console.log(state);
  return { fileUploadFlag: state };
}

export default connect(mapStateToProps)(MyTable);

이전 - (18) FileUpload에 대한 이벤트 보완

 

반응형

댓글