 |
|
난이도 : 고급 Andrei Cioroianu, 선임 자바 개발자 겸 컨설턴트, Devsphere
2008 년 3 월 25 일 전형적인 웹 애플리케이션들은 CSS(Cascading Style Sheet)와 자바스크립트를 JSF(JavaServer Faces) 같은 서버 쪽 프레임워크와 함께 사용해야 합니다. CSS는 Ajax 또는 다른 애플리케이션에서 사용하는 웹 컴포넌트의 외관 특성을 변경하여 보기 좋고 눈에 띄도록 보여줄 수 있습니다. 2회로 구성된 이 기사의 Part 1에서는, 표준 JSF 컴포넌트에서 CSS와 관련된 속성을 살펴볼 것입니다. 그 외에 안에 들어 있는 컴포넌트의 기본 스타일을 정하는 커스텀 JSF 컴포넌트를 만드는 방법에 대해 알아보겠습니다. 이것을 배우면 웹 애플리케이션에 있는 모든 페이지가 일관되게 보이도록 설정하는 것이 매우 간편해질 것입니다. 이 기술을 사용해 다른 컴포넌트의 속성을 설정할 수도 있습니다. 이것에 관해서는 Part 2에서 다룰 것이고, 거기에서는 자바스크립트를 사용해 JSF 폼을 동적으로 만드는 방법을 보여줄 것입니다.
JSF 컴포넌트의 스타일 속성 사용하기
거의 대부분의 JSF HTML 컴포넌트는 두 개의 선택적인(optional) 속성 style과 styleClass를 가지고 있으며, 이것들은 HTML을 보여줄 때 style과 class 속성으로 렌더링된다. <h:dataTable>과 <h:panelGrid> 같은 몇몇 컴포넌트는 단면을 보여주기 위한 추가적인 스타일 속성을 가지고 있다. 이번 절에서는 JSF HTML 라이브러리 중에서 CSS와 관련된 속성을 살펴볼 것이다.
JSF 페이지에 CSS 파일 연결하기
만약 웹 페이지가 특유의 스타일을 가지고 있다면, 스타일 규칙을 페이지 헤더의 <style> 엘리먼트를 사용해 정의할 수 있다. 또한 단일 JSF 컴포넌트에 대한 스타일 정보를 style 속성을 사용해 정의할 수도 있다. 하지만 대부분의 경우, 별도의 CSS 파일로 스타일 규칙을 분리하여 여러 페이지에 적용할 수 있도록 하는 것을 선호할 것이다. Listing 1에 보이는 것처럼 <link> 태그를 사용해 외부 스타일 시트를 웹 페이지에 연결할 수 있다.
Listing 1. <link> 태그 사용하기
<link rel="stylesheet" type="text/css"
href="${pageContext.request.contextPath}/MyStyles.css">
<link rel="stylesheet" type="text/css"
href="<%=request.getContextPath()%>/styles/MoreStyles.css">
|
 |
MyFaces Tomahawk 사용하기
만약 JSF 컴포넌트 대신에 HTML 태그 사용을 더 좋아한다면, MyFaces Tomahawk의 <t:stylesheet> 컴포넌트를 사용해 <link> 태그를 렌더링할 수 있다.
|
|
Listing 1에서 href 속성은 절대 URI들을 포함하고 있다. 상대 주소를 사용해 CSS 파일을 연결할 수도 있지만, 만약 JSF 페이지들의 URL이 /faces/ 같은 접두어를 가지고 있다면, ${pageContext.request.contextPath}나 <%=request.getContextPath()%>를 사용해 컨텍스트 경로를 추가하는 것이 더 좋다. 그럴 경우에는 CSS 파일, 이미지 또는 몇몇 다른 자원이 /faces/ 접두어를 포함하고 있는 HTTP 요청을 할 것이다. 즉, Faces 서블릿에서 JSF가 아닌 파일들은 제공되지 않는다는 뜻이다. 이렇게 동작하긴 하지만 그다지 효율적이지는 않다. .faces 접미어를 사용하면 JSF 기반 애플리케이션에서 상대 경로 링크를 안전하게 사용할 수 있다. 이렇게 하면 위에서 언급한 문제가 해결된다.
스타일 규칙 정의하고 사용하기
CSS 파일은 스타일 시트 규칙을 포함하고 있으며 이 규칙들은 HTML 엘리먼트에 적용된다. 이 HTML 엘리먼트들은 JSF 컴포넌트들에 의해 렌더링되거나 단순하게 JSF 페이지의 컴포넌트들 사이에 포함될 수 있다. 예를 들어, 마우스가 올려졌을 때에만 링크에 밑줄을 보이게 하고 싶다면, 다음과 같은 규칙을 정의할 수 있다(Listing 2 참조).
Listing 2. 링크 스타일 규칙
a { text-decoration: none; }
a:hover { text-decoration: underline; }
|
이 규칙들은 JSF 페이지에 직접 포함되어 있거나 <h:commandLink> 같은 JSF 컴포넌트에 의해 생성된 <a> 엘리먼트에 모두 적용될 것이다(Listing 3).
Listing 3. HTML 링크와 JSF 링크
<a href="LinkStyles.faces">HTML Link</a>
<h:commandLink value="JSF Link"/>
|
Listing 4는 JSF 컴포넌트에 인라인 스타일을 적용하는 방법을 보여준다.
Listing 4. 스타일 속성을 가지고 있는 JSF 컴포넌트
<h:commandLink value="Red Link" style="color: red"/>
|
하지만 대부분의 경우, CSS 파일에 스타일 클래스들(Listing 5 참조)을 정의할 것이고 이것을 사용해 같은 스타일을 여러 컴포넌트에 적용할 것이다.
Listing 5. 스타일 클래스 예제
.GreenClass { color: green; }
|
스타일 클래스 이름은 Listing 6처럼 JSF의 styleClass 속성에서 사용될 것이다.
Listing 6. styleClass 속성이 있는 JSF 컴포넌트
<h:commandLink value="Green Link" styleClass="GreenClass"/>
|
Listing 1부터 6에 나와 있는 소스 코드는 이 기사의 샘플 코드 링크에 포함되어 있는 LinkStyles.jsp와 LinkStyles.css 파일에서 찾을 수 있다(다운로드 참조)
여러 스타일 클래스를 가지고 있는 JSF 컴포넌트
이전에 언급했듯이 스타일 속성이 한 가지 이상인 JSF 컴포넌트가 있다. 예를 들어, <h:message>와 <h:messages>는 다음 열 개의 CSS 관련 속성을 가지고 있다.
-
style
-
styleClass
-
errorClass
-
errorStyle
-
fatalClass
-
fatalStyle
-
infoClass
-
infoStyle
-
warnClass
-
warnStyle
렌더링될 메시지의 심각성 단계에 따라 이 중에서 단 두 개만 쓰인다. UISelectOne과 UISelectMany 컴포넌트의 JSF 태그들은 목록에 보일 아이템에 대한 속성으로 enabledClass와 disabledClass 속성을 사용할 수 있다. <h:dataTable>과 <h:panelGrid> 태그는 주 테이블, 헤더, 푸터, 열, 컬럼에 적용할 class 속성을 가지고 있다. 다음 예제는 스크린샷에 있는 데이터 테이블에서 CSS 관련 속성을 어떻게 사용하는지 보여준다.
그림 1. TableStyles 예제
먼저, 테이블 컴포넌트에서 사용할 데이터 모델이 필요하다. OrderBean 클래스(Listing 7)는 javax.faces.model.ArrayDataModel을 상속하였다. 실제 애플리케이션에서는 모델의 데이터를 데이터베이스에서 가져오겠지만, 간단하게 하기 위해 OrderBean은 정적(static) 배열로 초기화했다.
Listing 7. OrderBean 예제
package jsfcssjs;
import javax.faces.model.ArrayDataModel;
public class OrderBean extends ArrayDataModel {
private static ItemBean items[] = new ItemBean[] {
new ItemBean("Domain Name", 3, 7.99f),
new ItemBean("SSL Certificate", 1, 119.00f),
new ItemBean("Web Hosting", 1, 19.95f),
new ItemBean("Email Box", 20, 0.15f),
new ItemBean("E-Commerce Setup", 1, 25.00f),
new ItemBean("Technical Support", 1, 50.00f)
};
public OrderBean() {
super(items);
}
public float getTotalPrice() {
float total = 0;
for (int i = 0; i < items.length; i++)
total += items[i].getItemPrice();
return total;
}
}
|
ItemBean 클래스(Listing 8 참조)는 읽기/쓰기가 가능한 description, quantity, unitPrice 그리고 읽기만 가능한 itemPrice 속성을 가지고 있다.
Listing 8. ItemBean 예제
package jsfcssjs;
public class ItemBean implements java.io.Serializable {
private String description;
private int quantity;
private float unitPrice;
public ItemBean() {
}
public ItemBean(String description, int quantity, float unitPrice) {
this.description = description;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
...
public float getItemPrice() {
return unitPrice * quantity;
}
}
|
TableStyles.css 파일(Listing 9)은 데이터 테이블에서 사용하는 스타일을 정의하고 있다.
Listing 9. TableStyles.css 예제
body { font-family: Arial; }
.tablebg { background-color: #D0D0A0; }
.header { font-weight: bold; }
.footer { font-weight: bold; }
.text { text-align: left; }
.number { text-align: right; }
.graybg { background-color: #DDDDDD; }
.whitebg { background-color: #FFFFFF; }
|
TableStyles.jsp 예제(Listing 10)는 TableStyles.css에 정의한 스타일 클래스를 사용하는 JSF 테이블 컴포넌트를 포함하고 있다. styleClass의 값은 <table> 엘리먼트에서 사용되었다. headerClass와 footerClass 속성은 테이블 셀의 헤더와 푸터에서 사용할 스타일을 정의했다. columnClasses 속성은 쉼표로 구분한 클래스 목록을 사용해 테이터 셀의 <td> 엘리먼트에 적용되었다.
또 다른 클래스 목록을 rowClasses 속성을 사용해 기술할 수 있다. 이것들은 테이블의 <tr> 엘리먼트에 사용되었다. TableStyles.jsp 예제에서 행(row) 클래스는 두 개뿐이지만 테이블에는 행이 여섯 개가 있다. <h:dataTable> 컴포넌트의 JSF 렌더러는 두 가지 스타일 클래스를 변환할 것이다. Graybg는 첫 번째, 세 번째, 다섯 번째 줄에 사용하고 whitebg 클래스는 두 번째, 네 번째, 여섯 번째 줄에 적용될 것이다. var 속성은 row라는 이름의 변수를 정의하고, 이것은 렌더링 중에 있는 현재 줄의 데이터에 접근할 때 사용한다. 전체 데이터는 orderBean 모델에서 가져올 것이고, 그 데이터들은 value 속성을 사용해 테이블에 묶이게 된다.
Listing 10. TableStyles.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>Order Table</title>
<link rel="stylesheet" type="text/css"
href="<%=request.getContextPath()%>/styles/TableStyles.css">
</head>
<body>
<h1>Order Table</h1>
<h:dataTable id="table" var="row" value="#{orderBean}"
styleClass="tablebg" headerClass="header" footerClass="footer"
columnClasses="text,number,number,number"
rowClasses="graybg,whitebg" border="0" cellpadding="5">
<h:column>
<f:facet name="header">
<h:outputText value="Description"/>
</f:facet>
<h:outputText value="#{row.description}"/>
<f:facet name="footer">
<h:outputText value="Total Price"/>
</f:facet>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Quantity"/>
</f:facet>
<h:outputText value="#{row.quantity}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Unit Price"/>
</f:facet>
<h:outputText value="#{row.unitPrice}">
<f:convertNumber type="currency" currencyCode="USD"
minFractionDigits="2" maxFractionDigits="2"/>
</h:outputText>
</h:column>
<h:column footerClass="footer number">
<f:facet name="header">
<h:outputText value="Item Price"/>
</f:facet>
<h:outputText value="#{row.itemPrice}">
<f:convertNumber type="currency" currencyCode="USD"
minFractionDigits="2" maxFractionDigits="2"/>
</h:outputText>
<f:facet name="footer">
<h:outputText value="#{orderBean.totalPrice}">
<f:convertNumber type="currency" currencyCode="USD"
minFractionDigits="2" maxFractionDigits="2"/>
</h:outputText>
</f:facet>
</h:column>
</h:dataTable>
</body>
</html>
</f:view>
|
Listing 11은 TableStyles.jsp 페이지에 의해 생성된 HTML을 보여준다.
Listing 11. TableStyles.jsp에 의해 생성된 HTML
<html>
<head>
<title>Order Table</title>
<link rel="stylesheet" type="text/css"
href="/jsf12css/styles/TableStyles.css">
</head>
<body>
<h1>Order Table</h1>
<table id="table" class="tablebg" border="0" cellpadding="5">
<thead>
<tr>
<th class="header" scope="col">Description</th>
<th class="header" scope="col">Quantity</th>
<th class="header" scope="col">Unit Price</th>
<th class="header" scope="col">Item Price</th>
</tr>
</thead>
<tfoot>
<tr>
<td class="footer">Total Price</td>
<td class="footer"></td>
<td class="footer"></td>
<td class="footer number">$240.92</td>
</tr>
</tfoot>
<tbody>
<tr class="graybg">
<td class="text">Domain Name</td>
<td class="number">3</td>
<td class="number">$7.99</td>
<td class="number">$23.97</td>
</tr>
<tr class="whitebg">
<td class="text">SSL Certificate</td>
<td class="number">1</td>
<td class="number">$119.00</td>
<td class="number">$119.00</td>
</tr>
<tr class="graybg">
...
</tr>
<tr class="whitebg">
...
</tr>
<tr class="graybg">
...
</tr>
<tr class="whitebg">
...
</tr>
</tbody>
</table>
</body>
</html>
|
TableStyles.jsp 예제의 JSF 테이블에 있는 마지막 <h:column> 컴포넌트는 footerClass 속성을 사용해 주문의 총 개수를 보여주기 위해 두 개의 클래스를 사용했다. footer와 number 클래스는 푸터 셀의 <td>에 사용된다. <h:column> 컴포넌트의 footerClass 속성은 JSF 1.2에서만 사용할 수 있다. 만약 이전 버전의 JSF를 사용한다면, 총계를 보여주는 <h:outputText> 컴포넌트를 <div> 엘리먼트로 감싸 number 클래스의 정렬 효과를 줄 수 있다(Listing 12). 소스 코드 압축 파일에는 JSF 1.1과 JSF 1.2 버전에 대한 코드 두 가지가 담겨 있다.
Listing 12. JSF 1.1 버전의 TableStyles.jsp 예제
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<f:view>
...
<h:dataTable ...>
...
<h:column>
...
<f:facet name="footer">
<h:panelGroup>
<f:verbatim><div class="number"></f:verbatim>
<h:outputText value="#{orderBean.totalPrice}">
<f:convertNumber type="currency" currencyCode="USD"
minFractionDigits="2" maxFractionDigits="2"/>
</h:outputText>
<f:verbatim></div></f:verbatim>
</h:panelGroup>
</f:facet>
</h:column>
</h:dataTable>
...
</f:view>
|
JSF 컴포넌트에 기본 스타일 설정하기
수많은 JSF 페이지를 사용하는 매우 규모가 큰 애플리케이션을 개발할 때, 아마 모든 컴포넌트 개체에 styleClass 속성을 일일이 설정하는 것에 싫증을 느낄지도 모른다. 표준 JSF HTML 컴포넌트들은 기본 스타일을 가지고 있지 않다. 그런데 이번 절에서 보게 되겠지만, JSF 컴포넌트들을 순회하는 것과 프로그래밍을 통해 기본 스타일 클래스를 설정하는 것이 가능하다. 이런 조작을 하기 위해, 이 트리들이 어떻게 만들어졌으며 HTML이 어떻게 만들어지는지 이해할 필요가 있다.
JSF 컴포넌트 트리가 어떻게 생성되는지 이해하기
모든 페이스 요청은 페이스 서블릿(Faces Servlet)이 가로챈다. 이것은 모든 JSF 기반 웹 애플리케이션의 web.xml에 지시자로 등록되어 있다. 각각의 요청이 전달되면, 페이스 서블릿은 javax.faces.context.FacesContext 객체를 초기화하고 javax.faces.lifecycle.Lifecycle 객체의 execute()와 render() 메서드를 호출한다. execute() 메서드는 JSF 요청 처리 생명 주기 중에서 마지막 것(Render Response라고 부르는 것)을 제외한 모든 과정을 처리한다. 그것은 render() 메서드에서 호출된다.
 |
상태를 저장하는 방법들
web.xml에서 설정할 수 있는 javax.faces.STATE_SAVING_METHOD 컨텍스트 매개변수의 값에 따라 컴포넌트 트리가 서버 또는 클라이언트에 저장된다. JSF 구현체는 session 스코프를 사용해 컴포넌트 트리를 서버쪽에 저장할 수 있다. 이 방법은 효율적이지만, 만약 사용자가 브라우저의 뒤로가기 버튼을 클리하면, 이미 전송된 폼으로 돌아가 JSF 프레임워크를 혼란에 빠트릴 수 있다. 하지만 그것보다는 각각의 컴포넌트 트리를 클라이언트쪽에 저장하고 숨겨진 폼 엘리먼트가 페이지 객체와 연관되어 있는 컴포넌트 트리에 대한 복사본을 가지고 있게 하는 것이 좀 더 나은 선택이다. 컴포넌트 트리를 클라이언트쪽에 저장하는 것은 객체 직렬화와 역직렬화 때문에, 트래픽을 증가시키고 추가적인 CPU 리소스를 사용하게 되지만, 이 방법이 session 스코프를 사용하는 것보다 훨씬 안정적이다.
|
|
(Restore View라고 하는) 첫 번째 단계에서, JSF 구현체는 애플리케이션의 뷰 핸들러를 얻어온다. 이것은 javax.faces.application.ViewHandler 객체로, 만약 요청이 post라면 restoreView() 메서드를 호출한다(예를 들어 사용자가 Submit 버튼을 클릭했을 때). 이 경우, 컴포넌트 트리는 생성되어야 하고 이전 Faces 요청을 처리하는 동안 저장되어야 한다(상태를 저장하는 방법들 참조).
만약 현재 요청이 post가 아니라면, (예를 들어 사용자가 Faces 링크를 클릭했을 때), JSF 구현체는 현재 FacesContext 객체의 renderResponse() 메서드를 호출한다. 그리고 ViewHandler 객체에 createView() 메서드를 호출한다. renderResponse() 호출은 JSF 구현체에 현재 단계인 Restore View를 넘어 Render Response 단계로 가도록 지시한다. 기본 뷰 핸들러의 createView() 메서드는 javax.faces.component.UIViewRoot 컴포넌트 하나만을 생성한다. 트리의 나머지 부분은 JSF 구현체가 뷰 핸들러의 renderView() 메서드를 호출했을 때, Render Response 단계에서 생성될 것이다.
컴포넌트 트리가 복구되거나 반드시 만들어야 하는지에 관계없이, JSF 구현체는 HTTP 요청을 renderView() 메서드가 실행되는 어느 시점에선가 연관된 JSF 페이지에 전달한다. 예를 들어 /MyApp/MyPage.faces나 /MyApp/faces/MyPage.jsp를 요청했을 때, 실행되는 JSF 페이지의 URI는 /MyApp/MyPage.jsp다.
모든 JSF 페이지는 <f:view>와 <h:form> 같은 컴포넌트 태그를 가지고 있다. 이것들은 JSP 태그 핸들러가 지원한다. 이것들은 자바 클래스로 javax.servlet.jsp.tagext 패키지에 정의되어 있는 인터페이스를 구현하고 있다. 페이지가 실행되면 JSP 컨테이너가 태그 핸들러의 객체를 생성하고 그것들의 메서드를 호출한다. JSF 1.2 컴포넌트에 있는 모든 태그 핸들러는 javax.faces.webapp.UIComponentELTag를 확장하고 있다. 만약 JSF 1.1 API를 사용하고 있다면, 태그 핸들러 클래스들은 javax.faces.webapp.UIComponentTag 또는 javax.faces.webapp.UIComponentBodyTag를 확장한다. 만약 컴포넌트 트리가 Restore View 단계에서 복구되지 않았다면 어떤 버전의 JSF를 사용하든지 JSF 태그 핸들러는 컴포넌트 인스턴스를 Render Response 단계에서 생성할 것이다.
아마도 기본 스타일을 설정하기 위해 javax.faces.event.PhaseListener를 구현하려고 시도해 봤을 것이다. 하지만 이것은 사용자들이 링크를 클릭하여 요청하거나 웹 브라우저에서 URL을 입력하여 요청할 때는 동작하지 않을 것이다. 이런 경우, JSF 구현체는 컴포넌트 트리를 복구할 수 없다. 반드시 Render Response 단계에서 생성되어야 한다. 마지막 단계 이전까지 컴포넌트 트리가 존재하지 않는 상태로 와서, 응답을 렌더링한 다음에는 컴포넌트의 스타일을 수정하기에는 너무 늦는다. HTML 결과가 이미 생성되었기 때문이다. 따라서 기본 스타일은 반드시 컴포넌트를 렌더링하는 Render Response 단계 전에 설정되어야 한다.
렌더링 직전에 컴포넌트 트리 변경하기
JSF 컴포넌트는 rendersChildren이라는 매우 재미있는 읽기 전용 속성을 가지고 있다. 이 속성이 false라면, 컴포넌트를 렌더링할 때 encodeBegin()과 encodeEnd()만 호출된다. getRendersChildren()이 true를 반환하면, Render Response 단계에서 encodeChildren()도 호출된다. 이 메서드들은 일반적으로 렌더링을 다른 클래스에 위임한다. 그 클래스들은 반드시 javax.faces.render.Renderer 인터페이스를 구현해야 하지만 JSF 컴포넌트도 렌더러 없이 스스로 HTML을 생성할 수 있다.
JSF 1.1을 사용할 때 인코딩 메서드들은 초기에 태그 핸들러 메서드에 의해 호출된다. 그런데 만약 컴포넌트 렌더러 자신이 child라면, 내부에 속해 있는 컴포넌트들의 모든 태그 핸들러가 인코딩 메서드를 호출하는 것을 멈춘다. 렌더링은 getRendersChildren() 메서드가 true를 반환하는 컴포넌트의 책임이 되기 때문이다. 이 렌더링 과정은 JSF 1.1에서 비효율적으로 구현되어 있었다. 버퍼링을 최소화했지만, 애플리케이션 개발자로 하여금 <f:verbatim> 컴포넌트들에 HTML 컨텐츠를 포함시키도록 했기 때문이다. JSF 1.2에서는 <f:verbatim>을 사용할 필요가 없다. 렌더링이 모든 컴포넌트가 생성된 이후에만 수행되기 때문이다.
JSF 1.1을 사용하든 1.2를 사용하든 관계없이, encodeChildren()이 호출되면 내부에 포함하고 있는 컴포넌트들은 전부 초기화되고 렌더링될 준비가 될 것이다. 그러므로 바로 여기에 기본 스타일을 적용할 수 있다. 계속해서, JSF 컴포넌트 트리를 재귀적으로 순환하며 어떻게 자신이 가지고 있는 내부 컴포넌트에 styleClass 속성을 설정하는 커스텀 컴포넌트를 생성하는지 살펴볼 것이다.
여기서 소개할 기술은 기본 스타일뿐만 아니라 JSF 컴포넌트의 어떤 속성이나 프로퍼티를 설정하는 데 사용할 수 있다. 따라서 나중에 재사용하기 위한 일반적인 기본 클래스를 생성하는 것이 좋겠다. SetupComponent 클래스는 JSF API의 UIComponentBase 클래스를 확장하고 있다. 그리고 추상 메서드 setup()을 정의하였다. 이 메서드는 렌더링이 가능한 모든 내부 컴포넌트에서 호출될 것이다. (Listing 13에 보이는) setupTree() 메서드는 JSF 컴포넌트 트리의 재귀적인 순환을 수행한다.
Listing 13. SetupComponent 클래스의 setup()과 setupTree() 메서드
package jsfcssjs;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
...
public abstract class SetupComponent extends UIComponentBase {
protected abstract void setup(FacesContext ctx, UIComponent comp);
protected void setupTree(FacesContext ctx, UIComponent comp) {
if (!comp.isRendered())
return;
setup(ctx, comp);
for (UIComponent child : comp.getChildren())
setupTree(ctx, child);
}
...
}
|
UIComponentBase를 확장한 클래스에 있는 getRendersChildren() 메서드(Listing 14 참조)는 true를 반환하도록 재정의되어 있다. 따라서 설치된 컴포넌트는 자신이 가지고 있는 하위 컴포넌트들의 렌더링을 제어할 수 있다.
Listing 14. SetupComponent 클래스의 getRendersChildren() 메서드
public abstract class SetupComponent extends UIComponentBase {
...
public boolean getRendersChildren() {
return true;
}
...
}
|
Listing 15는 encodeChildren() 메서드다. 이것은 각각의 하위 컴포넌트에 encodeTree() 메서드를 호출하기 전에 setupTree()를 호출하도록 재정의되어 있다.
Listing 15. SetupComponent 클래스의 encodeChildren() 메서드
public abstract class SetupComponent extends UIComponentBase {
...
public void encodeChildren(FacesContext ctx)
throws IOException {
if (!isRendered())
return;
setupTree(ctx, this);
for (UIComponent child : getChildren())
encodeTree(ctx, child);
}
...
}
|
encodeTree() 메서드(Listing 16 참조)는 현재 컴포넌트를 위한 인코딩 메서드들을 호출하고, 만약 getRendersChildren()이 false를 반환하면 재귀적으로 하위 컴포넌트들에 의해 호출된다.
Listing 16. SetupComponent 클래스의 encodeTree() 메서드
public abstract class SetupComponent extends UIComponentBase {
...
protected void encodeTree(FacesContext ctx, UIComponent comp)
throws IOException {
if (!comp.isRendered())
return;
comp.encodeBegin(ctx);
if (comp.getRendersChildren())
comp.encodeChildren(ctx);
else
for (UIComponent child : comp.getChildren())
encodeTree(ctx, child);
comp.encodeEnd(ctx);
}
}
|
커스텀 컴포넌트 생성하고 설정하기
Listing 17은 DefaultStylesComponent 클래스다. 이것은 SetupComponent를 확장했고, styleClass 속성을 설정하기 위해 setup() 메서드를 구현했다. 그 값들은 컴포넌트 패밀리 이름과 렌더러 이름을 바탕으로 설정된다.
Listing 17. DefaultStylesComponent
package jsfcssjs;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import java.util.Map;
public class DefaultStylesComponent extends SetupComponent {
protected void setup(FacesContext ctx, UIComponent comp) {
Map<String, Object> attrMap = comp.getAttributes();
if (attrMap.get("styleClass") != null)
return;
String familyName = getLastName(comp.getFamily());
String rendererName = getLastName(comp.getRendererType());
if (familyName == null || rendererName == null)
return;
String className = "Default" + familyName;
if (!familyName.equals(rendererName))
className += rendererName;
attrMap.put("styleClass", className);
}
protected String getLastName(String fullName) {
if (fullName == null)
return null;
int dotIndex = fullName.lastIndexOf('.');
if (dotIndex != -1)
return fullName.substring(dotIndex+1);
else
return fullName;
}
public String getFamily() {
return "DefaultStyles";
}
}
|
Listing 18에 보이는 것처럼 DefaultStylesComponent는 faces-config.xml에 설정되어 있다.
Listing 18. faces-config.xml에 DefaultStylesComponent 설정하기
<faces-config ...>
...
<component>
<component-type>DefaultStylesComponent</component-type>
<component-class>jsfcssjs.DefaultStylesComponent</component-class>
<component-extension>
<component-family>DefaultStyles</component-family>
</component-extension>
</component>
</faces-config>
|
커스텀 태그 구현하기
DefaultStylesTag 클래스(Listing 19 참조)는 JSF 1.2 API에 정의되어 있는 UIComponentELTag클래스를 확장하였고, getComponentType()과 getRendererType() 메서드가 각각 DefaultStylesComponent와 null을 반복적으로 반환하도록 구현하였다. JSF 1.1을 사용한다면 태그 클래스는 반드시 UIComponentELTag가 아닌 UIComponentTag를 확장해야 한다.
Listing 19. DefaultStylesTag 클래스
package jsfcssjs;
import javax.faces.webapp.UIComponentELTag;
public class DefaultStylesTag extends UIComponentELTag {
public String getComponentType() {
return "DefaultStylesComponent";
}
public String getRendererType() {
return null;
}
}
|
css.tld 파일(Listing 20 참조)은 커스텀 태그의 defaultStyles 이름과 속성을 정의하고 있다. id, binding, rendered 속성은 UIComponentELTag로부터 상속 받았다.
Listing 20. JSF 1.2와 JSF 2.1의 css.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>css</short-name>
<uri>/css.tld</uri>
<tag>
<name>defaultStyles</name>
<tag-class>jsfcssjs.DefaultStylesTag</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.DefaultStylesComponent</type>
</deferred-value>
</attribute>
<attribute>
<name>rendered</name>
<required>false</required>
<deferred-value>
<type>boolean</type>
</deferred-value>
</attribute>
</tag>
</taglib>
|
JSF 1.1을 사용하고 있다면, JSP 1.2 표준에 정의되어 있는 문법대로 TLD 파일을 작성해야 한다(Listing 21).
Listing 21. JSF 1.1과 JSP 1.2에서 사용할 css.fld 파일
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>css</short-name>
<uri>/css.tld</uri>
<tag>
<name>defaultStyles</name>
<tag-class>jsfcssjs.DefaultStylesTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>id</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>binding</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>rendered</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
</taglib>
|
JSF 페이지에서 커스텀 컴포넌트 사용하기
 |
developerWroks Ajax 참고자료 센터
Ajax 참고자료 센터에서 Ajax 애플리케이션을 개발할 때 사용할 무료 도구, 코드, 정보를 확인하라. Ajax 전문가 Jack Herrington이 운영하는 active Ajax 커뮤니티 포럼은 여러분이 궁금해 하는 것들을 바로 알려줄 수 있는 동료들과 연결시켜 줄 것이다.
|
|
이번 절에서 소개할 JSF 예제는 배경화면 색과 경계선을 사용해 JSF 컴포넌트의 외관을 멋지게 꾸미는 방법을 보여줄 것이다.
그림 2. DefaultStyles 예제
Listing 22는 DefaultStyles.css 파일이다. 이 파일은 DefaultStyles.jsp에서 사용하는 몇 가지 스타일 클래스를 정의한다.
Listing 22. DefaultStyles.css 예제
.DefaultForm {}
.DefaultPanelGrid { background-color: #FFFFFF; }
.DefaultInputText { background-color: #FFDDDD;
border-style: ridge; border-width: thin; }
.DefaultInputTextarea { background-color: #DDFFDD;
border-style: groove; border-width: thick; }
.DefaultSelectOneMenu { background-color: #DDDDFF;
border-style: inset; border-width: medium; }
.DefaultCommandButton { background-color: #DDDDDD;
border-style: outset; border-width: medium; }
.SpecialInputText { background-color: #DDFFFF;
border-style: double; border-width: thick; }
.EnabledOption { color: #FF0000; }
.DisabledOption { color: #0000FF; }
|
DefaultStyles.jsp 예제(Listing 23 참조)는 <css:defaultStyles> 컴포넌트를 사용해 styleClass 속성이 없는 JSF 컴포넌트의 기본 스타일을 설정했다. 컴포넌트가 사용하는 것 중 하나가 SpecialInputText 스타일 클래스다. EnabledOption과 DisabledOption 클래스는 드롭 다운 리스트의 아이템에 적용될 것이다.
Listing 23. DefaultStyles.jsp 예제
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="css" uri="/css.tld" %>
<f:view>
<html>
<head>
<title>Default Styles</title>
<link rel="stylesheet" type="text/css"
href="<%=request.getContextPath()%>/styles/DefaultStyles.css">
</head>
<body>
<h1>Default Styles</h1>
<css:defaultStyles>
<h:form id="form">
<h:panelGrid columns="1" border="0" cellspacing="5">
<h:inputText id="text" size="30" value="default text style"/>
<h:inputText id="special" size="30" value="special text style"
styleClass="SpecialInputText"/>
<h:selectOneMenu id="menu" enabledClass="EnabledOption"
disabledClass="DisabledOption">
<f:selectItem itemValue="First" itemLabel="First"/>
<f:selectItem itemValue="Second" itemLabel="Second"/>
<f:selectItem itemValue="Third" itemLabel="Third"
itemDisabled="true"/>
</h:selectOneMenu>
<h:inputTextarea id="area" rows="5" cols="30"
value="default text area style"/>
<h:commandButton id="button" value="Submit"/>
</h:panelGrid>
</h:form>
</css:defaultStyles>
</body>
</html>
</f:view>
|
Listing 24는 DefaultStyles.jsp 페이지에 의해 생성된 HTML이다. 모든 폼 엘리먼트가 class 속성을 가지고 있는 것을 보았을 것이다. 이 클래스 이름들은 Default로 시작하고 <css:defaultStyles> 컴포넌트에 의해 설정되었다.
Listing 24. DefaultStyles.jsp에 의해 생성된 HTML
<html>
<head>
<title>Default Styles</title>
<link rel="stylesheet" type="text/css"
href="/jsf12css/styles/DefaultStyles.css">
</head>
<body>
<h1>Default Styles</h1>
<form id="form" ... class="DefaultForm" ...>
<input type="hidden" name="form" value="form" />
<table class="DefaultPanelGrid" border="0" cellspacing="5">
<tbody>
<tr>
<td><input id="form:text" type="text" ...
class="DefaultInputText" .../></td>
</tr>
<tr>
<td><input id="form:special" type="text" ...
class="SpecialInputText" .../></td>
</tr>
<tr>
<td><select id="form:menu" ... class="DefaultSelectOneMenu" ...>
<option value="First" class="EnabledOption">First</option>
<option value="Second" class="EnabledOption">Second</option>
<option value="Third" disabled="disabled"
class="DisabledOption">Third</option>
</select></td>
</tr>
<tr>
<td><textarea id="form:area" ...
class="DefaultInputTextarea" ...> ... </textarea></td>
</tr>
<tr>
<td><input id="form:button" type="submit" ...
class="DefaultCommandButton" /></td>
</tr>
</tbody>
</table>
<input type="hidden" ... id="javax.faces.ViewState" value="..." />
</form>
</body>
</html>
|
결론
이 기사는 표준 JSF 컴포넌트가 제공하는 CSS 지원 기능을 살펴보았다. 간단한 것들은 style과 styleClass라는 두 개의 부가적인 속성만을 가지고 있다. 그리드와 데이터 테이블 같은 좀 더 복잡한 컴포넌트들은 그것들의 단면(facet)과 엘리먼트를 위한 추가적인 CSS 관련 속성들을 가지고 있다. 본 연재의 Part 1에서는 JSF 컴포넌트 트리가 어떻게 생성되는지 그리고 컴포넌트들이 렌더링되기 전에, 기본 스타일과 같은 부가 기능을 추가하기 위해 이 트리들을 어떻게 변경할 수 있는지 살펴보았다. 계속해서 Part 2에서는 JSF 폼을 더 동적으로 표현해주는 자바스크립트 기반 기술들을 살펴볼 것이다.
다운로드 하십시오 | 설명 | 이름 | 크기 | 다운로드 방식 |
|---|
| 이 기사의 예제 애플리케이션 | jsfcssjs_part1_src.zip | 25KB | HTTP |
|---|
참고자료 교육
- 한국 developerWorks 웹 개발 존에는 웹 2.0 개발 도구와 정보가 가득하다.
-
JSF 기술에 대한 정보를 찾아 여러분의 애플리케이션에 적용하라.
- developerWorks Ajax 참고자료 센터에는 Ajax 기사가 상당히 많으며 Ajax 개발을 시작할 때 유용한 정보를 포함하고 있다.
-
JSF 1.2 명세는 JSF 기술을 전부 설명하고 있다.
- W3C에서 CSS에 대한 좋은 기초 자료를 제공하고 있다.
제품 및 기술 얻기
토론
필자소개  | |  | Andrei Cioroianu는 Devsphere 창립자로, 자바 EE 개발과 Ajax/JSF 컨설팅 서비스를 제공하고 있다. Andrei Cioroianu는 자바와 웹 서비스 기술을 1997년부터 사용해 왔고, 10년 동안 복잡한 기술 문제를 해결하고 있으며 상용 제품, 고객 애플리케이션 그리고 오픈 소스 프레임워크 전반을 다루고 있다. devsphere.com에서 Andrei를 만날 수 있을 것이다. |
기사에 대한 평가
 |
| 이 문서 북마킹 하기
|
|