본문 바로가기
개발/React

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

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

프로젝트 전체 링크

 

이전 - (17) redux로 File Upload 상태 관리하기

현재 - (18) File Upload에 대한 이벤트 보완

다음 - (19) 불러오기 버튼 추가하기

 

깃허브에서 코드 확인하기


이전 글에서 store에 FileUploadFlag를 관리할 수 있게 되었다.

이제 아래의 문제를 보완해보자.

 

1) file을 불러와도 drag & drop 영역이 사라지지 않는다.

2) DOWNLOAD 버튼/displayCell은 file이 업로드 된 후에 필요하다. (즉, MyTable은 file이 업로드 된 후 보인다.)

 

→ fileUpload가 성공하면 flag를 true로 변경한다.

flag가 on이면 FileUpload의 drag & drop 영역은 사라지게 한다.

그리고 MyTable이 나타나게 한다.


먼저 이전에 App.js의 주석을 풀어주자.

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

 

FileUpload에 store를 연결한다.

flagOff는 필요없지만 이전 예제처럼 그대로 추가한다.

flagOn을 handleOnDrop과 handleUpload에 넘겨줘서 실행시킨다.

FileUpload 전/후 비교

fileUploadFlag가 off가 되면 drag & drop 영역을 없애면 되므로, 

아래와 같이 { !fileUploadFlag && drag & drop 영역 } 으로 간단히 해결한다.

// FileUpload.js
import React, { useState } from "react";
import { connect } from "react-redux";
import { actionCreators } from "./store";

...

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

  return (
    <div>
      {!fileUploadFlag && (
        <div

          onDrop={(e) => lib.handleOnDrop(e, setDropFlag, setCsvObject, flagOn)}

        >

          <input
            onChange={(e) => lib.handleUpload(e, setCsvObject, flagOn)}
          />
        </div>
      )}
    </div>
  );
};

 

flagOn를 drop/upload에 넘겨주었으므로, library.js도 아래와 같이 변경한다.

// library.js

...

export const handleOnDrop = (e, setState, setCsvObject, flagOff) => {
  e.preventDefault();

  ...
  
  setState(false);
  flagOff();
  return false; 
};

export const handleUpload = (e, setCsvObject, flagOff) => {
  
  ...
  
  fileReader.onload = function () {
    //console.log(fileReader.result); 
    parsingCsv(fileReader.result, setCsvObject);
  };
  flagOff();
}

 

MyTable에서는 flag를 on/off 할 필요 없이, flag의 변경에 대해 반응만 하면 된다.

즉, Upload 성공 시 flag on에 의해 MyTable이 나타나게 하면 된다.

FileUpload와는 반대로 { fileUploadFlag && MyTable 영역 } 이 된다.

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

...

const MyTable = ({ csvFile, fileUploadFlag }) => {

  ...
  
  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);

 

파일 업로드 전에는 drag & drop 영역만 있다가,

file upload(drop, 파일 선택)가 완료된 후에는 Table이 나타나는 것을 알 수 있다.

 

최종 코드는 아래와 같다.

//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">
        <MyTable csvFile={csvObject}/>
        <FileUpload setCsvObject={setCsvObject} />
      </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);

  return (
    <div>
      {!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);

 

// library.js

...

export const handleOnDrop = (e, setState, setCsvObject, flagOff) => {
  e.preventDefault();

  ...
  
  setState(false);
  flagOff();
  return false; 
};

export const handleUpload = (e, setCsvObject, flagOff) => {
  
  ...
  
  fileReader.onload = function () {
    //console.log(fileReader.result); 
    parsingCsv(fileReader.result, setCsvObject);
  };
  flagOff();
}

 

//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");
  
    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);

이전 - (17) redux로 File Upload 상태 관리하기

다음 - (19) 불러오기 버튼 추가하기

반응형

댓글