개요
웹 애플리케이션은 SPA(Single Page Application)로 동작하며 jsp로 작성한다.
웹 애플리케이션의 메인 페이지의 html의 구조는 다음과 같다.
<html>
<head>
<!-- 폰트, css 등의 정적 리소스 링크 -->
</head>
<body>
<!-- 공통 컨텐트
기능별 컨텐트
-->
<!-- 스크립트 링크 -->
<script src="..."></script>
<script src="..."></script>
<!-- 페이지 스크립트 -->
<script>
...
$(function() {
...
});
</script>
</body>
</html>
- 정적 리소스의 링크는 <head ../> 에 위치시킨다.
- <script../>는 <body../>의 가장 마지막에 위치시킨다.
- 기능별 화면은 <body../>의 기능별 컨텐트를 jsp로 구현하여 제공한다.
- 기능별 컨텐트를 구현하는 jsp에서도 <script../>는 가장 마지막에 위치시킨다.
서비스와의 통신
서비스와 http(s) 통신을 하려면 다음과 같은 function들을 사용한다.
- ajax(...)
- json(...)
- upload(...)
위 function들은 다음 설정을 파라미터로 받는다.
{
url: "...", // 서비스 url
data: {...}, // 파라미터
success: function(resp) {} // 성공시 실행하는 함수
}
ajax
- GET 방식으로 접속하려면 ajax.get({...})
- POST 방식으로 접속하려면 ajax.post({...})
json
- GET 방식으로 접속하려면 json.get({...})
- POST 방식으로 접속하려면 json.post({...})
upload
데이터와 파일을 업로드하려면 upload({...})를 사용한다.
데이터 바인딩
서비스에서 받은 데이터를 페이지에 표시하고 바인딩할 때는
- Dataset
- DatasetControl
을 사용한다.
Dataset
Dataset은
- 내부에 데이터를 갖는다.
- 데이터 설정, 선택, 추가, 수정, 제거를 위한 메소드를 실행하면
- 해당 메소드 실행 후 현재 상태를 알려주는 이벤트 핸들러를 실행한다.
- 주로 사용되는 이벤트 핸들러는 다음과 같다.
- onDatasetChange
- onCurrentChange
- onSelectionChange
DatasetControl
DatasetControl은
- 내부에 dataset을 갖는다.
- 서비스에 접속해 데이터를 받거나 전달하고
- 사용자의 데이터 변경을 dataset에 전달한다.
- 그 결과 발생하는 dataset의 이벤트를 자신의 이벤트 핸들러로 전달한다.
- 그 중 주로 사용되는 이벤트 핸들러는 다음과 같다.
- onDatasetChange
- onCurrentChange
- onSelectionChange
- 개발자는 DatasetControl의 이벤트 핸들러를 override하여 화면을 업데이트한다.
데이터 목록 표시
데이터 목록을 <table../>
로 표시하려면
- DatasetControl.onDatasetChange 이벤트 핸들러에서
- <template ../>의 컨텐트를 한 행(row)의 템플릿으로 하여
- 데이터 한 행의 값들로 html 컨텐트를 만든 후
- 대상 테이블의
<tbody../>
를 업데이트 한다.
테이블
다음은 사용자 목록을 표시할 table의 예다.
<table>
<thead>
<tr><th><input onchange="userControl.select(this.checked);" type="checkbox"></th>
<th>계정</th>
<th>이름</th>
<th>이메일</th>
<th>전화번호(무선)</th>
<th>등록일자</th>
</tr>
</thead>
<tbody id="userList">
</tbody>
<template id="userRow">
<tr data-key="{USER_ID}">
<td><input value="{USER_ID}" onchange="userControl.select('{USER_ID}', this.checked);" type="checkbox"></td>
<td {onclick} {ondblclick}>{USER_ACNT}</td>
<td {onclick} {ondblclick}>{USER_NM}</td>
<td {onclick} {ondblclick}>{EML_ADRS}</td>
<td {onclick} {ondblclick}>{MBL_TELNO}</td>
<td {onclick} {ondblclick}>{REG_DT}</td>
</tr>
</template>
<template id="userNotFound">
<tr><td valign="top" colspan="6" class="dataTables_empty text-center">사용자 정보를 찾지 못했습니다.</td>
</tr>
</template>
</table>
- template#userRow의
<tr data-key="...">
는 목록 중 현재 행이 달라졌을 때 찾기 위한 값을 갖는 애트리뷰트다. - template의 컨텐트 중
{...}
로 표시된 부분은 데이터의 필드값으로 치환할 부분을 나타낸다. - {onclick}, {ondblclick}은 DatasetControl의 이벤트 핸들러에서 직접 치환할 부분을 나타내는 커스텀 마킹이다.
다음은 위 table에 데이터를 표시하는 스크립트 예다.
var userControl = new UserControl();
...
userControl.setData({
userList:${userList},
userStart:${userStart},
userFetch:${userFetch},
userTotal:${userTotal}
});
...
userControl.onDatasetChange = obj => {
let userList = userControl.dataset,
trs = userList.empty ?
[document.getElementById("userNotFound").innerHTML] :
userList.inStrings(
document.getElementById("userRow").innerHTML,
(str, dataItem) => {
let userID = dataItem.getValue("USER_ID");
return str
.replace(/{onclick}=""/gi, 'onclick="userControl.setCurrent(\'{USER_ID}\');"')
.replace(/{ondblclick}=""/gi, 'ondblclick="userControl.getInfo(\'{USER_ID}\')"')
}
);
$("#userList").html(trs.join());
$("th input[type='checkbox']").prop("checked", false);
};
- userControl의 setData(...)를 호출하면 onDatasetChange(...) 이벤트 핸들러를 실행한다.
- userControl의 onDatasetChange(...) 메소드를 override한다.
- Dataset.inString(...) 메소드는 주어진 문자열에서
- 커스텀 마킹({onclick}, {ondblclick})을 처리하는 함수를 실행하고
- '{필드이름}' 부분을 치환한다.
페이징
페이징을 위한 컨텐트 생성도 onDatasetChange 핸들러에서 처리한다.
<ul id="userPaging" class="pagination pagination-primary">
</ul>
userControl.onDatasetChange = obj => {
...
$("#userPaging").setPaging({
list: userControl.dataset,
prefix: userControl.prefix,
start: obj.userStart,
totalSize: obj.userTotal,
fetchSize: obj.userFetch,
func: "userControl.load({index})"
});
};
현재 데이터행 표시
- 데이터 목록 중 사용자가 보는 현재 데이터 행을 변경시키려면 DatasetControl.setCurrent(...) 메소드를 호출한다.
- 현재 데이터 행이 변경되면 DatasetControl.onCurrentChange(...) 핸들러가 호출된다.
- 변경된 현재 데이터 행을 화면에 업데이트하려면 DatasetControl.onCurrentChange를 override한다.
다음은 사용자 목록의 현재 데이터 목록이 변경되면 테이블의 하이라이트 행을 업데이트하는 예다.
userControl.onCurrentChange = item => {
if (!item) return;
let key = item.data.USER_ID;
$("#userList").setCurrentRow(key);
};
데이터행 선택(체크)
- 데이터 목록 중 데이터행(들)을 선택(체크)하려면 DatasetControl.select(...) 메소드를 호출한다.
- 데이터행을 선택하면 DatasetControl.onSelectionChange(...) 핸들러가 호출된다.
- 데이터행 선택을 화면에 업데이트하려면 DatasetControl.onSelectionChange를 override한다.
다음은 사용자 목록의 데이터 행들을 선택하면 테이블의 해당 행의 체크박스를 업데이트하는 예다.
userControl.onSelectionChange = selected => {
let userList = userControl.dataset;
let keys = selected.map(e => userList.getKey(e));
$("#userList input[type='checkbox']").each(function() {
let checkbox = $(this);
checkbox.prop("checked", keys.includes(checkbox.val()));
});
};
데이터 폼 바인딩
데이터 목록 중 특정 행의 데이터를 폼으로 표시하거나 사용자의 데이터 변경을 포착하려면
데이터 행의 필드값을 표시할 요소(input, select, textarea...) 태그에
- 'id'나 'name' 애트리뷰트는 데이터 소유 객체의 프로퍼티 이름을 명시한다.
data-map="컬럼이름"
형식의 커스텀 애트리뷰트를 명시한다.
다음은 사용자 데이터를 표시하는 폼의 예다.
<form id="userForm">
<div>
<label for="account">계정</label>
<div>
<input name="account" type="text" required data-map="USER_ACNT" placeholder="사용자 계정" />
</div>
</div>
<div>
<label for="name">이름</label>
<div>
<input name="name" type="text" required data-map="USER_NM" placeholder="사용자 이름" />
</div>
</div>
<div>
<div>
<label for="birthday">생년월일</label>
<div>
<input name="birthday" type="text" data-map="BRDT" placeholder="YYYY-MM-DD"/>
</div>
</div>
<div>
<label for="gender">성별</label>
<div>
<div>
<input name="gender" value="M" type="radio" data-map="GENDER"/>
<label for="male">남자</label>
</div>
<div>
<input name="gender" value="F" type="radio" data-map="GENDER"/>
<label for="female">여자</label>
</div>
</div>
</div>
<div>
<label for="emailAddress">이메일 주소</label>
<div>
<input name="emailAddress" type="email" required data-map="EML_ADRS" placeholder="이메일 주소" />
</div>
</div>
</form>
다음 코드는 사용자 데이터를 위 폼에 바인딩하는 예다.
var userFields = new FormFields("#user-form");
...
userControl.setInfo = obj => {
...
userFields.set(userControl, obj);
..
};
폼에 설정된 데이터를 객체로 변환할 때는 FormFields.get()
메소드를 사용한다.
var userObj = userFields.get();
유효성 체크
데이터를 서비스로 보내기 전에 유효성을 체크하려면 폼의 요소(input, select, textarea...) 태그에
- type="...", required, pattern, maxLength, max, min, step 등의 애트리뷰트를 명시한다.
- 해당 요소를 대상으로 하는 `' 태그를 명시한다.
- 데이터를 서비스로 보내기 전에 `$("selector").validInputs()'를 호출하여 유효성을 체크한다.
다음은 위 사용자 폼의 유효성을 체크하는 코드다.
function saveUser() {
if (!$("#user-form").validInputs()) return;
...
}
위 코드의 결과 유효하지 않은 데이터가 있으면 다음과 같은 다이얼로그를 표시한다.
- validInputs()는 기본적인 유효성 체크만 실행한다.
- 보다 복잡한 유효성 체크는 개발자가 직접 구현한다.
DatasetSupport
Dataset의 데이터에 대한 바인딩을 지원하는 DatasetSupport를 참고한다.
다이얼로그
dialog.open(conf)
은
- 다이얼로그(팝업) 창을 표시한다.
- 간단한 메시지, 확인 메시지나 데이터 폼 등을 표시하는데 사용된다.
설정 파라미터
dialog.open(conf)
의 conf는 다이얼로그의 설정 파라미터로
- id: 다이얼로그의 아이디
- title: 다이얼로그의 제목
- content: 다이얼로그의 컨텐트(필수)
- init: 다이얼로그가 로드된 후 최초 실행할 function
- onOK: 사용자가 '확인' 버튼을 클릭했을 때 실행할 function
- onClose: 다이얼로그를 닫을 때 실행할 function
- size: 다이얼로그 크기(sm || lg || xl)
- timeout: 다이얼로그가 보여질 시간. 0이면 사용자가 닫을 때까지 대기
간단 메시지
간단한 메시지를 표시하려면 다음과 같이 한다.
dialog.alert("간단한 메시지");
확인 메시지
확인 메시지를 표시하려면 다음과 같이 한다.
dialog.alert({
content: "변경된 내용을 저장하시겠습니까?",
onOK: function() {...}
});