참고
- Mui Toggle Button으로 편집기 버튼 만들기 with React Color Picker
- width, height, placeholder, sort
- afterSelection으로 수식 입력줄 구현하기
- Download CSV 구현 (콤마, 줄바꿈, 따옴표 처리)
- Mui Drawer로 Handsontable Option 관리하기
- Column Width, Row Height 로컬 스토리지에 저장하기
- Mui 토글 버튼으로 셀 스타일 편집 기능 만들기
- Handsontable 깃허브 연동하기 (data, style, comment, merge 저장하기)
아래와 같이 셀 스타일을 편집해 보자.
위 기능을 사용하기 위해서, outsideClickDeselects를 false여야 한다.
편집 버튼을 누를 때, outsideClickDeselects에 의해 셀 선택이 해제되기 때문이다.
로컬 스토리지에 customizing 하고 있다면 해당 코드를 지운다.
const myDefaultOptions = {
trueFalseOptions: {
...
// outsideClickDeselects: false /* 셀 외부 클릭 시, 셀 선택 해제 */,
},
...
};
그리고 테이블을 생성하는 곳에서 false로 처리한다.
outsideClickDeselects: false /* 셀 외부 클릭 시, 셀 선택 해제 */,
셀 스타일
(1, 1) 셀이 아래와 같이 편집되었다고 가정하자.
위의 그림대로 설정되려면 (1, 1) 셀에 대해 아래와 같이 코드를 작성한다.
cells: function(row, col, prop) {
let cellProperties = {};
if (row === 1 && col === 1) {
cellProperties.className = "htCenter htMiddle";
cellProperties.renderer = function(instance, td) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
td.style.fontWeight = "bold";
td.style.fontStyle = "italic";
td.style.textDecoration = "underline line-through";
td.style.color = "blue";
td.style.backgroundColor = "red";
};
}
return cellProperties;
},
편집기 설정
편집기에서 alignment horizontal / vertical 버튼을 클릭하면 className을 변경한다.
그리고 굵게 / 기울임 / 밑줄 / 취소선 / 글자색 / 배경색을 설정하도록 한다.
여기서는 취소선도 필요하기 때문에 링크를 참고하여 아래 코드를 추가하였다.
import FormatStrikethroughIcon from '@mui/icons-material/FormatStrikethrough';
...
<ToggleButton value="underline" aria-label="underline">
<FormatUnderlinedIcon />
</ToggleButton>
<ToggleButton value="line-through" aria-label="line-through">
<FormatStrikethroughIcon />
</ToggleButton>
그리고 handsontable에서 사용하는 className대로 value를 변경한다.
<StyledToggleButtonGroup>
<ToggleButton value="htLeft" aria-label="left aligned">
<ToggleButton value="htCenter" aria-label="centered">
<ToggleButton value="htRight" aria-label="right aligned">
</StyledToggleButtonGroup>
<StyledToggleButtonGroup>
<ToggleButton value="htTop" aria-label="top aligned">
<ToggleButton value="htMiddle" aria-label="middle">
<ToggleButton value="htBottom" aria-label="bottom aligned">
</StyledToggleButtonGroup>
셀 스타일 확인하기
현재 선택한 셀의 정보를 useState로 기억한다.
그리고 cellSelected에서 관리하는 코드를 추가한다.
const [selectedCell, setSelectedCell] = useState([0, 0]);
...
const cellSelected = () => {
let selectedLast = myTable.getSelectedLast();
if (selectedLast[0] < 0 || selectedLast[1] < 0) return;
let value = myTable.getValue() || "";
setDisplaySetInfo(value);
setSelectedCell([selectedLast[0], selectedLast[1]]);
};
HandontableToggleButton에 props로 현재 table과 선택된 셀 정보를 넘긴다.
<Box sx={{ m: 2 }}>
...
<HandsontableToggleButton
myHandsOnTable={myHandsOnTable}
selectedCell={selectedCell}
/>
...
<div id="hot-app" style={{ marginTop: "13px" }}></div>
</Box>
test 버튼을 추가하여 셀 스타일 정보를 아래와 같이 확인할 수 있다.
const test = () => {
let getCellInfo = myHandsOnTable.getCell(1, 1);
console.log(getCellInfo);
console.log(getCellInfo.className);
console.log(getCellInfo.style.fontWeight);
console.log(getCellInfo.style.fontStyle);
console.log(getCellInfo.style.textDecoration);
console.log(getCellInfo.style.color);
console.log(getCellInfo.style.backgroundColor);
};
className에 htCenter와 htMiddle이 포함되었고, fontWeight와 fontStyle에서 굵게와 기울임을 확인할 수 있다.
그리고 textDecoration에 밑줄과 취소선이 포함되어 있고, 글자색, 배경색도 확인할 수 있다.
선택한 셀 정보로 버튼 상태 변경하기
현재 선택된 셀에 설정된 대로 버튼 상태를 변경해 보자.
편집 버튼에서는 selectedCell이 변경될 때마다 버튼 상태가 변해야 한다.
const setButtonState = () => {
if(myHandsOnTable === undefined) return;
console.log(myHandsOnTable.getSelectedRange());
}
useEffect(() => {
setButtonState();
}, [selectedCell])
하지만 선택된 셀은 여러 개 일 수 있다. (getSelectedRange로 확인)
따라서 selectedRangeCells에서 가장 처음 나온 셀을 기준으로 상태를 변경하도록 하자.
const setButtonState = () => {
if(myHandsOnTable === undefined) return;
let selecetedRangeCells = myHandsOnTable.getSelectedRange();
let baseCell = selecetedRangeCells[0].from;
let getCellInfo = myHandsOnTable.getCell(baseCell.row, baseCell.col);
console.log(getCellInfo);
console.log(getCellInfo.className);
console.log(getCellInfo.style.fontWeight);
console.log(getCellInfo.style.fontStyle);
console.log(getCellInfo.style.textDecoration);
console.log(getCellInfo.style.color);
console.log(getCellInfo.style.backgroundColor);
}
아래의 코드를 추가한다.
const getCellInfoBase = () => {
let selecetedRangeCells = myHandsOnTable.getSelectedRange();
if(selecetedRangeCells === undefined) return undefined;
let baseCell = selecetedRangeCells[0].from;
return myHandsOnTable.getCell(baseCell.row, baseCell.col);
}
const getHorizontalStatus = (className) => {
let status = ["htLeft", "htCenter", "htRight"];
let current = className.split(" ");
return current.filter((item) => status.includes(item))[0];
};
const getVerticalStatus = (className) => {
let status = ["htTop", "htMiddle", "htBottom"];
let current = className.split(" ");
return current.filter((item) => status.includes(item))[0];
};
const setButtonState = () => {
if (myHandsOnTable === undefined) return;
let cellInfo = getCellInfoBase();
let className = cellInfo.className;
let horizontal = getHorizontalStatus(className) || ""; // undefined 처리
let vertical = getVerticalStatus(className) || "";
setHorizontalAlignment(horizontal);
setVerticalAlignment(vertical);
let fontWeight = cellInfo.style.fontWeight;
let fontStyle = cellInfo.style.fontStyle;
let textDecoration = cellInfo.style.textDecoration.split(" ");
setFormats([fontWeight, fontStyle, ...textDecoration]);
setFontColor(cellInfo.style.color);
setBgColor(cellInfo.style.backgroundColor);
};
useEffect(() => {
setButtonState();
}, [selectedCell]);
이제 선택한 셀에 대해 버튼의 상태가 변하게 된다.
셀 스타일 적용하기
이제 버튼을 누를 때, 셀 스타일을 적용해 보자.
alignment의 경우 버튼만 변경이 되었다.
const handleAlignment = (event, newAlignment, type) => {
console.log(newAlignment, type);
if (type === "horizontal") setHorizontalAlignment(newAlignment);
else if (type === "vertical") setVerticalAlignment(newAlignment);
};
현재 선택한 셀을 확인해서, className을 변경하자.
const handleAlignment = (event, newAlignment, type) => {
console.log(newAlignment, type);
let cellInfo = getCellInfo();
if(cellInfo === undefined) return;
let className = cellInfo.className;
let split = className.split(" ");
if (type === "horizontal") {
setHorizontalAlignment(newAlignment);
let horizontal = getHorizontalStatus(className);
split = split.filter((item) => item !== horizontal); // 현재 설정 값 삭제
}
else if (type === "vertical") {
setVerticalAlignment(newAlignment);
let vertical = getVerticalStatus(className);
split = split.filter((item) => item !== vertical); // 현재 설정 값 삭제
}
if(newAlignment) split.push(newAlignment); // 새로 설정된 값 추가.
cellInfo.className = split.join(" ");
};
하나의 셀에 대해 정상적으로 정렬 상태가 변한다.
하지만 선택된 셀을 모두 변경해야 하기 때문에 getCellInfoRange 함수를 추가하여 모든 셀을 변경한다.
const getCellInfoRange = () => {
let selecetedRangeCells = myHandsOnTable.getSelectedRange();
if(selecetedRangeCells === undefined) return undefined;
let cellPositions = [];
for(let cell of selecetedRangeCells) {
for(let r = cell.from.row; r <= cell.to.row; r++) {
for(let c = cell.from.col; c <= cell.to.col; c++)
cellPositions.push([r, c]);
}
}
return cellPositions;
}
const handleAlignment = (event, newAlignment, type) => {
console.log(newAlignment, type);
let cellPositions = getCellInfoRange();
if(cellPositions === undefined) return;
if (type === "horizontal") setHorizontalAlignment(newAlignment);
else if (type === "vertical") setVerticalAlignment(newAlignment);
for(let pos of cellPositions) {
let cellInfo = myHandsOnTable.getCell(pos[0], pos[1]);
let className = cellInfo.className;
let split = className.split(" ");
if (type === "horizontal") {
let horizontal = getHorizontalStatus(className);
split = split.filter((item) => item !== horizontal); // 현재 설정 값 삭제
}
else if (type === "vertical") {
let vertical = getVerticalStatus(className);
split = split.filter((item) => item !== vertical); // 현재 설정 값 삭제
}
if(newAlignment) split.push(newAlignment); // 새로 설정된 값 추가.
cellInfo.className = split.join(" ");
}
};
이제 선택된 셀을 모두 설정할 수 있다.
마찬가지로 다른 스타일도 적용한다.
const handleFormat = (event, newFormats) => {
console.log(newFormats);
let cellPositions = getCellInfoRange();
if(cellPositions === undefined) return;
setFormats(newFormats);
for(let pos of cellPositions) {
let cellInfo = myHandsOnTable.getCell(pos[0], pos[1]);
cellInfo.style.fontWeight = newFormats.includes("bold") ? "bold" : "";
cellInfo.style.fontStyle = newFormats.includes("italic") ? "italic" : "";
let deco = [];
if(newFormats.includes("underline")) deco.push("underline");
if(newFormats.includes("line-through")) deco.push("line-through");
cellInfo.style.textDecoration = deco.join(" ");
}
};
const handleToggleCompactPicker = (event, type) => {
// 선택된 셀이 없을 때는 color picker가 나오지 않도록 설정
let cellPositions = getCellInfoRange();
if(cellPositions === undefined) return;
...
};
const handleChangeComplete = (color, event) => {
let cellPositions = getCellInfoRange();
if(cellPositions === undefined) return;
let colorType = formats.includes("fontColor") ? "fontColor" : "bgColor";
console.log(colorType, color.hex);
if (colorType === "fontColor") setFontColor(color.hex);
else setBgColor(color.hex);
for(let pos of cellPositions) {
let cellInfo = myHandsOnTable.getCell(pos[0], pos[1]);
if (colorType === "fontColor") cellInfo.style.color = color.hex
else cellInfo.style.backgroundColor = color.hex
}
};
정상적으로 잘 동작하는지 확인해 보자.
전체 코드는 다음과 같다.
CustomHansOnTable.js
...
<div>
<button onClick={test}>test</button>
<Box sx={{ m: 2 }}>
<Button
sx={{ m: 2 }}
variant="outlined"
color="primary"
onClick={downloadCSV}
>
Download CSV
</Button>
{/* <input id="search_field" type="search" placeholder="search" />
<p>
<span id="resultCount">0</span> results
</p> */}
<HandsontableToggleButton
myHandsOnTable={myHandsOnTable}
selectedCell={selectedCell}
/>
<DisplayCellStyle>
<span>{displayCellInfo}</span>
</DisplayCellStyle>
<div id="hot-app" style={{ marginTop: "13px" }}></div>
</Box>
</div>
HandsontableToggleButton.js
import React, { useState } from "react";
import { styled } from "@mui/material/styles";
import Box from "@mui/material/Box";
import FormatAlignLeftIcon from "@mui/icons-material/FormatAlignLeft";
import FormatAlignCenterIcon from "@mui/icons-material/FormatAlignCenter";
import FormatAlignRightIcon from "@mui/icons-material/FormatAlignRight";
//import FormatAlignJustifyIcon from "@mui/icons-material/FormatAlignJustify";
import VerticalAlignBottomIcon from "@mui/icons-material/VerticalAlignBottom";
import VerticalAlignCenterIcon from "@mui/icons-material/VerticalAlignCenter";
import VerticalAlignTopIcon from "@mui/icons-material/VerticalAlignTop";
import FormatBoldIcon from "@mui/icons-material/FormatBold";
import FormatItalicIcon from "@mui/icons-material/FormatItalic";
import FormatUnderlinedIcon from "@mui/icons-material/FormatUnderlined";
import FormatStrikethroughIcon from "@mui/icons-material/FormatStrikethrough";
import FormatColorFillIcon from "@mui/icons-material/FormatColorFill";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import Divider from "@mui/material/Divider";
import Paper from "@mui/material/Paper";
import ColorizeIcon from "@mui/icons-material/Colorize";
import ToggleButton from "@mui/material/ToggleButton";
import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
import { CompactPicker } from "react-color";
import { useEffect } from "react";
const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }) => ({
"& .MuiToggleButtonGroup-grouped": {
margin: theme.spacing(0.5),
border: 0,
"&.Mui-disabled": {
border: 0,
},
"&:not(:first-of-type)": {
borderRadius: theme.shape.borderRadius,
},
"&:first-of-type": {
borderRadius: theme.shape.borderRadius,
},
},
}));
const HandsontableToggleButton = ({ myHandsOnTable, selectedCell }) => {
const [horizontalAlignment, setHorizontalAlignment] = useState("");
const [verticalAlignment, setVerticalAlignment] = useState("");
const [formats, setFormats] = useState(() => []);
const [showCompactPicker, setShowCompactPicker] = useState(false);
const [pickerPosition, setPickerPosition] = useState({ top: 0, left: 0 });
const [fontColor, setFontColor] = useState("#000000");
const [bgColor, setBgColor] = useState("#FFFFFF");
const getCellInfoBase = () => {
let selecetedRangeCells = myHandsOnTable.getSelectedRange();
if (selecetedRangeCells === undefined) return undefined;
let baseCell = selecetedRangeCells[0].from;
return myHandsOnTable.getCell(baseCell.row, baseCell.col);
};
const getCellInfoRange = () => {
let selecetedRangeCells = myHandsOnTable.getSelectedRange();
if (selecetedRangeCells === undefined) return undefined;
let cellPositions = [];
for (let cell of selecetedRangeCells) {
for (let r = cell.from.row; r <= cell.to.row; r++) {
for (let c = cell.from.col; c <= cell.to.col; c++)
cellPositions.push([r, c]);
}
}
return cellPositions;
};
const getHorizontalStatus = (className) => {
let status = ["htLeft", "htCenter", "htRight"];
let current = className.split(" ");
return current.filter((item) => status.includes(item))[0];
};
const getVerticalStatus = (className) => {
let status = ["htTop", "htMiddle", "htBottom"];
let current = className.split(" ");
return current.filter((item) => status.includes(item))[0];
};
const handleAlignment = (event, newAlignment, type) => {
console.log(newAlignment, type);
let cellPositions = getCellInfoRange();
if (cellPositions === undefined) return;
if (type === "horizontal") setHorizontalAlignment(newAlignment);
else if (type === "vertical") setVerticalAlignment(newAlignment);
for (let pos of cellPositions) {
let cellInfo = myHandsOnTable.getCell(pos[0], pos[1]);
let className = cellInfo.className;
let split = className.split(" ");
if (type === "horizontal") {
let horizontal = getHorizontalStatus(className);
split = split.filter((item) => item !== horizontal); // 현재 설정 값 삭제
} else if (type === "vertical") {
let vertical = getVerticalStatus(className);
split = split.filter((item) => item !== vertical); // 현재 설정 값 삭제
}
if (newAlignment) split.push(newAlignment); // 새로 설정된 값 추가.
cellInfo.className = split.join(" ");
}
};
const handleFormat = (event, newFormats) => {
console.log(newFormats);
let cellPositions = getCellInfoRange();
if (cellPositions === undefined) return;
setFormats(newFormats);
for (let pos of cellPositions) {
let cellInfo = myHandsOnTable.getCell(pos[0], pos[1]);
cellInfo.style.fontWeight = newFormats.includes("bold") ? "bold" : "";
cellInfo.style.fontStyle = newFormats.includes("italic") ? "italic" : "";
let deco = [];
if (newFormats.includes("underline")) deco.push("underline");
if (newFormats.includes("line-through")) deco.push("line-through");
cellInfo.style.textDecoration = deco.join(" ");
}
};
const handleToggleCompactPicker = (event, type) => {
let cellPositions = getCellInfoRange();
if (cellPositions === undefined) return;
const iconButton = event.currentTarget;
const rect = iconButton.getBoundingClientRect();
const pickerTop = rect.bottom + window.scrollY;
const pickerLeft = rect.left + window.scrollX;
setPickerPosition({ top: pickerTop, left: pickerLeft });
setShowCompactPicker((prev) => !prev);
};
const handleChangeComplete = (color, event) => {
let cellPositions = getCellInfoRange();
if (cellPositions === undefined) return;
let colorType = formats.includes("fontColor") ? "fontColor" : "bgColor";
console.log(colorType, color.hex);
if (colorType === "fontColor") setFontColor(color.hex);
else setBgColor(color.hex);
for (let pos of cellPositions) {
let cellInfo = myHandsOnTable.getCell(pos[0], pos[1]);
if (colorType === "fontColor") cellInfo.style.color = color.hex;
else cellInfo.style.backgroundColor = color.hex;
}
};
const getColorPicker = () => {
let colorType = formats.includes("fontColor") ? "fontColor" : "bgColor";
return (
<CompactPicker
color={colorType === "fontColor" ? fontColor : bgColor}
onChangeComplete={handleChangeComplete}
/>
);
};
const handleClose = () => {
let fms = formats.filter(
(item) => (item === "fontColor" || item === "bgColor") === false
);
setFormats(fms);
setShowCompactPicker(false);
};
const setButtonState = () => {
if (myHandsOnTable === undefined) return;
let cellInfo = getCellInfoBase();
let className = cellInfo.className;
let horizontal = getHorizontalStatus(className) || ""; // undefined 처리
let vertical = getVerticalStatus(className) || "";
setHorizontalAlignment(horizontal);
setVerticalAlignment(vertical);
let fontWeight = cellInfo.style.fontWeight;
let fontStyle = cellInfo.style.fontStyle;
let textDecoration = cellInfo.style.textDecoration.split(" ");
setFormats([fontWeight, fontStyle, ...textDecoration]);
setFontColor(cellInfo.style.color);
setBgColor(cellInfo.style.backgroundColor);
};
useEffect(() => {
setButtonState();
}, [selectedCell]);
return (
<div>
<Box sx={{ m: 2, marginBottom: 5 }}>
<Paper
elevation={0}
sx={{
display: "flex",
border: (theme) => `1px solid ${theme.palette.divider}`,
flexWrap: "wrap",
width: "580px",
}}
>
<StyledToggleButtonGroup
size="small"
value={horizontalAlignment}
exclusive
onChange={(e, alignment) =>
handleAlignment(e, alignment, "horizontal")
}
aria-label="text alignment"
>
<ToggleButton value="htLeft" aria-label="left aligned">
<FormatAlignLeftIcon />
</ToggleButton>
<ToggleButton value="htCenter" aria-label="centered">
<FormatAlignCenterIcon />
</ToggleButton>
<ToggleButton value="htRight" aria-label="right aligned">
<FormatAlignRightIcon />
</ToggleButton>
{/* <ToggleButton value="justify" aria-label="justified">
<FormatAlignJustifyIcon />
</ToggleButton> */}
</StyledToggleButtonGroup>
<StyledToggleButtonGroup
size="small"
value={verticalAlignment}
exclusive
onChange={(e, alignment) =>
handleAlignment(e, alignment, "vertical")
}
aria-label="text alignment"
>
<ToggleButton value="htTop" aria-label="top aligned">
<VerticalAlignTopIcon />
</ToggleButton>
<ToggleButton value="htMiddle" aria-label="middle">
<VerticalAlignCenterIcon />
</ToggleButton>
<ToggleButton value="htBottom" aria-label="bottom aligned">
<VerticalAlignBottomIcon />
</ToggleButton>
</StyledToggleButtonGroup>
<Divider flexItem orientation="vertical" sx={{ mx: 0.5, my: 1 }} />
<StyledToggleButtonGroup
size="small"
value={formats}
onChange={handleFormat}
aria-label="text formatting"
>
<ToggleButton value="bold" aria-label="bold">
<FormatBoldIcon />
</ToggleButton>
<ToggleButton value="italic" aria-label="italic">
<FormatItalicIcon />
</ToggleButton>
<ToggleButton value="underline" aria-label="underline">
<FormatUnderlinedIcon />
</ToggleButton>
<ToggleButton value="line-through" aria-label="line-through">
<FormatStrikethroughIcon />
</ToggleButton>
<ToggleButton
value="fontColor"
aria-label="fontColor"
onClick={(e) => handleToggleCompactPicker(e, "fontColor")}
>
<ColorizeIcon />
<ArrowDropDownIcon />
</ToggleButton>
<ToggleButton
value="bgColor"
aria-label="bgColor"
onClick={(e) => handleToggleCompactPicker(e, "bgColor")}
>
<FormatColorFillIcon />
<ArrowDropDownIcon />
</ToggleButton>
</StyledToggleButtonGroup>
</Paper>
{showCompactPicker && (
<div
className="compact-picker-container"
style={{
position: "absolute",
top: pickerPosition.top + "px",
left: pickerPosition.left + "px",
zIndex: 1000,
}}
>
<div
style={{
position: "fixed",
top: "0px",
right: "0px",
bottom: "0px",
left: "0px",
}}
onClick={handleClose}
/>
{getColorPicker()}
</div>
)}
</Box>
</div>
);
};
export default HandsontableToggleButton;
MyHandsonTable.js
import React, { useState, useEffect } from "react";
import CustomHansOnTable from "./CustomHansOnTable";
import Box from "@mui/material/Box";
import Drawer from "@mui/material/Drawer";
import Button from "@mui/material/Button";
import Divider from "@mui/material/Divider";
import FormControlLabel from "@mui/material/FormControlLabel";
import Checkbox from "@mui/material/Checkbox";
import FormControl from "@mui/material/FormControl";
import FormHelperText from "@mui/material/FormHelperText";
import Input from "@mui/material/Input";
import InputLabel from "@mui/material/InputLabel";
const MY_OPTIONS = "MY_OPTIONS";
const myDefaultOptions = {
trueFalseOptions: {
colHeaders: true,
rowHeaders: true,
wordWrap: false /* 줄 바꿈 off */,
manualColumnResize: true,
manualRowResize: true,
manualColumnMove: true,
manualRowMove: true,
allowInsertColumn: true,
allowInsertRow: true,
allowRemoveColumn: true,
allowRemoveRow: true,
autoWrapCol: true /* 마지막 셀 아래에서 다음 셀 위로 이동 */,
autoWrapRow: true /* 마지막 셀 옆에서 다음 셀 처음으로 이동 */,
dragToScroll: true /* 표를 클릭 후 드래그를 할 때, 같이 스크롤 되는지 여부 */,
persistentState: false /* 열 정렬 상태, 열 위치 및 열 크기를 로컬 스토리지에 저장 */,
// outsideClickDeselects: false /* 셀 외부 클릭 시, 셀 선택 해제 */,
readOnly: true /* true : 모든 셀을 readOnly로 설정*/,
enterBeginsEditing: true /* true : 엔터 클릭 시 편집 모드, false : 다음 셀로 이동 */,
copyable: true /* 복사 가능 여부 */,
copyPaste: true /* 복사, 붙여넣기 가능 여부 */,
undo: true /* false : ctrl + z 비활성화 */,
trimWhitespace: false /* 자동 trim() 실행 후 셀에 저장 */,
contextMenu: true /* 마우스 왼쪽 버튼 클릭 시 컨텍스트 메뉴 */,
comments: true /* 주석, 메모 기능 context menu에 추가 */,
manualColumnFreeze: true /* freezeColumn context menu에 추가 */,
observeChanges: true,
},
numberOptions: {
width: 1000,
height: 1000,
startCols: 5 /* data가 없는 경우 기본 설정 */,
startRows: 5 /* data가 없는 경우 기본 설정 */,
maxCols: 100 /* 주어진 값보다 큰 Column은 제거 */,
maxRows: 100 /* 주어진 값보다 큰 Row는 제거 */,
minCols: 1 /* 최소한의 Column */,
minRows: 1 /* 최소한의 Row */,
minSpareRows: 0 /* 빈 열 자동 추가 */,
minSpareCols: 0 /* 빈 행 자동 추가 */,
fixedColumnsLeft: 0,
fixedRowsTop: 0,
fixedRowsBottom: 0,
rowHeaderWidth: 55 /* 행 헤더 너비 */,
},
cellInfo: {
colWidths: 60,
rowHeights: 25,
},
};
const MyHandsonTable = () => {
const [myOptions, setMyOptions] = useState(myDefaultOptions);
const [state, setState] = useState({ right: false });
const toggleDrawer = (anchor, open) => (event) => {
if (
event.type === "keydown" &&
(event.key === "Tab" || event.key === "Shift")
) {
return;
}
setState({ ...state, [anchor]: open });
};
const changeTrueFalseOptions = (option, value) => {
console.log(myOptions);
let temp = { ...myOptions };
temp.trueFalseOptions[option] = !value;
setMyOptions(temp);
localStorage.setItem(MY_OPTIONS, JSON.stringify(temp));
};
const makeTrueFalseCheckBox = () => {
let pair = Object.entries(myOptions.trueFalseOptions);
pair = pair.map((item) => [item[0], Boolean(item[1])]);
return pair.map((item, idx) => (
<FormControlLabel
key={idx}
control={<Checkbox checked={item[1]} />}
label={item[0]}
onChange={() => changeTrueFalseOptions(item[0], item[1])}
/>
));
};
const changeNumberOptions = (option, value) => {
let temp = { ...myOptions };
if (isNaN(Number(value))) return;
temp.numberOptions[option] = Number(value);
setMyOptions(temp);
localStorage.setItem(MY_OPTIONS, JSON.stringify(temp));
};
const makeNumberInput = () => {
let pair = Object.entries(myOptions.numberOptions);
pair = pair.map((item) => [item[0], Number(item[1])]);
return pair.map((item, idx) => (
<FormControl key={idx} sx={{ m: 2 }} variant="standard">
<InputLabel htmlFor="component-error">{item[0]}</InputLabel>
<Input
value={item[1]}
onChange={(e) => changeNumberOptions(item[0], e.target.value)}
/>
</FormControl>
));
};
const list = () => (
<Box sx={{ width: 600 }}>
<Box sx={{ m: 2, flexDirection: "row" }}>{makeTrueFalseCheckBox()}</Box>
<Divider />
<Box sx={{ m: 2, flexDirection: "row" }}>
<FormHelperText sx={{ color: "blue" }}>
0 이상 숫자를 입력하세요.
</FormHelperText>
{makeNumberInput()}
</Box>
</Box>
);
const initLocalStorage = () => {
let localOptions = localStorage.getItem(MY_OPTIONS);
if (localOptions === null) return;
setMyOptions(JSON.parse(localOptions));
};
useEffect(() => {
initLocalStorage();
}, []);
return (
<div>
<div>
{["right"].map((anchor) => (
<React.Fragment key={anchor}>
<Button
sx={{ m: 2 }}
variant="contained"
color="secondary"
onClick={toggleDrawer(anchor, true)}
>
Options Setting
</Button>
<Drawer
anchor={anchor}
open={state[anchor]}
onClose={toggleDrawer(anchor, false)}
>
{list()}
</Drawer>
</React.Fragment>
))}
<CustomHansOnTable myOptions={myOptions} />
</div>
</div>
);
};
export default MyHandsonTable;
'개발 > React' 카테고리의 다른 글
리액트 - Handsontable 깃허브 연동하기 (data, style, comment, merge 저장하기) (1) | 2023.09.30 |
---|---|
리액트 - Handsontable 셀 스타일 로컬 스토리지에 저장하기 (전체 코드) (0) | 2023.09.30 |
리액트 - Handsontable Column Width, Row Height 로컬 스토리지에 저장하기 (0) | 2023.09.30 |
리액트 - Mui Drawer로 Handsontable Option 관리하기 (0) | 2023.09.30 |
리액트 - Handsontable Download CSV 구현 (콤마, 줄바꿈, 따옴표 처리) (0) | 2023.09.30 |
댓글