반응형
해당 글에서는 Spring Boot의 타임리프(Thymeleaf)를 기반으로 CDN을 통하여 오픈소스 ‘Toast UI Grid’를 이용한 설정 및 활용방안에 대해서 공유합니다.
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란?
- 데이터 편집, 필터링, 정렬 등과 같은 기능을 갖춘 강력한 라이브러리이며 편집기 또는 렌더러를 원하는 형식으로 사용자 지정하여 사용 할 수 있는 그리드를 의미합니다.
[출처] 공식사이트
2. 주요 기능
💡 해당 글은 해당 페이지를 구성하기 위해 주요 사용한 기능들입니다.
💡 추가로 다른 기능을 확인하기 위해서는 공식 홈페이지를 참고하시면 될것 같습니다.
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 구성은 이전에 작성한 글을 참고하였습니다.
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
[참고] 공식 사이트 - API Document
반응형
'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 |