티스토리 뷰
AG-Grid 란?
한마디로 데이터 그리드를 제공해 주는 친구입니다.
서버사이드 랜더링, 엑셀 추출, Master-Detail 구조, Tree, Pivot 등을 지원하며,
무료 버전과 유료 버전으로 나뉘어있지만 무료 버전만으로도 많은 기능을 제공하고 있습니다.
AG-Grid를 사용한다면
JS 스크립트 기반으로 어떤 플랫폼에서도 사용이 가능합니다.
복잡한 그리드를 Ag-grid를 통해 손쉽게 그릴 수 있으며 수많은 데이터를 한눈에 정리가 되는 장점이 있습니다.
개발환경 : React 18, ag-grid-community 26, ag-grid-react 26
AG-Grid 세팅하기
npm i ag-grid-community ag-grid-react --save
npm으로 설치합니다.
i 는 install의 줄임을 뜻합니다.
--save 옵션은 package.json 파일의 dependencies 항목에 플러그인 정보를 저장하겠다는 의미입니다.
개발환경이 다르면 버전 차이로 안될 가능성이 있습니다.
Column 생성
// Aggrid.js
import React, { Component } from "react";
import { AgGridReact } from "ag-grid-react";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-alpine.css";
export default class Aggrid extends Component {
constructor(props) {
super(props);
this.state = {
// 컬럼 생성
columnDefs: [
{ headerName: "제목", field: "title" },
{ headerName: "저자", field: "author" },
{ headerName: "출판 연도", field: "year" },
{ headerName: "장르", field: "genre" },
{ headerName: "상태", field: "status" },
],
};
}
render() {
const { columnDefs } = this.state;
return (
<div className="ag-theme-alpine" style={{ width: "97vw", height: "48vh" }}>
<AgGridReact headerHeight="30" columnDefs={columnDefs} />
</div>
);
}
}
Aggrid.js를 파일을 만들고 그리드를 생성하기 위해 라이브러리 AgGridReact와 필요한 CSS를 import를 합니다. 그리드의 두 가지 필수 구성 속성인 Column과 Data는 필수로 들어가야 합니다. 일단 Column 생성해 보겠습니다. Column 생성하기 위해 columnDefs 변수에 headerName과 field를 만들고 <AgGridReact> 컴포넌트에 연결하면 Column 생성됩니다.
Data 생성
// import 생략
export default class Aggrid extends Component {
constructor(props) {
super(props);
this.state = {
columnDefs: [
{ headerName: "제목", field: "title" },
{ headerName: "저자", field: "author" },
{ headerName: "출판 연도", field: "year" },
{ headerName: "장르", field: "genre" },
{ headerName: "상태", field: "status" },
],
// 데이터 추가
rowData: [
{ title: "지금 이대로 좋다", author: "법륜 스님", year: 2012, genre: "에세이", status: "대출 가능" },
{ title: "미움받을 용기", author: "기시미 이치로, 고가 후미타케", year: 2014, genre: "자기계발", status: "대출 중" },
{ title: "언어의 온도", author: "이기주", year: 2016, genre: "에세이", status: "대출 가능" },
{ title: "해리 포터와 마법사의 돌", author: "J.K. 롤링", year: 1997, genre: "판타지", status: "대출 중" },
{ title: "나미야 잡화점의 기적", author: "히가시노 게이고", year: 2012, genre: "소설", status: "대출 가능" },
],
};
}
render() {
const { columnDefs, rowData } = this.state;
return (
<div className="ag-theme-alpine" style={{ width: "97vw", height: "48vh" }}>
<AgGridReact headerHeight="30" columnDefs={columnDefs} rowData={rowData} />
</div>
);
}
}
행(rowData) 안에 데이터를 넣기 위해 state에 rowData를 생성합니다. 열(columnDefs) 개수만큼 컬럼들을 생성하고 필요한 value 값을 넣습니다. <AgGridReact> 컴포넌트에 데이터를 생성한 rowData 변수를 연결시키면 그리드는 완성됩니다...!
정리
지금까지 기본적인 AG-Grid를 생성해보았습니다!
이제부터 좀 더 본격적인 기능과 실제 응용 프로그램에서 사용하는 기능들을 추가할 생각입니다.
간단하게 세 가지로 추려본다면...
1. 검색 및 정렬
2. Axios를 통한 CRUD
3. TreeData 사용한 계층 구조 표현
1. 검색 및 정렬
AG-Grid에서 검색 및 정렬 기능을 사용하기 위해 floatingFiltersHeight와 defaultColDef 추가 설정이 필요합니다. 자세한 내용은 공식 문서에 잘 나와있어서 참고하시길 바랍니다.
- floatingFiltersHeight : 각 열 헤더 아래에 있는 검색 필터의 높이를 설정합니다.
- defaultColDef : 그리드의 모든 열에 대해 공통으로 적용할 기능들을 정의합니다.
추가 설정 코드
Aggrid.js
this.state = {
defaultColDef: {
sortable: true,
filter: 'agTextColumnFilter',
floatingFilter: true,
resizable: true,
}
}
<AgGridReact
floatingFiltersHeight="30"
defaultColDef={this.state.defaultColDef}
/>
이처럼 floatingFiltersHeight을 30으로 설정하여 높이를 나타내고 defaultColDef는 사용하고 싶은 기능들을 정의하여 적용시킬 수 있습니다. defaultColDef에 정의된 속성들을 하나하나 알아보겠습니다.
- sortable : 열 머리글을 클릭하여 그리드를 정렬할 수 있는 기능으로, 머리글을 클릭하면 오름차순, 내림차순 및 정렬 기본으로 전환됩니다. 실제 응용 프로그램에서 많은 열과 수많은 행이 있을 경우 정렬해 주는 기능은 필수입니다.
- filter : 한마디로 검색 기능입니다. 머리글을 가리키면 그리드에 작은 열 메뉴 아이콘이 표시됩니다. 그것을 누르면 필터의 종류와 필터링할 텍스트를 선택할 수 있는 필터링 UI가 있는 팝업이 표시됩니다.
- floatingFilter : 검색을 하기 위해 머리글을 한 번 클릭하고 검색하는 것은 불편합니다. header 밑행에 바로 검색할 수 있게 설정합니다.
- resizeable : header의 사이즈 간격 조절이 가능하게 해 줍니다.
2. Axios를 통한 CRUD
[ Read ]
Axios.get("http://localhost:8080/api/data" )
.then((res) => {
this.setState({ rowData: res.data })
})
.errer((err) => { console.log("읽기 실패")})
읽기는 간단합니다. Axios를 통해 API 요청하여 데이터를 가져와서 rowData 값을 집어넣으면 끝입니다.
[ Create ]
Create는 아래 코드와 같이 Modals.js 만들어서 도서 정보를 입력받고, Axios를 통해 서버에 POST 요청을 보내는 기능으로 구현하였습니다. 그러면 서버에서 data 객체를 받아 insert 문을 실행하여 DB에 저장하면 됩니다.
Modals.js
export default class Modals extends Component {
constructor(props) {
super(props);
this.state = {
title: "",
author: "",
year: "",
genre: "",
status: "",
};
}
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
handleSubmit = () => {
const data = {
title: this.state.title,
author: this.state.author,
year: this.state.year,
genre: this.state.genre,
status: this.state.status,
};
Axios.post("http://localhost:8080/api/create", data)
.then((res) => {
alert("저장되었습니다.");
this.props.onHide(); // 모달 창 닫기
})
.catch((err) => {
if (err.response && err.response.data && err.response.data.message) {
this.setState({ showAlert: true, alertMessage: err.response.data.message });
} else {
alert("생성 실패되었습니다.");
}
});
};
render() {
return (
<Modal show={this.props.show} onHide={this.props.onHide}>
<Modal.Header closeButton>
<Modal.Title>도서 등록</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form>
<Form.Group>
<Form.Label>제목</Form.Label>
<Form.Control type="text" name="title" value={this.state.title} onChange={this.handleChange} />
</Form.Group>
<Form.Group>
<Form.Label>저자</Form.Label>
<Form.Control type="text" name="author" value={this.state.author} onChange={this.handleChange} />
</Form.Group>
<Form.Group>
<Form.Label>출판 연도</Form.Label>
<Form.Control type="text" name="year" value={this.state.year} onChange={this.handleChange} />
</Form.Group>
<Form.Group>
<Form.Label>장르</Form.Label>
<Form.Control type="text" name="genre" value={this.state.genre} onChange={this.handleChange} />
</Form.Group>
<Form.Group>
<Form.Label>상태</Form.Label>
<Form.Control type="text" name="status" value={this.state.status} onChange={this.handleChange} />
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={this.props.onHide}>닫기</Button>
<Button variant="primary" onClick={this.handleSubmit}>저장</Button>
</Modal.Footer>
</Modal>
);
}
}
사실 이렇게 쉽게 하면 편하지만 유효성 검사를 해야 합니다.
입력 값 유효성 체크를 하기 위해 react-bootstrap의 form validation를 이용하였습니다.
Fom 태그 속성 등록
<Form noValidate validated={this.state.validated} onSubmit={this.handleSubmit}>
Requried 추가
<Form.Control
type="text"
placeholder="제목을 입력하세요"
name="title"
value={this.state.title}
onChange={this.handleChange}
required // 추가
/>
<Form.Control> 요소에 required 속성을 추가하여 필수 입력 필드로 설정합니다. 빈 값일 때 Form.Control.Feedback을 통해 오류 메시지를 표시합니다.
Feedback 추가
<Form.Control.Feedback type="invalid">제목을 입력하세요.</Form.Control.Feedback>
유효성 검사 오류 메시지를 사용자에게 표시합니다.
Form validation 검사 체크
handleSubmit = (e) => {
e.preventDefault();
const form = e.currentTarget;
// 유효성 검사
if (form.checkValidity() === false) {
e.stopPropagation(); // form 제출 이벤트 중지
this.setState({ validated: true }); // 유효성 검사 상태 업데이트
return;
}
}
handleSubmit 함수에서 form.checkValidity() === false를 사용하여 폼의 유효성을 체크합니다. 유효하지 않은 경우 폼 제출을 중지하고, 유효할 경우 데이터 전송을 시도합니다.
[ Update ]
Update는 따로 수정 Button 만들 필요가 없습니다. 왜냐하면 AG-Grid에서 지원하는 onCellClicked 이벤트가 있습니다. onCellClicked는 셀을 클릭했을 때 호출되는 함수로, 함수 안에는 cell의 데이터 정보들이 담겨있습니다. 실제로 로그를 찍어보면 아래와 같습니다.
이처럼 onCellClicked 이벤트를 사용하여 셀의 데이터를 가져오고, 해당 데이터를 Modify 모달 창에 전달하여 사용자가 내용을 수정할 수 있도록 합니다. 그런 다음 서버에 PUT 요청을 보내어 데이터를 수정합니다.
Aggrid.js
export default class Aggrid extends Component {
constructor(props) {
super(props);
this.state = {
defaultColDef: {
onCellClicked: this.onUpdateClick, // 셀 클릭 시 호출될 함수
},
formData: {},
};
}
// 셀 클릭 시 호출되는 함수
onUpdateClick = (params) => {
console.log(params);
this.setState({
formData: params.data, // 클릭한 셀의 데이터
modifyOpen: true, // 모달 열기
});
};
// 모달 닫기 함수
modifyOpenButton = () => {
this.setState({ modifyOpen: false });
};
render() {
const { columnDefs, rowData, defaultColDef, modifyOpen, formData } = this.state;
return (
<div className="ag-theme-alpine" style={{ width: "97vw", height: "28vh" }}>
<AgGridReact
headerHeight="30"
columnDefs={columnDefs}
rowData={rowData}
floatingFiltersHeight="30"
defaultColDef={defaultColDef} />
</div>
{
modifyOpen && <Modify show={modifyOpen} onHide={this.modifyOpenButton} data={formData} />
}
);
}
}
defaultColDef 변수에 onCellClicked 추가하여 셀을 클릭했을 때 호출되는 함수로, 클릭한 셀의 데이터를 formData에 저장하고 모달을 열도록 설정합니다. Modify 모달을 this.state.modifyOpen이 true일 때 렌더링하며, formData를 Modify 모달의 data 속성으로 전달합니다.
Modify.js
export default class Modify extends Component {
constructor(props) {
...생략
}
// Update 이벤트
handleSubmit = (e) => {
e.preventDefault();
const form = e.currentTarget;
// 유효성 검사
if (form.checkValidity() === false) {
e.stopPropagation();
this.setState({ validated: true });
return;
}
const data = {
id: this.props.data.id,
title: this.state.title,
author: this.state.author,
year: this.state.year,
genre: this.state.genre,
status: this.state.status,
};
Axios.put("http://localhost:8080/api/update", data)
.then((res) => {
alert("수정되었습니다.");
this.props.onHide(); // 모달 닫기
})
.catch((err) => {
if (err.response && err.response.data && err.response.data.message) {
this.setState({ showAlert: true, alertMessage: err.response.data.message });
} else {
alert("수정 실패되었습니다.");
}
});
};
render() {
...생략
}
}
Form 필드에 대한 유효성 검사를 수행하며, 입력 값이 유효한 경우 handleSubmit에서 서버에 PUT 요청을 보내어 데이터를 수정합니다.
[ Remove ]
Ag-Grid에서 체크박스를 사용하여 선택된 셀의 데이터를 삭제할 수 있습니다. 선택된 셀의 ID를 서버에 전달하여 실제 데이터를 삭제하고, 클라이언트 측에서도 그리드에서 항목을 제거하여 동기화합니다. 체크박스와 삭제 기능을 추가해 보겠습니다.
constructor(props) {
super(props);
this.state = {
columnDefs: [
{ headerName: "제목", field: "title", checkboxSelection: true }, // 체크박스 컬럼 추가
{ headerName: "저자", field: "author" },
{ headerName: "출판 연도", field: "year" },
{ headerName: "장르", field: "genre" },
{ headerName: "상태", field: "status" },
],
};
this.gridApi = null; // Grid API를 저장할 변수
}
<AgGridReact
rowSelection="multiple" // 여러 행 선택 허용
onGridReady={(params) => { this.gridApi = params.api }} // Grid API 저장
/>
체크박스 추가하기 위해서 columnDefs의 첫 번째 항목으로 체크박스 컬럼을 추가했습니다. 만약 체크가 한 개가 아닌 여러 개를 체크하려면 rowSelection='multiple' 설정해 주면 됩니다. 체크박스를 클릭한 셀의 ID 가져올 수 있도록 onGridReady를 이용하여 gridApi에 params를 담아줍니다.
// 삭제 버튼 클릭 시 호출되는 함수
onRemoveClick = () => {
if (!this.gridApi) return; // gridApi 없으면 반환
const selectedNodes = this.gridApi.getSelectedNodes(); // 선택된 노드 가져오기
const selectedIds = selectedNodes.map((node) => node.data.id); // 선택된 노드의 ID 배열
if (selectedIds.length > 0) {
Axios.delete("http://localhost:8080/api/remove", { ids: selectedIds }) // 삭제 요청
.then((response) => {
alert("삭제되었습니다.");
this.gridApi.applyTransaction({ remove: selectedNodes.map((node) => node.data) }); // 그리드에서 항목 제거
})
.catch((error) => {
alert("삭제 실패: " + error.message);
});
} else {
alert("삭제할 데이터가 없습니다.");
}
};
<button className="SaveBtnRemove" onClick={this.onRemoveClick}>삭제</button>
체크박스를 설정했다면
1. gridApi를 통해 node의 id 값을 가져옵니다. 만약 체크박스를 안 했다면 "삭제할 데이터가 없습니다." 경고창이 나온다.
2. 여러 개의 id 값을 담기 위해 map 함수를 써서 배열 안에 담아줍니다.
3. 서버에 배열 타입으로 넘겨서 axios delete로 id 값들을 넘겨줍니다.
3. TreeData 사용한 계층 구조 표현
TreeData는 부모와 자식 관계로 이루어진 상위/하위 데이터로 상위 폴더 한 개에 여러 개의 자식을 넣을 수 있어서 데이터의 계층을 직관적으로 표현할 수 있습니다. 주로 폴더와 파일 구조, 조직의 직원 계층 등 사용됩니다. 비슷한 성격을 가진 RowGroup은 행을 그룹화를 하여 여러 개의 열을 표현할 수 있습니다. 데이터의 집합이나 요약 정보를 그룹화하는 데 사용됩니다. 둘 다 트리 데이터 구조를 나타낼 수 있으므로 프로젝트에 적합한 걸 사용하시면 됩니다.
[ 사용 방법 ]
<AgGridReact
headerHeight='30'
columnDefs={columnDefs}
rowData={this.recursion(data)}
floatingFiltersHeight='30'
defaultColDef={defaultColDef}
rowSelection='multiple'
onGridReady={params => {this.gridApi = params.api}}
rowData={this.recursion(data)} // 데이터의 계층 구조를 재구성
treeData={true} // 트리 데이터 모드 활성화
getDataPath= {data => { return data.hierarchy; }} // 데이터의 계층 구조 제공
/>
TreeData 활성화하기 위해 treeData 속성을 설정하여 트리 데이터 모드를 true 설정합니다. 이를 통해 데이터의 계층 구조가 트리 형태로 표현됩니다. getDataPath 속성은 콜백 함수를 통해 각 행의 계층 구조를 data.hierarchy 배열을 사용하여 데이터의 상위/하위 관계를 나타냅니다. rowData 속성에 data를 부모와 자식을 구성하기 위해 this.recursion(data) 설정합니다.
// 재귀 함수를 사용하여 트리 구조를 생성
recursion = (data, parent, childHierachy) => {
var newData =[];
if(data) {
data.forEach((d,i) => {
var parentHierachy = [];
d.hierarchy =parentHierachy;
if(parent) {
d.parent= parent;
parentHierachy = [...childHierachy];
d.hierarchy = parentHierachy;
}
parentHierachy.push(i);
newData.push(d);
// 재귀함수
if(d.children) {
newData = [
...newData,
...this.recursion(
d.children,
d,
parentHierachy
)
]
}
})
}
return newData;
}
recursion(재귀) 함수는 주어진 트리 구조 데이터를 순회하면서 각 데이터 항목에 계층 경로를 추가하고, 모든 항목을 평탄화된 배열로 반환합니다. 이를 통해 Ag-Grid에서 트리 데이터를 적절히 표시할 수 있게 됩니다.
이와 같이 TreeData를 사용하면 계층 구조를 가진 데이터를 효과적으로 표현할 수 있으며, recursion 함수로 데이터 구조를 변형하여 트리 구조를 생성할 수 있습니다.
'∙React' 카테고리의 다른 글
하이차트 (Highcharts) 사용하기 (0) | 2024.08.05 |
---|---|
React-Tooltip 사용하기 (0) | 2024.08.05 |
Rc-Tree 사용하기 (0) | 2024.08.05 |
Excel 변환하기 - React (0) | 2022.02.15 |