본문 바로가기
개발/React

리액트 - Mui 토글 버튼으로 Handsontable 셀 스타일 편집 기능 만들기

by 피로물든딸기 2023. 9. 30.
반응형

리액트 전체 링크

 

참고

- Mui Toggle Button으로 편집기 버튼 만들기 with React Color Picker

 

- Project Settings (전체 코드)

- True / False Options

- Selected Options

- Number Options

- width, height, placeholder, sort

- 주석, comment, memo, tooltip

- Merge Cells, 셀 합치기

- Search 구현

- Columns Data Type

- Cell 커스터마이징

- afterSelection으로 수식 입력줄 구현하기

- Download CSV 구현 (콤마, 줄바꿈, 따옴표 처리)

- Mui Drawer로 Handsontable Option 관리하기

- Column Width, Row Height 로컬 스토리지에 저장하기

- Mui 토글 버튼으로 셀 스타일 편집 기능 만들기 

- 셀 스타일 로컬 스토리지에 저장하기 (전체 코드)

- Handsontable 깃허브 연동하기 (data, style, comment, merge 저장하기)


아래와 같이 셀 스타일을 편집해 보자.

 

위 기능을 사용하기 위해서, outsideClickDeselectsfalse여야 한다.

편집 버튼을 누를 때, 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]]);
  };

 

HandontableToggleButtonprops로 현재 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);
  };

 

classNamehtCenterhtMiddle이 포함되었고, fontWeightfontStyle에서 굵게기울임을 확인할 수 있다.

그리고 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;
반응형

댓글