어리바리 신입 개발자의 얼렁뚱땅 개발 기록 ✨
23.07.30 / [데이터 테이블/jquery/Spring boot] 서버에서 데이터 받아서 데이터 테이블과 ajax로 동적 테이블 만들기 본문
23.07.30 / [데이터 테이블/jquery/Spring boot] 서버에서 데이터 받아서 데이터 테이블과 ajax로 동적 테이블 만들기
낫쏘링 2023. 7. 30. 05:30
데이터 테이블은 부트스트랩에서 제공되는 테이블 중 하나이다.
이미 제이쿼리 파일 안에 데이터 정렬, 페이지네이션, 검색, 페이지개수 등 구현되어 있어서 잘 가져다 쓰기만 하면 된다.
나는 이걸 몰라서 프로젝트 내내 제이쿼리로 열심히 그리다가 거의 끝나갈 때 쯤 알게 됐다...! ㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎ
아무튼 데이터 테이블을 써서 구현하면서 사용한 정보들을 정리해봤다.
[ 1. 데이터 테이블의 옵션 ]
- 테이블의 가로 길이 자동 설정
autoWidth : true
- 화면에 한 번에 나타나는 데이터 행 개수를 선택하는 select box / 나는 별로 안 쓰고 싶어서 false로 해놨다.

lengthChange : false
- 화면에 한 번에 나타나는 데이터 행 개수
- lengthChange 안 쓰고 그냥 10개로 고정했다.
pageLength : 10
- 검색 기능 이것도 일단은 false...
searching : false
- 테이블의 정보 이것도 필요 없음!

info : false
- 페이지 네이션 종류
- simple : Previous / Next
- simple_numbers : Previous / 숫자 / Next (설정 안하면 이게 기본이다.)
- full : First / Previous / Next / Last
- full_numbers : First / Previous / 숫자 / Next / Last
pagingType : "full_numbers"
- 나는 영어로 되어있는 버튼을 한글로 (처음으로 / 이전 / 다음 / 마지막으로) 바꾸고 싶어서
jquery.dataTables.min.js 에서 이렇게 수정했다.
// 수정 전
oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previos"}
// 수정 후
oPaginate:{sFirst:"처음으로",sLast:"마지막으로",sNext:"다음",sPrevious:"이전"}
- 컬럼 조작(체크박스에 정렬이랑 검색 적용 안되게 하려고 사용했다. - 체크박스가 0번 인덱스에 있음)
columnDefs: [{
targets: 0, // 0번 인덱스를
searchable: false, // 검색 안함!
orderable: false, // 정렬도 사용 안함!
className: 'sorting_disabled' // 클래스 이름 추가 하기
}]
- 컬럼(columns)과 데이터(data) 추가 (나는 동적으로 넣어줄거기 때문에 예시와는 좀 다르게 사용했음)
컬럼 명이 들어가는 이유는 알겠는데 데이터는 어떻게 들어가는 건지...?
=> 앞에 있는 data가 데이터를 담고 있기 때문에 data에서 컬럼1, 컬럼2에 맞는 값을 찾아서 넣어준다.
title : 데이터 테이블에서 실제로 출력할 컬럼 이름 / 그냥 컬럼1, 컬럼2 그대로 넣고 싶으면 생략해도 된다.
defaultContent : 말 그대로 기본 값으로 컬럼명에 일치하는 데이터가 null일 경우 기본 값을 넣어준다.
// 자바스크립트로 데이터 저장 (배열 안에 객체가 있는 형태)
const dataSetting = [
{ "컬럼1": "값1", "컬럼2": "값2" },
{ "컬럼1": "값3", "컬럼2": "값4" }
];
// "data"에 컬럼명을 넣으면.....
columns : [
{"title" : "컬럼1번" "data" : "컬럼1", "defaultContent" : "기본값1" , "className : "클래스이름1"},
{"title" : "컬럼2번" "data" : "컬럼2", "defaultContent" : "기본값2" , "className : "클래스이름2"}
],
// dataSetting을 할당 받은 data가 dataSetting을 열심히 돌면서 컬럼명과 일치하는 값을
// 컬럼과 짝을 만들어줘서 넣어준다.
// 보통 서버에서 데이터를 받아와서 쓰기 때문에
// ajax에서 받아온 데이터 이름이나 스크립트를 통해 담아준 배열 이름을 넣어서 사용한다.
data: dataSetting (데이터를 담고 있는 변수 이름)
// 서버에서 받아오지 않고 그냥 직접 데이터를 넣어줄 수 도 있다... 그런데 굳이...?? 그냥 html에 직접 넣으면 되는 걸...
[ 내가 동적으로 columns와 data를 사용한 방법 ]
1. ajax로 Controller로 데이터 보내기
// 다섯 개의 테이블을 동적으로 만들어줄거기 때문에 tableId라는 변수를 만들어 준다.
// 당연히 클릭하는 탭 마다 tableId가 다르다.
let tableId = $(e.target).attr('aria-controls');
// ajax로 데이터를 받아온다.
// 앞서 담아뒀던 tableId를 인수로 보내준다. 받아 올 데이터 타입은 json
$.ajax({
url: '/admin/point/pointStandardManageClick',
method: 'GET',
data: { 'tableId' : tableId },
dataType: 'json'
});
2. Controller에서 Service 메서드 호출하고 값 담아서 ajax로 반환 하기
// ajax가 데이터를 요청하는 컨트롤러의 메서드 (컨트롤러에 RequestMapping으로"/admin/point"설정해둔 상태)
// GET 방식으로 "/admin/point/pointStandardManageClick" URL으로 요청했기 때문에 이 메서드를 찾아서 요청한다.
@GetMapping("/pointStandardManageClick")
// 경고 제외하기 (노란색 경고)
@SuppressWarnings({ "unchecked" })
// view가 아닌 body 내용을 반환하게 함 (xml이나 json 반환할 때 사용)
@ResponseBody
// ajax를 통해 보내진 tableId를 매개변수와 바인딩해준다.
public List<Map<String,Object>> pointStandardMange(@RequestParam(value="tableId")String tableId,
Model model) {
// adminPointService라는 클래스의 getPointStandard 메서드를 호출하고 반환 된 값을 담아준다.
Map<String,Object> pointStandardResultMap = adminPointService.getPointStandard(tableId);
List<Map<String,Object>> pointStandardList = (List<Map<String,Object>>)pointStandardResultMap.get("pointStandardList");
model.addAttribute("title", "포인트 관련 기준 관리");
model.addAttribute("pointStandardList", pointStandardList);
return pointStandardList;
}
3. Service 에서 mapper 메서드 호출하고 값 담아서 Controller로 반환 하기
// adminPointController에서 호출한 메서드
public Map<String, Object> getPointStandard(String tableId){
String tableDbName = null;
// 컬럼 순서를 지켜서 받아올 거기 때문에 HashMap 대신 LinkedHashMap을 사용한다.
Map<String,Object> paramMap = new LinkedHashMap<String,Object>();
List<Map<String,Object>> pointStandardList = null;
// adminPointMapper에서 return 값 받아오기
// 테이블 마다 컬럼 내용과 개수가 다르기 때문에 각각 받아온다.
// day_maximum_count 테이블의 경우 데이터에 포인트와 등급이 섞여 있기 때문에
// point를 따로 전달해서 호출해준다.
if(tableId.equals("pills-max")) {
tableDbName = "day_maximum_count";
pointStandardList = adminPointMapper.getPointMaxCountStandard("point");
}else if(tableId.equals("pills-expire")) {
tableDbName = "point_expire_standard";
pointStandardList = adminPointMapper.getPointExpireStandard();
}else if(tableId.equals("pills-save")) {
tableDbName = "point_save_standard";
pointStandardList = adminPointMapper.getPointSaveStandard();
}else if(tableId.equals("pills-refund")) {
tableDbName = "point_refund_standard";
pointStandardList = adminPointMapper.getPointRefundStandard();
}else {
tableDbName = "point_save_use_type";
pointStandardList = adminPointMapper.getPointTypeStandard();
}
// controller에 전달될 data
paramMap.put("tableId", tableId);
paramMap.put("pointStandardList", pointStandardList);
return paramMap;
};
4. mapper 에서 쿼리 실행 후 값 담아서 Service로 반환 하기
/* 5-2 포인트 환급 기준 조회 */
public List<Map<String,Object>> getPointRefundStandard();
/* 4-2 포인트 적립 기준 조회 */
public List<Map<String,Object>> getPointSaveStandard();
/* 3-2 포인트 타입 기준 조회 */
public List<Map<String,Object>> getPointTypeStandard();
/* 2-2 포인트 만료 기간 기준 조회 */
public List<Map<String,Object>> getPointExpireStandard();
/* 1-2 하루 최대 적립 포인트 횟수 기준 조회 */
// Service에서 따로 받아왔던 "point"가 인수로 들어간다.
public List<Map<String,Object>> getPointMaxCountStandard(String type);
// select : select 쿼리 사용 / delete , update 등 ... 있음
// id : mapper 인터페이스에서 선언할 메서드 이름
// parameterType : 파라미터 타입, 여기서는 받아오는 값이 없기 때문에 생략
// resultType : 반환 타입, service에서 LinkedHashMap을 쓰기로 했었음(순서 보장)
// 컬럼 별칭을 데이터 테이블에 그대로 넣어줄거기 때문에 한글로 작성했다.
// 나머지 4개 테이블은 생략...
<select id="getPointExpireStandard" resultType="java.util.LinkedHashMap">
/* 2-2 포인트 만료 기간 기준 조회 */
SELECT
CAST(SUBSTRING_INDEX(point_expire_standard_code,'code',-1) AS UNSIGNED) AS 'No',
point_expire_standard_code AS '유효 기간 코드',
point_expire AS '유효 기간',
CASE
WHEN code_use = 'Y' THEN '사용가능'
WHEN code_use = 'N' THEN '사용불가능'
END AS '코드 사용 유무',
set_datetime AS '최초 등록일',
admin_id AS '관리자 ID',
up_datetime AS '최종 수정일'
FROM
point_expire_standard
ORDER BY No
</select>
5. Controller에서 받아온 데이터 이용해서 동적으로 테이블 바꿔주기
// ajax 실행이 끝나고 받아온 값을 response라고 하자
done(function( response ) {
// 만약 response가 true일 경우 실행한다. (값이 있을 경우)
if(response){
// key(title) : <th> , value(data) : <td>에 담아주기 위해 배열 선언
// data가 null일 경우 기본 값을 넣어주는 것을 이용해서 0번 인덱스에 체크박스 넣기
const newData = [
{
data: null,
defaultContent: '<input type="checkbox" class="custom-control custom-checkbox checkbox checks">',
orderable: false,
searchable: false,
targets: 0
}
];
// response : 배열 , list : 객체 (배열 안에 객체 여러 개가 존재하는 형태)
for (let obj of response){
for(let key in obj){
newData.push({
title: key, // key 자체를 문자열로 컬럼에 넣어줄 예정
data: key // key와 일치하는 value를 행으로 넣어줄 예정 (data의 키)
});
}
break; // 어차피 컬럼은 객체 하나만 돌아도 되기 때문에 한 번만 돌고 반복문을 끝내준다.
}
// 동적으로 테이블을 수정할 때 받아온 데이터의 컬럼 개수와 html에 존재하는 th 태그의 개수가 다르면 오류가 발생한다.
// 그래서 th와 td를 한번 지워준다. (th의 체크박스는 살려둔다.)
// thead의 th와 tbody의 td 비우기
$('thead tr th:not(:first-child)').remove();
$('tbody').remove();
// 위에서 담아준 배열의 길이 = 새로 받아 온 컬럼의 개수
// 단, 체크박스를 미리 담아줬기 때문에 배열의 길이에서 -1 해줘야 함
// 그 만큼 for문을 돌면서 th태그를 붙여준다.
// 그러면 th태그의 개수와 받아온 데이터의 컬럼 개수가 같아진다
const newDataNum = newData.length-1;
for(let i = 0 ; i < newDataNum ; i += 1) {
$('thead tr').append('<th></th>');
}
// 데이터 테이블 초기화
// coulums는 위에서 컬럼만 담아준 배열을 넣어준다.
// data는 ajax 결과로 받아온 배열을 넣어준다.
// newData의 key가 title과 data이기 때문에 data에 넣어준 컬럼명과
// data가 받은 response의 key가 일치하는 값을 찾아서 넣어준다.
// 다른 옵션들은 생략...
$('#pointStandardManage').each(function() {
const table = $(this);
table.DataTable({
data: response,
columns: newData
});
});
)}
)}
완성~~
탭을 누를 때 마다 새로고침 되지 않고 테이블만 바로바로 바뀐다!


[ 데이터 테이블 사용할 때 주의할 점 ]
- 한 번 데이터 테이블을 초기화 하고 다시 초기화 하면 오류가 발생한다.

DataTables warning: table id=pointStandardManage - Cannot reinitialise DataTable.
For more information about this error, please see http://datatables.net/tn/3
- 한마디로 테이블을 초기화 할 수 없다는 뜻!
- 참고로 데이터 테이블 초기화는 데이터 테이블 설정을 해주는걸 초기화라고 한다.
- 나 같은 경우는 이미 페이지가 처음 로딩 되는 동시에 테이블을 초기화하도록 해놨는데
- 이후에 ajax를 통해 데이터를 받아서 동적으로 테이블을 수정할 때 다시 초기화를 해야했다.
- 그래서 초기화를 여러 번 할 때에는 먼저 데이터 테이블을 파괴한 후 초기화를 해야한다.
// 데이터 테이블 파괴
$('#pointStandardManage').DataTable().destroy();
// 데이터 테이블 초기화
$('#pointStandardManage').each(function() {
const table = $(this);
table.DataTable({
autoWidth : true,
lengthChange : false,
pageLength : 10,
searching : false,
info : false,
pagingType : "full_numbers",
columnDefs: [{
targets: 0,
searchable: false,
orderable: false,
className: 'sorting_disabled'
}],
data: response,
columns: newData
});
});