 |  |
|
난이도 : 중급 Andrei Cioroianu, 선임 자바 개발자 겸 컨설턴트, Devsphere
옮긴이: 백기선 dwkorea@kr.ibm.com
2008 년 9 월 16 일 본 연재의 Part 1에서 자바스크립트 코드를 사용하여 Ajax 요청을 보내고 Ajax 응답을 처리하는 방법을 살펴보았습니다. Part2 에서는 규약과 JSP 태그 파일을 사용하여 셋업과 설정을 최소화하는 형태로 HTML 폼을 생성하는 방법을 알아보았습니다. 이번 Part 3에서는, 서버 쪽 검증은 물론 자바스크립트 기반의 클라이언트 쪽 검증을 개발하는 방법을 살펴볼 것입니다. JSP 태그 파일을 사용하여 자바스크립트쪽을 보조하여 구현하겠습니다. 애플리케이션을 다시 시작할 필요 없이 변경이 있을 때 자동으로 다시 읽어 들이는 리소스-번들 사용법도 배우겠습니다.
클라이언트 쪽 검증은 에러 수정을 요하는 폼을 사용자에게 다시 보내는 횟수를 줄이거나 아예 없애주기 때문에 유용하다. 그렇지만, 자바스크립트 기반 검증만으로는 충분하지 않다. 프로그램을 사용해 서버로 전송되는 데이터를 직접 넣어 주거나 사용자 브라우저에서 자바스크립트 사용을 금지했을 수도 있기 때문이다. 본 기사에서는 클라이언트와 서버 쪽 검증 모두 살펴보겠다.
클라이언트 쪽 검증 계층 구조 만들기
이 절에서는 사용자가 웹 브라우저에 입력한 값을 자바스크립트로 검사하는 객체 계층 구조를 만들 때, 생성자와 프로토타입을 사용하는 방법을 살펴보겠다. 이 절에 있는 코드는 샘플 애플리케이션(다운로드 참조)에 있는 valid.js 파일에서 참조할 수 있다.
기본 검사기(validator) 만들기
자바스크립트에서는 특정 함수와 new 연산자를 같이 사용해 객체를 만들 수 있다. 이른바 "생성자"라 부르는 것들은 객체의 속성을 초기화할 때 반드시 this를 사용해야 한다. Listing 1은 Validator() 함수를 보여준다. 이 함수는 네 개의 속성으로 폼 이름, 엘리먼트 이름, ID, 메시지를 받고 있다. 이 매개변수의 값들은 현재 객체의 속성으로 저장된다.
같은 생성자를 사용한 객체들은 prototype 객체가 가지고 있는 속성들을 공유할 수 있다. Listing 1에서 defaultMsgIndex는 new Validator(...)를 사용하여 생성한 모든 객체가 공유할 수 있는 속성이다. 객체의 속성이나 메서드에 접근하고자 할 때, 자바스크립트 엔진은 객체의 멤버들부터 찾아본다. 해당 속성이나 메서드가 거기에 없다면, 엔진은 객체의 프로토타입을 확인한다. 그리고 그 프로토타입의 프로토타입까지 계속 찾아 보는 식이다. 따라서 나중에 살펴보겠지만, 프로토타입 체인은 상속을 구현하는 데 이용할 수 있다. 본 기사의 예제에서 Validator는 객체 상속 구조에서 기본이 된다.
Listing 1. Validator() 생성자
function Validator(formName, elemName, outId, msg) {
if (formName)
this.formName = formName;
if (elemName)
this.elemName = elemName;
if (outId)
this.outId = outId;
if (msg)
this.msgList = ["", msg];
}
Validator.prototype.defaultMsgIndex = 0;
|
모든 자바스크립트 함수는 모든 객체의 메서드가 될 수 있다. 객체 생성자 내부에서 this.methodName = functionName 형태로 사용하면 된다. 메서드는 보통 같은 생성자를 사용하여 만든 객체들이 공통으로 사용하기 때문에, Listing 2처럼 constructorName.prototype.methodName 형태로 생성자의 프로토타입에 메서드를 연관시킬 수 있다.
Listing 2. 검사기의 getFormValues() 메서드
Validator.prototype.getFormValues = function() {
var elemValues = new Array();
var form = document.forms[this.formName];
var formElements = form.elements;
for (var i = 0; i < formElements.length; i++) {
var element = formElements[i];
if (element.name == this.elemName) {
var elemType = element.type.toLowerCase();
if (elemType == "text" || elemType == "textarea"
|| elemType == "password" || elemType == "hidden")
elemValues[elemValues.length] = element.value;
else if (elemType == "checkbox" && element.checked)
elemValues[elemValues.length]
= element.value ? element.value : "On";
else if (elemType == "radio" && element.checked)
elemValues[elemValues.length] = element.value;
else if (elemType.indexOf("select") != -1)
for (var j = 0; j < element.options.length; j++) {
var option = element.options[j];
if (option.selected) {
elemValues[elemValues.length]
= option.value ? option.value : option.text;
}
}
}
}
return elemValues;
}
|
Listing 2에 있는 getFormValues() 메서드는 elemName 속성값과 동일한 이름을 가진 폼 엘리먼트의 값을 반환한다. 브라우저에서 HTML 폼을 표현하는 DOM 객체는 document.forms[this.formName]을 사용해 얻어온다. getFormValues()는 얻어온 elements를 순회하면서, 각각의 엘리먼트를 HTML 타입에 맞게 다룬다.
텍스트, 패스워드 또는 감춰진 필드일 경우 getFormValues() 메서드는 엘리먼트의 값을 반환하는 배열에 추가한다. 엘리먼트의 타입이 체크박스 또는 라디오 버튼일 경우에는 HTML 엘리먼트가 체크된 상태일 때만 추가한다. 마지막으로 엘리먼트가 리스트일 경우 선택된 아이템의 값들을 반환하는 배열에 담는다.
계층 구조에 있는 각각의 Validator는 단일 값을 확인하는 메서드를 필요로 할 것이다. 기본 Validator의 경우, verify() 메서드(Listing 3 참조)가 0을 반환하지만, 다른 Validator들은 자신만의 로직으로 메서드를 교체해야 한다. verify()가 반환하는 값은 msgList에 있는 메시지를 꺼내올 때 사용한다.
Listing 3. Validator의 verify() 메서드
Validator.prototype.verify = function(value) {
return 0;
}
|
showMsg() 메서드(Listing 4 참조)는 innerHTML 속성을 사용해 메시지를 HTML 엘리먼트에 넣는다. HTML 엘리먼트를 표현하는 객체는 document.getElementById(this.outId)를 사용해 가져온다. 메시지 인코딩은 htmlEncode()를 사용한다. 이 함수는 본 연재 Part 1에서 살펴봤다. 이 코드 전문은 encode.js에서 찾아볼 수 있다.
Listing 4. Validator의 showMsg() 메서드
Validator.prototype.showMsg = function(index) {
document.getElementById(this.outId).innerHTML
= htmlEncode(this.msgList[index]);
}
|
Listing 5는 execute() 메서드다. 이 메서드는 getFormValues()가 반환하는 값들을 순회하면서 verify() 메서드를 호출한다. execute() 메서드 마지막에 msgIndex 값이 0이면 사용자가 입력한 값이 유효한 것으로 판단한다. 이 경우, showMsg()는 결과로 보여줄 엘리먼트의 innerHTML 속성에 비어있는 문자열을 넣어 이전 메시지를 없앤다. 그렇지 않을 경우, showMsg()를 호출한 뒤 웹 페이지에 에러 메시지가 나타날 것이다.
Listing 5. Validator의 execute() 메서드
Validator.prototype.execute = function() {
var msgIndex = this.defaultMsgIndex;
var elemValues = this.getFormValues();
for (var i = 0; i < elemValues.length; i++)
if (elemValues[i]) {
msgIndex = this.verify(elemValues[i]);
if (msgIndex != 0)
break;
}
this.showMsg(msgIndex);
return msgIndex == 0;
}
|
구체적인 Validator 만들기
valid.js 파일에는 요청 필드에 대한 검사기가 들어있다. The RequiredValidator() 생성자(Listing 6)는 Validator() 함수와 같은 매개변수를 취한다. Validator()로 객체 속성을 초기화하기 위해 RequiredValidator() 생성자는 base()라는 이름의 메서드를 세팅한다. base()의 도움으로 Validator()를 호출한 후, RequiredValidator()는 현재 객체에서 base() 메서드를 지운다. 더 이상 필요하지 않기 때문이다.
Listing 6. RequiredValidator() 생성자와 그 프로토타입 세팅
function RequiredValidator(formName, elemName, outId, msg) {
this.base = Validator;
this.base(formName, elemName, outId, msg);
delete this.base;
}
RequiredValidator.prototype = new Validator();
RequiredValidator.prototype.defaultMsgIndex = 1;
RequiredValidator.prototype.verify = function(value) {
return value.length > 0 ? 0 : 1;
}
|
모든 RequiredValidator 객체에 대한 프로토타입은 속성이 없는 Validator 객체로, Validator() 생성자에 인자를 넘겨줄 수 없기 때문이다. 이 프로토타입을 설정함으로써, RequiredValidator는 Validator의 프로토타입에 정의해둔 속성과 메서드를 "상속" 받는다.
예를 들어, RequiredValidator 객체에 execute() 메서드를 호출하면, 자바스크립트 엔진은 먼저 해당 객체와 연관된 메서드를 찾는다. RequiredValidator가 execute() 메서드를 가지고 있지 않기 때문에, 다음으로 이 객체의 프로토타입에서 찾게 된다. 그렇게 쭉 따라서 프로토타입의 프토토타입까지 가게 된다. 이 예제에서, RequiredValidator의 프로토타입은 Validator 객체이고 이 객체의 프토토타입이 execute() 메서드를 가지고 있다.
RequiredValidator의 프토토타입의 defaultMsgIndex 속성에는 1을 설정했다. 엘리먼트의 값을 분실했을 때 에러를 신호화해야 하기 때문이다. verify() 메서드는 값이 비어있지 않으면 0을 반환하고 필수 속성이 입력되지 않았으면 1을 반환한다.
Listing 7은 LengthValidator 코드다. 이 코드는 RequiredValidator와 매우 흡사하다. LengthValidator() 생성자는 일곱 개의 매개변수를 받는다. 폼 이름, 검사해야 하는 값을 가지고 있는 엘리먼트의 이름, 에러 메시지를 출력할 때 사용할 엘리먼트의 ID, 검사해야 할 값의 최소 길이와 최대 길이, 두 개의 에러 메시지다. LengthValidator의 프로토타입은 새로운 Validator 객체로 RequiredValidator와 같은 경우다. verify() 메서드는 값이 유요하면 0을, 길이가 너무 짧으면 1을, 길이가 너무 길면 2를 반환한다.
Listing 7. LengthValidator() 생성자와 프로토타입 설정
function LengthValidator(formName, elemName, outId, min, max, msgMin, msgMax) {
this.base = Validator;
this.base(formName, elemName, outId);
delete this.base;
this.min = min;
this.max = max;
this.msgList = ["", msgMin, msgMax];
}
LengthValidator.prototype = new Validator();
LengthValidator.prototype.verify = function(value) {
if (value.length < this.min)
return 1;
if (value.length > this.max && this.max > 0)
return 2;
return 0;
}
|
웹 페이지에서 Validator 관리를 간편하게 하기 위해, valid.js 파일에 ValidatorList() 생성자(Listing 8 참조)를 정의하고 있다. 이 생성자는 Validator 배열을 감싸는 객체를 생성한다. add() 메서드는 Validator 객체를 배열에 추가한다. execute() 메서드는 Validator 목록을 순회하면서, 주어진 이름과 추가로 주어진 엘리먼트 이름에 해당하는 속성을 가진 Validator에 execute() 메서드를 호출한다. 따라서 ValidatorList의 execute() 메서드를 사용해 전체 웹 폼을 검사하거나, 폼 엘리먼트 중 하나만을 검사할 때 사용할 수 있다.
Listing 8. ValidatorList() 생성자와 프로토타입 설정
function ValidatorList() {
this.validatorArray = new Array();
}
ValidatorList.prototype.add = function(validator) {
this.validatorArray[this.validatorArray.length] = validator;
}
ValidatorList.prototype.execute = function(formName, elemName) {
var valid = true;
for (var i = 0; i < this.validatorArray.length; i++) {
var validator = this.validatorArray[i];
if (validator.formName == formName)
if (!elemName || validator.elemName == elemName)
if (!validator.execute())
valid = false;
}
return valid;
}
var pageValidators = new ValidatorList();
|
서버 쪽 Validator 라이브러리 만들기
이전 절에서는 자바스크립트를 사용하여 사용자의 값을 클라이언트 쪽에서 검사하는 방법을 살펴봤다. 폼 데이터가 일단 제출되면, 보안 상의 이유나 사용자 브라우저에서 자바스크립트 기능을 꺼두었을 때를 대비하여 서버 쪽에서 다시 검증을 거치는 것이 좋은 습관이다. 이번 절에서는 Validator로 사용하는 몇몇 JSP 태그 파일을 살펴보겠다.
태그 파일은 서버 검증을 구현하는 쉬운 방법이다. JSP 문법을 사용하고 있으며 여러 페이지에서 재사용할 수 있기 때문이다. 또한, 같은 태그 파일로 웹 브라우저에서 검증을 하는 자바스크립트 코드도 생성할 수 있다. 이 기술은 웹 페이지에서 Validator의 속성을 한 번만 기술하기 때문에 코딩 양을 줄여준다.
JSP 태그 파일을 사용하여 Validator 만들기
앞서 살펴본 자바스크립트 Validator는 innerHTML의 도움을 받아 메시지를 보여주려면 <span> 엘리먼트가 있어야 한다. 이 엘리먼트는 errMsg.tag 파일(Listing 9 참조)이 생성한다.
Listing 9. errMsg.tag 파일
<%@ attribute name="id" required="true" rtexprvalue="true" %>
<%@ tag body-content="scriptless" %>
<span id="${id}" class="ValidationError"><jsp:doBody/></span>
|
valid.css 파일(Listing 10 참조)은 ValidationError 스타일 클래스를 정의하며, 이것을 에러 메시지가 사용한다.
Listing 10. valid.css 파일
.ValidationError { color: red; }
|
required.tag 파일(Listing 11 참조)은 서버 쪽에서 필수 필드를 검사하고 자바스크립트 코드 조각을 생성하여 클라이언트 쪽에서 RequiredValidator를 설정하고 있다. 이 태그 파일은 두 개의 속성을 정의한다. 필수 필드의 이름과 부가적인 에러 메시지다.
msg 속성이 제공되지 않으면, required.tag는 <dvu:useDefaultMsg/>를 사용하여 다른 태그 파일을 호출한다. 그렇게 해서 defaultValidMsg 맵을 설정하는데, 이 안에는 validator의 기본 메시지들이 들어있다. 결과적으로, required 메시지를 msg 변수에 담는다.
다음으로, required.tag 파일은 <dvu:errMsg>를 사용하여 <span> 엘리먼트를 생성한다. 이 엘리먼트는 에러 메시지가 존재하면 그것을 감싸고 있다. 넘겨받은 이름에 해당하는 요청 매개변수가 없다면, 사용자가 필수 요소를 비어있는 상태로 뒀다는 것이다. 그럼 태그 파일은 <c:out>으로 메시지를 출력하고 <dfu:addError> 태그로 에러를 맵핑한다. 이 태그는 본 연재의 Part 2에서 살펴봤다.
그 다음, required.tag 파일은 웹 브라우저가 실행할 약간의 자바스크립트 코드를 생성한다. 이 코드는 RequiredValidator 객체를 생성한다. 이 객체는 자바스크립트 pageValidators라는 validator 묶음에 추가된다. 본 연재의 앞 절에서 RequiredValidator를 살펴봤다. RequiredValidator() 생성자로 넘길 문자열 매개변수는 <da:jstring> 태그를 사용해서 인코딩한다. 이 태그는 본 연재의 Part 1에서 살펴봤다.
Listing 11. required.tag 파일
<%@ attribute name="name" required="true" rtexprvalue="true" %>
<%@ attribute name="msg" required="false" rtexprvalue="true" %>
<%@ tag body-content="empty" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="da" tagdir="/WEB-INF/tags/dynamic/ajax" %>
<%@ taglib prefix="dfu" tagdir="/WEB-INF/tags/dynamic/forms/util" %>
<%@ taglib prefix="dvu" tagdir="/WEB-INF/tags/dynamic/valid/util" %>
<c:if test="${empty msg}">
<dvu:useDefaultMsg/>
<c:set var="msg" value="${defaultValidMsg.required}"/>
</c:if>
<c:set var="outId" value="${name}Required"/>
<dvu:errMsg id="${outId}">
<c:if test="${formPost && empty param[name]}">
<c:out value="${msg}"/>
<dfu:addError name="${name}" msg="${msg}"/>
</c:if>
</dvu:errMsg>
<script type="text/javascript">
pageValidators.add(new RequiredValidator(
<da:jstring value="${formName}"/>, <da:jstring value="${name}"/>,
<da:jstring value="${outId}"/>, <da:jstring value="${msg}"/>));
</script>
|
length.tag 파일(Listing 12 참조)은 또 다른 검사기이며 요청 매개변수의 길이를 검사한다. 검사 대상이 되는 매개변수는 보통 폼 엘리먼트에서 제출한 값이다. 태그 파일은 다섯 개의 속성을 가지고 있다. 엘리먼트 이름, 최소 길이, 최대 길이, 두 개의 부가적인 에러 메시지다. 태그 파일은 <span> 엘리먼트를 생성해 에러 메시지를 보여주고 이것은 required.tag의 경우처럼 <dvu:errMsg>가 생성한다. length.tag가 생성한 자바스크립트 코드는 클라이언트에서 LengthValidator 객체를 생성한다.
Listing 12. length.tag 파일
<%@ attribute name="name" required="true" rtexprvalue="true" %>
<%@ attribute name="min" required="false" rtexprvalue="true"
type="java.lang.Long" %>
<%@ attribute name="max" required="false" rtexprvalue="true"
type="java.lang.Long" %>
<%@ attribute name="msgMin" required="false" rtexprvalue="true" %>
<%@ attribute name="msgMax" required="false" rtexprvalue="true" %>
<%@ tag body-content="empty" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="da" tagdir="/WEB-INF/tags/dynamic/ajax" %>
<%@ taglib prefix="dfu" tagdir="/WEB-INF/tags/dynamic/forms/util" %>
<%@ taglib prefix="dvu" tagdir="/WEB-INF/tags/dynamic/valid/util" %>
<dvu:useDefaultMsg/>
<c:if test="${empty msgMin}">
<c:set var="msgMin" value="${defaultValidMsg.too_short}"/>
</c:if>
<c:if test="${empty msgMax}">
<c:set var="msgMax" value="${defaultValidMsg.too_long}"/>
</c:if>
<c:set var="outId" value="${name}Length"/>
<dvu:errMsg id="${outId}">
<c:if test="${formPost && !empty param[name]}">
<c:if test="${min != null && fn:length(param[name]) < min}">
<c:out value="${msgMin}"/>
<dfu:addError name="${name}" msg="${msgMin}"/>
</c:if>
<c:if test="${max != null && fn:length(param[name]) > max}">
<c:out value="${msgMax}"/>
<dfu:addError name="${name}" msg="${msgMax}"/>
</c:if>
</c:if>
</dvu:errMsg>
<script type="text/javascript">
pageValidators.add(new LengthValidator(<da:jstring value="${formName}"/>,
<da:jstring value="${name}"/>, <da:jstring value="${outId}"/>,
${min != null ? min : 0}, ${max != null ? max : 0},
<da:jstring value="${msgMin}"/>, <da:jstring value="${msgMax}"/>));
</script>
|
JSP 태그 파일로 리소스 번들 생성하기
자바(Java™) 애플리케이션에서 메시지는 보통 리소스 번들에 담는다. 이 번들은 일반적으로 .properties 파일에 코딩하며 이 파일을 CLASSPATH에 둔다. 리소스 번들에 있는 무언가를 변경하면, 애플리케이션을 다시 실행하여 .properties 파일을 리프레시해야 한다. 애플리케이션을 재실행하고 싶지 않다면, 메시지와 다른 텍스트 자원들을 JSP 태그 파일에 두면 된다. 애플리케이션 서버는 자동으로 태그 파일이 변경될 때마다 다시 읽어준다.
Listing 13은 useDefaultMsg.tag 파일을 보여준다. 이 파일은 validator의 기본 메시지를 정의하고, 그것들을 JSP request 스코프에 있는 <jsp:useBean>으로 생성한 java.util.HashMap 객체로 그룹핑한다. 메시지들은 <c:set> JSTL 태그를 사용해 맵에 넣는다. 모든 요청마다, useDefaultMsg.tag 파일은 ${pageContext.request.locale}에 담겨 있는 사용자가 선호하는 위치 정보를 기반으로 validator에서 사용할 메시지를 맵에서 선택한다.
Listing 13. useDefaultMsg.tag 파일
<%@ tag body-content="scriptless" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<c:if test="${defaultValidMsg_en == null}">
<jsp:useBean id="defaultValidMsg_en" scope="request" class="java.util.HashMap">
<c:set target="${defaultValidMsg_en}" property="required" value="Required"/>
<c:set target="${defaultValidMsg_en}" property="too_short" value="Too short"/>
<c:set target="${defaultValidMsg_en}" property="too_long" value="Too long"/>
</jsp:useBean>
<c:if test="${!initParam.debug}">
<c:set var="defaultValidMsg_en" scope="application"
value="${requestScope.defaultValidMsg_en}"/>
</c:if>
</c:if>
...
<c:if test="${defaultValidMsg == null}">
<c:choose>
<c:when test="${fn:startsWith(pageContext.request.locale, 'en')}">
<c:set var="defaultValidMsg" scope="request"
value="${defaultValidMsg_en}"/>
</c:when>
...
</c:choose>
</c:if>
|
본 예제는 자동 리로딩이 가능한 리소스를 구현하는 방법을 보여주고 있다. 실제 애플리케이션에서, 아마도 이 메커니즘을 활용하여 리소스를 정의하는 더 간단한 문법을 만들 수도 있을 것이다. 예를 들어, <c:set> 대신에 커스텀 태그를 사용해 모든 메시지의 target 속성을 기술할 수도 있다. 또한 번역하기 수월하도록 각각의 위치 정보에 따라 리소스 파일을 분리하고 싶을 수도 있다.
개발을 할 때는, web.xml 파일에 debug 매개변수를 true로 설정하는 것이 좋다(Listing 14 참조). 그렇게 해서 모든 메시지 맵을 요청을 할 때마다 다시 생성하게 할 수 있다. 그렇게 해야만 웹 애플리케이션을 다시 시작하지 않고도 모든 변경 사항을 반영할 수 있다. 실제 제품 배포 환경에서는, debug 매개변수를 false로 바꿔 application 스코프에서 맵을 캐싱할 수 있게 하는 것이 좋다.
Listing 14. web.xml 파일
<web-app ...>
<context-param>
<param-name>skin</param-name>
<param-value>default</param-value>
</context-param>
<context-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</context-param>
</web-app>
|
웹 폼에서 자바스크립트 검사기 활성화하기
지금까지 사용자가 입력한 값을 클라이언트와 서버 쪽에서 검증하는 방법을 살펴보았다. 다음으로, 자바스크립 기반 검증을 웹 브라우저에서 어떻게 활성화하는지 익힐 것이다. 폼 엘리먼트의 onblur 속성은 엘리먼트가 키보드 포커스를 잃었을 때 특정 자바스크립트를 실행할 수 있게 해준다. 이것을 이용해 사용자가 입력한 값을 바로 확인할 수 있다. 또한, <form> 태그의 onsubmit 속성을 사용하여 서브밋 버튼을 누르자마자 특정 자바스크립트를 실행할 수 있다. 그때 사용자가 입력한 값이 유효하지 않다면, false를 반환하여 폼 처리를 취소할 수 있다.
본 연재의 Part 2에서 JSP 태그 파일을 사용해 HTML 폼을 생성하는 방법을 살펴봤다. 이 검증 기능을 추가하기 위해 그 때 살펴본 태그들을 변경할 필요는 없다. 태그 파일이 attrList.tag 파일(Listing 15 참조)을 사용하여 폼 엘리먼트의 동적 속성들을 생성하기 때문이다. Part 2에서 이미 attrList.tag 파일에 기본 스타일을 추가하는 방법을 살펴봤다. 여기서는 이 파일을 수정해 onblur와 onsubmit 속성을 추가했다. 이 속성들은 자바스크립트 validator를 실행하는 함수 호출을 담고 있다.
class, onblur, onsubmit 속성들은 addMap이라는 이름의 java.util.HashMap에 담겨있는 HTML 엘리먼트에 추가되어야 한다. 그럼, attrList.tag는 map에 있는 엘리먼트들을 순회하면서 해당 엘리먼트에 HTML 속성을 생성해준다.
Listing 15. attrList.tag 파일
<%@ attribute name="tag" required="true" rtexprvalue="true" %>
<%@ attribute name="map" required="true" rtexprvalue="true"
type="java.util.Map" %>
<%@ tag body-content="empty" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<jsp:useBean id="addMap" class="java.util.HashMap"/>
<c:if test="${!empty initParam.skin}">
<c:set target="${addMap}" property="class"
value="${initParam.skin}_${tag}"/>
</c:if>
<c:if test="${tag=='input_text' || tag=='input_password' || tag=='input_radio'
|| tag=='textarea' || tag=='select'}">
<c:set target="${addMap}" property="onblur"
value="pageValidators.execute('${formName}', this.name)"/>
</c:if>
<c:if test="${tag=='form'}">
<c:set target="${addMap}" property="onsubmit"
value="return pageValidators.execute('${formName}')"/>
</c:if>
<c:forEach var="attr" items="${map}">
<c:if test="${addMap[attr.key] == null}">
${attr.key}="${fn:escapeXml(attr.value)}"
</c:if>
<c:if test="${addMap[attr.key] != null}">
<c:set var="sep" value="${fn:startsWith(attr.key, 'on') ? ';' : ' '}"/>
<c:set target="${addMap}" property="${attr.key}"
value="${attr.value}${sep}${addMap[attr.key]}"/>
</c:if>
</c:forEach>
<c:forEach var="attr" items="${addMap}">
${attr.key}="${fn:escapeXml(attr.value)}"
</c:forEach>
|
예제 애플리케이션에 검증 추가하기
본 연재의 Part 2에서 폼 태그 파일을 사용한 JSP 예제가 있었다. 이번 절에서는 해당 웹 폼에 검증을 추가해보겠다.
웹 폼 수정하기
Listing 16은 SupportForm.jsp 페이지를 수정한 것이다. 첫 번째 변경 사항은 <%@page%> 지시자로, 이것은 autoFlush="false"와 buffer="64kb" 속성을 가지고 있다. 기본적으로, JSP 페이지는 버퍼가 8kb다. 동적으로 HTML을 생성하는 이 버퍼가 꽉 차면 자동으로 플러시(flush)한다. 버퍼를 플러시하면, 해당 페이지는 더 이상 <jsp:forward>를 사용할 수 없다.
SupportForm.jsp에서 사용한 <df:form> 태그를 구현하는 form.tag 파일은 사용자가 입력한 값이 유효할 때 <jsp:forward>를 사용하여 HTTP 요청을 SupportConfirm.jsp 페이지로 보낸다. 검증 코드를 추가하여, SupportForm.jsp가 생성하는 HTML 크기가 8kb를 넘게 됐다. 따라서 페이지 버퍼도 같이 늘려 포워딩이 제대로 동작하도록 해야 한다. 또한, autoFlush는 기능을 사용하지 않도록 해서 웹 페이지가 생성한 전체 HTML이 버퍼를 넘지 않아도 디버깅이 수월하도록 한다.
SupportForm.jsp 페이지 헤더 부분에 추가 변경 사항을 발견했을 것이다. 여기서 검증 태그라이브러리(dv 접두어), valid.css 파일, 두 개의 자바스크립트 파일(encode.js와 valid.js)을 선언했다. 이 안에는 HTML 인코딩과 클라이언트 쪽 검증을 처리하는 자바스크립트 함수가 들어있다. SupportForm.jsp 페이지 전반에 걸쳐, 대부분의 폼 요소는 <dv:required>가 처리하고 몇몇은 <dv:length>가 입력한 문자열의 길이를 확인하고 있다.
Listing 16. SupportForm.jsp 예제
<%@ page autoFlush="false" buffer="64kb"%>
<%@ taglib prefix="df" tagdir="/WEB-INF/tags/dynamic/forms" %>
<%@ taglib prefix="dv" tagdir="/WEB-INF/tags/dynamic/valid" %>
<jsp:useBean id="supportBean" scope="request" class="formsdemo.SupportBean"/>
<html>
<head>
<title>Support Form</title>
<link rel="stylesheet" href="forms.css" type="text/css">
<link rel="stylesheet" href="valid.css" type="text/css">
<script src="encode.js" type="text/javascript">
</script>
<script src="valid.js" type="text/javascript">
</script>
</head>
<body>
<h1>Support Form</h1>
<df:form name="supportForm" model="${supportBean}"
action="SupportConfirm.jsp">
<p>Name:
<dv:required name="name"/>
<dv:length name="name" max="60"/> <br>
<df:input name="name" type="text" size="40"/>
<p>Email:
<dv:required name="email"/>
<dv:length name="email" max="60"/> <br>
<df:input name="email" type="text" size="40"/>
<p>Versions:
<dv:required name="versions"/> <br>
<df:select name="versions" multiple="true" size="5">
<df:option>2.0.1</df:option>
<df:option>2.0.0</df:option>
<df:option>1.1.0</df:option>
<df:option>1.0.1</df:option>
<df:option>1.0.0</df:option>
</df:select>
<p>Platform:
<dv:required name="platform"/> <br>
<df:input name="platform" type="radio" value="Windows"/> Windows <br>
<df:input name="platform" type="radio" value="Linux"/> Linux <br>
<df:input name="platform" type="radio" value="Mac"/> Mac <br>
<p>Browser:
<dv:required name="browser"/> <br>
<df:select name="browser" size="1">
<df:option value=""></df:option>
<df:option value="IE">IE</df:option>
<df:option value="Firefox">Firefox</df:option>
<df:option value="Netscape">Netscape</df:option>
<df:option value="Mozilla">Mozilla</df:option>
<df:option value="Opera">Opera</df:option>
<df:option value="Safari">Safari</df:option>
</df:select>
<p><df:input name="crash" type="checkbox"/> Causes browser crash
<p>Problem:
<dv:required name="problem"/>
<dv:length name="problem" min="100" max="2000"
msgMin="Cannot have less than 100 characters"
msgMax="Cannot have more than 2000 characters"/> <br>
<df:textarea name="problem" rows="10" cols="40"/>
<p><df:input name="submit" type="submit" value="Submit"/>
</df:form>
</body>
</html>
|
생성된 HTML 분석하기
SupportForm.jsp 페이지가 생성한 HTML을 살펴보면(그 일부로 Listing 17 참조), 클라이언트 쪽 검증과 관련된 다음 속성과 코드 조각 들을 살펴볼 수 있을 것이다.
onsubmit 속성을 가지고 있는 <form> 엘리먼트: 전체 폼이 유효할 때만 true를 반환하는 자바스크립트를 호출한다.
<input>, <textarea>, <select> 엘리먼트는 키보드 포커스를 잃자마자 그 값들을 검사하기 위해 onblur 속성(attribute)을 사용한다.
- 각각의 validator 태그는 다음을 생성한다.
- 에러 메시지를 삽입할
<span> 엘리먼트
- validator 객체를 생성하는 자바스크립트 코드
Listing 17. SupportForm.jsp가 생성한 HTML
<html>
<head>
<title>Support Form</title>
<link rel="stylesheet" href="forms.css" type="text/css">
<link rel="stylesheet" href="valid.css" type="text/css">
<script src="encode.js" type="text/javascript">
</script>
<script src="valid.js" type="text/javascript">
</script>
</head>
<body>
<h1>Support Form</h1>
<form name="supportForm" method="POST" class="default_form"
onsubmit="return pageValidators.execute('supportForm')">
...
<p>Email:
<span id="emailRequired" class="ValidationError">
</span>
<script type="text/javascript">
pageValidators.add(new RequiredValidator(
"supportForm", "email", "emailRequired", "Required"));
</script>
<span id="emailLength" class="ValidationError">
</span>
<script type="text/javascript">
pageValidators.add(new LengthValidator(
"supportForm", "email", "emailLength", 0, 60,
"Too short", "Too long"));
</script>
<br>
<input name="email" type="text" size="40" class="default_input_text"
onblur="pageValidators.execute('supportForm', this.name)">
...
</form>
</body>
</html>
|
결론
본 기사에서, 자바스크립트 생성자와 프로토타입을 사용하여 클라이언트 쪽 검사기 계층 구조를 만드는 방법을 살펴봤다. 또한 JSP 태그 파일을 기반으로 서버 쪽 검사기와 리소스 번들을 만들었다. 본 연재의 다음 기사를 기대하기 바란다. 다음에는 JSP 태그 파일로 JSF스러운 컴포넌트를 만드는 방법을 살펴볼 것이다.
다운로드 하십시오 | 설명 | 이름 | 크기 | 다운로드 방식 |
|---|
| 이 글의 예제 애플리케이션 | wa-aj-simplejava3.zip | 21KB | HTTP |
|---|
참고자료 - 본 연재의 Part 1에서는 Ajax 요청을 보내고 Ajax 응답을 처리하는 자바스크립트 코드를 생성하는 방법을 살펴봤다.
- 이번 연재의 Part 2에서는 편리한 JSP 태그 파일로 HTML 폼을 만들어 셋업과 설정을 최소화했다.
- 한국 developerWorks 웹 개발 존은 웹 2.0 개발과 관련된 많은 도구와 정보로 가득하다.
- developerWorks Ajax 참고자료 센터에는 오늘 당장 Ajax 애플리케이션 개발을 시작할 때 유용한 자료가 많다.
- developer.mozilla.org를 방문하여 자바스크립트 도구와 문서 들을 참조하라.
- JSP 홈 페이지에서 JSP와 관련된 많은 자료를 참조하라.
필자소개  | |  | Andrei Cioroianu는 Devsphere 창립자로, 자바 EE 개발과 Ajax/JSF 컨설팅 서비스를 제공하고 있다. Andrei Cioroianu는 자바와 웹 서비스 기술을 1997년부터 사용해 왔고, 10년 동안 복잡한 기술 문제를 해결하고 있으며 상용 제품, 고객 애플리케이션 그리고 오픈 소스 프레임워크 전반을 다루고 있다. devsphere.com에서 Andrei를 만날 수 있을 것이다. |
기사에 대한 평가
 |
| 이 문서 북마킹 하기
|
|  |