해당 글에서는 Spring Boot의 타임리프(Thymeleaf)를 기반으로 CDN을 통하여 오픈소스 ‘Toast UI Grid’를 이용한 설정 및 활용방안에 대해서 공유합니다.
![](https://t1.daumcdn.net/keditor/emoticon/face/large/073.png)
1) 개발 환경
💡 해당 환경에서는 ‘Package Manager’를 이용한 방식이 아닌 ‘CDN’을 통한 방식을 사용하여 구성하였습니다.
개발환경 | 버전 |
java | 1.8 |
Spring Boot | 2.7.4 |
빌드관리도구 | Gradle 7.5 |
개발 툴 | IntelliJ IDEA 2022.3 |
spring-boot-starter-thymeleaf | 2.7.4 |
thymeleaf-layout-dialect | 3.1.0 |
Lombok | |
jquery | 3.6.1 |
[더 알아보기]
💡 CDN(Content Delivery Network / Content Distribution Network)이란?
- 프로젝트 내에 라이브러리를 설치하는 방식이 아닌 공식 홈페이지에서 제공하는 URL을 import 하여서 JS / CSS를 다운로드하여서 즉시 자바스크립트 내에서 사용할 수 있는 방식을 의미합니다.
💡 CDN 파일 다운로드
- CDN 링크에 접근하여서 해당 파일들을 복사하여서 프로젝트 내에 파일로 구성하여 Import 해오는 방식을 의미합니다. 이 방식은 매번 프로젝트가 CDN으로 import 해오는 시간을 줄일 수 있지만 새로운 새로운 버전이 릴리즈가 되었을 때 버전이 변경되지 않는 단점이 있습니다.
2) Toast UI Grid 이해하기
1. Toast UI Grid
💡 Toast UI Grid란?
- 데이터 편집, 필터링, 정렬 등과 같은 기능을 갖춘 강력한 라이브러리이며 편집기 또는 렌더러를 원하는 형식으로 사용자 지정하여 사용 할 수 있는 그리드를 의미합니다.
[출처] 공식사이트
TOAST UI :: Make Your Web Delicious!
TOAST UI is an open-source JavaScript UI library maintained by NHN Cloud.
ui.toast.com
2. 주요 기능
💡 해당 글은 해당 페이지를 구성하기 위해 주요 사용한 기능들입니다.
💡 추가로 다른 기능을 확인하기 위해서는 공식 홈페이지를 참고하시면 될것 같습니다.
https://nhn.github.io/tui.grid/latest/Grid/
Occurs when the grid data is updated and the grid is rendered onto the DOM The event occurs only in the following API as below. 'resetData', 'restore', 'reloadData', 'readData', 'setPerPage' with 'dataSource', using 'dataSource' PROPERTIES
nhn.github.io
2.1. '컬럼 별' 기능
기능 | 속성 명 | 설명 |
순번(rownum) 기능 | rowHeaders | 그리드의 순차적인 채번을 제공합니다. |
정렬(Sorting) 기능 | sortable | 그리드 내의 컬럼의 정렬을 제공합니다. |
필터(Filter) 기능 | filter | 그리드 내의 필터로 검색을 제공합니다. |
유효성 검증(Validation) 기능 | validation | 그리드 내의 컬럼에 대해서 유효성 검증을 제공합니다. |
리사이즈(Resize) 기능 | resizable | 그리드 내의 간격을 리사이즈 하는 기능을 제공합니다. |
숨김 기능 | hidden | 그리드 내의 컬럼을 숨길 수 있는 기능을 제공합니다. |
텍스트 정렬 기능 | align | 그리드 내의 텍스트의 정렬을 하는 기능을 제공합니다. |
수정 기능 | editor | 그리드 내의 수정 할 수 있는 기능을 제공합니다. |
2.2. 그리드 공통 기능
기능 | 속성 명 | 설명 |
테마 지정 기능 | Grid.applyTheme | 그리드의 테마를 지정하는 기능을 제공합니다. (clean, stripe, default) |
언어 선택 기능 | Grid.setLanguage | 그리드의 언어를 선택 할 수 있는 기능을 제공합니다. (en, ko) |
페이징(Pagination) 기능 | pageOptions | 그리드의 클라이언트 내에서 페이징 처리를 할 수 있는 기능을 제공합니다. |
합계 및 평균 Counting 기능 | summary | 그리드의 요약(합계/평균,...)의 기능을 제공합니다 |
컨텍스트 메뉴 (Context Menu) 기능 | contextMenu | 그리드의 컨텍스트 메뉴를 제공합니다 (Copy, Copy Column, Copy Row, Export - csv) |
3) 실제 구성하기
1. Toast UI Grid 전체 흐름
- HTML이 최초 화면상에 출력이 됩니다.
- HTML이 구성된 이후에 Javascript 내에 그리드를 구성한 함수를 호출합니다.
- Grid 생성
- Grid 공통 옵션 구성(디자인, 페이징)
- Grid의 데이터를 로드(API 통신)
- Grid 컬럼 별 구성
- Gird의 이벤트 핸들러 구성(그리드 라이프 사이클, 이벤트 핸들러 구성)
- 최종적으로 Javascript 내 함수에서 구성한 그리드를 HTML로 그려줍니다.
2. Spring Boot 의존성 주입
💡 Thymeleaf에 대한 설정을 수행합니다.
dependencies {
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect' // Thymeleaf Layout
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // Thymeleaf Template
compileOnly 'org.projectlombok:lombok' // Lombok
}
[참고] Thymeleaf 구성은 이전에 작성한 글을 참고하였습니다.
[Java/Library] Thymeleaf, Thymeleaf Layout 적용하기
해당 글에서는 Spring Boot에서 Thymeleaf에 대한 환경 구성을 하고 Thymeleaf Layout을 적용하기 위한 방법에 대해서 공유합니다. 1) 개발환경 💡 Thymeleaf Template / Thymeleaf Template Layout을 구성을 위한 개발환
adjh54.tistory.com
3. 스크립트 의존성 주입
💡 해당 방법은 ‘파일을 다운로드 받아서’ 프로젝트 내에서 ‘Import’를 해오는 방식으로 구성을 하였습니다.
💡 해당 방법을 위해 직접 CDN 페이지로 접속하여 해당 소스를 복사하여 파일로 구성하였습니다.
<!--
- import JQuery
-->
<script th:inline="javascript" type="text/javascript" th:src="@{/lib/jquery-3.6.1.min.js}"></script>
<!--
- import Toast UI Grid JS & Pagination
- 그리드 내의 페이징을 사기 위해는 먼저 페이징을 import 해야합니다.
- reference: https://github.com/nhn/tui.grid/issues/891
-->
<script th:inline="javascript" type="text/javascript" th:src="@{/lib/tui-pagination.js}"></script>
<script th:inline="javascript" type="text/javascript" th:src="@{/lib/tui-grid.js}"></script>
<!-- import Toast UI Grid CSS & Pagination -->
<link th:inline="css" th:href="@{/css/tui-pagination.css}" rel="stylesheet"/>
<link th:inline="css" th:href="@{/css/tui-grid.css}" rel="stylesheet"/>
4. 그리드 초기 구성 및 컬럼별 기능 구성
💡 그리드의 ‘컬럼 별’로 선택하여 사용하는 기본 기능들에 대해서 응용을 해봅니다.
1. 컬럼 별 기능
기능 | 속성 명 | 설명 |
순번(rownum) 기능 | rowHeaders | 그리드의 순차적인 채번을 제공합니다. |
정렬(Sorting) 기능 | sortable | 그리드 내의 컬럼의 정렬을 제공합니다. |
필터(Filter) 기능 | filter | 그리드 내의 필터로 검색을 제공합니다. |
유효성 검증(Validation) 기능 | validation | 그리드 내의 컬럼에 대해서 유효성 검증을 제공합니다. |
리사이즈(Resize) 기능 | resizable | 그리드 내의 간격을 리사이즈 하는 기능을 제공합니다. |
숨김 기능 | hidden | 그리드 내의 컬럼을 숨길 수 있는 기능을 제공합니다. |
텍스트 정렬 기능 | align | 그리드 내의 텍스트의 정렬을 하는 기능을 제공합니다. |
수정 기능 | editor | 그리드 내의 수정 할 수 있는 기능을 제공합니다. |
2. 실제 기능 구현
<!DOCTYPE html>
<!-- Default Layout Import-->
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{common/layouts/defaultLayout}"
layout:fragment="content"
>
<head>
<title></title>
<!--
- import JQuery
-->
<script th:inline="javascript" type="text/javascript" th:src="@{/lib/jquery-3.6.1.min.js}"></script>
<!--
- import Toast UI Grid JS & Pagination
- 그리드 내의 페이징을 사기 위해는 먼저 페이징을 import 해야합니다.
- reference: https://github.com/nhn/tui.grid/issues/891
-->
<script th:inline="javascript" type="text/javascript" th:src="@{/lib/tui-pagination.js}"></script>
<script th:inline="javascript" type="text/javascript" th:src="@{/lib/tui-grid.js}"></script>
<!-- import Toast UI Grid CSS & Pagination -->
<link th:inline="css" th:href="@{/css/tui-pagination.css}" rel="stylesheet"/>
<link th:inline="css" th:href="@{/css/tui-grid.css}" rel="stylesheet"/>
<!-- IN-line JS 영역-->
<script th:inline="javascript" type="text/javascript">
$(document).ready(async () => {
await gridLoad();
});
/**
*
* @return void
*/
const gridLoad = () => {
// 그리드 생성
const Grid = tui.Grid;
/**
* 그리드 테마 적용하기 (clean, stripe, default)
*
* @reference https://nhn.github.io/tui.grid/latest/tutorial-example07-themes
* @reference https://seokbong.tistory.com/14
*/
Grid.applyTheme('stripe');
// Toast UI Grid 구성하기
const grid = new Grid({
// ================================== 공통 옵션 적용 ==============================
el: document.getElementById('grid'), // [필수] Container element
data: [
{
"userId": "adjh54",
"userNm": "Lee",
"userMail": "adjh54@gmail.com",
"userGender": "man",
"userAge": 20,
"userHobby": "H1",
"userSq": 1,
},
{
"userId": "ckask123",
"userNm": "jong",
"userMail": "ckask@gmail.com",
"userGender": "woman",
"userAge": 22,
"userHobby": "H3",
"userSq": 2,
},
{
"userId": "hahahoho",
"userNm": "ha",
"userMail": "hahahoho@gmail.com",
"userGender": "man",
"userAge": 23,
"userHobby": "H4",
"userSq": 3,
},
], // [선택] 그리드 데이터 조회
/**
* [Option] 순번 기능
* @reference : http://nhn.github.io/tui.grid/latest/tutorial-example11-row-headers
*/
rowHeaders: [{
type: 'rowNum',
header: "순번",
width: 50,
}],
// ================================== 컬럼 옵션 적용 ==============================
columns: [
// [Column-1] 사용자 아이디
{
header: '사용자 아이디', // [필수] 컬럼 이름
name: 'userId', // [필수] 컬럼 매핑 이름 값
sortable: true, // [선택] 컬럼의 정렬 여부
resizable: true, // [선택] 컬럼의 리사이즈 여부 옵션
editor: 'text', // [선택] 수정 옵션
// [선택] 필터 옵션
filter: {
type: 'text',
showApplyBtn: true,
showClearBtn: true
},
////^[a-z|A-Z]*$/
// [선택] 유효성 검증 옵션
validation: {
required: true,
dataType: 'string',
regExp: /^[a-z|A-Z||0-9 ]*$/, // [정규식] 영문 숫자에 대한 조합만 가능
},
},
// [Column-2] 사용자 이름
{
header: '사용자 이름', // [필수] 컬럼 이름
name: 'userNm', // [필수] 컬럼 매핑 이름 값
sortable: true, // [선택] 컬럼의 정렬 여부
resizable: true, // [선택] 컬럼의 리사이즈 여부 옵션
editor: 'text', // [선택] 수정 옵션
// [선택] 필터 옵션
filter: {
type: 'text',
showApplyBtn: true,
showClearBtn: true
},
// [선택] 유효성 검증 옵션
validation: {
required: true,
dataType: 'string',
regExp: /^[a-z|A-Z|ㄱ-ㅎ|가-힣|0-9 ]*$/, // [정규식] 특수문자를 제외한 공백포함 사용 가능
},
},
// [Column-3] 사용자 이메일
{
header: '사용자 이메일', // [필수] 컬럼 이름
name: 'userMail', // [필수] 컬럼 매핑 이름 값
sortable: true, // [선택] 컬럼의 정렬 여부
resizable: true, // [선택] 컬럼의 리사이즈 여부 옵션
editor: 'text', // [선택] 수정 옵션
// [선택] 필터 옵션
filter: {
type: 'text',
showApplyBtn: true,
showClearBtn: true
},
// [선택] 유효성 검증 옵션
validation: {
unique: true, // [선택] 유일성 체크 확인 옵션
required: true,
dataType: 'string',
regExp: /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i
},
},
// [Column-4] 사용자 나이
{
header: '사용자 나이', // [필수] 컬럼 이름
name: 'userAge', // [필수] 컬럼 매핑 이름 값
sortable: true, // [선택] 컬럼의 정렬 여부
resizable: true, // [선택] 컬럼의 리사이즈 여부 옵션
editor: 'text', // [선택] 수정 옵션
align: 'center', // [선택] 텍스트 정렬 옵션
// [선택] 필터 옵션
filter: {
type: 'text',
showApplyBtn: true,
showClearBtn: true
},
// [선택] 유효성 검증 옵션
validation: {
unique: true, // [선택] 유일성 체크 확인 옵션
dataType: 'number', // [선택] 데이터 타입 체크 옵션
required: true, // [선택] 필수 여부 체크 옵션
min: 1, // [선택] 최소값
max: 100, // [선택] 최대값
}
},
// [Column-5] 사용 여부
{
header: '사용자 성별', // [필수] 컬럼 이름
name: 'userGender', // [필수] 컬럼 매핑 이름 값
sortable: true, // [선택] 컬럼의 정렬 여부
resizable: true, // [선택] 컬럼의 리사이즈 여부 옵션
align: 'center', // [선택] 텍스트 정렬 옵션
filter: 'select', // [선택] 필터 옵션
formatter: 'listItemText', // [선택] select box 옵션
editor: {
type: 'radio',
options: {
listItems:
[
{text: '남성', value: "man"},
{text: '여성', value: "woman"},
]
},
},
},
// [Column-5] 사용자 취미
{
header: '사용자 취미', // [필수] 컬럼 이름
name: 'userHobby', // [필수] 컬럼 매핑 이름 값
align: 'center', // [선택] 텍스트 정렬 옵션
sortable: true, // [선택] 컬럼의 정렬 여부
resizable: true, // [선택] 컬럼의 리사이즈 여부 옵션
formatter: 'listItemText', // [선택] 값을 기반으로 select box 옵션
// [Option] 필터 옵션
filter: {
type: 'select',
showApplyBtn: true,
showClearBtn: true
},
// [Option] select 옵션
editor: {
type: 'select',
options: {
listItems: [
{text: "독서", value: "H1"},
{text: "복싱", value: "H2"},
{text: "스킨스쿠버", value: "H3"},
{text: "탁구", value: "H4"},
]
}
},
},
// [Column-5] 사용자 시퀀스 - Hidden
{
header: '사용자 시퀀스', // [필수] 컬럼 이름
name: 'userSq', // [필수] 컬럼 매핑 이름 값
hidden: true, // [선택] 숨김 여부
},
]
});
};
</script>
</head>
<body style="background-color: gray">
<div>
<div>
<!--타이틀-->
<h1 style="margin-bottom: 50px; margin-top: 50px; text-align: center">사용자 리스트</h1>
<!-- 테이블 추가 이벤트 -->
<div class="btn-wrapper" style="margin-bottom: 10px;">
</div>
<!-- Toast Grid Load -->
<div id="grid"></div>
</div>
</div>
</body>
</html>
3. 실제 구현 화면
💡 실제 컬럼별 기능들만을 적용하여서 구성한 그리드가 완료되었습니다.
5. 그리드 전체 기능 구성
💡 그리드의 ‘컬럼 별’로 선택하여 사용하는 기본 기능들에 대해서 응용을 해봅니다.
1. 전체 기능 구성
기능 | 속성 명 | 설명 |
테마 지정 기능 | Grid.applyTheme | 그리드의 테마를 지정하는 기능을 제공합니다. (clean, stripe, default) |
언어 선택 기능 | Grid.setLanguage | 그리드의 언어를 선택 할 수 있는 기능을 제공합니다. (en, ko) |
페이징(Pagination) 기능 | pageOptions | 그리드의 클라이언트 내에서 페이징 처리를 할 수 있는 기능을 제공합니다. |
합계 및 평균 Counting 기능 | summary | 그리드의 요약(합계/평균,...)의 기능을 제공합니다 |
컨텍스트 메뉴 (Context Menu) 기능 | contextMenu | 그리드의 컨텍스트 메뉴를 제공합니다 (Copy, Copy Column, Copy Row, Export - csv) |
2. 실제 기능 구성
💡 전체 기능을 기반으로 구성한 그리드 코드입니다.
<!DOCTYPE html>
<!-- Default Layout Import-->
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{common/layouts/defaultLayout}"
layout:fragment="content"
>
<head>
<title></title>
<!--
- import JQuery
-->
<script th:inline="javascript" type="text/javascript" th:src="@{/lib/jquery-3.6.1.min.js}"></script>
<!--
- import Toast UI Grid JS & Pagination
- 그리드 내의 페이징을 사기 위해는 먼저 페이징을 import 해야합니다.
- reference: https://github.com/nhn/tui.grid/issues/891
-->
<script th:inline="javascript" type="text/javascript" th:src="@{/lib/tui-pagination.js}"></script>
<script th:inline="javascript" type="text/javascript" th:src="@{/lib/tui-grid.js}"></script>
<!-- import Toast UI Grid CSS & Pagination -->
<link th:inline="css" th:href="@{/css/tui-pagination.css}" rel="stylesheet"/>
<link th:inline="css" th:href="@{/css/tui-grid.css}" rel="stylesheet"/>
<!-- IN-line JS 영역-->
<script th:inline="javascript" type="text/javascript">
$(document).ready(async () => {
// 2. HTML이 수행된 뒤 함수를 호출한다.
await gridLoad();
});
/**
*
* @return void
*/
const gridLoad = () => {
// 3. 그리드 생성
const Grid = tui.Grid;
/**
* [선택] 그리드 테마 적용하기 (clean, stripe, default)
*
* @reference https://nhn.github.io/tui.grid/latest/tutorial-example07-themes
* @reference https://seokbong.tistory.com/14
*/
Grid.applyTheme('stripe');
/**
* [선택] Grid Confirm 메시지
*/
Grid.setLanguage('en', {
display: {
noData: '데이터가 존재하지 않습니다.',
loadingData: '데이터가 로딩중입니다.',
resizeHandleGuide: 'You can change the width of the column by mouse drag, ' +
'and initialize the width by double-clicking.'
},
net: {
confirmCreate: 'Are you sure you want to create {{count}} data?',
confirmUpdate: 'Are you sure you want to update {{count}} data?',
confirmDelete: 'Are you sure you want to delete {{count}} data?',
confirmModify: 'Are you sure you want to modify {{count}} data?',
noDataToCreate: '등록 하려는 데이터가 존재하지 않습니다. ',
noDataToUpdate: '수정 하려는 데이터가 존재하지 않습니다. ',
noDataToDelete: 'No data to delete.',
noDataToModify: '수정 할 데이터가 존재하지 않습니다. ',
failResponse: 'An error occurred while requesting data.\nPlease try again.'
}
}); // set Korean
const grid = new Grid({
// ================================== 공통 옵션 적용 ==============================
el: document.getElementById('grid'), // [필수] Container element
data: [
{
"userId": "adjh54",
"userNm": "Lee",
"userMail": "adjh54@gmail.com",
"userGender": "man",
"userAge": 20,
"userHobby": "H1",
"userSq": 1,
},
{
"userId": "ckask123",
"userNm": "jong",
"userMail": "ckask@gmail.com",
"userGender": "woman",
"userAge": 22,
"userHobby": "H3",
"userSq": 2,
},
{
"userId": "hahahoho",
"userNm": "ha",
"userMail": "hahahoho@gmail.com",
"userGender": "man",
"userAge": 23,
"userHobby": "H4",
"userSq": 3,
},
], // [선택] 그리드 데이터 조회
/**
* [선택] 순번 기능
* @reference : http://nhn.github.io/tui.grid/latest/tutorial-example11-row-headers
*/
rowHeaders: [{
type: 'rowNum',
header: "순번",
width: 50,
}],
/**
* [선택] Pagination 처리
* https://github.com/nhn/tui.grid/blob/master/packages/toast-ui.grid/docs/ko/infinite-scroll.md
*/
pageOptions: {
perPage: 10,
useClient: true,
},
// ================================== 컬럼 옵션 적용 ==============================
columns: [
// [Column-1] 사용자 아이디
{
header: '사용자 아이디', // [필수] 컬럼 이름
name: 'userId', // [필수] 컬럼 매핑 이름 값
sortable: true, // [선택] 컬럼의 정렬 여부
resizable: true, // [선택] 컬럼의 리사이즈 여부 옵션
editor: 'text', // [선택] 수정 옵션
// [선택] 필터 옵션
filter: {
type: 'text',
showApplyBtn: true,
showClearBtn: true
},
////^[a-z|A-Z]*$/
// [선택] 유효성 검증 옵션
validation: {
required: true,
dataType: 'string',
regExp: /^[a-z|A-Z||0-9 ]*$/, // [정규식] 영문 숫자에 대한 조합만 가능
},
},
// [Column-2] 사용자 이름
{
header: '사용자 이름', // [필수] 컬럼 이름
name: 'userNm', // [필수] 컬럼 매핑 이름 값
sortable: true, // [선택] 컬럼의 정렬 여부
resizable: true, // [선택] 컬럼의 리사이즈 여부 옵션
editor: 'text', // [선택] 수정 옵션
// [선택] 필터 옵션
filter: {
type: 'text',
showApplyBtn: true,
showClearBtn: true
},
// [선택] 유효성 검증 옵션
validation: {
required: true,
dataType: 'string',
regExp: /^[a-z|A-Z|ㄱ-ㅎ|가-힣|0-9 ]*$/, // [정규식] 특수문자를 제외한 공백포함 사용 가능
},
},
// [Column-3] 사용자 이메일
{
header: '사용자 이메일', // [필수] 컬럼 이름
name: 'userMail', // [필수] 컬럼 매핑 이름 값
sortable: true, // [선택] 컬럼의 정렬 여부
resizable: true, // [선택] 컬럼의 리사이즈 여부 옵션
editor: 'text', // [선택] 수정 옵션
// [선택] 필터 옵션
filter: {
type: 'text',
showApplyBtn: true,
showClearBtn: true
},
// [선택] 유효성 검증 옵션
validation: {
unique: true, // [선택] 유일성 체크 확인 옵션
required: true,
dataType: 'string',
regExp: /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i
},
},
// [Column-4] 사용자 나이
{
header: '사용자 나이', // [필수] 컬럼 이름
name: 'userAge', // [필수] 컬럼 매핑 이름 값
sortable: true, // [선택] 컬럼의 정렬 여부
resizable: true, // [선택] 컬럼의 리사이즈 여부 옵션
editor: 'text', // [선택] 수정 옵션
align: 'center', // [선택] 텍스트 정렬 옵션
// [선택] 필터 옵션
filter: {
type: 'text',
showApplyBtn: true,
showClearBtn: true
},
// [선택] 유효성 검증 옵션
validation: {
unique: true, // [선택] 유일성 체크 확인 옵션
dataType: 'number', // [선택] 데이터 타입 체크 옵션
required: true, // [선택] 필수 여부 체크 옵션
min: 1, // [선택] 최소값
max: 100, // [선택] 최대값
}
},
// [Column-5] 사용 여부
{
header: '사용자 성별', // [필수] 컬럼 이름
name: 'userGender', // [필수] 컬럼 매핑 이름 값
sortable: true, // [선택] 컬럼의 정렬 여부
resizable: true, // [선택] 컬럼의 리사이즈 여부 옵션
align: 'center', // [선택] 텍스트 정렬 옵션
filter: 'select', // [선택] 필터 옵션
formatter: 'listItemText', // [선택] select box 옵션
editor: {
type: 'radio',
options: {
listItems:
[
{text: '남성', value: "man"},
{text: '여성', value: "woman"},
]
},
},
},
// [Column-5] 사용자 취미
{
header: '사용자 취미', // [필수] 컬럼 이름
name: 'userHobby', // [필수] 컬럼 매핑 이름 값
align: 'center', // [선택] 텍스트 정렬 옵션
sortable: true, // [선택] 컬럼의 정렬 여부
resizable: true, // [선택] 컬럼의 리사이즈 여부 옵션
formatter: 'listItemText', // [선택] 값을 기반으로 select box 옵션
// [Option] 필터 옵션
filter: {
type: 'select',
showApplyBtn: true,
showClearBtn: true
},
// [Option] select 옵션
editor: {
type: 'select',
options: {
listItems: [
{text: "독서", value: "H1"},
{text: "복싱", value: "H2"},
{text: "스킨스쿠버", value: "H3"},
{text: "탁구", value: "H4"},
]
}
},
},
// [Column-5] 사용자 시퀀스 - Hidden
{
header: '사용자 시퀀스', // [필수] 컬럼 이름
name: 'userSq', // [필수] 컬럼 매핑 이름 값
hidden: true, // [선택] 숨김 여부
},
],
/**
* [Option] 최종 요약 데이터 출력 : max, min, sum, avg
* @reference : https://nhn.github.io/tui.grid/3.9.0/tutorial-example09-using-summary
*/
summary: {
height: 40,
position: 'bottom', // or 'top'
columnContent: {
userHobby: {
template: (valueMap) => {
return `사용자 합계 : ${valueMap.cnt}`
}
}
}
},
});
};
</script>
</head>
<body style="background-color: gray">
<div>
<div>
<!--타이틀-->
<h1 style="margin-bottom: 50px; margin-top: 50px; text-align: center">사용자 리스트</h1>
<!-- 테이블 추가 이벤트 -->
<div class="btn-wrapper" style="margin-bottom: 10px;">
</div>
<!-- Toast Grid Load -->
<div id="grid"></div>
</div>
</div>
</body>
</html>
3. 결과 화면
💡 전체 기능에서 구성한 컨텍스트 메뉴, 페이징, 합계 계산이 잘 나옴을 확인하였습니다.
4) API 통신을 이용한 그리드 수행
💡 DataGrid를 이용하여 API 서버와의 연동을 통해 조회, 등록, 수정 기능을 구현하는 예시입니다.
1. 기능 구성
- API와 데이터 통신
- 데이터 조회
- 데이터 등록
- 데이터 수정
2. 클라이언트 구성
2.1. DataSource 구성
💡 별도의 함수를 만들어서 해당 부분을 불러오도록 처리를 하였습니다.
<!DOCTYPE html>
<!-- Default Layout Import-->
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{common/layouts/defaultLayout}"
layout:fragment="content"
>
<head>
<title></title>
<!-- IN-line JS 영역-->
<script th:inline="javascript" type="text/javascript">
$(document).ready(async () => {
// 2. HTML이 수행된 뒤 함수를 호출한다.
await gridLoad();
});
/**
* [API] TOAST 데이터 조회, 수정, 삭제를 위한 API 통신
*
* @reference : https://github.com/nhn/tui.grid/blob/master/packages/toast-ui.grid/docs/ko/data-source.md
* @reference Document : https://github.com/nhn/tui.grid/blob/master/packages/toast-ui.grid/docs/ko/data-source.md
* @reference API: https://nhn.github.io/tui.grid/latest/Grid#readData
* @return {{api: {readData: {method: string, url: string}}}}
*/
const toastUiDataSource = () => {
return {
initialRequest: true, // [Essential] 초기 데이터 조회를 위한 readData API 요청 여부
api: {
hideLoadingBar: false, // [Options] 로딩바 숨김 여부
/*
* [API] 사용자 리스트를 조회합니다.
* @reference: [Request] Query String / [Response] JSONObject
*/
readData: {
url: '/api/v1/admin/user',
method: 'GET',
initParams: {
// [Option] Query String으로 보낼 데이터
// perPage: 100, // [Options] 페이지에 보여줄 개수
// page: 0, // [Options] 시작 페이지
// sortColumn: "userSq", // [Options] Sorting 여부
// sortAscending: true // [Options] 오름 차순 여부
},
},
/*
* [API] 사용자를 추가합니다.
* @reference: [Request] Grid Row(JSON) / [Response] JSONObject
*/
createData: {
url: '/api/v1/admin/user',
method: 'POST',
contentType: 'application/json', // Request 데이터 전송방식(JSON)
},
/*
* [API] 사용자를 수정합니다.
* @reference: [Request] Grid Row(JSON) / [Response] JSONObject
*/
updateData: {
url: '/api/v1/admin/user',
method: 'PUT',
contentType: 'application/json', // Request 데이터 전송방식(JSON)
},
},
}
};
/**
*
* @return void
*/
const gridLoad = () => {
// 3. 그리드 생성
const Grid = tui.Grid;
const grid = new Grid({
// ================================== 공통 옵션 적용 ==============================
el: document.getElementById('grid'), // [필수] Container element
data: toastUiDataSource(), // [선택] 그리드 데이터 조회
});
/**
* [Event] 성공 / 실패와의 관계 없이 '응답'을 받은 경우 반환되는 값
*/
grid.on('response', (ev) => responseList(ev));
const appendBtn = document.getElementById('appendBtn'); // 그리드 추가 버튼
const saveBtn = document.getElementById('saveBtn'); // 그리드 추가/저장 버튼
const registerBtn = document.getElementById('registerBtn'); // 그리드 등록 버튼
/**
* '추가' 버튼 수행 이벤트 등록
*/
appendBtn.addEventListener('click', () => {
grid.prependRow({
"userId": "",
"userNm": "",
"userMail": "",
"userAge": "",
"userGender": "man",
"userHobby": "H1",
},
{focus: true}
);
grid.setPerPage(10); // 페이지당 10개씩 보여지고 페이지 새로고침을 수행합니다.
});
/**
* '저장' 버튼 수행 이벤트 등록 => 수정
*/
saveBtn.addEventListener('click', (e) => {
transactionValidateMsg(grid, 'modify');
});
/**
* '등록' 버튼 수행 이벤트 등록 => 등록
* '추가' 버튼 이후에 저장하는 데이터
*/
registerBtn.addEventListener('click', () => {
// 유효성 검증을 데이터를 반환 받습니다.
transactionValidateMsg(grid, 'register')
});
};
/**
* [함수] dataSource 선언한 API 함수 호출이 발생 할 경우 반환값을 리턴해주는 함수 입니다.
*
* @param ev
* @return void
*/
const responseList = (ev) => {
const {response} = ev.xhr;
const responseObj = JSON.parse(response);
const {result, data: {contents}} = responseObj;
if (result) {
console.log(typeof data);
console.log(contents);
// select
if (contents.length === undefined) {
if (contents.type === "insert") {
alert("사용자 등록이 완료되었습니다.");
}
if (contents.type === "update") {
alert("사용자 수정이 완료되었습니다.")
}
}
} else alert("해당 처리가 되지 않았습니다. 관리자에게 문의해주세요.")
console.log('result : ', responseObj.result, " data: ", responseObj.data);
}
</script>
</head>
<body style="background-color: gray">
<div>
<!--타이틀-->
<h1 style="margin-bottom: 50px; margin-top: 50px; text-align: center">사용자 리스트</h1>
<!-- 테이블 추가 이벤트 -->
<div class="btn-wrapper" style="margin-bottom: 10px;">
<!-- 테이블 버튼 구성 -->
<div class="btn-wrapper" style="margin-bottom: 10px;">
<button
id="saveBtn"
th:class="btnStyle"
style="float: right;
margin-left: 20px;"
>수정
</button>
<button
id="registerBtn"
class="transactionBtn"
th:class="btnStyle"
style="float: right;"
>
등록
</button>
<button
id="appendBtn"
th:class="btnStyle"
style="float: right;"
>
추가
</button>
</div>
<!-- Toast Grid Load -->
<div id="grid"></div>
</div>
</body>
</html>
2.2. API로 호출을 보냈을때 ‘pagination’과 ‘contents’를 필수적으로 받아야 합니다
💡 insert 처리 이후 Response 값
{
"pagination": "",
"contents": {
"type": "insert",
"isSuccess": true
}
}
💡 update 처리 이후 Response 값
{
"pagination": "",
"contents": {
"type": "update",
"isSuccess": true
}
}
3. API 구성
💡 그리드의 클라이언트에서 요청하는 'Controller'에서는 아래와 같이 데이터를 받습니다.
/**
* 관리자 화면에서 사용되는 사용자 관련 페이지를 담당합니다.
*
* @author : lee
* @fileName : AdminController
* @since : 2022/12/28
*/
@Slf4j
@Controller
@Tag(name = "AdminController", description = "관리자 View Document")
public class AdminController {
private final UserService userService;
public AdminController(UserService userService) {
this.userService = userService;
}
/**
* [VIEW] 사용자 관리 페이지 출력
*
* @param model 전송 할 데이터
* @return 페이지
*/
@GetMapping("/admin/userList")
@Operation(summary = "사용자 관리 페이지 출력", description = "사용자 페이지를 출력합니다.")
public String userView(Model model) {
log.debug("사용자 페이지를 출력합니다.");
return "pages/user/userListPage";
}
/**
* [API] 사용자 리스트 조회
* 해당 Response는 Toast UI Grid 에 맞는 Response 값으로 구성하여 반환한다.
*
* @return responseData
*/
@GetMapping("api/v1/admin/user")
@Operation(summary = "사용자 리스트 조회", description = "사용자 리스트를 조회해오는 API")
public ResponseEntity<ToastUiResponseDto> selectUserList(
) {
log.debug("사용자 리스트를 조회합니다.");
UserVO userVO = UserVO.userBuilder().build();
// [STEP1] 사용자 리스트를 조회한다.
ToastUiResponseDto selectGridUserList = userService.selectGridUserList(userVO);
log.debug("resultList :: " + selectGridUserList);
return new ResponseEntity<>(selectGridUserList, HttpStatus.OK);
}
/**
* [API] 사용자 등록
*
* @param createdRows 사용자 등록을 위한 JSONObject 데이터
* @return ToastUiResponseDto 해당 Response는 Toast UI Grid 에 맞는 Response 값으로 구성하여 반환한다.
*/
@PostMapping("api/v1/admin/user")
@Operation(summary = "사용자 등록", description = "사용자 등록 API입니다.")
public ResponseEntity<ToastUiResponseDto> insertUser(@RequestBody JSONObject createdRows) {
log.debug("사용자를 등록합니다.");
ToastUiResponseDto responseData = userService.insertGridUser(createdRows);
return new ResponseEntity<>(responseData, HttpStatus.OK);
}
/**
* [API] 사용자 수정
*
* @param updatedRows JSONObject : 사용자 수정 데이터
* @return ToastUiDto : Toast UI 전용 Response 로 반환합니다.
*/
@PutMapping("api/v1/admin/user")
@Operation(summary = "사용자 수정 API", description = "사용자를 수정합니다.")
public ResponseEntity<ToastUiResponseDto> updateTemplate(@RequestBody JSONObject updatedRows) {
log.debug("Toast UI Grid로부터 받은 JSONObject로 사용자를 수정합니다.");
ToastUiResponseDto toastUiResponseDto = userService.updateGridUser(updatedRows);
return new ResponseEntity<>(toastUiResponseDto, HttpStatus.OK);
}
}
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.HashMap;
/**
* [공통] 페이지 관리
*
* @author : lee
* @fileName : ToastUiDto
* @since : 2022/12/28
*/
@Getter
@ToString
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Schema(description = "Toast UI Grid 응답 VO")
public class ToastUiResponseDto {
private boolean result;
private HashMap<String, Object> data;
@Builder
ToastUiResponseDto(boolean result, HashMap<String, Object> data) {
this.result = result;
this.data = data;
}
}
💡 ToastUiResponseDto를 구성할때 아래와 같이 구성하여 데이터를 보냈습니다.
/**
* Toast Grid UI 에서 사용하는 사용자 리스트 조회
*
* @param userVO 사용자 정보
* @return ToastUiResponseDto
*/
@Override
@Transactional(readOnly = true)
public ToastUiResponseDto selectGridUserList(UserVO userVO) {
UserMapper um = sqlSession.getMapper(UserMapper.class);
List<UserVO> selectGridUserList = um.selectGridUserList(userVO);
// [STEP1] Toast Grid에 맞는 Response 값을 구성해서 전달합니다.
HashMap<String, Object> resultMap = new HashMap<>();
resultMap.put("contents", selectGridUserList);
resultMap.put("pagination", ""); // 값은 존재하지 않지만 필수적으로 전달을 해야한다.
// [STEP2] 최종 Toast Grid 에 맞는 Response 값 구성하여 반환합니다.
return ToastUiResponseDto.builder().result(true).data(resultMap).build();
}
💡 클라이언트로 받은 API 내에서 확인한 INSERT 응답값
{
"createdRows": [
{
"userId": "adjh5452",
"userNm": "Lee",
"userMail": "Lee@naver.com",
"userAge": "20",
"userGender": "man",
"userSq": 3,
"userHobby": "H1",
"rowKey": 4,
"_attributes":
{
"rowNum": 1,
"checked": false,
"disabled": false,
"checkDisabled": false,
"className":
{
"row": [],
"column":
{}
}
}
}]
}
💡 클라이언트로 받은 API 내에서 확인한 UPDATE 응답값
{
"updatedRows": [
{
"userId": "adjh5452",
"userNm": "Lee",
"userMail": "Lee@naver.com",
"userAge": "20",
"userGender": "man",
"userSq": 3,
"userHobby": "H2",
"rowKey": 4,
"_attributes":
{
"rowNum": 21,
"checked": false,
"disabled": false,
"checkDisabled": false,
"className":
{
"row": [],
"column":
{}
}
}
}]
}
오늘도 감사합니다 😄
99) Reference
[참고] 공식 Github
GitHub - nhn/tui.grid: 🍞🔡 The Powerful Component to Display and Edit Data. Experience the Ultimate Data Transformer!
🍞🔡 The Powerful Component to Display and Edit Data. Experience the Ultimate Data Transformer! - GitHub - nhn/tui.grid: 🍞🔡 The Powerful Component to Display and Edit Data. Experience the Ultimate Da...
github.com
[참고] 공식 사이트 - API Document
https://nhn.github.io/tui.grid/latest/Grid/
Occurs when the grid data is updated and the grid is rendered onto the DOM The event occurs only in the following API as below. 'resetData', 'restore', 'reloadData', 'readData', 'setPerPage' with 'dataSource', using 'dataSource' PROPERTIES
nhn.github.io
'Javascript & Typescript > 라이브러리' 카테고리의 다른 글
[JS/Thymeleaf] jQuery DatePicker 활용방법 (0) | 2023.08.01 |
---|---|
[JS/Library] Webpack 이해하기 - 1 : 주요 용어 (0) | 2022.11.04 |
[JS] Next.js 이해하기(정의, 사용목적) (0) | 2022.08.15 |
[JS/library] Prettier 환경설정 방법 (3) | 2022.02.01 |