반응형
이전 - (10) csv 파일 handsontable로 연동하기
현재 - (11) AutoSizeInput에 파일 이름 연동하기
이제 편집한 csv를 server에 저장해보자.
server에 저장하기 전에 파일 이름을 불러오고, 파일 이름도 수정할 수 있는 input을 먼저 구현해보자.
저장할 file의 이름이 있어야, 해당되는 파일이 서버에 없는 경우 그대로 저장하고,
서버에 있다면, 덮어 씌울 건지 확인할 수 있다.
input은 파일 이름에 따라 size가 알맞게 수정되도록 하자.
AutoSizeInput.js를 만들고 아래의 코드를 복사하자.
//AutoSizeInput.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
const sizerStyle = {
position: 'absolute',
top: 0,
left: 0,
visibility: 'hidden',
height: 0,
overflow: 'scroll',
whiteSpace: 'pre',
};
const INPUT_PROPS_BLACKLIST = [
'extraWidth',
'injectStyles',
'inputClassName',
'inputRef',
'inputStyle',
'minWidth',
'onAutosize',
'placeholderIsMinWidth',
];
const cleanInputProps = (inputProps) => {
INPUT_PROPS_BLACKLIST.forEach(field => delete inputProps[field]);
return inputProps;
};
const copyStyles = (styles, node) => {
node.style.fontSize = styles.fontSize;
node.style.fontFamily = styles.fontFamily;
node.style.fontWeight = styles.fontWeight;
node.style.fontStyle = styles.fontStyle;
node.style.letterSpacing = styles.letterSpacing;
node.style.textTransform = styles.textTransform;
};
const isIE = (typeof window !== 'undefined' && window.navigator) ? /MSIE |Trident\/|Edge\//.test(window.navigator.userAgent) : false;
const generateId = () => {
// we only need an auto-generated ID for stylesheet injection, which is only
// used for IE. so if the browser is not IE, this should return undefined.
return isIE ? '_' + Math.random().toString(36).substr(2, 12) : undefined;
};
class AutoSizeInput extends Component {
static getDerivedStateFromProps (props, state) {
const { id } = props;
return id !== state.prevId ? { inputId: id || generateId(), prevId: id } : null;
}
constructor (props) {
super(props);
this.state = {
inputWidth: props.minWidth,
inputId: props.id || generateId(),
prevId: props.id,
};
}
componentDidMount () {
this.mounted = true;
this.copyInputStyles();
this.updateInputWidth();
}
componentDidUpdate (prevProps, prevState) {
if (prevState.inputWidth !== this.state.inputWidth) {
if (typeof this.props.onAutosize === 'function') {
this.props.onAutosize(this.state.inputWidth);
}
}
this.updateInputWidth();
}
componentWillUnmount () {
this.mounted = false;
}
inputRef = (el) => {
this.input = el;
if (typeof this.props.inputRef === 'function') {
this.props.inputRef(el);
}
};
placeHolderSizerRef = (el) => {
this.placeHolderSizer = el;
};
sizerRef = (el) => {
this.sizer = el;
};
copyInputStyles () {
if (!this.mounted || !window.getComputedStyle) {
return;
}
const inputStyles = this.input && window.getComputedStyle(this.input);
if (!inputStyles) {
return;
}
copyStyles(inputStyles, this.sizer);
if (this.placeHolderSizer) {
copyStyles(inputStyles, this.placeHolderSizer);
}
}
updateInputWidth () {
if (!this.mounted || !this.sizer || typeof this.sizer.scrollWidth === 'undefined') {
return;
}
let newInputWidth;
if (this.props.placeholder && (!this.props.value || (this.props.value && this.props.placeholderIsMinWidth))) {
newInputWidth = Math.max(this.sizer.scrollWidth, this.placeHolderSizer.scrollWidth) + 2;
} else {
newInputWidth = this.sizer.scrollWidth + 2;
}
// add extraWidth to the detected width. for number types, this defaults to 16 to allow for the stepper UI
const extraWidth = (this.props.type === 'number' && this.props.extraWidth === undefined)
? 16 : parseInt(this.props.extraWidth) || 0;
newInputWidth += extraWidth;
if (newInputWidth < this.props.minWidth) {
newInputWidth = this.props.minWidth;
}
if (newInputWidth !== this.state.inputWidth) {
this.setState({
inputWidth: newInputWidth,
});
}
}
getInput () {
return this.input;
}
focus () {
this.input.focus();
}
blur () {
this.input.blur();
}
select () {
this.input.select();
}
renderStyles () {
// this method injects styles to hide IE's clear indicator, which messes
// with input size detection. the stylesheet is only injected when the
// browser is IE, and can also be disabled by the `injectStyles` prop.
const { injectStyles } = this.props;
return isIE && injectStyles ? (
<style dangerouslySetInnerHTML={{
__html: `input#${this.state.inputId}::-ms-clear {display: none;}`,
}} />
) : null;
}
render () {
const sizerValue = [this.props.defaultValue, this.props.value, ''].reduce((previousValue, currentValue) => {
if (previousValue !== null && previousValue !== undefined) {
return previousValue;
}
return currentValue;
});
const wrapperStyle = { ...this.props.style };
if (!wrapperStyle.display) wrapperStyle.display = 'inline-block';
const inputStyle = {
boxSizing: 'content-box',
width: `${this.state.inputWidth}px`,
...this.props.inputStyle,
};
const { ...inputProps } = this.props;
cleanInputProps(inputProps);
inputProps.className = this.props.inputClassName;
inputProps.id = this.state.inputId;
inputProps.style = inputStyle;
return (
<div className={this.props.className} style={wrapperStyle}>
{this.renderStyles()}
<input {...inputProps} ref={this.inputRef} />
<div ref={this.sizerRef} style={sizerStyle}>{sizerValue}</div>
{this.props.placeholder
? <div ref={this.placeHolderSizerRef} style={sizerStyle}>{this.props.placeholder}</div>
: null
}
</div>
);
}
}
AutoSizeInput.propTypes = {
className: PropTypes.string, // className for the outer element
defaultValue: PropTypes.any, // default field value
extraWidth: PropTypes.oneOfType([ // additional width for input element
PropTypes.number,
PropTypes.string,
]),
id: PropTypes.string, // id to use for the input, can be set for consistent snapshots
injectStyles: PropTypes.bool, // inject the custom stylesheet to hide clear UI, defaults to true
inputClassName: PropTypes.string, // className for the input element
inputRef: PropTypes.func, // ref callback for the input element
inputStyle: PropTypes.object, // css styles for the input element
minWidth: PropTypes.oneOfType([ // minimum width for input element
PropTypes.number,
PropTypes.string,
]),
onAutosize: PropTypes.func, // onAutosize handler: function(newWidth) {}
onChange: PropTypes.func, // onChange handler: function(event) {}
placeholder: PropTypes.string, // placeholder text
placeholderIsMinWidth: PropTypes.bool, // don't collapse size to less than the placeholder
style: PropTypes.object, // css styles for the outer element
value: PropTypes.any, // field value
};
AutoSizeInput.defaultProps = {
minWidth: 1,
injectStyles: true,
};
export default AutoSizeInput;
위 코드는 링크를 참고하였다.
MyTable.js에 AutoSizeInput component를 추가하자.
//MyTable.js
...
const [value, setValue] = useState("");
...
<AutoSizeInput
placeholder="파일 이름 입력"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
그러면 아래처럼 한글/영어에 상관없이 알맞게 input의 size가 조절된다.
이제 node의 파일을 불러오면 FileName이 AutoSizeInput에 들어가도록 변경하자.
file의 이름은 App.js의 file에서 useState를 이용하고 있으므로, file을 MyTable에 넘긴다.
//App.js
...
<MyTable csvFile={csvObject} file={file}/>
file이 변경될 때마다, setValue를 호출해서 fileName을 변경해주면 autoSizeInput에 반영된다.
//MyTable.js
const MyTable = ({ csvFile, fileUploadFlag, file }) => {
...
useEffect(() => {
setValue(file);
}, [file]);
최종 코드는 아래와 같다.
React
//AutoSizeInput.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
const sizerStyle = {
position: 'absolute',
top: 0,
left: 0,
visibility: 'hidden',
height: 0,
overflow: 'scroll',
whiteSpace: 'pre',
};
const INPUT_PROPS_BLACKLIST = [
'extraWidth',
'injectStyles',
'inputClassName',
'inputRef',
'inputStyle',
'minWidth',
'onAutosize',
'placeholderIsMinWidth',
];
const cleanInputProps = (inputProps) => {
INPUT_PROPS_BLACKLIST.forEach(field => delete inputProps[field]);
return inputProps;
};
const copyStyles = (styles, node) => {
node.style.fontSize = styles.fontSize;
node.style.fontFamily = styles.fontFamily;
node.style.fontWeight = styles.fontWeight;
node.style.fontStyle = styles.fontStyle;
node.style.letterSpacing = styles.letterSpacing;
node.style.textTransform = styles.textTransform;
};
const isIE = (typeof window !== 'undefined' && window.navigator) ? /MSIE |Trident\/|Edge\//.test(window.navigator.userAgent) : false;
const generateId = () => {
// we only need an auto-generated ID for stylesheet injection, which is only
// used for IE. so if the browser is not IE, this should return undefined.
return isIE ? '_' + Math.random().toString(36).substr(2, 12) : undefined;
};
class AutoSizeInput extends Component {
static getDerivedStateFromProps (props, state) {
const { id } = props;
return id !== state.prevId ? { inputId: id || generateId(), prevId: id } : null;
}
constructor (props) {
super(props);
this.state = {
inputWidth: props.minWidth,
inputId: props.id || generateId(),
prevId: props.id,
};
}
componentDidMount () {
this.mounted = true;
this.copyInputStyles();
this.updateInputWidth();
}
componentDidUpdate (prevProps, prevState) {
if (prevState.inputWidth !== this.state.inputWidth) {
if (typeof this.props.onAutosize === 'function') {
this.props.onAutosize(this.state.inputWidth);
}
}
this.updateInputWidth();
}
componentWillUnmount () {
this.mounted = false;
}
inputRef = (el) => {
this.input = el;
if (typeof this.props.inputRef === 'function') {
this.props.inputRef(el);
}
};
placeHolderSizerRef = (el) => {
this.placeHolderSizer = el;
};
sizerRef = (el) => {
this.sizer = el;
};
copyInputStyles () {
if (!this.mounted || !window.getComputedStyle) {
return;
}
const inputStyles = this.input && window.getComputedStyle(this.input);
if (!inputStyles) {
return;
}
copyStyles(inputStyles, this.sizer);
if (this.placeHolderSizer) {
copyStyles(inputStyles, this.placeHolderSizer);
}
}
updateInputWidth () {
if (!this.mounted || !this.sizer || typeof this.sizer.scrollWidth === 'undefined') {
return;
}
let newInputWidth;
if (this.props.placeholder && (!this.props.value || (this.props.value && this.props.placeholderIsMinWidth))) {
newInputWidth = Math.max(this.sizer.scrollWidth, this.placeHolderSizer.scrollWidth) + 2;
} else {
newInputWidth = this.sizer.scrollWidth + 2;
}
// add extraWidth to the detected width. for number types, this defaults to 16 to allow for the stepper UI
const extraWidth = (this.props.type === 'number' && this.props.extraWidth === undefined)
? 16 : parseInt(this.props.extraWidth) || 0;
newInputWidth += extraWidth;
if (newInputWidth < this.props.minWidth) {
newInputWidth = this.props.minWidth;
}
if (newInputWidth !== this.state.inputWidth) {
this.setState({
inputWidth: newInputWidth,
});
}
}
getInput () {
return this.input;
}
focus () {
this.input.focus();
}
blur () {
this.input.blur();
}
select () {
this.input.select();
}
renderStyles () {
// this method injects styles to hide IE's clear indicator, which messes
// with input size detection. the stylesheet is only injected when the
// browser is IE, and can also be disabled by the `injectStyles` prop.
const { injectStyles } = this.props;
return isIE && injectStyles ? (
<style dangerouslySetInnerHTML={{
__html: `input#${this.state.inputId}::-ms-clear {display: none;}`,
}} />
) : null;
}
render () {
const sizerValue = [this.props.defaultValue, this.props.value, ''].reduce((previousValue, currentValue) => {
if (previousValue !== null && previousValue !== undefined) {
return previousValue;
}
return currentValue;
});
const wrapperStyle = { ...this.props.style };
if (!wrapperStyle.display) wrapperStyle.display = 'inline-block';
const inputStyle = {
boxSizing: 'content-box',
width: `${this.state.inputWidth}px`,
...this.props.inputStyle,
};
const { ...inputProps } = this.props;
cleanInputProps(inputProps);
inputProps.className = this.props.inputClassName;
inputProps.id = this.state.inputId;
inputProps.style = inputStyle;
return (
<div className={this.props.className} style={wrapperStyle}>
{this.renderStyles()}
<input {...inputProps} ref={this.inputRef} />
<div ref={this.sizerRef} style={sizerStyle}>{sizerValue}</div>
{this.props.placeholder
? <div ref={this.placeHolderSizerRef} style={sizerStyle}>{this.props.placeholder}</div>
: null
}
</div>
);
}
}
AutoSizeInput.propTypes = {
className: PropTypes.string, // className for the outer element
defaultValue: PropTypes.any, // default field value
extraWidth: PropTypes.oneOfType([ // additional width for input element
PropTypes.number,
PropTypes.string,
]),
id: PropTypes.string, // id to use for the input, can be set for consistent snapshots
injectStyles: PropTypes.bool, // inject the custom stylesheet to hide clear UI, defaults to true
inputClassName: PropTypes.string, // className for the input element
inputRef: PropTypes.func, // ref callback for the input element
inputStyle: PropTypes.object, // css styles for the input element
minWidth: PropTypes.oneOfType([ // minimum width for input element
PropTypes.number,
PropTypes.string,
]),
onAutosize: PropTypes.func, // onAutosize handler: function(newWidth) {}
onChange: PropTypes.func, // onChange handler: function(event) {}
placeholder: PropTypes.string, // placeholder text
placeholderIsMinWidth: PropTypes.bool, // don't collapse size to less than the placeholder
style: PropTypes.object, // css styles for the outer element
value: PropTypes.any, // field value
};
AutoSizeInput.defaultProps = {
minWidth: 1,
injectStyles: true,
};
export default AutoSizeInput;
//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";
import AutoSizeInput from "./AutoSizeInput";
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, file }) => {
const [displayIndex, setDisplayIndex] = useState("");
const [displayCell, setDisplayCell] = useState("");
const [value, setValue] = 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]);
useEffect(() => {
setValue(file);
}, [file]);
return (
<div>
{fileUploadFlag && (
<div>
<button onClick={csvDownLoad}>DOWNLOAD</button>
<AutoSizeInput
placeholder="파일 이름 입력"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<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);
//App.js
import React, { useEffect, useState } from "react";
import FileUpload from "./components/FileUpload";
import MyFileList from "./components/MyFileList";
import MyTable from "./components/MyTable";
import MyToggles from "./components/MyToggles";
import * as mnode from "./components/nodelibrary";
const csvObjectDefault = {
HEIGHT: 0,
WIDTH: 0,
csv: [],
};
const nodeTest = () => {
mnode.getFileFolderList(mnode.PATH, "csv");
return;
}
const App = () => {
const [csvObject, setCsvObject] = useState(csvObjectDefault);
const [version, setVersion] = useState("");
const [country, setCountry] = useState("");
const [file, setFile] = useState("");
const [fileList, setFileList] = useState([]);
const getFileList = () => {
if(version === "" || country === "") return;
let path = `${mnode.PATH}/${version}/${country}`;
mnode.getFileList(path, "csv", setFileList);
}
useEffect(getFileList, [version, country]);
return (
<div>
<MyToggles
version={version}
setVersion={setVersion}
country={country}
setCountry={setCountry}
/>
<hr style={{ borderColor: "grey" }} />
<MyFileList fileList={fileList} setFile={setFile}/>
<button onClick={nodeTest}>서버 연결</button>
<button onClick={() => console.log(csvObject)}>print csv</button>
<div className="App">
<FileUpload setCsvObject={setCsvObject} pathInfo={{ version, country, file }} />
<MyTable csvFile={csvObject} file={file}/>
</div>
</div>
);
};
export default App;
반응형
'개발 > Node JS' 카테고리의 다른 글
Node js, React 파일 관리 시스템 만들기 (13) (0) | 2021.07.19 |
---|---|
Node js, React 파일 관리 시스템 만들기 (12) (0) | 2021.07.18 |
Node js, React 파일 관리 시스템 만들기 (10) (2) | 2021.07.16 |
Node js, React 파일 관리 시스템 만들기 (9) (0) | 2021.07.16 |
Node js, React 파일 관리 시스템 만들기 (8) (0) | 2021.07.15 |
댓글