반응형
현재 - (1) Project Setting
다음 - (2) fetch로 GET 요청하고 응답 받기
React Handsontable에 서버에서 파일을 불러오는 시스템을 만들어보자.
사용하는 node, npm ver은 아래와 같다.
$ node -v
v12.13.0
$ npm -v
6.12.0
handsontable-csv의 상위 폴더 React와 동일한 위치에 server 폴더를 만든다.
server.js가 node의 main이다.
그리고 log를 관리할 폴더와 router를 관리할 폴더를 미리 만들어둔다.
server.js에서 간단히 로그를 찍어보자.
//server.js
console.log("hello node js");
실행시 출력되는 것을 알 수 있다.
$ node server.js
hello node js
express를 이용하여 서버를 만든다. npm을 이용해 설치한다.
npm install --save express
다시 아래와 같이 코드를 바꾼다.
//server.js
const express = require('express');
const app = express();
app.listen(3002, () => console.log('Node.js Server is running on port 3002...'));
아래와 같이 서버가 실행되었다.
(윈도우에서 git bash를 이용하여 실행하였다.)
$ node server.js
Node.js Server is running on port 3002...
React Handsontable에서 사용하였던 react 코드들은 아래와 같다.
node js, express로 폴더, 파일을 불러와서 csv를 불러올 수 있도록 할 예정이다.
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {Provider} from "react-redux";
import store from './components/store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
//App.js
import React, { useState } from "react";
import FileUpload from "./components/FileUpload";
import MyTable from "./components/MyTable";
const csvObjectDefault = {
HEIGHT: 0,
WIDTH: 0,
csv: [],
};
const App = () => {
const [csvObject, setCsvObject] = useState(csvObjectDefault);
return (
<div>
<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");
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);
//store.js
import { createStore } from "redux";
const FLAG_ON = "FLAG_ON";
const FLAG_OFF = "FLAG_OFF";
const flagOn = () => {
return {
type: FLAG_ON,
};
}
const flagOff = () => {
return {
type: FLAG_OFF,
};
}
const reducer = (state = false, action) => {
switch (action.type) {
case FLAG_ON: /* File Upload 성공 */
return true;
case FLAG_OFF: /* File Upload 전 */
return false;
default:
return state;
}
};
/* subscribe */
const store = createStore(reducer);
export const actionCreators = {
flagOn,
flagOff
}
export default store;
// library.js
const DELIMITER = ',';
const APOSTROPHE = '"';
export const handleOnDragLeave = (e, setState) => {
e.preventDefault();
setState(false);
return false;
};
export const handleDragOver = (e, setState) => {
e.preventDefault();
setState(true);
return false;
};
export const handleOnDrop = (e, setState, setCsvObject, flagOff) => {
e.preventDefault();
let file = e.dataTransfer.files[0];
let fileReader = new FileReader();
fileReader.readAsText(file, "utf-8"); // or euc-kr
fileReader.onload = function () {
//console.log(fileReader.result);
parsingCsv(fileReader.result, setCsvObject);
return;
};
setState(false);
flagOff();
return false;
};
export const handleUpload = (e, setCsvObject, flagOff) => {
let file = e.target.files[0];
let fileReader = new FileReader();
if(file === undefined) return; /* 방어 코드 추가 */
fileReader.readAsText(file, "utf-8"); // or euc-kr
fileReader.onload = function () {
//console.log(fileReader.result);
parsingCsv(fileReader.result, setCsvObject);
};
flagOff();
}
export const mySplit = (line, delimiter, ignore) => {
let spt = [];
let tmp = "";
let flag = false;
for(let i = 0; i < line.length; i++)
{
if(ignore === line[i] && flag === true)
{
flag = false;
continue;
}
else if(ignore === line[i])
{
flag = true;
continue;
}
if(line[i] === delimiter && flag === false) {
spt.push(tmp);
tmp = "";
continue;
}
tmp += line[i];
}
spt.push(tmp);
return spt;
}
export const parsingCsv = (file, setCsvObject) => {
let height, width;
let obj = {
HEIGHT: 0,
WIDTH: 0,
csv: [],
};
obj.csv = [];
let sptLine = file.split(/\r\n|\n/);
let maxLength = 0;
for(let line of sptLine) {
let spt = mySplit(line, DELIMITER, APOSTROPHE);
if(maxLength < spt.length) maxLength = spt.length;
}
height = 0;
for(let line of sptLine)
{
if(line === "") continue;
let spt = mySplit(line, DELIMITER, APOSTROPHE);
let pushCount = maxLength - spt.length;
for(let i = 0; i < pushCount; i++) spt.push("");
obj.csv.push(spt);
height++;
}
width = obj.csv[0].length;
obj.HEIGHT = height;
obj.WIDTH = width;
setCsvObject(obj);
return;
}
export const makeTable = (csvObject, height, width) => {
let table = [];
for(let h = 0; h < csvObject.HEIGHT; h++)
{
let line = [];
for(let w = 0; w < csvObject.WIDTH; w++) line.push(csvObject.csv[h][w]);
for(let w = 0; w < width; w++) line.push("");
table.push(line);
}
for(let h = 0; h < height; h++)
{
let dummy = [];
for(let w = 0; w < csvObject.WIDTH + width; w++) dummy.push("");
table.push(dummy);
}
return table;
}
export const downLoadCsv = (contents, fileName = "MyFile.csv") => {
let fileDown = "data:csv;charset=utf-8," + contents;
let encodedUri = encodeURI(fileDown);
let link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
export const rowToAlpha = (row) => {
const numToAlpha = [
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J",
"K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
"U", "V", "W", "X", "Y", "Z",
];
if (row <= 26) return numToAlpha[row - 1];
if (26 < row && row <= 26 * 26) {
let front = numToAlpha[parseInt(row / 26) - 1];
let back = numToAlpha[(row % 26) - 1];
if (row % 26 === 0) {
back = "Z";
front = numToAlpha[parseInt(row / 26) - 2];
}
return front + back;
}
return "too_long";
}
// DragDropTest.scss
#drag-drop-field {
border: 3px dashed blue;
width: 70vw;
height: 70vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
p {
font-size: 30px;
}
&.in {
border: 3px dashed red;
}
}
// FileUpload.scss
#drag-drop-field {
border: 3px dashed blue;
width: 70vw;
height: 70vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
p {
font-size: 30px;
}
&.in {
border: 3px dashed red;
}
}
반응형
'개발 > Node JS' 카테고리의 다른 글
Node js, React 파일 관리 시스템 만들기 (6) (0) | 2021.07.14 |
---|---|
Node js, React 파일 관리 시스템 만들기 (5) (0) | 2021.07.14 |
Node js, React 파일 관리 시스템 만들기 (4) (0) | 2021.07.08 |
Node js, React 파일 관리 시스템 만들기 (3) (0) | 2021.07.07 |
Node js, React 파일 관리 시스템 만들기 (2) (0) | 2021.07.03 |
댓글