 |
|
난이도 : 고급 Andrei Cioroianu, 선임 자바 개발자 겸 컨설턴트, Devsphere
옮긴이: 백기선 dwkorea@kr.ibm.com
2008 년 5 월 27 일 두 파트로 이루어진 본 연재의 첫 번째 기사에서, 저자이자 자바(Java™) 개발자인 Andrei Cloroiany는 JSF(JavaServer Faces)에서 스타일 속성을 사용하는 방법을 설명하고 그들의 속성에 기본값을 적용하는 방법을 보여주었습니다. 이 연재의 두 번째 기사인 본 글에서는 표준 JSF 컴포넌트의 자바스크립트 관련 속성을 살펴보겠습니다. DOM(Document Object Model) API, 자바스크립트 그리고 CSS에 기반을 둔 몇 가지 웹 기술을 배울 것입니다. 웹 페이지를 다시 읽어들이지 않고 부가적인 JSF 컴포넌트를 숨기거나 보이는 방법, 클라이언트의 웹 브라우저에서 동작하는 검증을 구현하는 방법, 개발자들이 커스텀 컴포넌트를 만들어 웹 폼의 입력 요소에 도움말 메시지를 보여주는 방법을 알아볼 것입니다.
이벤트 처리와 사용자 인터페이스 수정하기
많은 JSF HTML 컴포넌트들이 자바스크립트 관련 속성을 가지고 있고 이것들을 사용하여 특정 UI 이벤트가 발생했을 때 웹 브라우저가 실행할 코드 조각을 설정해둘 수 있다. 예를 들어, 표준 JSF 컴포넌트에서 지원하는 마우스 이벤트는 일곱 가지가 있다.
-
onmouseover
-
onmouseout
-
onmousemove
-
onmousedown
-
onmouseup
-
onclick
-
ondblclick
UI 컴포넌트가 키보드 포커스를 받거나 벗어날 때 발생하는 이벤트를 onfocus와 onblur 속성을 사용하여 잡아낼 수 있다. onkeydown, onkeyup, onkeypress 이벤트는 키를 누르거나 키에서 손을 땔 때 발생한다. <h:form> 컴포넌트는 onsubmit과 onreset 속성을 지원한다. 입력 컴포넌트들은 onchange와 onselect 속성을 가지고 있으며 이것을 사용하여 폼 엘리먼트의 상태가 바뀔 때 특정 자바스크립트를 호출할 수 있다.
또한 JSF 컴포넌트에 의해 렌더링되는것이 아니라 JSF 컴포넌트에 직접 포함되어 보일 HTML 엘리먼트의 자바스크립트 관련 속성을 사용할 수도 있다. 예를 들어, <body> 태그는 onload와 onunload 속성을 가지고 있다. onload 이벤트는 웹 브라우저가 페이지 로딩을 마쳤을 때 발생한다. onunload 이벤트는 사용자가 페이지를 떠날 때 발생한다.
일반적인 자바스크립트 이벤트 핸들러는 브라우저에 있는 DOM API를 사용해 JSF 컴포넌트에 의해 렌더링되는 HTML 엘리먼트의 속성을 수정한다. 여러분은 손쉽게 DOM Core API를 사용하여 HTML 엘리먼트를 나타낸는 객체들에 접근할 수 있다. 예를 들어, document.getElementById(...)를 사용하여 해당 ID를 가진 엘리먼트를 찾을 수 있다.
DOM HTML API는 DOM Core API를 확장하여 HTML 문서에 특화된 메서드와 속성을 추가했다. document.forms.myFormId를 사용하여 웹 브라우저에서 폼을 표현하는 객체를 얻을 수 있으며 폼을 구성하는 객체들의 배열은 myForm.elements로 접근할 수 있다. className은 매우 유용한 속성으로 HTML 엘리먼트의 class 속성을 바꿀 수 있게 해준다.
DOM HTML 표준은(참고자료 참조) 클라이언트 쪽에 있는 페이지를 구성하는 엘리먼트 객체의 모든 표준 속성과 메서드를 정의한다. IE를 포함하여, 파이어폭스, 네스케이프, 사파리 그리고 오페라와 같은 브라우저들은 대부분 거기에 더해서 innerHTML이라는 추가 속성으로 HTML 엘리먼트의 내용을 바꿀 수 있는 기능을 추가로 지원한다.
본 절에 있는 예제에서 JSF HTML 컴포넌트의 자바스크립트 관련 속성을 사용하는 방법과 DOM HTML API를 이용해 사용자 인터페이스를 변경하는 방법을 살펴보겠다.
JSF 페이지에 스크립트 추가하기
자바스크립트 코드는 보통의 웹 페이지들처럼 <script> 엘리먼트를 사용해 JSF 페이지에도 추가할 수 있다(Listing 1 참조). 웹 브라우저에서 document.write()로 HTML 내용을 생성하기 위해 자바스크립트 코드를 사용할 수 있다. 하지만 이런 방법은 거의 필요가 없다. 대부분의 경우, 페이지 헤더에 <script> 엘리먼트를 추가하여 onclick, onsubmit 그리고 onchange와 같은 이벤트 속성에서 호출할 자바스크립트를 포함시킬 것이다. 또한 <noscript> 엘리먼트를 사용해 만약 자바스크립트가 사용자의 브라우저에서 사용하지 않도록 설정되어 있을 때 경고 메시지를 보여주도록 설정할 수 있다.
Listing 1. <script> 태그 사용하기
<html>
<head>
<script type="text/javascript">
function myEventHandler(...) {
...
}
</script>
</head>
<body>
<noscript>
This page requires JavaScript.
</noscript>
...
</body>
</html>
|
 |
Apache MyFaces Tobago
HTML 태그보다 JSF 컴포넌트 사용을 선호한다면, MyFaces Tobago의 <tc:script> 컴포넌트를 사용할 수 있다. 이것을 사용하면 여러분 대신에 <script> 엘리먼트를 렌더링할 것이다. |
|
같은 함수를 여러 페이지에서 호출하고 싶다면, 자바스크립트 코드를 .js 파일에 넣는다. 외부 스크립트는 반드시 <script> 태그에 있는 src 속성을 사용해 웹 페이지에 추가해야 한다(Listing 2 참조). 여기서 /faces/ 접두어가 스크립트의 URL에 포함되지 않았다는 것에 주목하자. src 속성에 상대 경로로 URI를 사용했기 때문이다. 이런 이슈를 제거하는 가장 간단한 방법은 .faces 접미어를 사용하는 것이다. 만약 JSF 페이지를 요청할 때 /faces/ 접두어를 붙이는 것을 선호한다면, <script> 태그의 src 속성에 자바스크립트 파일을 나타낼 때 문맥 경로를 포함한 절대 경로로 URI를 설정하라.
Listing 2. 외부 스크립트 추가하기
<script type="text/javascript"
src="${pageContext.request.contextPath}/scripts/MyScript.js">
</script>
<script type="text/javascript"
src="<%=request.getContextPath()%>/AnotherScript.js">
</script>
|
부가적인 JSF 컴포넌트 감추기와 보여주기
본 연재의 Part 1에서 JSF 컴포넌트의 스타일 클래스들을 styleClass 속성을 사용해 서버 쪽에 설정하는 방법을 살펴보았다. 또 자바스크립트와 DOM을 사용해 클라이언트 쪽의 스타일(style) 클래스들을 설정하거나 변경할 수 있다. 다음 예제는 display CSS 속성을 사용해 부가적인 구성 요소 그룹을 닫거나 펼치는 것을 보여주고 있다. 간단한 검색 폼(그림 1 참조)은 반드시 입력해야 하는 테스트 필드 하나와 두 개의 체크 박스 그리고 드롭다운 목록으로 구성되어 있다. 사용자가 More Options를 클릭하면, Match Case와 Language 컴포넌트가 보일 것이다. 만약 사용자가 More Options에 체크를 해제하면, 해당 부가 컴포넌트들은 사라질 것이다.
그림 1. 검색 폼 예제
SearchForm.jsp 예제(Listing 3 참조)는 표준 JSF 컴포넌트를 사용해 웹 폼을 구성했다. 부가적인 컴포넌트들은 HTML 테이블로 렌더링할 <h:panelGrid> 컨테이너 안에 위치하고 있다. 부가적인 패널은 클라이언트 쪽에서 updatePanelClass()라는 자바스크립트를 호출하여 보여주거나 감추도록 되어 있다. 그 이름이 암시하듯이, 이 함수는 <h:panelGrid>에 의해 렌더링될 <table> 엘리먼트의 스타일 클래스를 변경한다. updatePanelClass() 함수는 사용자가 More Oprions라는 레이블을 가진 체크 박스의 상태를 변경할 때마다 호출된다. <h:selectBooleanCheckbox> 컴포넌트의 onclick 속성에 updatePanelClass()를 호출하도록 코딩했기 때문이다.
Listing 3. SearchForm.jsp 예제
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<f:view>
<html>
<head>
<title>Search Form</title>
...
</head>
<body onload="initForm()">
<h1>Search Form</h1>
<h:form id="searchForm">
<h:panelGrid columns="1" border="0" cellspacing="5">
<h:panelGroup>
<h:outputLabel value="Text: " for="text"/>
<h:inputText id="text" value="#{searchBean.text}"
required="true" requiredMessage="Required" size="20"/>
<h:message for="text"/>
</h:panelGroup>
<h:panelGroup>
<h:selectBooleanCheckbox id="moreOptions"
value="#{searchBean.moreOptions}"
onclick="updatePanelClass()"/>
<h:outputLabel value="More Options" for="moreOptions"/>
</h:panelGroup>
<h:panelGrid id="optionsPanel"
columns="1" border="0" cellspacing="5">
<h:panelGroup>
<h:selectBooleanCheckbox id="matchCase"
value="#{searchBean.matchCase}"/>
<h:outputLabel value="Match Case" for="matchCase"/>
</h:panelGroup>
<h:panelGroup>
<h:outputLabel value="Language: " for="language"/>
<h:selectOneMenu id="language" value="#{searchBean.language}">
<f:selectItem itemValue="English" itemLabel="English"/>
<f:selectItem itemValue="Spanish" itemLabel="Spanish"/>
<f:selectItem itemValue="French" itemLabel="French"/>
</h:selectOneMenu>
</h:panelGroup>
</h:panelGrid>
<h:commandButton id="search" value="Search"
action="#{searchBean.searchAction}"/>
</h:panelGrid>
</h:form>
</body>
</html>
</f:view>
|
SearchForm.jsp 페이지에 의해 생성된 HTML은 Listing 4에 보이는 것과 같다. 표준 JSF 컴포넌트가 어떻게 searchForm: 접두어를 searchForm이라는 ID를 가진 JSF 폼 안에 있는 컴포넌트에 의해 렌더링된 HTML 엘리먼트들의 ID에 추가했는지 관찰해보자.
Listing 4. SearchForm.jsp에 의해 생성된 HTML
<html>
<head>
<title>Search Form</title>
...
</head>
<body onload="initForm()">
<h1>Search Form</h1>
<form id="searchForm" name="searchForm" method="post"
action="/jsf12js/SearchForm.faces"
enctype="application/x-www-form-urlencoded">
...
<table border="0" cellspacing="5">
<tbody>
<tr>
<td><label for="searchForm:text">Text: </label>
<input id="searchForm:text" type="text"
name="searchForm:text" size="20" /></td>
</tr>
<tr>
<td><input id="searchForm:moreOptions" type="checkbox"
name="searchForm:moreOptions" checked="checked"
onclick="updatePanelClass()" />
<label for="searchForm:moreOptions">More Options</label></td>
</tr>
<tr>
<td><table id="searchForm:optionsPanel" border="0" cellspacing="5">
<tbody>
<tr>
<td><input id="searchForm:matchCase" type="checkbox"
name="searchForm:matchCase" />
<label for="searchForm:matchCase">Match Case</label></td>
</tr>
<tr>
<td><label for="searchForm:language">Language: </label>
<select id="searchForm:language" name="searchForm:language" size="1">
<option value="English">English</option>
<option value="Spanish">Spanish</option>
<option value="French">French</option>
</select></td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td><input id="searchForm:search" type="submit"
name="searchForm:search" value="Search" /></td>
</tr>
</tbody>
</table>
...
</form>
</body>
</html>
|
Listing 5는 SearchForm.jsp 페이지의 <style> 엘리먼트를 보여준다. SearchForm.jsp의 헤더에는 스타일 클래스와 자바스크립트 함수를 담고 있다. visible 클래스는 부가적인 컴포넌트의 왼쪽 여백(margin)을 설정하고 다른 설정은 하고 있지 않다. HTML 테이블이 기본으로 보일 것이기 때문이다. hidden 클래스는 display CSS 속성을 none으로 설정해 테이블 보여주기 기능을 끄고 있다.
Listing 5. SearchForm.jsp 스타일 클래스
<style type="text/css">
.visible { margin-left: 40px; }
.hidden { display: none; }
</style>
|
updatePanelClass() 함수(Listing 6 참조)는 searchForm:moreOptions 체크 박스와 searchForm:optionsPanel 테이블을 document.getElementById()를 사용해 가져온다. panel 객체는 <h:panelGrid id="optionsPanel">에 의해 렌더링되는 <table id="searchForm:optionsPanel"> 엘리먼트를 나타내고, checkbox 객체는 <h:selectBooleanCheckbox id="moreOptions">에 의해 렌더링되는 <input id="searchForm:moreOptions"> 엘리먼트를 나타낸다. updatePanelClass() 함수는 checkbox 객체의 상세를 checked DOM 속성을 사용해 가져오고 panel 테이블의 스타일 클래스를 className DOM 속성을 사용해 설정한다.
Listing 6. SearchForm.jsp의 updatePanelClass() 함수
function updatePanelClass() {
var checkbox = document.getElementById("searchForm:moreOptions");
var panel = document.getElementById("searchForm:optionsPanel");
panel.className = checkbox.checked ? "visible" : "hidden";
}
|
SearchForm.jsp 페이지의 헤더는 initForm() 함수(Listing 7 참조)도 가지고 있다. 이 함수는 <body> 엘리먼트의 onload 속성에서 호출하도록 코딩되어 있다. 이 함수는 폼의 텍스트 필드 위치를 잡고 focus()를 호출하여 브라우저에서 페이지가 로딩되었을 때 사용자가 컴포넌트를 클릭할 필요 없이 바로 입력을 할 수 있도록 해준다. 그런 다음, initForm()은 updatePanelClass()를 호출하여 부가적인 패널의 클래스를 초기화한다.
Listing 7. SearchForm.jsp의 initForm() 함수
function initForm() {
var text = document.getElementById("searchForm:text");
text.focus();
updatePanelClass();
}
|
SearchForm.jsp 예제의 입력 컴포넌트 값들은 간단한 빈의 속성으로 연결된다. 그리고 검색 버튼은 searchAction()이라는 메서드를 호출한다. SearchBean 클래스 코드는 Listing 8에 있다.
Listing 8. SearchBean 클래스
package jsfcssjs;
public class SearchBean implements java.io.Serializable {
private String text;
private boolean moreOptions;
private boolean matchCase;
private String language;
public SearchBean() {
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
...
public String searchAction() {
System.out.print("Text: " + text);
if (moreOptions) {
if (matchCase)
System.out.print(", Match Case");
System.out.print(", Language: " + language);
}
System.out.println();
return null;
}
}
|
클라이언트 쪽에서 검증 구현하기
JSF 프레임워크는 서버 쪽에서 동작하는 몇 가지 검증기를 제공한다. JSF 검증기가 에러를 발견하면, 폼은 다시 사용자에게 돌아가고 사용자가 그것을 다시 수정할 수 있도록 한다. 실패하는 폼 보내기 횟수를 최소화하기 위해 자바스크립트 코드를 사용해 클라이언트 쪽에서 사용자가 입력한 값을 검증할 수도 있다. 다음 예제는 텍스트 영역 컴포넌트에 입력한 데이터의 최대 길이를 검사하는 방법을 보여준다. 사용자가 입력한 텍스트의 현재 길이를 보여준다. 사용자가 Submit 버튼을 클릭하면, 자바스크립트 함수를 사용해 입력한 값을 검증한다.
ClientValidation 예제
 |
maxlength 속성 사용하기
<h:inputText> 컴포넌트는 maxlength 속성을 사용해 사용자가 입력한 데이터의 길이를 제한할 수 있는 기능을 제공한다. 한 줄로 입력하는 필드와는 달리, 여러 줄을 입력하는 <h:inputTextarea> 컴포넌트는 maxlength 속성을 가지고 있지 않다. 그것이 렌더링하는 <textarea> HTML 태그에서 그런 것을 지원하지 않기 때문이다. |
|
ClientValidation.jsp 예제(Listing 9 참조)의 JSF 폼은 <h:inputTextarea> 컴포넌트 한 개로 구성되어 있다. 이 컴포넌트는 onkeyup 속성을 사용해 사용자가 키를 입력할 때마다validateText() 함수를 호출한다. 이 함수는 또한 <body> 태그의 onload 속성이 validateText() 호출을 포함하고 있기 때문에 브라우저가 페이지를 로딩한 다음에 한 번 호출된다. 사용자가 입력한 값이 클라이언트 쪽에서 검증됐어도 <h:inputTextarea> 컴포넌트는 required 속성과 <f:validateLength> 검증기를 사용해 입력한 값을 서버 쪽에서도 검증한다. 자바스크립트를 사용자의 브라우저에서 쓰지 않을 수도 있기 때문이다. 서버 쪽 검증은 이런 심술 굳은 사용자들 때문에 반드시 필요하다.
Listing 9. ClientValidation.jsp 예제
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<f:view>
<html>
...
<body onload="validateText()">
<h1>Client-Side Validation</h1>
<h:form id="validForm" onsubmit="return validateForm()">
<h:panelGrid columns="1" border="0" cellspacing="5">
<h:panelGroup>
<h:outputText value="Text (max #{textBean.maxLength} chars): "/>
<f:verbatim><span id="charCount"></span></f:verbatim>
</h:panelGroup>
<h:panelGroup>
<h:inputTextarea id="textArea" value="#{textBean.text}"
required="true" rows="5" cols="30"
onkeyup="validateText()">
<f:validateLength maximum="#{textBean.maxLength}"/>
</h:inputTextarea>
<h:message for="textArea"/>
</h:panelGroup>
<h:commandButton id="submit" value="Submit"
action="#{textBean.submitAction}"/>
</h:panelGrid>
</h:form>
</body>
</html>
</f:view>
|
validateText() 함수(Listing 10 참조)는 페이지의 폼을 document.forms.validForm을 사용해 참조하고 form.elements["validForm:textArea"]를 사용해 텍스트 영역을 보여주는 객체를 얻어낸다. 그런 다음, 자바스크립트 코드는 입력한 텍스트의 길이를 확인하고 만약에 사용자가 입력한 값이 유효하지 않을 때 에러 메시지를 반환한다. 추가로, validateText()는 ClientValidation.jsp의 <span id="charCount"> 엘리먼트 innerHTML 속성에 설정된 현재 길이를 보여준다. span 엘리먼트의 className DOM 속성을 사용해 스타일 클래스도 수정한다.
Listing 10. ClientValidation.jsp의 validateText() 함수
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<f:view>
<html>
<head>
<title>Client-Side Validation</title>
<style type="text/css">
.valid { color: green; }
.error { color: red; }
</style>
<script type="text/javascript">
function validateText() {
var form = document.forms.validForm;
var textArea = form.elements["validForm:textArea"];
var length = textArea.value.length;
var maxLength = <h:outputText value="#{textBean.maxLength}"/>;
var error = null;
if (length == 0)
error = "Text cannot be empty.";
else if (length > maxLength)
error = "Text cannot have more than "
+ maxLength + " characters."
var span = document.getElementById("charCount");
span.innerHTML = length;
span.className = (error == null) ? "valid" : "error";
return error;
}
...
</script>
</head>
...
</html>
</f:view>
|
maxLength 변수는 Listing 10에 있는 자바스크립트 함수에서 초기화한다. ID가 textBean인 빈의 같은 이름을 가진 속성을 사용해 초기화했다. 이 속성의 값은 faces-config.xml 파일에 기술되어 있다(Listing 11 참조).
Listing 11. faces-config.xml에 설정되어 있는 TextBean
<faces-config xmlns="http://java.sun.com/xml/ns/javaee" ... version="1.2">
<managed-bean>
<managed-bean-name>textBean</managed-bean-name>
<managed-bean-class>jsfcssjs.TextBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>maxLength</property-name>
<value>100</value>
</managed-property>
</managed-bean>
...
</faces-config>
|
ClientValidation.jsp 예제는 두 번째 자바스크립트 함수인 validateForm()(Listing 12 참조)을 가지고 있다. 이 함수는 Submit 버튼을 클릭할 때 호출된다. <h:form>의 onsubmit 속성에 validateForm() 호출 바로 앞에 return 키워드를 사용한 것에 주목하라. 만약 반환된 값이 false면, 사용자가 입력한 값이 유효하지 않다는 뜻이다. 이 경우 웹 브라우저는 폼을 서버로 보내지 않는다. validateForm()이 에러를 사용자에게 alert() 함수를 사용해 보여줄 것이기 때문에 유효하지 않은 입력값을 보내지 않는 것이 타당하다.
Listing 12. ClientValidation.jsp의 validateForm() 함수
function validateForm() {
var error = validateText();
if (error)
alert(error);
return error == null;
}
|
사용자가 입력한 값이 유효하다면, validateForm()은 true를 반환하고 웹 브라우저는 폼 데이터를 서버로 전송하여 JSF 프레임워크가 TextBean 클래스에 있는 submitAction() 메서드를 호출하게 될 것이다(Listing 13 참조).
Listing 13. TextBean 클래스
package jsfcssjs;
public class TextBean implements java.io.Serializable {
private String text;
private int maxLength;
...
public String submitAction() {
System.out.println("Length: " + text.length());
return null;
}
}
|
위의 <h:inputTextarea> 컴포넌트에 대한 최대 길이 검증은 ClientValidation.jsp 예제에서 사용한 코드다. 이 방법이 해당 작업을 하기 위한 가장 단순한 방법이다. 하지만 코드를 아무런 변경 없이 재사용할 수가 없다. 구현하는 기능이 특정 페이지에 종속적이지 않다면, 자바스크립트 코드를 .js 파일로 빼내는 것이 좋은 생각이다. 게다가 커스텀 JSF 컴포넌트를 사용해 자바스크립 기반 메커니즘을 설정해 커스텀 속성을 기존 컴포넌트에 추가할 수도 있다. 다음 절에서는 표준 JSF 컴포넌트를 개선하는 일반적인 UI 기능을 구현하는 방법을 살펴보겠다.
새로운 UI 기능을 가능하게 하는 커스텀 속성 사용하기
본 연재의 Part 1에서 커스텀 컴포넌트를 사용해 JSF 컴포넌트들에 기본 스타일을 설정할 수 있는 법을 살펴보았다. 이번 절에서는, 그와 같은 기술을 사용해 자바스크립트와 관련된 속성을 설정하는 방법을 살펴볼 것이다. 추가로, 여기서 보일 예제는 커스텀 속성을 사용하는데 이 속성은 <f:attribute> 태그와 함께 표준 JSF 컴포넌트로 추가되었다.
커스텀 JSF 컴포넌트 개발하기
다음으로 <f:attribute name="helpOnFocus" value="...">(Listing 14 참조)을 가지고 있는 모든 내부 컴포넌트의 onfocus와 onblur 속성을 수정하는 커스컴 컴포넌트를 만드는 방법을 살펴볼 것이다. onfocus와 onblur 속성의 자바스크립트 표현식은 웹 브라우저에서 해당 엘리먼트에 키보드 포커스를 얻거나 잃을 때 실행하게 될 것이다. <js:helpOnFocus>라는 이름의 커스텀 컴포넌트는 내부의 입력 컴포넌트들의 onfocus 속성을 변경하여 <input> 엘리먼트가 포커스를 얻었을 때 <div id="helpOnFocus"> 엘리먼트를 사용해 도움말 메시지를 보여주도록 할 것이다. 내부 컴포넌트들의 onblur 속성을 변경하여 <input> 엘리먼트가 키보드 포커스를 잃었을 때 도움말 메시지를 제거하도록 할 것이다.
Listing 14. JSP 페이지에서 <js:helpOnFocus> 컴포넌트 사용하기
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="js" uri="/js.tld" %>
...
<script type="text/javascript" src=".../HelpOnFocus.js">
</script>
...
<js:helpOnFocus>
...
<h:inputText ...>
<f:attribute name="helpOnFocus" value="... Help message ..."/>
</h:inputText>
...
</js:helpOnFocus>
...
<div id="helpOnFocus">
</div>
...
|
Part 1에서 살펴봤던 SetupComponent 클래스는, JSF 컴포넌트 트리를 순회하면서 내부에 있는 컴포넌트들을 렌더링하기 직전에 그 속성을 변경할 수 있도록 해줬다. HelpOnFocusComponent 클래스(Listing 15 참조)는 SetupComponent를 확장하고 있으며 setup() 메서드를 구현하여 rendered 속성이 true인 모든 내부 컴포넌트에서 호출된다. getAttribute() 메서드는 컴포넌트의 속성맵에서 속성 값 하나를 가져온다. insertCall() 메서드는 기존의 값은 보존하면서 주어진 속성에 대한 함수 호출을 할당한다. 속성이 이미 함수 호출을 포함하고 있다면 아무것도 하지 않는다. 이런 일은 포스트 후에 사용자에게 똑같은 폼을 다시 반환했을 때 발생한다. 이런 경우, 커스텀 컴포넌트는 이미 onfocus와 onblur 속성을 이전 요청을 처리하면서 내부 컴포넌트에 추가했을 것이다. 이 속성들은 JSF 프레임워크가 요청 사이의 모든 컴포넌트의 상태를 저장하기 때문에 이미 자바스크립트 호출을 포함하고 있다.
Listing 15. HelpOnFocusComponent 클래스
package jsfcssjs;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import java.util.Map;
public class HelpOnFocusComponent extends SetupComponent {
protected void setup(FacesContext ctx, UIComponent comp) {
Map<String, Object> attrMap = comp.getAttributes();
String helpOnFocus = getAttribute(attrMap, "helpOnFocus");
if (helpOnFocus != null) {
String helpParam[] = new String[] {
EncodeUtils.encodeString(helpOnFocus).toString() };
insertCall(attrMap, "onfocus", "showHelpOnFocus", helpParam);
insertCall(attrMap, "onblur", "clearHelpOnBlur", null);
}
}
protected String getAttribute(Map<String, Object> attrMap, String attrName) {
Object attrValue = attrMap.get(attrName);
if (attrValue != null)
return attrValue.toString();
else
return null;
}
protected void insertCall(Map<String, Object> attrMap, String attrName,
String functionName, String functionParams[]) {
String attrValue = getAttribute(attrMap, attrName);
if (attrValue != null && attrValue.indexOf(functionName) != -1)
return;
StringBuilder buf = EncodeUtils.encodeCall(
functionName, functionParams);
if (attrValue != null && attrValue.length() > 0) {
buf.append(';');
buf.append(attrValue);
}
attrMap.put(attrName, buf.toString());
}
public String getFamily() {
return "HelpOnFocus";
}
}
|
HelpOnFocus.js 파일(Listing 16 참조)은 showHelpOnFocus()와 clearHelpOnBlur() 함수를 포함하고 있다. 이것들의 호출은 onfocus와 onblur 속성에서 호출하도록 코딩되어 있다. setInnerHTML() 함수는 주어진 id에 해당하는 HTML 엘리먼트에 주어진 content를 삽입한다.
Listing 16. HelpOnFocus.js 파일
function setInnerHTML(id, content) {
document.getElementById(id).innerHTML = content;
}
function showHelpOnFocus(msg) {
setInnerHTML("helpOnFocus", msg);
}
function clearHelpOnBlur() {
setInnerHTML("helpOnFocus", "");
}
|
보통 커스텀 JSF 컴포넌트들처럼, HelpOnFocusComponent는 반드시 faces-config.xml(Listing 17 참조)에 설정해야 한다.
Listing 17. faces-config.xml에 HelpOnFocusComponent 설정하기
<faces-config xmlns="http://java.sun.com/xml/ns/javaee" ... version="1.2">
...
<component>
<component-type>HelpOnFocusComponent</component-type>
<component-class>jsfcssjs.HelpOnFocusComponent</component-class>
<component-extension>
<component-family>HelpOnFocus</component-family>
</component-extension>
</component>
</faces-config>
|
Listing 18은 HelpOnFocusTag 클래스를 보여준다. 이 클래스는 커스텀 컴포넌트에서 사용할 태그 핸들러를 구현하고 있다. UIComponentELTag 베이스 클래스는 JSF 1.2 API에 해당한다. 만약 JSF 1.1 기반 애플리케이션에서 커스텀 태그를 사용하고자 한다면, 태그 핸들러의 상위 클래스는 UIComponentTag여야 한다.
Listing 18. HelpOnFocusTag 클래스
package jsfcssjs;
import javax.faces.webapp.UIComponentELTag;
public class HelpOnFocusTag extends UIComponentELTag {
public String getComponentType() {
return "HelpOnFocusComponent";
}
public String getRendererType() {
return null;
}
}
|
커스텀 태그의 이름과 속성은 js.tld 파일에 기술되어 있다(Listing 19 참조). HelpOnFocusTag 핸들러는 자신의 속성에 대한 세터 메서드를 하나도 가지고 있지 않다. 모두 UIComponentELTag로부터 상속받기 때문이다.
Listing 19. js.tld 파일
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/javaee" ... version="2.1">
<tlib-version>1.0</tlib-version>
<short-name>js</short-name>
<uri>/js.tld</uri>
<tag>
<name>helpOnFocus</name>
<tag-class>jsfcssjs.HelpOnFocusTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>id</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>binding</name>
<required>false</required>
<deferred-value>
<type>jsfcssjs.HelpOnFocusComponent</type>
</deferred-value>
</attribute>
<attribute>
<name>rendered</name>
<required>false</required>
<deferred-value>
<type>boolean</type>
</deferred-value>
</attribute>
</tag>
</taglib>
|
자바스크립트 인코딩 유틸리티
위에서 살펴본 HelpOnFocusComponent 클래스는 EncodeUtils 클래스가 제공하는 도우미 메서드를 사용하고 있다. encodeString() 메서드(Listing 20 참조)는 백스페이스, 탭, CR, LF 그리고 아스키가 아닌 문자를 제거해 반환된 문자열을 자바스크립트 코드에서 사용할 수 있도록 해준다. 추가로, ", &, < 그리고 > 문자를 각각 ", &, <, >로 변환하여 HTML 페이지에서도 해당 문자가 제대로 보이도록 해준다. 쌍따옴표는 구분자로 사용된다. 예를 들어, abc \ " & < > [TAB] [LF] [CR] 123을 encodeString() 메서드에 넘기면, "abc \\ " & < > \t \n \r 123"을 반환받을 것이다.
Listing 20. EncodeUtils 클래스에 있는 encodeString() 메서드
package jsfcssjs;
public class EncodeUtils {
public static StringBuilder encodeString(String str) {
if (str == null)
return null;
StringBuilder buf = new StringBuilder();
buf.append('"');
int n = str.length();
for (int i = 0; i < n; i++) {
char ch = str.charAt(i);
switch (ch) {
case '\\': buf.append("\\\\"); break;
case '\'': buf.append("\\\'"); break;
case '"': buf.append("""); break;
case '&': buf.append("&"); break;
case '<': buf.append("<"); break;
case '>': buf.append(">"); break;
case '\t': buf.append("\\t"); break;
case '\r': buf.append("\\r"); break;
case '\n': buf.append("\\n"); break;
default: {
if (' ' <= ch && ch <= '~')
buf.append(ch);
else {
buf.append("\\u");
for (int j = 3; j >= 0; j--) {
int h = (((int) ch) >> (j*4)) & 0x0f;
buf.append((char) (h<10 ? '0'+h : 'a'+h-10));
}
}
}
}
}
buf.append('"');
return buf;
}
...
}
|
encodeCall() 메서드(Listing 21 참조)는 주어진 매개변수를 사용해 함수 호출을 구성한다.
Listing 21. EncodeUtils 클래스의 encodeCall() 메서드
public class EncodeUtils {
...
public static StringBuilder encodeCall(
String functionName, String functionParams[]) {
StringBuilder buf = new StringBuilder();
buf.append(functionName);
buf.append('(');
if (functionParams != null)
for (int i = 0; i < functionParams.length; i++) {
if (i > 0)
buf.append(',');
buf.append(functionParams[i]);
}
buf.append(')');
return buf;
}
}
|
인코딩 메서드를 좀 더 잘 이해하기 위해 Listing 22에 있는 예제를 살펴보자. 이미 onfocus 속성을 가지고 있는 입력 컴포넌트가 있다. helpOnFocus 속성 또한 이 컴포넌트에 <f:attribute>를 사용해 추가되었고 전체 폼은 <js:helpOnFocus> 컴포넌트로 감싸져 있다.
Listing 22. 문자 인코딩 예제
<% request.setAttribute("msg", "abc \\ \" & < > \t \n \r 123"); %>
...
<js:helpOnFocus>
<h:form>
<h:inputText ... onfocus="alert('focus')">
<f:attribute name="helpOnFocus" value="#{msg}"/>
</h:inputText>
</h:form>
</js:helpOnFocus>
...
<div id="helpOnFocus">
</div>
...
|
페이지가 실행되면, <js:helpOnFocus> 컴포넌트는 내부의 컴포넌트 트리를 순회하면서 onfocus와 onblur 속성을 설정하여 도움말 메시지를 보여주거나 제거한다. Listing 23은 JSF 컴포넌트에 의해 렌더링된 HTML을 보여준다. onfocus 속성은 showHelpOnFocus() 호출을 포함하고 있는데 여기에 Listing 22 예제에서 살펴보았던 alert('focus') 표현식을 포함하고 있다. & 문자와 " 구분자는 <h:inputText> 컴포넌트에 의해 한 번 더 제거하고 원래 값을 보여주도록 인코딩된다.
Listing 23. 인코딩된 문자열
<form ...>
...
<input ... onblur="clearHelpOnBlur()"
onfocus="showHelpOnFocus("abc \\ &quot; &amp;
&lt; &gt; \t \n \r 123");alert('focus')" />
...
</form>
...
<div id="helpOnFocus">
</div>
...
|
웹 브라우저가 Listing 23의 HTML 조각을 파싱하고 디코딩하면, onfocus 속성으로부터 가져오는 값은 showHelpOnFocus("abc \\ " & < > \t \n \r 123");alert('focus')가 될 것이다. 브라우저는 이 코드를 자신의 자바스크립트 엔진에 넘길 것이고 그럼 그것은 showHelpOnFocus() 함수를 호출하여 웹 페이지의 <div id="helpOnFocus"> 엘리먼트 안에 abc \ " & < > [TAB] [NL] [CR] 123을 넣을 것이고 따라서 브라우저는 abc \ " & < > 123을 보여줄 것이다.
 |
developerWorks Ajax 리소스 센터
Ajax 리소스 센터를 확인하고, Ajax와 관련된 무료 도구, 코드 그리고 개발과 관련된 정보를 확인하라. Ajax 전문가 Jack Herringtom이 주관하는 active Ajax 커뮤니티 포럼에서 여러분이 찾고 있는 질문에 답해줄 수 있는 사람들을 만날 수 있다. |
|
JSF 페이지 안에서 커스텀 컴포넌트 사용하기
HelpOnFocus.jsp 페이지(Listing 24 참조)는 <js:helpOnFocus> 컴포넌트를 사용하는 또 다른 예제다. JSF 폼은 세 개의 입력 컴포넌트인 텍스트 필드, 체크 박스 그리고 드롭다운 목록으로 구성되어 있다. 이 모든 컴포넌트는 <f:attribute> 태그를 사용해 도움말 메시지를 기술하고 있다.
Listing 24. HelpOnFocus.jsp 예제
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="js" uri="/js.tld" %>
<f:view>
...
<script type="text/javascript"
src="<%=request.getContextPath()%>/HelpOnFocus.js">
</script>
...
<js:helpOnFocus>
...
<h:inputText id="text" ...>
<f:attribute name="helpOnFocus"
value="Enter the text you want to search for."/>
</h:inputText>
...
<h:selectBooleanCheckbox id="matchCase" ...>
<f:attribute name="helpOnFocus"
value="Distinguish between lowercase and uppercase
characters."/>
</h:selectBooleanCheckbox>
...
<h:selectOneMenu id="language" ...>
...
<f:attribute name="helpOnFocus"
value="Select the language of the searched text."/>
</h:selectOneMenu>
...
<f:verbatim>
<div id="helpOnFocus">
</div>
</f:verbatim>
...
</js:helpOnFocus>
...
</f:view>
|
Listing 25는 HelpOnFocus.jsp 페이지에 의해 생성된 HTML을 보여준다.
Listing 25. HelpOnFocus.jspHelpOnFocus.jsp에 의해 생성된 HTML
...
<script type="text/javascript"
src="/jsf12js/HelpOnFocus.js">
</script>
...
<input id="searchForm:text" type="text" ... onblur="clearHelpOnBlur()"
onfocus="showHelpOnFocus("Enter the text you want to search for.")"/>
...
<input id="searchForm:matchCase" type="checkbox" ... onblur="clearHelpOnBlur()"
onfocus="showHelpOnFocus("Distinguish between lowercase and uppercase
\r\n characters.")" />
...
<select id="searchForm:language" ... onblur="clearHelpOnBlur()"
onfocus="showHelpOnFocus("Select the language of the searched text.")">
...
</select>
...
<div id="helpOnFocus">
</div>
...
|
결론
본 기사는 자바스크립트 이벤트 핸들러를 개발해 JSF 컴포넌트에 의해 렌더링되는 HTML을 수정하는 방법을 살펴보고 다음 몇 가지 웹 기술들을 소개하였다.
-
className 속성을 설정하여 class 속성의 값 변경하기
-
innerHTML 속성을 사용해 HTML 엘리먼트에 콘텐츠 추가하기
- CSS를 사용해 JSF 컴포넌트 감추기와 보여주기
- 자바스크립트를 사용해 클라이언트 쪽 검증 구현하기
감싸는 컴포넌트로 커스컴 컴포넌트를 사용해 기존에 존재하는 JSF 컴포넌트에 새로운 기능을 추가하는 방법도 살펴보았다.
다운로드 하십시오 | 설명 | 이름 | 크기 | 다운로드 방식 |
|---|
| 이 기사의 예제 응용 프로그램 | jsfcssjs_p2.zip | 30KB | HTTP |
|---|
참고자료 교육
제품 및 기술 얻기
토론
필자소개  | |  | Andrei Cioroianu는 Devsphere 창립자로, 자바 EE 개발과 Ajax/JSF 컨설팅 서비스를 제공하고 있다. Andrei Cioroianu는 자바와 웹 서비스 기술을 1997년부터 사용해 왔고, 10년 동안 복잡한 기술 문제를 해결하고 있으며 상용 제품, 고객 애플리케이션 그리고 오픈 소스 프레임워크 전반을 다루고 있다. devsphere.com에서 Andrei를 만날 수 있을 것이다. |
기사에 대한 평가
 |
| 이 문서 북마킹 하기
|
|