DataTable 이란?
- 자바스크립트에서 테이블을 쉽게 그리게 하며 제어할 수 있는 다양한 기능을 제공하는 자바스크립트 라이브러리.
초기 설정
1. js 라이브러리 추가
라이브러리 추가 시 cdn 방법 또는 파일을 직접 다운받아 추가하는 방법이 있음.
cdn 방법
<script src="https://cdn.datatables.net/1.13.1/js/jquery.dataTables.min.js"></script>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.1/css/jquery.dataTables.min.css">
파일 추가 방법 (경로 주의!)
1. 테이블 작성
datatables 사용을 위해 html에 테이블 헤더만 작성해준다.
이때 <table> 테그에 id 를 꼭 넣어주기.
나는 첫번째 열을 체크박스로 하기 위해 th안 checkbox 테그를 넣어줌.
2. 데이터 테이블 사용해보기
해당 html에 매핑할 js파일을 생성.
아래와 같이 작성.
$(document).ready(function () {
var airport = [
{
airportOfEng: 'BUSAN',
airportOfKor: '부산',
areaCode: 'ASIA',
lastModifyDateTime: '2025-06-09T10:11:08',
lastModifyUserName: 'ADMIN',
nationCode: 'KOR'
}
];
$("#airportTable").DataTable({
data: passengers,
columns: [
{ data: null}, // 체크박스
{
data: null,
render: function (data, type, row, meta) {
return meta.row + 1; // 데이터 개수에 따라 넘버링
}
},
{ data: "iataCode" , render: function(data, type, full, meta) {
return '<a>' + full.iataCode + '</a>';
}
},
{ data: "airportOfKor" },
{ data: "airportOfEng"},
{ data: "nationCode"},
{ data: "areaCode"},
{ data: "useYn" },
{ data: "lastModifyUserName" },
{ data: "lastModifyDateTime" , render: (data, type, full, meta) => {
return moment(full.lastModifyDateTime).format('yyyy-MM-DD HH:mm:ss');
}}
],
columnDefs: [
{
orderable: false, // ordering: false설정 외 정렬 개별 설정 가능
className: 'select-checkbox',// 첫번째 행 체크박스 설정 (클래스 명으로 설정)
targets: 0, // 열 번호
data: null,
defaultContent: '' // 전달되는 값이 없을때 기본으로 입력될 내용.
// 생략 가능
},
{
searchable: false,
orderable: false,
targets: 1,
data: null,
defaultContent: ''
}
]
});
});
위 코드와 같이 json 형식 데이터를 table에 쉽게 표현 가능.
3. ajax + datatables
ajax 로 호출 후 해당 결과를 통해 datatables 그리는 방식 예제
// 테이블 객체를 전역변수로 선언
let _AIRPORT_TABLE_;
$(document).ready(function() {
initDataTables();
}
function initDataTables() {
$.ajax({
url: _URL_,
type: "GET",
success: function (response) {
if (!response.data || response.data.length === 0) {
console.warn("No data available.");
response.data = [];
}
$("#airportTable").DataTable().destroy();
$("#airportTable_filter").remove();
_AIRPORT_TABLE_ = $("#airportTable").removeAttr(
"width").DataTable({
order: [],
info: false,
lengthChange: false,
dom: 'Bfrtip',
data: response.data,
destroy: true,
autoWidth: false,
searching: true,
paging: true,
pageLength: 10,
columns: [
{ data: null},
{
data: null,
render: function (data, type, row, meta) {
return meta.row + 1;
}
},
{ data: "iataCode" , render: function(data, type, full, meta) {
return '<a onclick="openAirportDetailView(' + full.seq + ');" style="color:orange; cursor:pointer;">' + full.iataCode + '</a>';
}
},
{ data: "airportOfKor" },
{ data: "airportOfEng"},
{ data: "nationCode"},
{ data: "areaCode"},
{ data: "useYn" },
{ data: "lastModifyUserName" },
{ data: "lastModifyDateTime" , render: (data, type, full, meta) => {
return moment(full.lastModifyDateTime).format('yyyy-MM-DD HH:mm:ss');
}},
],
columnDefs: [
{
searchable: false,
orderable: false,
targets: 1,
data: null,
defaultContent: ''
},
{
orderable: false,
className: 'select-checkbox',
targets: 0,
data: null,
defaultContent: ''
}
],
language: {
"emptyTable": "There is no data."
, "paginate": {
"next": '<img src="/images/next.png" alt="next">',
"previous": '<img src="/images/previous.png" alt="Previous">'
}
},
select: {
style: 'multi',
selector: 'td:first-child'
},
"drawCallback": function (settings) {
$('.dataTables_processing').hide();
},
initComplete: function (settings, json) {
$("#checkAll").prop("checked", false);
},
});
})
};
4. ajax + datatables ( 서버 사이드 페이징 )
데이터 테이블이 호출하는 방식으로, 서버 페이징이 필요할때 아래형태로 사용함.
예제 3과 ajax 위치가 달라졌고, serverSide: true 가 추가됨.
아래 예제에서 ajax안 항목에 data와 dataSrc가 있음.
- data 는 서버에 요청할 데이터, dataSrc는 요청에 대한 결과 중 테이블에 표시할 데이터를 추출하는 항목임.
// 테이블 객체를 전역변수로 선언
let _AIRPORT_TABLE_;
$(document).ready(function() {
initDataTables();
}
function initDataTables() {
_AIRPORT_TABLE_ = $("#airportTable").DataTable({ // html 에서 선언한 테이블 ID
dom: 'rtip', // dom 조작을 위한 코드
processing: false, // 데이터 테이블 기본 로딩 UI
serverSide: true, // 서버 사이드 페이징
destroy: true, // 테이블 파괴(?) 여부
paging: true, // 데이터 테이블에서 제공하는 페이징
ordering: false, // 데이터 정렬 (true 시 헤더에 열마다 정렬 아이콘 생성됨)
ajax: {
url: _URL_,
type: "GET",
cache: false,
data: function(d){ // 서버로 전달될 데이터
let tmp = {};
tmp['draw'] = d.draw; // 데이터 요청 횟수
tmp['pageNum'] = (d.start / d.length) + 1; // 페이지 번호
tmp['pageSize'] = d.length; // 한 페이지에 보여줄 데이터 개수
},
dataSrc: function (result) { // 요청 결과
if (result.success === true) {
return result.data; // 테이블에 들어갈 데이터
} else {
alert('통신 실패!!!');
return []; // fallback
}
},
error: function(xhr, status, err) {
if (error) {
error(status);
} else {
alert('error');
}
}
},
columns: [
{ data: null}, // 체크박스
{
data: null,
render: function (data, type, row, meta) {
return meta.row + 1; // 데이터 개수에 따라 넘버링
}
},
{ data: "nationCode"},
{ data: "areaCode"},
{ data: "useYn" },
{ data: "lastModifyUserName" },
{ data: "lastModifyDateTime" , render: (data, type, full, meta) => {
return moment(full.lastModifyDateTime).format('yyyy-MM-DD HH:mm:ss');
}},
],
columnDefs: [
{
orderable: false, // ordering: false설정 외 정렬 개별 설정 가능
className: 'select-checkbox',// 첫번째 행 체크박스 설정 (클래스 명으로 설정)
targets: 0, // 열 번호
data: null,
defaultContent: '' // 전달되는 값이 없을때 기본으로 입력될 내용.
// 생략 가능
},
{
searchable: false,
orderable: false,
targets: 1,
data: null,
defaultContent: ''
}
],
language: {
emptyTable: "There is no data.", // 테이블 전체 값이 없을때 해당 문구 뜸.
paginate: { // 페이징 시 표출.
next: '<img src="/images/next.png" alt="Next">', // 버튼을 이미지로 대체
previous: '<img src="/images/previous.png" alt="Previous">'
}
},
select: {
style: 'multi',
selector: 'td:first-child'
},
"drawCallback": function( settings ) {
$('.dataTables_processing').hide(); // 전체 테이블 그린 후 proceccing 숨기기
},
initComplete: function (settings, json) {
$("#checkAll").prop("checked", false);
},
});
}
해당 코드와 소통하기 위한 서버 코드는 아래와 같다.
@GetMapping("/airports")
public ResponseEntity<ApiResultPagination<List<AirportResultDTO>>> getAirportList(
AirportParamDTO param) {
List<...DTO> airportList = this....service.getListByCondition(param);
PageInfo<...DTO> pi = new PageInfo<>(airportList);
List<AirportResultDTO> responseList = pi.getList().stream()
.map(AirportResultDTO::fromDTO)
.toList();
ApiResultPagination<List<AirportResultDTO>> result = new ApiResultPagination<>();
result.setSuccess(true);
result.setDraw(param.getDraw());
result.setRecordsTotal((int) pi.getTotal());
result.setRecordsFiltered((int) pi.getTotal());
result.setData(responseList);
return new ResponseEntity<>(result, HttpStatus.OK);
}
..서비스 코드 생략..
<select id="select...List" parameterType="com....management.....param.....DTO" resultType="com.....management........DTO">
<![CDATA[
SELECT SEQ
, CODE_GRP
, CODE_VALUE1
, CODE_VALUE2
, CODE_VALUE3
, CODE_VALUE4
, CODE_VALUE5
, CODE_VALUE6
, REMARK
, REG_DT
, REG_USER_ID
, LST_MOD_DT
, LST_MOD_USER_ID
, USE_YN
FROM TEST_TABLE
]]>
<where>
CODE_GRP = #{codeGrp}
</where>
</select>
5. ajax + datatables ( 서버 사이드 페이징 + search)
위 코드에서 검색 기능을 추가해봄.
우선 html 추가하기
<section class="content">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-8">
<form class="d-flex" onsubmit="return false">
<-- ============= 검색 시작 =============== -->
<select class="mr-2 search-select selectBox" id="selectSearchTerm" aria-label="nationList">
<option value="iataCode">IATA 3자리 코드</option>
<option value="airportOfKor">한글 공항명</option>
<option value="airportOfEng">영문 공항명</option>
<option value="nationCode">3자리 국가 코드</option>
<option value="areaCode">지역 코드</option>
</select>
<input class="form-control mr-2 search-box" id="searchBox" name="searchBox" type="search" placeholder="Search" aria-label="Search">
<-- ============= 검색 시작 =============== -->
</form>
</div>
<div class="col-4 text-right">
<button type="button" class="btn-open-modal" id="btnOpenModalNew" data-backdrop="static">등록</button>
<button type="button" class="btn-delete" id="btnAirportDelete" data-backdrop="static">삭제</button>
</div>
</div>
<table id="airportTable" class="table is-stripe display" style="width: 100%; text-align: center;">
<thead>
<tr>
<th style="text-align: center;"><input type="checkbox" name="checkAll" id="checkAll"/></th>
<th style="text-align: center;">No</th>
<th style="text-align: center;">IATA 3자리 공항 코드</th>
<th style="text-align: center;">한글 공항명</th>
<th style="text-align: center;">영문 공항명</th>
<th style="text-align: center;">3자리 국가코드</th>
<th style="text-align: center;">지역</th>
<th style="text-align: center;">사용 여부</th>
<th style="text-align: center;">최종 수정자</th>
<th style="text-align: center;">최종 수정 일시</th>
</tr>
</thead>
</table>
</div>
</div>
</section>
짠
그리고 input 에 따른 검색 코드 추가하기
// 테이블 객체를 전역변수로 선언
let _AIRPORT_TABLE_;
// 검색 전역변수 추가
let searchTerm = "";
let searchValue = "";
$(document).ready(function() {
initDataTables();
}
function initDataTables() {
_AIRPORT_TABLE_ = $("#airportTable").DataTable({ // html 에서 선언한 테이블 ID
dom: 'rtip', // dom 조작을 위한 코드
processing: false, // 데이터 테이블 기본 로딩 UI
serverSide: true, // 서버 사이드 페이징
destroy: true, // 테이블 파괴(?) 여부
paging: true, // 데이터 테이블에서 제공하는 페이징
searching: false, // 데이터 테이블에서 제공하는 검색
ordering: false, // 데이터 정렬 (true 시 헤더에 열마다 정렬 아이콘 생성됨)
ajax: {
url: _URL_,
type: "GET",
cache: false,
data: function(d){ // 서버로 전달될 데이터
let tmp = {};
tmp['draw'] = d.draw; // 데이터 요청 횟수
tmp['pageNum'] = (d.start / d.length) + 1; // 페이지 번호
tmp['pageSize'] = d.length; // 한 페이지에 보여줄 데이터 개수
// ----------------------- 서버 요청 데이터 추가 ----------------------------
if (searchTerm && searchValue) {
tmp[searchTerm] = searchValue;
}
// ---------------------------------------------------------------------------
},
dataSrc: function (result) { // 요청 결과
if (result.success === true) {
return result.data; // 테이블에 들어갈 데이터
} else {
alert('통신 실패!!!');
return []; // fallback
}
},
error: function(xhr, status, err) {
if (error) {
error(status);
} else {
alert('error');
}
}
},
columns: [
... 생략 ...
],
columnDefs: [
... 생략 ...
],
... 생략 ...
});
// ---------------------------- 검색 이벤트 추가 -------------------------------
$("#searchBox").on("input", function () {
searchTerm = $("#selectSearchTerm").val();
searchValue = $(this).val();
_AIRPORT_TABLE_.ajax.reload();
});
// -----------------------------------------------------------------------------
}
이렇게 작성하면 아래와 같이 입력했을때
아래와 같이 요청된다.
혹시 잘 안된다면 datatables는 클래스 기반으로 동작하기도 하니 위 코드에 있는 클래스명이나 이름을 전부 넣어보도록하자..
이제 요청 데이터를 기반으로 검색하도록 수정해주자.
@GetMapping("/airports")
public ResponseEntity<ApiResultPagination<List<AirportResultDTO>>> getAirportList(
AirportParamDTO param) {
...ListByConditionParamDTO conditionParamDTO = ...ListByConditionParamDTO.builder()
.codeGrp(CODE_GRP)
.codeValue1(param.getIataCode())
.codeValue2(param.getAirportOfKor())
.codeValue3(param.getAirportOfEng())
.codeValue4(param.getAreaCode())
.codeValue5(param.getNationCode())
.build();
List<...DTO> airportList = this....service.getListByCondition(conditionParamDTO);
PageInfo<...DTO> pi = new PageInfo<>(airportList);
List<AirportResultDTO> responseList = pi.getList().stream()
.map(AirportResultDTO::fromDTO)
.toList();
ApiResultPagination<List<AirportResultDTO>> result = new ApiResultPagination<>();
result.setSuccess(true);
result.setDraw(param.getDraw());
result.setRecordsTotal((int) pi.getTotal());
result.setRecordsFiltered((int) pi.getTotal());
result.setData(responseList);
return new ResponseEntity<>(result, HttpStatus.OK);
}
... 서비스 코드 생략 ...
<select id="select...List" parameterType="com....management.....param.....DTO" resultType="com.....management........DTO">
<![CDATA[
SELECT SEQ
, CODE_GRP
, CODE_VALUE1
, CODE_VALUE2
, CODE_VALUE3
, CODE_VALUE4
, CODE_VALUE5
, CODE_VALUE6
, REMARK
, REG_DT
, REG_USER_ID
, LST_MOD_DT
, LST_MOD_USER_ID
, USE_YN
FROM TEST_TABLE
]]>
<where>
CODE_GRP = #{codeGrp}
<include refid="condition"/>
</where>
</select>
<sql id="condition">
<if test="codeValue1 != null and codeValue1 != ''">
AND CODE_VALUE1 LIKE '%' || UPPER(#{codeValue1}) || '%'
</if>
<if test="codeValue2 != null and codeValue2 != ''">
AND CODE_VALUE2 LIKE '%' || UPPER(#{codeValue2}) || '%'
</if>
<if test="codeValue3 != null and codeValue3 != ''">
AND CODE_VALUE3 LIKE '%' || UPPER(#{codeValue3}) || '%'
</if>
<if test="codeValue4 != null and codeValue4 != ''">
AND CODE_VALUE4 LIKE '%' || UPPER(#{codeValue4}) || '%'
</if>
<if test="codeValue5 != null and codeValue5 != ''">
AND CODE_VALUE5 LIKE '%' || UPPER(#{codeValue5}) || '%'
</if>
<if test="codeValue6 != null and codeValue6 != ''">
AND CODE_VALUE6 LIKE '%' || UPPER(#{codeValue6}) || '%'
</if>
</sql>
여기까지 하면 클라이언트에서 요청하는 대로 데이터 검색이 가능하다.
근데 문제 발생..
서버사이드 페이징과 키 입력에 따른 검색을 같이쓰니,
검색 창에 입력이 있을때마다 데이터를 다시 호출해 테이블이 깜빡 거리니 정신사나웠다.
서버사이드 페이징을 하지않고, 데이터테이블에서 제공하는 페이징과 검색을 쓰면 해결될것같음.
사실 데이터가 많지 않음에도 서버사이드 페이징을 하는게 마음에 들지않았어서 잘됐다 싶다.
js 코드 수정
function initDataTables() {
$.ajax({
url: _URL_,
type: "GET",
success: function (response) {
if (!response.data || response.data.length === 0) {
console.warn("No data available.");
response.data = [];
}
$("#airportTable").DataTable().destroy();
$("#airportTable_filter").remove();
_AIRPORT_TABLE_ = $("#airportTable").removeAttr(
"width").DataTable({
order: [],
info: false,
lengthChange: false,
dom: 'Bfrtip',
// serverSide: true, // 기본값 false여서 지워도 OK
data: response.data,
destroy: true,
autoWidth: false,
searching: true,
paging: true,
pageLength: 10,
columns: [
{ data: null},
{
data: null,
render: function (data, type, row, meta) {
return meta.row + 1;
}
},
{ data: "iataCode" , render: function(data, type, full, meta) {
return '<a>' + full.iataCode + '</a>';
}
},
{ data: "airportOfKor" },
{ data: "airportOfEng"},
{ data: "nationCode"},
{ data: "areaCode"},
{ data: "useYn" },
{ data: "lastModifyUserName" },
{ data: "lastModifyDateTime" , render: (data, type, full, meta) => {
return moment(full.lastModifyDateTime).format('yyyy-MM-DD HH:mm:ss');
}},
],
columnDefs: [... 생략 ...],
... 생략 ...
});
// --------------------------- 검색 --------------------------------------
// 키 입력 검색
$("#searchBox").on("input keyup", function () {
let searchTerm = $("#selectSearchTerm").val();
let searchValue = $(this).val();
// 컬럼 인덱스 값 맵핑 (!! columns 및 html th 에 정의한 헤더 **중요**!!)
const columnIndexes = {
"iataCode": 2,
"airportOfKor": 3,
"airportOfEng": 4,
"nationCode": 5,
"areaCode": 6
};
let columnIndex = columnIndexes[searchTerm];
_AIRPORT_TABLE_.columns().search("");
if (columnIndex !== undefined) {
_AIRPORT_TABLE_.column(columnIndex).search(searchValue).draw();
}else{
_AIRPORT_TABLE_.draw();
}
});
// ------------------------- 검색 --------------------------------------
}
})
};
컨트롤러 수정
@GetMapping("/airports")
public ResponseEntity<ApiResult<List<AirportResultDTO>>> getAirportList() {
List<...DTO> airportList = this....service.getListByCondition(param);
return ResponseEntity.ok(
new ApiResult<>(true, airportList.stream().map(code -> AirportResultDTO.builder()
.iataCode(code.getCodeValue1())
.airportOfKor(code.getCodeValue2())
.airportOfEng(code.getCodeValue3())
.areaCode(code.getCodeValue4())
.nationCode(code.getCodeValue5())
.build()
).toList()));
}
... 서비스 코드 생략...
<select id="select...List" parameterType="com....management.....param.....DTO" resultType="com.....management........DTO">
<![CDATA[
SELECT SEQ
, CODE_GRP
, CODE_VALUE1
, CODE_VALUE2
, CODE_VALUE3
, CODE_VALUE4
, CODE_VALUE5
, CODE_VALUE6
, REMARK
, REG_DT
, REG_USER_ID
, LST_MOD_DT
, LST_MOD_USER_ID
, USE_YN
FROM TEST_TABLE
]]>
<where>
CODE_GRP = #{codeGrp}
</where>
</select>
이렇게 수정을 했고, 이제 내가 원하는대로 동작함.
헤
정리
당연한 얘기겠지만 시행착오를 정리해보자면
데이터 양 | 검색 방식 | 장점 |
적음 | 입력 즉시 검색 (서버사이드 X) | 실시간 반응, 편의성 좋음 |
많음 | 검색 버튼 생성 후 버튼 클릭으로 요청 (서버사이드 O) | 서버 부하 감소, UX 안정 |
끝
아 퇴근마ㅏㅏ렵다
'HTML & CSS & JavaScript' 카테고리의 다른 글
[JavaScript] DOM 요소 선택 , 생성 , 삽입 , 교체 , 제거 (0) | 2023.01.24 |
---|