WEB/REACT

[REACT] ag grid를 사용하여 테이블 만들기/ editable, row selection, cell renderer

자바칩 프라푸치노 2021. 12. 28. 18:51

회사에서 어드민 쪽 페이지를 만들게 되어서 여러가지 그리드 라이브러리들을 살펴봤다.

집중적으로 본 것은 mui data grid, react-table, ag grid가 있다.

난이도는 mui data grid <= ag grid <<<<<< react-table

mui data grid와 ag grid는 개발하기가 매우 쉽고 코드가 깔끔하다.

그 중에서 ag grid는 pivot 모드와 context모드 등등 advanced한 기능이 잘 되어있고

대용량 데이터를 처리하기에 적합하다. 

그래서 ag grid가 채택되었고 스터디를 진행했다.

 

<포함된 내용>

1. 설치

2. 기본구조

3. editable cell/ row selection

4. 중첩 컬럼

5. 선택 한 값 가져오기

6. cell renderer

 

[설치]

npm install --save ag-grid-community ag-grid-react

처음에는 yarn add ag-grid-react 로 설치했었는데

이것만 설치하면 안되고 ag-grid-community도 같이 설치해야한다.

 

 

 

[기본구조]

import { AgGridReact, AgGridColumn } from "ag-grid-react";

AgGridReact 컴포넌트에 표에 들어갈 기본적인 옵션들과 데이터를 넣고

AgGridColumn 컴포넌트에 컬럼에 해당하는 옵션들과 값들을 넣어서 정의하는 것이다.

코드를 보면서 이해해보자.

 

 

[row selection/ editable cell]

//AgGrid.jsx

import React, { useState } from "react";
import { AgGridReact, AgGridColumn } from "ag-grid-react";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-alpine.css";
import "./styles.css";
import Button from "@mui/material/Button";

const AgGrid = () => {
  const [gridApi, setGridApi] = useState(null);
  const [gridColumnApi, setGridColumnApi] = useState(null);
  const [rowData, setRowData] = useState(null);
  const [selectedRows, setSelectedRows] = useState([]);
  const [btndisabled, setBtnDisabled] = useState(true);
  const onGridReady = (params) => {
    setGridApi(params.api);
    setGridColumnApi(params.columnApi);

    const updateData = (data) => params.api.setRowData(data);

    fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
      .then((resp) => resp.json())
      .then((data) => updateData(data));
  };

  const onSelectionChanged = () => {
    const data = gridApi.getSelectedRows();

    if (data.length > 0) {
      setBtnDisabled(false);
    } else {
      setBtnDisabled(true);
    }
    setSelectedRows(gridApi.getSelectedRows());
  };

  const onCellValueChanged = (e) => {
    console.log("changed", e.data);
  };
  return (
    <>
      <div style={{ width: "100%", height: "100%" }}>
        <div
          id="myGrid"
          style={{
            height: "600px",
            width: "100%",
          }}
          className="ag-theme-alpine"
        >
          <h1>editable table</h1>
          <div>
            <Button variant="contained" disabled={btndisabled}>
              action1
            </Button>
            <Button variant="contained" disabled={btndisabled}>
              action1
            </Button>
            <Button variant="contained" disabled={btndisabled}>
              action1
            </Button>
          </div>
          <AgGridReact
            rowData={rowData}
            rowSelection={"multiple"}
            suppressRowClickSelection={false}
            defaultColDef={{
              editable: true,
              sortable: true,
              minWidth: 100,
              filter: true,
              resizable: true,
              floatingFilter: true,
              flex: 1,
            }}
            sideBar={{
              toolPanels: ["columns", "filters"],
              defaultToolPanel: "",
            }}
            onGridReady={onGridReady}
            onSelectionChanged={onSelectionChanged}
            onCellEditingStopped={(e) => {
              onCellValueChanged(e);
            }}
          >
            <AgGridColumn
              headerName="..HELLO."
              headerCheckboxSelection={true}
              checkboxSelection={true}
              floatingFilter={false}
              suppressMenu={true}
              minWidth={50}
              maxWidth={50}
              width={50}
              flex={0}
              resizable={false}
              sortable={false}
              editable={false}
              filter={false}
              suppressColumnsToolPanel={true}
            />
            <AgGridColumn headerName="Participant">
              <AgGridColumn field="athlete" minWidth={170} />
              <AgGridColumn field="country" minWidth={150} />
            </AgGridColumn>
            <AgGridColumn field="sport" />
            <AgGridColumn headerName="Medals">
              <AgGridColumn
                field="total"
                columnGroupShow="closed"
                filter="agNumberColumnFilter"
                width={120}
                flex={0}
              />
              <AgGridColumn
                field="gold"
                columnGroupShow="open"
                filter="agNumberColumnFilter"
                width={100}
                flex={0}
              />
              <AgGridColumn
                field="silver"
                columnGroupShow="open"
                filter="agNumberColumnFilter"
                width={100}
                flex={0}
              />
              <AgGridColumn
                field="bronze"
                columnGroupShow="open"
                filter="agNumberColumnFilter"
                width={100}
                flex={0}
              />
            </AgGridColumn>
            <AgGridColumn field="year" filter="agNumberColumnFilter" />
          </AgGridReact>
        </div>
      </div>
    </>
  );
};

export default AgGrid;

 

코드를 살펴보자

<AgGridReact
      rowData={rowData}
      rowSelection={"multiple"}
      suppressRowClickSelection={false}
      defaultColDef={{
      editable: true,
      sortable: true,
      minWidth: 100,
      filter: true,
      resizable: true,
      floatingFilter: true,
      flex: 1,
      }}
      sideBar={{
      toolPanels: ["columns", "filters"],
      defaultToolPanel: "",
      }}
      onGridReady={onGridReady}
      onSelectionChanged={onSelectionChanged}
      onCellEditingStopped={(e) => {
      onCellValueChanged(e);
      }}
      >

먼저 AgGridReact 컴포넌트로 컬럼 컴포넌트들을 감싸주면 된다.

rowData => data로 뿌려주고 싶은 값들이다.

key값과 AgGridColumn의 field에 있는 값이 같아야 해당 값이 해당 column에 나타난다.

 

 

여기서는 rowData를 useState로 선언하고 

onGridReady 함수로 ag grid오픈 api에 fetch해서 가져오고 있다. 

  const onGridReady = (params) => {
    setGridApi(params.api);
    setGridColumnApi(params.columnApi);

    const updateData = (data) => params.api.setRowData(data);

    fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
      .then((resp) => resp.json())
      .then((data) => updateData(data));
  };

이 그리드를 생성할 때 onGridReady함수가 실행된다. 

  const onGridReady = (params) => {
    console.log("params", params);
    setGridApi(params.api);
    setGridColumnApi(params.columnApi);

    const updateData = (data) => params.api.setRowData(data);

    axios
      .get("http://localhost:3001/nosnos")
      .then((res) => updateData(res.data));
  };

axios를 사용하고 싶으면 fetch부분만 바꾸면 된다.

 

 


defaultColDef에 여러가지 옵션들을 지정해서 이 그리드가 어떤 기능을 가질 지 정의할 수 있다. 

여기서는 editable, sortable, filter, resizable 등을 true로 해놓아서 

cell 을 편집할 수 있고, column을 클릭하면 자동으로 sort가 되고 한다.

 

여러가지 props 들은 공식 페이지에서 확인 할 수 있다. 

 

     <AgGridColumn
              headerName=""
              headerCheckboxSelection={true}
              checkboxSelection={true}
              floatingFilter={false}
              suppressMenu={true}
              minWidth={50}
              maxWidth={50}
              width={50}
              flex={0}
              resizable={false}
              sortable={false}
              editable={false}
              filter={false}
              suppressColumnsToolPanel={true}
            />

row selection을 구현하고 싶다면 맨 첫번째  column을 checkbox 컬럼으로 만들면 된다.

이 컬럼은 다른 컬럼과 달리 field값이 없고 checkbox에 관련된 props들이 있다. 

컬럼 자체가 checkbox여서 전체 선택이 가능하다. 

 

 

 

 

 

 

[중첩 컬럼]

메달 컬럼은 펼칠 수 있을 것만 같이 생겼다. 

펼치면 이렇게 아까는 없었던 Gold, Silver, Bronze 컬럼이 나온다.

         <AgGridColumn headerName="Medals">
              <AgGridColumn
                field="total"
                columnGroupShow="closed"
                filter="agNumberColumnFilter"
                width={120}
                flex={0}
              />
              <AgGridColumn
                field="gold"
                columnGroupShow="open"
                filter="agNumberColumnFilter"
                width={100}
                flex={0}
              />
              <AgGridColumn
                field="silver"
                columnGroupShow="open"
                filter="agNumberColumnFilter"
                width={100}
                flex={0}
              />
              <AgGridColumn
                field="bronze"
                columnGroupShow="open"
                filter="agNumberColumnFilter"
                width={100}
                flex={0}
              />
            </AgGridColumn>

이렇게 컬럼 안에 컬럼을 쓰면 된다. 

columnGroupShow 라는 속성이 있는데 total은 이 그룹이 닫혀있을때 보여주겠다는 것이고

그 외는 open 되었을 때 보여주겠다는 속성이 들어있다.

 

 

 

 

[선택 한 값 가져오기]

추가로 나는 선택 한 row가 있을 때만 위의 button을 disabled = false로 바꾸고 싶었다.

그럴려면 row를 선택했는지 안했는지 여부와, 선택된 값을 가져와야 한다.

 

  <AgGridReact
            ...
            onSelectionChanged={onSelectionChanged}
           ...
          >

그럴려면 onSelectionChanged 함수를 사용하면 된다. 

selection이 바뀌었을 때 실행된다. 

선택 된 data 는 gridApi.getSelectedRows() 로 접근할 수 있다. 

 

 

 

[cell renderer]

동적으로 cell을 렌더링해서 가져오는 방법이다.

import React, { forwardRef, useImperativeHandle, useState } from "react";
import { render } from "react-dom";
import { AgGridReact, AgGridColumn } from "ag-grid-react";
// import "ag-grid-community/dist/styles/ag-grid.css";
// import "ag-grid-community/dist/styles/ag-theme-alpine.css";

const MoodRenderer = forwardRef((props, ref) => {
  const imageForMood = (mood) =>
    "https://www.ag-grid.com/example-assets/smileys/" +
    (mood === "Happy" ? "happy.png" : "sad.png");

  const [mood, setMood] = useState(imageForMood(props.value));

  useImperativeHandle(ref, () => {
    return {
      refresh(params) {
        setMood(imageForMood(params.value));
      },
    };
  });

  return <img width="20px" src={mood} alt="" />;
});

const GenderRenderer = (props) => {
  const image = props.value === "Male" ? "male.png" : "female.png";
  const imageSource = `https://www.ag-grid.com/example-assets/genders/${image}`;
  return (
    <span>
      <img src={imageSource} alt="" />
      {props.value}
    </span>
  );
};
const AgeRenderer = (props) => {
  const age = props.value > 19 ? "adult🍕" : "child🍗";
  return <span>{age}</span>;
};

const MyRenderer = (props) => {
  return <span>{props.value} 입니다.</span>;
};

const CellRendering = () => {
  const [gridApi, setGridApi] = useState(null);
  const [gridColumnApi, setGridColumnApi] = useState(null);

  const onGridReady = (params) => {
    setGridApi(params.api);
    setGridColumnApi(params.columnApi);
  };

  return (
    <div style={{ width: "100%", height: "500px" }}>
      <h2>cell render</h2>
      <div
        id="myGrid"
        style={{
          height: "100%",
          width: "100%",
        }}
        className="ag-theme-alpine"
      >
        <AgGridReact
          rowData={[
            {
              value: 14,
              type: "age",
            },
            {
              value: "female",
              type: "gender",
            },
            {
              value: "Happy",
              type: "mood",
            },
            {
              value: 21,
              type: "age",
            },
            {
              value: "male",
              type: "gender",
            },
            {
              value: "Sad",
              type: "mood",
            },
          ]}
          defaultColDef={{ flex: 1 }}
          frameworkComponents={{
            genderCellRenderer: GenderRenderer,
            moodCellRenderer: MoodRenderer,
            myCellRenderer: MyRenderer,
            ageCellRenderer: AgeRenderer,
          }}
          // onGridReady={onGridReady}
        >
          <AgGridColumn field="value" />
          <AgGridColumn
            headerName="Rendered Value"
            field="value"
            cellRendererSelector={(params) => {
              const moodDetails = { component: "moodCellRenderer" };
              const genderDetails = {
                component: "genderCellRenderer",
                params: {
                  values: ["Male", "Female"],
                },
              };
              if (params.data.type === "gender") return genderDetails;
              else if (params.data.type === "mood") return moodDetails;
              else return undefined;
            }}
          />
          <AgGridColumn field="type" />
          <AgGridColumn
            headerName="my render"
            field="value"
            cellRendererSelector={(params) => {
              const moodDetails = { component: "myCellRenderer" };
              const genderDetails = {
                component: "myCellRenderer",
                params: {
                  values: ["Male", "Femail"],
                },
              };
              const ageDetails = {
                component: "ageCellRenderer",
              };
              if (params.data.type === "gender") return genderDetails;
              else if (params.data.type === "mood") return moodDetails;
              else if (params.data.type === "age") return ageDetails;
              else return undefined;
            }}
          />
        </AgGridReact>
      </div>
    </div>
  );
};

export default CellRendering;

 

 

cell에 다른 컴포넌트를 넣겠다는 의미이니까 다른 컴포넌트를 정의해준다. 

const MoodRenderer = forwardRef((props, ref) => {
  const imageForMood = (mood) =>
    "https://www.ag-grid.com/example-assets/smileys/" +
    (mood === "Happy" ? "happy.png" : "sad.png");

  const [mood, setMood] = useState(imageForMood(props.value));

  useImperativeHandle(ref, () => {
    return {
      refresh(params) {
        setMood(imageForMood(params.value));
      },
    };
  });

  return <img width="20px" src={mood} alt="" />;
});
<AgGridReact
...
          frameworkComponents={{
            genderCellRenderer: GenderRenderer,
            moodCellRenderer: MoodRenderer,
            myCellRenderer: MyRenderer,
            ageCellRenderer: AgeRenderer,
          }}
         
        >

AgGridReact 에 frameworkComponents 에 컴포넌트를 지정한다.

  <AgGridColumn
            headerName="Rendered Value"
            field="value"
            cellRendererSelector={(params) => {
              const moodDetails = { component: "moodCellRenderer" };
              const genderDetails = {
                component: "genderCellRenderer",
                params: {
                  values: ["Male", "Female"],
                },
              };
              if (params.data.type === "gender") return genderDetails;
              else if (params.data.type === "mood") return moodDetails;
              else return undefined;
            }}
          />

그리고 이렇게 지정해주면 된다. 

이 cell이 gender 타입이면 genderCellRenderer로 지정했던 GenderRenderer 컴포넌트를 리턴하겠다는 뜻이고

mood이면 moodCellRenderer로 지정했던 MoodRenderer을 리턴하겠다는 뜻이다.

MoodRenderer컴포넌트는 위에서 봤듯이 mood 값이 happy냐 아니냐에 따라 다른 이모지가 나타난다.

셀마다 다른 컴포넌트를 넣을 때 사용할 수 있다. 

 

 

728x90