Facelets 非常適合 JSF
最終形成了專為 JSF 設計的視圖技術!
|
|
|
|
|
?
|
|
|
級別: 初級
Richard Hightower, 開發人員, ArcMind Inc.
2006 年 3 月 27 日
試圖把 JSF 和 JSP 結合起來就像試圖要把腳硬塞進手套一樣:可能做得到,但是只是更好的解決辦法出現之前的一個權宜之計。在這篇文章中,JSF 的熱心支持者 Rick Hightower 介紹了關于 Facelets 他最喜歡的內容:容易的 HTML 樣式的模板化和可重用的復合組件。
由于最近在 Java? 服務器外觀(JSF)項目上工作,我很有幸第一次使用了 Facelets。關于 Facelets,我最喜歡的是它讓我可以創建可重用的復合組件。能夠拿出一個頁面(例如 JSP)并把它變成組件,對于我的 JSF 開發來說真是莫大的好處。我的結論是什么?如果不用 Facelets,那么就無法得到能從 JSF 獲得的最大收獲。
JSF 和 Java 服務器頁面技術之間的不匹配,是 JSF 開發中的一個嚴重問題。問題是如何把 JSP 的動態內容集成到 JSF 基于組件的模型中。JSP 非常重視生成動態內容輸出,而 JSF 需要 JSP 來協調組件模型的構建。因為這個任務超出了 JSP 原來的目的,所以產生了距離。
大多數 JSF 開發人員只是學會了一事一議地解決這類問題,但是這就像在錘子上放一個枕頭,最終還會掉下來打傷腦袋。Facelets 是更加全面的解決方案:專為 JSF 組件模型度身定制的模板化語言。
Facelets 有以下吸引人的特性:
- 模板化(像 Tiles)
- 復合組件
- 定制的邏輯標記
- 表達式語言
- 對設計師友好的頁面開發
- 創建組件庫
這些特性比我想像的要更相關和統一。在這篇文章中,我討論前兩個:模板化和復合組件。我使用的 Web 應用程序基于為我的針對懷疑者的 JSF 系列開發的一個應用程序,我把它更新成使用 Facelets 視圖而不是 Tiles。在進一步閱讀之前,應當 下載示例代碼。如果要隨著討論一起操作,還需要 安裝 Facelets。
Facelets 概述
對于 Facelets 可能會做的最大一個錯誤假設,就是它只是 Tiles 的替代品。Facelets 遠不止如此:它是思考 JSF 的新方式。
|
選擇標記
雖然多數開發人員把 Facelets 用于 XHTML,實際上這個框架并不在意使用什么標記:它與 XUL (XULFaces)兼容, Kito Mann 已經用它為 JSF 中心提供 RSS。
|
|
JSP 是種生成 servlet 的模板化語言。JSP 的主體與 servlet 的 doGet()
和 doPost()
方法等價(也就是說,成為 jspService()
方法)。JSF 定制標記(例如 f:view
和 h:form
)只是調用 JSF 組件來呈現它們自己的當前狀態。JSF 組件模型的生命周期獨立于 JSP 生成的 servlet 的生命周期。這種獨立性就是混淆的來源。
與 JSP 不同,Facelets 這個模板化語言,從構建之初,就考慮了 JSF 的組件生命周期。使用 Facelets,生成的模板會構建組件樹,而不是 servlet。這就允許更好的重用,因為可以把組件組合成另一個組件。
Facelets 減少了編寫定制標記才能使用 JSF 的需求。Facelets 本身就可以使用 JSF 定制組件。溝通 JSF 和 Facelets 只需要很少的特殊編碼:要做的全部工作就是在 Facelet 標記庫文件中聲明 JSF 組件。在 Facelets 模板化語言中可以直接使用 JSF 組件,不用任何額外的開發。
Facelets 模板框架
在提供針對組件構建設計的模板框架方面,Facelets 與 Tapestry (請參閱 參考資料)類似。但是,對于具有 JSP 背景的我們來說,Facelets 看起來比 Tapestry 友好得多。它允許使用熟悉的 JSTL 樣式的標記和 JSTL/JSF/JSP 樣式的表達式語言。大大降低的學習曲線意味著可以更加迅速地開始開發。
|
Facelets 和 Tapestry
Facelets 與 Tapestry 很相似,可以相互比較。實際上,Tapestry 剛出現的時候,大大領先于它的時代,而 Facelets 確實借鑒了它的一些想法。但是,如果只把 Facelets 當成 JSF 版本的 Tapestry,那就錯了。這兩項技術是不同的。要了解關于 Tapestry 的更多內容,請參閱 Brett McLaughlin 兩部分的系列 “了解 Tapestry,第 1 部分”。
|
|
Facelets 允許定義能夠直接包含進頁面或者容易地添加到 Facelet 標記庫的組件集。實際上讓人高興的是在 Facelets 中定義定制標記(復合組件和類似 JSP 定制標記的標記)的迅速。使用這些組件集,Facelets 還允許定義站點模板(和更小的模板)。這與使用 Tiles 很相似,但是少了定義文件。也可以在定制 JSF 組件內部使用 Facelets,因為 Facelets API 提供了可以容易地與 JSF 組件集成的接口。
從 Tiles 到 Facelets
如前所述,在這里使用的示例 Web 應用程序基于為我的 針對懷疑者的 JSF 系列創建的示例。它為一家在線 CD 店管理庫存,創建、讀取、更新和刪除(CRUD)清單。它包含一個表單,讓用戶向系統輸入新 CD,有一個單選按鈕列表,允許用戶選擇音樂分類。當用戶選擇了一個分類時,就觸發某些 JavaScript 立即把表單提交回服務器。應用程序還包含一個 CD 清單,用戶可以根據標題或藝術家對清單中的 CD 排序。圖 1 是應用程序類的 UML 圖表:
圖 1. 在線 CD 商店示例的類圖
圖 2 提供了商店的 CD 清單頁面:
圖 2. 在線 CD 商店的清單頁面
原來的應用程序從 Tiles 得到視圖支持,現在我將用 Facelets 構建視圖。我先從用 Facelets 替換示例中的 Tiles 支持開始,然后編寫復合組件。在開始之前,需要已經安裝了 Facelets。
安裝 Facelets
安裝 Facelets 的步驟很容易。請注意,我假設已經下載并安裝了 示例應用程序。
-
下載 Facelets 發行包 并解壓縮。
- 把 jsf-facelets.jar 拷貝到 WEB-INF/lib 目錄(在應用程序部署時,它最終必須放在 WEB-INF/lib 目錄中)。
- 把 Facelet 初始化參數添加到 web.xml 文件中。
- 把 FaceletViewHandler 添加到 faces-config.xml 文件中。
步驟 1 和 2 比較基本。我將詳細介紹其他兩個步驟。
添加初始化參數
這一步假設已經安裝了工作正常的 JSF 應用程序(例如 在線 CD 商店示例),然后編輯現有的 web.xml 頁面,添加以下參數:
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>
|
這告訴 JSF 采用 xhtml 前綴,Facelet 渲染器能夠解釋這個前綴。
Facelets 有許多參數,請參閱 參考資料 獲得完整清單。如果示例有問題,請參考 DEVELOPMENT init
參數,它適合調試。把 REFRESH_PERIOD
參數設置為 low
在開發期間會有幫助。
添加 FaceletViewHandler
要讓 Facelets 模板生效,需要把 Facelets 視圖處理器告訴 JSF。JSF ViewHandler
是個插件,為不同的響應生成技術(包括 Facelets)處理 JSF 請求處理生命周期的 “渲染器響應和恢復視圖” 階段。(任何認為 JSF 不能擴展的人都是被誤導了!)通過添加以下視圖處理器到 faces-config.xml 中,就把 Facelets 插進了 JSF 中:
<application>
<locale-config>
<default-locale>en</default-locale>
</locale-config>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application>
|
用 Facelets 進行模板化
首先介紹 Facelets 模板化框架,因為它相對容易理解。創建和使用 Facelets 模板的步驟如下:
- 創建 layout.xhtml 頁面。
- 定義 Facelet 的命名空間,導入對 Facelets 的使用。
- 用
ui:insert
標記定義頁面的邏輯區域。
- 用純文本和
ui:include
標記定義合理的默認值。
我要逐步介紹這些步驟,用在線 CD 商店清單頁面作為我的布局示例。
步驟 1. 創建 layout.xhtml 頁面
layout.xhtml 頁面就是一個一般的 XHTML 文本文件,使用了以下文檔類型聲明:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
</html>
|
不要求進一步細節!
步驟 2. 定義 Facelets 的命名空間
為了用 Facelets 標記進行模板化,需要用 XML 命名空間像下面這樣導入它們:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets">
...
|
請注意 ui 命名空間的定義。
步驟 3. 用 ui:insert 標記定義頁面的邏輯區域
下面,定義布局的邏輯區域,例如頁面標題、小標題、導航、內容等等。下面是定義頁面標題的示例:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets">
<head>
<title><ui:insert name="title">Default title</ui:insert></title>
<link rel="stylesheet" type="text/css" href="./css/main.css"/>
</head>
...
|
請注意使用 ui:insert
標記定義了標題的邏輯區域。ui:insert
元素內的文本 “Default title” 定義了模板用戶不傳遞標題時顯示的文本。也可以像下面這樣編寫上面的內容:
步驟 4. 用純文本和 ui:includes 定義默認值
可以傳遞更多的純文本作為默認值。例如,請研究 layout.xhtml 中的以下代碼片段:
<div id="header">
<ui:insert name="header">
<ui:include src="header.xhtml"/>
</ui:insert>
</div>
|
在這里,我用了 ui:insert
標記定義邏輯區域,用 ui:include
標記插入默認值。默認情況下,使用布局的頁面會采用 header.xhtml 的內容作為標題文本,但是因為標題是 ui:insert
定義的邏輯區域,所以用這個模板也能傳遞不同的標題。對于擁有前端(例如,帶有購物車的目錄)和后端管理(例如添加新產品)的應用程序,后端站點在標題或導航上可能不同的鏈接。ui:include
標記可以容易地用新標題換掉默認標題。
清單 1 顯示了示例應用程序的清單頁面 list.xhtml 的完整代碼:
清單 1. 完整的 list.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets">
<head>
<title><ui:insert name="title">Default title</ui:insert></title>
<link rel="stylesheet" type="text/css" href="./css/main.css"/>
</head>
<body>
<div id="header">
<ui:insert name="header">
<ui:include src="header.xhtml"/>
</ui:insert>
</div>
<div id="left">
<ui:insert name="navigation" >
<ui:include src="navigation.xhtml"/>
</ui:insert>
</div>
<div id="center">
<br />
<span class="titleText"> <ui:insert name="title" /> </span>
<hr />
<ui:insert name="content">
<div>
<ui:include src="content.xhtml"/>
</div>
</ui:insert>
</div>
<div id="right">
<ui:insert name="news">
<ui:include src="news.xhtml"/>
</ui:insert>
</div>
<div id="footer">
<ui:insert name="footer">
<ui:include src="footer.xhtml"/>
</ui:insert>
</div>
</body>
</html>
|
現在已經知道了如何定義布局,我將介紹如何使用布局!
使用 Facelets 模板
為了調用模板,要使用 ui:composition
標記。為了把參數傳遞給模板,要使用 ui:define
標記,它是 ui:composition
標記的子元素。在清單 2 中,我調用了在線 CD 商店示例的布局頁面:
清單 2. 調用布局頁面
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<ui:composition template="/WEB-INF/layout/layout.xhtml">
<ui:define name="title">CD form</ui:define>
<ui:define name="content">
<!-- use the form tag to set up this form -->
<h:form id="cdForm">
...
...
...
</h:form>
</ui:define>
</ui:composition>
</html>
|
請注意在上面的調用中包含了以下命名空間:
- xmlns:h="http://java.sun.com/jsf/html"
- xmlns:f="http://java.sun.com/jsf/core"
有了 Facelets,就不必依賴 JSF 標記庫,所以要使用核心和 HTML JSF 組件,必須通過以上命名空間導入它們。
html
標記的使用看起來可能有些奇怪。畢竟,清單 2 所示的布局頁面要調用的模板已經有了一個 html
標記;所以這是不是意味著會得到兩個 html
標記?如果真的這樣,那么在 ui:composition
標記之外的內容全部被忽略,所以 html
標記所做的只是讓 HTML 編輯器能夠看到 HTML 片段。它不會影響運行時行為。
位置是一切
當頁面調用布局模板時,只需指定模板的位置,如下所示:
<ui:composition template="/WEB-INF/layout/layout.xhtml">
|
這個標記調用清單 1 所示的模板,所以我要做的全部工作就是把參數傳遞給模板。然后,在復合標記內部,可以傳遞像標題這樣的簡單文本:
<ui:define name="title">CD form</ui:define>
|
或者傳遞整個組件樹:
<ui:define name="content">
<!-- use the form tag to setup this form -->
<h:form id="cdForm">
...
...
...
</h:form>
</ui:define>
|
請注意,在我定義和傳遞的許多邏輯區域中,cdForm.xhtml 只傳遞兩個:內容和標題。
|
Tiles 與 Facelets
這篇文章帶了三個示例:第一個使用 Tiles,第二個使用 Facelets,第三個使用復合組件。之所以包含 Tiles 示例是為了可以比較和對比兩個框架中不同的模板化技術。請看看它,并看看自己有什么想法!
|
|
復合組件
如果只用 Facelets 定義和使用模板,那么可能會有點失敗。雖然 Facelets 的模板化特性完整而且豐富,但是它沒有 Tiles 之類的框架那么多的特性,后者還擅長定義默認值、相關模板的層次結構以及類似的東西。
但模板化不是 Facelets 真正出色的地方:Facelets 把它的精華放在復合組件上。(有趣的是,復合組件也給 Facelets 模板化帶來了一些好處;例如,在 Facelets 中可以舍棄 f:verbatim
標記和各種 h:outputText
標記,因為所有的東西都被當成組件樹中的組件。關于這方面的更多內容稍后介紹。)
對于這篇文章余下的部分,我將重點放在創建和使用復合組件的步驟上。但在開始之前,先要確保能夠清楚地理解是什么讓這些方便的小代碼段這么棒。
破壞 DRY 原則
您是否曾經編寫過像清單 3 所示的代碼片段?
清單 3. 復合組件之前的生活
<h:dataTable id="items" value="#{CDManagerBean.cds}" var="cd"
rowClasses="oddRow, evenRow" headerClass="tableHeader">
<!-- Title -->
<h:column>
<f:facet name="header">
<h:panelGroup>
<h:outputText value="Title" />
<f:verbatim>[</f:verbatim>
<h:commandLink styleClass="smallLink"
action="#{CDManagerBean.sort}">
<h:outputText id="ascTitle" value="asc" />
<f:param name="by" value="title"/>
<f:param name="order" value="asc"/>
</h:commandLink>
<h:outputText value="," />
<!-- Sort descending -->
<h:commandLink styleClass="smallLink"
action="#{CDManagerBean.sort}">
<h:outputText id="decTitle" value="dec" />
<f:param name="by" value="title"/>
<f:param name="order" value="dec"/>
</h:commandLink>
<f:verbatim>]</f:verbatim>
</h:panelGroup>
</f:facet>
<h:outputText value="#{cd.title}" />
</h:column>
<!-- Artist -->
<h:column>
<f:facet name="header">
<h:panelGroup>
<h:outputText value="Artist" />
<f:verbatim>[</f:verbatim>
<h:commandLink styleClass="smallLink"
action="#{CDManagerBean.sort}">
<h:outputText id="ascArtist" value="asc" />
<f:param name="by" value="artist"/>
<f:param name="order" value="asc"/>
</h:commandLink>
<h:outputText value="," />
<!-- Sort descending -->
<h:commandLink styleClass="smallLink"
action="#{CDManagerBean.sort}">
<h:outputText id="decArtist" value="dec" />
<f:param name="by" value="artist"/>
<f:param name="order" value="dec"/>
</h:commandLink>
<f:verbatim>]</f:verbatim>
</h:panelGroup>
</f:facet>
<h:outputText value="#{cd.artist}" />
</h:column>
|
這段來自 listing.xhtml 的代碼為示例應用程序的清單頁面生成列標題和升序/降序排列鏈接。請注意,必須在多個地方重復代碼,才能輸出多列。(在上面的示例中您還會注意到,我在 ${..}
和 #{..}
之間切換;這可能讓人迷惑,但它們做的是同樣的事!)
所有這些渲染標題列和藝術家列的重復代碼都破壞了 DRY 原則 —— 即,不要重復自己。那么您說,這里錯在哪兒呢?假設在清單中平均有 5 列,應用程序中有 20 個不同的清單。使用清單 3 的方法,就不得不重復這 35 行代碼 100 次,合計 3,500 行代碼!維護所有這些代碼會是種痛苦,但是如果決定修改清單的表示或添加一種通用的清單過濾方式,會怎么樣?需要更多、更多的工作。
現在拿清單 3 和這個代碼比較:
清單 4. 創建字段的新方式
<h:dataTable id="items" value="#{CDManagerBean.cds}" var="cd"
rowClasses="oddRow, evenRow" headerClass="tableHeader">
<a:column entity="${cd}" fieldName="title" backingBean="${CDManagerBean}"/>
<a:column entity="${cd}" fieldName="artist" backingBean="${CDManagerBean}"/>
|
看起來好像我只用 4 行代碼就替代了 70 行或更多行代碼!可以猜出,a:column
是個復合組件。在 Facelets 中,可以容易地定義這樣的組件,如清單 5 所示:
清單 5. column.xhtml 渲染帶有排序鏈接的列
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:z="http://www.qualcomm.com/jsf/core"
xmlns:c="http://java.sun.com/jstl/core"
xmlns:fn="http://java.sun.com/jsp/jstl/functions">
THIS TEXT WILL BE REMOVED
<ui:composition>
<!-- The label attribute is optional. Generate it if it is missing. -->
<c:if test="${empty label}">
<c:set var="label" value="${fieldName}" />
</c:if>
<!-- The sort attribute is optional. Set it to true if it is missing. -->
<c:if test="${empty sort}">
<c:set var="sort" value="${true}" />
</c:if>
<h:column>
<f:facet name="header">
<h:panelGroup>
${label}
<c:if test="${sort}">
[
<h:commandLink styleClass="smallLink"
action="#{backingBean.sort}">
<h:outputText value="asc" />
<f:param name="by"
value="${fieldName}"/>
<f:param name="order" value="asc"/>
</h:commandLink>
,
<!-- Sort descending -->
<h:commandLink styleClass="smallLink"
action="#{backingBean.sort}">
<h:outputText value="asc" />
<f:param name="by"
value="${fieldName}"/>
<f:param name="order" value="dec"/>
</h:commandLink>
]
</c:if>
</h:panelGroup>
</f:facet>
<!-- Display the field name -->
<h:outputText value="${entity[fieldName]}"/>
</h:column>
</ui:composition>
THIS TEXT WILL BE REMOVED AS WELL
</html>
|
要點
在進入更高級的示例之前,我想把您的注意力引到幾件事上。首先,請注意在清單 5 中,我如何用一種通用方式引用值綁定:
<h:outputText value="${entity[fieldName]}"/>
|
第二,當調用這個復合組件時,我把 entity
和 fieldName
作為屬性傳遞,如下所示:
<a:column entity="${cd}" fieldName="title" backingBean="${CDManagerBean}"/>
|
Facelets 使用的 EL 規范允許用圓點(.
)表示法或較少使用的映射表示法引用字段。例如,如果像上面那樣調用,那么 ${entity[fieldName]}
等價于 CDManager.title
。還請注意,我不需要 f:verbatim
標記或輔助的 h:outputText
。對于您編寫的任何 Facelets 頁面,都可以這樣。Facelets 知道 JSF 組件樹,它的唯一目的就是構建這個組件樹。這是使用 Facelets 與使用 JSP 和 Tiles 相比的另一個優勢。
編寫完成之后,就可以在其他許多地方使用 column.xhtml 復合組件。作為一個通用規則:如果正在破壞 DRY 原則,那么請考慮改用復合組件。
創建組件
現在通過 column.xhtml 示例已經快速查看了復合組件。下面一步一步地介紹創建復合組件的過程。以下是創建復合組件的步驟:
- 創建 Facelets 標記庫。
- 在 web.xml 中聲明標記庫。
- 用命名空間導入標記文件。
步驟 1. 創建 Facelets 標記文件
標記文件 是符合 facelet_taglib_1_0.dtd 的文件。在概念上它與 JSP 中的 TLD 文件相似。清單 6 是一個示例標記庫文件:
清單 6. 標記庫文件 —— arcmind.taglib.xml
<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
"facelet-taglib_1_0.dtd">
<facelet-taglib>
<namespace>http://www.arc-mind.com/jsf</namespace>
<tag>
<tag-name>field</tag-name>
<source>field.xhtml</source>
</tag>
<tag>
<tag-name>column</tag-name>
<source>column.xhtml</source>
</tag>
<tag>
<tag-name>columnCommand</tag-name>
<source>columnCommand.xhtml</source>
</tag>
</facelet-taglib>
|
arcmind.taglib.xml 文件聲明了三個標記:field
、column
(已經看過這個!)和 columnCommand
。需要做的只是用 tag-name
指定標記的名稱和實現文件的位置。實現文件的名稱是相對的。可以在示例 Web 應用程序下的 WEB-INF\facelets\tags 文件中找到所有這些代碼,包括 DTD。
請一定注意在上面的標記元素之前聲明的 namespace
元素:稍后需要通過它在其他 Facelets 頁面中使用這個標記庫。
步驟 2. 在 web.xml 中聲明標記庫
有了一個標記庫是很好,但是要讓它有用,還必須把它的存在告訴 Facelets。在 web.xml 文件中用 facelets.LIBRARIES init
參數做這件事,如下所示:
<context-param>
<param-name>facelets.LIBRARIES</param-name>
<param-value>
/WEB-INF/facelets/tags/arcmind.taglib.xml
</param-value>
</context-param>
|
將 facelets.LIBRARIES
以分號分隔的列表形式傳遞,就可以想定義多少就定義多少標記文件。
步驟 3. 用命名空間導入標記文件
創建了標記文件并在 Facelets 標記庫中定義了它之后,就可以使用它了。標記文件的使用要求把它聲明為 XML 命名空間,如下所示:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:a="http://www.arc-mind.com/jsf">
...
...
<a:column entity="${cd}" fieldName="title"
backingBean="${CDManagerBean}"/>
<a:column entity="${cd}" fieldName="artist"
backingBean="${CDManagerBean}"/>
<a:column entity="${cd}" fieldName="price"
backingBean="${CDManagerBean}" sort="${false}"/>
<a:columnCommand label="Edit" action="editCD"
backingBean="${CDManagerBean}"/>
|
請注意命名空間的定義如下所示:
xmlns:a="http://www.arc-mind.com/jsf"
|
命名空間的值與前面步驟 1 中標記庫中聲明的命名空間元素一樣。
高級技巧
上面只介紹了復合組件的基礎知識。用我目前為止介紹的內容能夠創建可重用組件。在我自己使用 Facelets 時,發現了一些可以讓復合組件更有用的小技巧,而且在某些情況下能夠解決一些小問題。例如,請考慮來自 cdForm.xhtml 模板的以下代碼片段:
清單 7. cdForm.xhtml 的片段
<h:form id="cdForm">
<h:inputHidden id="cdid" value="#{CDManagerBean.cd.id}" />
<h:panelGrid id="formGrid" columns="3" rowClasses="row1, row2">
<!-- Title -->
<h:outputLabel id="titleLabel" for="title" styleClass="label"
value="Title" />
<h:inputText id="title" value="#{CDManagerBean.cd.title}"
required="true" />
<h:message id="titleMessage" for="title" styleClass="errorText"/>
<!-- Artist -->
<h:outputLabel id="artistLabel" for="artist" styleClass="label"
value="Artist" />
<h:inputText id="artist" value="#{CDManagerBean.cd.artist}"
required="true" />
<h:message id="titleMessage" for="artist"
styleClass="errorText"/>
<!-- Price -->
<h:outputLabel id="priceLabel" for="price" styleClass="label" value="Price" />
<h:inputText id="price" value="#{CDManagerBean.cd.price}"
required="true">
<f:validateDoubleRange minimum="15.0" maximum="100.0" />
</h:inputText>
<h:message id="priceMessage" for="price" styleClass="errorText"/>
|
以上頁面在概念上與 清單 3 類似,但它為 Facelets 和一個字段復合組件留出了空間,可以避免重復代碼。基于這個代碼,應當可以容易地創建顯示字段的復合組件,但是有一個小麻煩。不知道您看到沒有?請看表單的 price
字段:它包含一個驗證器。
現在,如何把驗證器傳遞給復合組件?
傳遞子元素
這里有一個關于 Facelets 的小秘密:復合組件基本上也是一類模板。所以,可以用 ui:define
標記傳遞模板參數,帶上具體的 ui:insert
,或者也可以把體作為默認 ui:insert
傳遞。
清單 8 就是字段組件的這樣一種實現(field.xhtml):
清單 8. field.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:z="http://www.qualcomm.com/jsf/core"
xmlns:c="http://java.sun.com/jstl/core"
xmlns:fn="http://java.sun.com/jsp/jstl/functions"
xmlns:t="http://myfaces.apache.org/tomahawk">
THIS TEXT WILL BE REMOVED
<ui:composition>
<!-- The label is optional.
Generate it if it is missing. -->
<c:if test="${empty label}">
<c:set var="label" value="${fieldName}" />
</c:if>
<!-- The required attribute is optional,
initialize it to true if not found. -->
<c:if test="${empty required}">
<c:set var="required" value="true" />
</c:if>
<h:outputLabel id="${fieldName}Label"
value="${label}" for="#{fieldName}" />
<h:inputText id="#{fieldName}" value="#{entity[fieldName]}"
required="${required}">
<ui:insert />
</h:inputText>
<!-- Display any error message that are found -->
<h:message id="${fieldName}Message"
style="color: red; text-decoration: overline"
for="#{fieldName}" />
</ui:composition>
THIS TEXT WILL BE REMOVED AS WELL
</html>
|
目前為止,清單 8 的工作基本上是不出所料。請注意 h:inputText
內部未命名 ui:insert
標記的使用。理解了它之后,就可以像清單 9 所示那樣使用這個復合組件:
清單 9. 字段標記復合組件
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:a="http://www.arc-mind.com/jsf">
...
<h:form id="cdForm">
<!-- Title, Artist, Price -->
<a:field fieldName="title" entity="#{CDManagerBean.cd}" />
<a:field fieldName="artist" entity="#{CDManagerBean.cd}" />
<a:field fieldName="price" entity="#{CDManagerBean.cd}" >
<f:validateDoubleRange minimum="15.0" maximum="100.0" />
</a:field>
...
|
price
的字段標記被傳遞給驗證器,作為匿名插入。因為其他字段沒有定義體,所以匿名插入對于默認值沒有影響。
傳遞動作
在想傳遞動作綁定來創建像工具欄或導航列表這樣的元素時,問題就是使用標準的表達式語言,不能傳遞,但是有方法可以做到!使用從對象中引用字段的相同方式,可以引用對象中的方法。所以,要創建可以創建動作綁定的組件,可以像下面這樣做(來自 columnCommand.xhtml):
<h:commandLink id="#{action}" value="#{label}"
action="#{backingBean[action]}"/>
|
請研究動作屬性的 value
。請注意,我使用與前面從實體引用字段相同的方式,訪問到了方法。可以用以下語法調用這個組件:
<a:columnCommand label="Edit" action="editCD"
backingBean="${CDManagerBean}"/>
|
這個調用把 CDManagerBean
的 editCD()
方法綁定到鏈接。清單 10 顯示了 columnCommand.xhtml
的完整清單:
清單 10. columnCommand.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:z="http://www.qualcomm.com/jsf/core"
xmlns:c="http://java.sun.com/jstl/core">
THIS TEXT WILL BE REMOVED
<ui:composition>
<!-- The label is optional. Generate it if it is missing. -->
<c:if test="${empty label}">
<c:set var="label" value="${action}" />
</c:if>
<h:column>
<f:facet name="header">
<h:panelGroup>
<h:outputText value="#{label}" />
</h:panelGroup>
</f:facet>
<h:commandLink id="#{action}" value="#{label}"
action="#{backingBean[action]}"/>
</h:column>
</ui:composition>
THIS TEXT WILL BE REMOVED AS WELL
</html>
|
Facelets 的不足
我已經清楚地演示了使用 Facelets 的好處:即組件復合和模板框架,它的核心 是組件,而不是 Servlets 輸出。但是采用 Facelets 也有些不足。其中之一就是,對 Facelets 的 IDE 支持極少。只有一個 Eclipse IDE 實現完全支持 Facelets(商業版的實現,請參閱 參考資料),而且看起來還不支持代碼補足。
而且也沒對 Facelets 調試的 IDE 支持(也就是說,設置斷點之類的東西)。要想調試,需要閱讀 Facelets 手冊,打開 JDK 1.4 樣式的日志,根據開發情況設置它的 init
參數。
在有利方面來說,我發現使用 Facelets API 非常自然和直觀。調試在開始的時候有些怪異,但是后來就適應了。隨 Facelets 發行包提供的演示應用程序沒有定制標記或功能的示例,但是核心項目代碼中有,所以請用核心項目代碼作為指導。
如果要使用新的 JSF 組件庫,必須有公開這個庫的 Facelets 標記庫。有些主要的組件庫(例如 Oracle 和 Tomahawk)的標記庫存在,但是即使這些也需要調整。我必須調整 Tomahawk 標記庫才能在應用程序中得到 Tomahawk 的日歷組件。雖然編寫導出組件的標記庫文件比較容易,但是也是件麻煩事。如果想使用新的定制組件庫,就必須編寫標記庫文件。
因為在其他實現中的問題,Facelets 看起來只能用于 MyFaces 1.1.1 和 Sun 1.2 JSF 的參考實現(Sun 的 JSF RI 1.2 還沒有正式發布)。不能把 Facelets 用于 1.1 RI。雖然可以把 MyFaces 用于 IBM WebSphere,但不能把 Facelets 用于 IBM 的實現。(如果使用最新版本的 Facelets,必須使用最新構建的 MyFaces 1.1.2,它現在還沒推出。)
還要注意的是,MyFaces 1.1 和 JSF RI 1.2 的底層機制不同。盡管如此,Facelets 試圖把這兩者的實現保持為它們當前的形式(MyFaces 1.1.2 和 JSF RI 1.2),這看起來解釋了花在 Facelets 上的大量時間。如果雙方更團結協調一點,讓 Facelets 在兩個環境中做同樣的事上少花些時間,就可以把更多時間花在改進 Facelets 上。
結束語
雖然有些缺點,我還是強烈推薦您下載 Facelets 并盡快開始使用它。Facelets 是 JSF 的未來,或者將會是,使用它可以使您在任何 JSF 暴風雨中都干爽(DRY 的雙關語,譯者注)。如果還不能在當前項目中使用它,請在下一個項目中想著它。
我已經用 Facelets 創建了復合組件、定制 Facelet 標記和內部 CRUD 框架功能的整個庫。我發現了許多構建復合組件的技巧和技術(例如,自動生成復選框、日歷組件的字段標記,或者根據綁定到組件的值綁定類型的文本字段),超過了在這樣一篇介紹性文章中能涉及的內容。相反,我把重點放在讓您了解和運行復合組件。通過在這里學到的知識,只用最少的定制功能和定制 Facelets 標記,就可以創建動人的組件。
致謝
特別感謝 Jacob Hookom,他是 Facelets 的創造者,感謝他對本文的審閱和貢獻,還要感謝 Athenz 細致入微的編輯。
下載
描述 |
名字 |
大小 |
下載方法 |
Facelets source code |
j-facelets_code.zip |
267KB |
HTTP
|
Facelets source code with jars and wars |
j-faceletsjarsandwars.zip |
47MB |
HTTP
|
參考資料
學習