DatasetSupport
DatasetSupport란 Dataset의 데이터를 페이지의
- UI 요소에 표시
- UI 요소로 변경한 값을 Dataset에 적용
- 데이터 상태에 따라 UI 요소를 제어
하는 것을 돕는 자바스크립트 클래스들이다.
지원하는 DatasetSupport로는
- TableSupport
- PagingSupport
- CurrentDataSupport
가 있다.
시작하기
의존성 추가
DatasetSupport 클래스를 사용하려면 xit-web-res 모듈에 대한 dependency를 프로젝트에 추가해야 한다.
<dependency>
<groupId>cokr.xit.base</groupId>
<artifactId>xit-web-res</artifactId>
<version>23.04.01-SNAPSHOT</version>
</dependency>
프로젝트가 xit-foundation 모듈을 포함하면 자동으로 추가된다.
스크립트 링크 추가
위의 dependency를 추가한 후 JSP 파일에 다음과 같이 스크립트 파일에 대한 링크를 설정한다.
<script src="<c:url value="/webjars/js/base/base.js?${ver}"/>"></script>
<script src="<c:url value="/webjars/js/base/dataset.js?${ver}"/>"></script>
<script src="<c:url value="/webjars/js/base/dataset-support.js?${ver}"/>"></script>
Controller
서비스의 조회결과(데이터 + 페이징 정보)를 DatasetSupport가 사용하려면
Controller는 조회결과를 setPagingInfo(...)
메소드로 반환해야 한다.
CustomerService service = ...;
public ModelAndView getCustomerList(XXXQuery req) {
List<?> result = service.getList(req);
return setPagingInfo(
new ModelAndView("jsonView"), // ModelAndView
result, // 조회 결과
"customer" // 반환 객체들 이름의 접두사
);
}
위 메소드를 실행하면 조회결과를 다음과 같이 반환한다.
{
infoPrefix: "customer", // 반환 객체들 이름의 접두사
customerList: result, // 조회결과
customerPaging: { // 페이징 정보
start: 0, // 조회결과 데이터의 시작 인덱스(0부터 시작)
dataSize: 10, // 조회결과 중 반환하는 데이터 갯수
totalSize: 50, // 조회결과 데이터 전체 갯수
fetchSize: 10, // 한번에 반환하는 데이터 갯수
more: true, // 더 반환할 데이터가 있는지 여부
next: true, // 다음에 반환할 데이터가 있는지 여부
prev: false // 이전에 반환한 데이터가 있는지 여부
}
}
DatasetControl
콘트롤러가 위와 같이 반환한 조회결과를 사용하는 DatasetControl은 다음과 같이 선언한다.
var custCtrl = newCustomerControl({
prefix: "customer", // 조회결과 접두어, 필요할 경우에 설정
keys: ["CUST_ID"], // 조회결과의 키 필드 이름
doctx: "[data-doctx='customer'],#cust-form" // 조회결과 데이터를 사용할 element들의 부모 요소들의 selector
});
콘트롤러가 반환한 조회결과를 직접 DatasetControl에 설정하려면 다음과 같이 한다.
custCtrl.setData({
customerList: ${customerList},
customerPaging: ${customerPaging}
});
TableSupport
TableSupport는 DatasetControl의
- 데이터를 표시
- 사용자가 현재값로 지정한 행의 표시
- 사용자의 데이터 선택상태 표시
- 데이터의 로컬 정렬
- 데이터 상태에 따라 .enable-onfound, .enable-onselect 클래스를 갖는 UI 요소의 활성화 / 비활성화
을 지원한다.
TableSupport가 바인딩하는 테이블의 형태는 다음과 같다.
<div data-doctx="customer">
...
<table name="cust-table" ...>
<thead ...>
<tr><th...>No.</th>
<th...><%-- 전체 선택 / 선택 해제 --%>
<input onchange="custCtrl.select(this.checked);" type="checkbox"...>
</th>
<th data-sort="CUST_ID" ...>아이디</th> <%-- data-sort: 데이터 정렬 컬럼 지정 --%>
<th data-sort="CUST_NAME" ...>이름</th>
<th data-sort="PHONE_NO" ...>전화번호</th>
<th data-sort="EMAIL" ...>이메일</th>
<th ...>등록일자</th>
</tr>
</thead>
<tbody name="customerList"><%-- 조회결과를 표시할 테이블 body --%></tbody>
<%-- 조회결과를 테이블에 표시하기 위한 템플릿(필수)
{data-index}: 데이터 인덱스 마킹
{data-no}: 데이터 번호 마킹
data-index={data-index}: 데이터/행 식별을 위한 인덱스의 마킹
--%>
<template name="customerRow"><tr data-index="{data-index}">
<td {onclick} {ondblclick} ...>{data-no}</td>
<td ...>
<input name="data-index" value="{data-index}" type="checkbox"
onchange="custCtrl.select('{data-index}', this.checked);" ...>
</td>
<td {onclick} {ondblclick} ...>{CUST_ID}</td>
<td {onclick} {ondblclick} ...>{CUST_NAME}</td>
<td {onclick} {ondblclick} ...>{PHONE_NO}</td>
<td {onclick} {ondblclick} ...>{EMAIL}</td>
<td {onclick} {ondblclick} ...>{REG_DT}</td>
</tr></template>
<%-- 조회결과가 없을 때 테이블에 표시하기 위한 템플릿 --%>
<template name="customerNotFound">
<tr><td colspan="7" ...>고객 정보를 찾지 못했습니다.</td></tr>
</template>
</table>
...
<button class="enable-onfound">버튼 1</button>
<button class="enable-onselect">버튼 2</button>
</div>
TableSupport는 데이터를 갖는 DatasetControl과 연동하여 동작한다.
var custTable = new TableSupport({
ctrl: custCtrl, // 연동하는 DatasetControl 설정
table: "[name='cust-table']", // 연동 table
formatter: (tmpl, item) => tmpl // 템플릿 포맷 function
.replace(/{onclick}=""/gi, 'onclick="custCtrl.setCurrent(\'{data-index}\');"')
.replace(/{ondblclick}=""/gi, 'ondblclick="custCtrl.getCustomerInfo(\'{CUST_ID}\')"'),
selectionToggler: "th [type='checkbox']", // 전체 선택 / 선택 해제를 위한 체크박스
refreshOnModify: ["CUST_NAME", "PHONE_NO", "EMAIL"] // 값이 변경됐을 때 테이블을 갱신해야하는 필드 이름
});
위 설정으로 생성한 TableSupport는 대상 table의 template 중
- 첫번째를 데이터 행을 위한 템플릿
- 두번째를 데이터가 없을 때를 위한 템플릿
간주한다.
TableSupport가 사용할 template을 식별해야할 경우 다음 설정을 추가한다.
var custTable = new TableSupport({
...
tr: "[name='customerRow']",
notFound: "[name='customerNotFound']",
...
});
위와 같이 생성한 TableSupport는 연동한 DatasetControl의 이벤트 핸들러에서 필요한 메소드를 호출한다.
// 데이터셋 변경
custCtrl.onDatasetChange = (obj, option) => {
custTable.renderList(option); // table 표시
...
};
// 현재 데이터 설정
custCtrl.onCurrentChange = item => {
custTable.setCurrentRow(item); // 현재 tr 설정
...
};
// 데이터 선택 변경 -> 체크박스 표시
custCtrl.onSelectionChange = selected => custTable.setSelections(selected);
// 데이터 정렬 -> table 헤더 표시
custCtrl.onSort = sorter => custTable.updateSortables(sorter);
// 데이터 추가 / 치환 -> table 표시
custCtrl.onAppend = custCtrl.onReplace = () => custTable.renderList();
// 필드 데이터 변경 -> table 표시
custCtrl.onModify = (changed, item) => {
custTable.updateModified(changed);
...
};
PagingSupport
PagingSupport는 DatasetControl과 연동하여
- 데이터 페이징을 위한 링크를 생성
- 페이징 상태 정보를 나타내는 컨텐츠를 설정
한다.
PagingSupport는 다음과 같이 생성한다.
var custPaging = new PagingSupport({
ctrl: custCtrl, // 연동하는 DatasetControl
linkContainer: "[name='customerPaging']", // 생성하는 페이징 링크들의 부모 요소 selector
func: "custCtrl.load({index})" // 페이징 링크를 클랙했을 때 실행할 function
});
위와 같이 생성한 PagingSupport는 연동한 DatasetControl의 이벤트 핸들러에서 필요한 메소드를 호출한다.
// 데이터셋 변경
custCtrl.onDatasetChange = (obj, option) => {
...
custPaging.setPaging(option); // 페이징 표시
};
CurrentDataSupport
CurrentDataSupport는 DatasetControl과 연동하여
- 현재 데이터를 UI 요소에 표시
- UI 요소로 변경된 데이터를 DatasetControl의 데이터에 적용
- 현재 데이터 상태에 따라 .enable-ondirtyitem, .enable-onnewitem 클래스를 갖는 UI 요소의 활성화/
- 서버 전송 포맷으로 데이터 반환
- UI 요소 바인딩을 위한 새 데이터 반환
을 지원한다.
CurrentDataSupport가 바인딩하는 UI 요소는
- data-field 애트리뷰트로 DatasetControl 데이터의 컬럼이름을 지정한다.
- name, 또는 id 애트리뷰트로 서버로 데이터를 전송할 때 자바 클래스의 프로퍼티로 매핑한다.
다음은 그 예다.
<form id="cust-form" ...>
...
<input name="custId" type="text" required data-field="CUST_ID" class="enable-onnewitem ..."/>
<input name="custName" type="text" required data-field="CUST_NAME" .../>
<input name="address" type="text" data-field="ADDRESS" .../>
<input name="phoneNo" type="text" required data-field="PHONE_NO" .../>
<input name="email" type="text" data-field="EMAIL" .../>
<button onclick="custCtrl.saveCustomer();" type="button" class="enable-ondirtyitem ...">저장</button>
...
</form>
위의 UI 요소에 바인딩하는 CurrentDataSupport는 다음과 같이 생성한다.
var currentCust = new CurrentDataSupport({
ctrl: custCtrl,
selector: "[data-field]"
});
위 예에서 currentCust는
- custCtrl의 데이터와 연동
- cust-form의 data-field 애트리뷰트를 갖는 요소와 바인딩
한다.
위와 같이 생성한 CurrentDataSupport는 연동한 DatasetControl의 이벤트 핸들러에서 필요한 메소드를 호출한다.
// 현재 데이터 설정
custCtrl.onCurrentChange = item => {
...
currentCust.setCurrent(item); // 현재 데이터 표시
};
// 필드 데이터 변경 -> table 표시
custCtrl.onModify = (changed, item) => {
...
currentCust.updateModified(changed, item);
};
CurrentDataSupport.getData() 메소드는 현재 설정된 각 컬럼의 값을 자바 클래스의 프로퍼티로 매핑한 객체로 반환한다. 예를 들어 currentCust.getData()를 호출하면 다음 형태의 객체를 반환한다.
{
"custId": "2",
"custName": "고객 2",
"address": "주소 2",
"phoneNo": "010-2222-2222",
"email": "cust-02@xit.co.kr",
"useYN": "Y"
}
CurrentDataSupport.newData(function(data) {...}) 메소드는
- data-field 애트리뷰트로 지정한 컬럼들로 된 새 객체를 생성하고
- 초기화 함수를 적용
하여 반환한다.
다음은 위 예제 페이지의 data-field 컬럼으로 이루어진 새 객체를 만들고 DatasetControl의 데이터에 추가하는 예다.
var newData = currentCust.newData(data => {
data.USE_YN = "Y";
data.REG_DT = new Date();
});
custCtrl.dataset.append(newData);
newData는 다음과 같은 형태의 객체를 반환한다.
{
"CUST_ID": null,
"CUST_NAME": null,
"ADDRESS": null,
"PHONE_NO": null,
"EMAIL": null,
"USE_YN": "Y",
"REG_DT": "2024-08-19T08:47:04.711Z"
}