Facelets 非常適合 JSF
最終形成了專(zhuān)為 JSF 設(shè)計(jì)的視圖技術(shù)!
|
|
|
|
|
?
|
|
|
級(jí)別: 初級(jí)
Richard Hightower, 開(kāi)發(fā)人員, ArcMind Inc.
2006 年 3 月 27 日
試圖把 JSF 和 JSP 結(jié)合起來(lái)就像試圖要把腳硬塞進(jìn)手套一樣:可能做得到,但是只是更好的解決辦法出現(xiàn)之前的一個(gè)權(quán)宜之計(jì)。在這篇文章中,JSF 的熱心支持者 Rick Hightower 介紹了關(guān)于 Facelets 他最喜歡的內(nèi)容:容易的 HTML 樣式的模板化和可重用的復(fù)合組件。
由于最近在 Java? 服務(wù)器外觀(JSF)項(xiàng)目上工作,我很有幸第一次使用了 Facelets。關(guān)于 Facelets,我最喜歡的是它讓我可以創(chuàng)建可重用的復(fù)合組件。能夠拿出一個(gè)頁(yè)面(例如 JSP)并把它變成組件,對(duì)于我的 JSF 開(kāi)發(fā)來(lái)說(shuō)真是莫大的好處。我的結(jié)論是什么?如果不用 Facelets,那么就無(wú)法得到能從 JSF 獲得的最大收獲。
JSF 和 Java 服務(wù)器頁(yè)面技術(shù)之間的不匹配,是 JSF 開(kāi)發(fā)中的一個(gè)嚴(yán)重問(wèn)題。問(wèn)題是如何把 JSP 的動(dòng)態(tài)內(nèi)容集成到 JSF 基于組件的模型中。JSP 非常重視生成動(dòng)態(tài)內(nèi)容輸出,而 JSF 需要 JSP 來(lái)協(xié)調(diào)組件模型的構(gòu)建。因?yàn)檫@個(gè)任務(wù)超出了 JSP 原來(lái)的目的,所以產(chǎn)生了距離。
大多數(shù) JSF 開(kāi)發(fā)人員只是學(xué)會(huì)了一事一議地解決這類(lèi)問(wèn)題,但是這就像在錘子上放一個(gè)枕頭,最終還會(huì)掉下來(lái)打傷腦袋。Facelets 是更加全面的解決方案:專(zhuān)為 JSF 組件模型度身定制的模板化語(yǔ)言。
Facelets 有以下吸引人的特性:
- 模板化(像 Tiles)
- 復(fù)合組件
- 定制的邏輯標(biāo)記
- 表達(dá)式語(yǔ)言
- 對(duì)設(shè)計(jì)師友好的頁(yè)面開(kāi)發(fā)
- 創(chuàng)建組件庫(kù)
這些特性比我想像的要更相關(guān)和統(tǒng)一。在這篇文章中,我討論前兩個(gè):模板化和復(fù)合組件。我使用的 Web 應(yīng)用程序基于為我的針對(duì)懷疑者的 JSF 系列開(kāi)發(fā)的一個(gè)應(yīng)用程序,我把它更新成使用 Facelets 視圖而不是 Tiles。在進(jìn)一步閱讀之前,應(yīng)當(dāng) 下載示例代碼。如果要隨著討論一起操作,還需要 安裝 Facelets。
Facelets 概述
對(duì)于 Facelets 可能會(huì)做的最大一個(gè)錯(cuò)誤假設(shè),就是它只是 Tiles 的替代品。Facelets 遠(yuǎn)不止如此:它是思考 JSF 的新方式。
|
選擇標(biāo)記
雖然多數(shù)開(kāi)發(fā)人員把 Facelets 用于 XHTML,實(shí)際上這個(gè)框架并不在意使用什么標(biāo)記:它與 XUL (XULFaces)兼容, Kito Mann 已經(jīng)用它為 JSF 中心提供 RSS。
|
|
JSP 是種生成 servlet 的模板化語(yǔ)言。JSP 的主體與 servlet 的 doGet()
和 doPost()
方法等價(jià)(也就是說(shuō),成為 jspService()
方法)。JSF 定制標(biāo)記(例如 f:view
和 h:form
)只是調(diào)用 JSF 組件來(lái)呈現(xiàn)它們自己的當(dāng)前狀態(tài)。JSF 組件模型的生命周期獨(dú)立于 JSP 生成的 servlet 的生命周期。這種獨(dú)立性就是混淆的來(lái)源。
與 JSP 不同,F(xiàn)acelets 這個(gè)模板化語(yǔ)言,從構(gòu)建之初,就考慮了 JSF 的組件生命周期。使用 Facelets,生成的模板會(huì)構(gòu)建組件樹(shù),而不是 servlet。這就允許更好的重用,因?yàn)榭梢园呀M件組合成另一個(gè)組件。
Facelets 減少了編寫(xiě)定制標(biāo)記才能使用 JSF 的需求。Facelets 本身就可以使用 JSF 定制組件。溝通 JSF 和 Facelets 只需要很少的特殊編碼:要做的全部工作就是在 Facelet 標(biāo)記庫(kù)文件中聲明 JSF 組件。在 Facelets 模板化語(yǔ)言中可以直接使用 JSF 組件,不用任何額外的開(kāi)發(fā)。
Facelets 模板框架
在提供針對(duì)組件構(gòu)建設(shè)計(jì)的模板框架方面,F(xiàn)acelets 與 Tapestry (請(qǐng)參閱 參考資料)類(lèi)似。但是,對(duì)于具有 JSP 背景的我們來(lái)說(shuō),F(xiàn)acelets 看起來(lái)比 Tapestry 友好得多。它允許使用熟悉的 JSTL 樣式的標(biāo)記和 JSTL/JSF/JSP 樣式的表達(dá)式語(yǔ)言。大大降低的學(xué)習(xí)曲線意味著可以更加迅速地開(kāi)始開(kāi)發(fā)。
|
Facelets 和 Tapestry
Facelets 與 Tapestry 很相似,可以相互比較。實(shí)際上,Tapestry 剛出現(xiàn)的時(shí)候,大大領(lǐng)先于它的時(shí)代,而 Facelets 確實(shí)借鑒了它的一些想法。但是,如果只把 Facelets 當(dāng)成 JSF 版本的 Tapestry,那就錯(cuò)了。這兩項(xiàng)技術(shù)是不同的。要了解關(guān)于 Tapestry 的更多內(nèi)容,請(qǐng)參閱 Brett McLaughlin 兩部分的系列 “了解 Tapestry,第 1 部分”。
|
|
Facelets 允許定義能夠直接包含進(jìn)頁(yè)面或者容易地添加到 Facelet 標(biāo)記庫(kù)的組件集。實(shí)際上讓人高興的是在 Facelets 中定義定制標(biāo)記(復(fù)合組件和類(lèi)似 JSP 定制標(biāo)記的標(biāo)記)的迅速。使用這些組件集,F(xiàn)acelets 還允許定義站點(diǎn)模板(和更小的模板)。這與使用 Tiles 很相似,但是少了定義文件。也可以在定制 JSF 組件內(nèi)部使用 Facelets,因?yàn)?Facelets API 提供了可以容易地與 JSF 組件集成的接口。
從 Tiles 到 Facelets
如前所述,在這里使用的示例 Web 應(yīng)用程序基于為我的 針對(duì)懷疑者的 JSF 系列創(chuàng)建的示例。它為一家在線 CD 店管理庫(kù)存,創(chuàng)建、讀取、更新和刪除(CRUD)清單。它包含一個(gè)表單,讓用戶向系統(tǒng)輸入新 CD,有一個(gè)單選按鈕列表,允許用戶選擇音樂(lè)分類(lèi)。當(dāng)用戶選擇了一個(gè)分類(lèi)時(shí),就觸發(fā)某些 JavaScript 立即把表單提交回服務(wù)器。應(yīng)用程序還包含一個(gè) CD 清單,用戶可以根據(jù)標(biāo)題或藝術(shù)家對(duì)清單中的 CD 排序。圖 1 是應(yīng)用程序類(lèi)的 UML 圖表:
圖 1. 在線 CD 商店示例的類(lèi)圖
圖 2 提供了商店的 CD 清單頁(yè)面:
圖 2. 在線 CD 商店的清單頁(yè)面
原來(lái)的應(yīng)用程序從 Tiles 得到視圖支持,現(xiàn)在我將用 Facelets 構(gòu)建視圖。我先從用 Facelets 替換示例中的 Tiles 支持開(kāi)始,然后編寫(xiě)復(fù)合組件。在開(kāi)始之前,需要已經(jīng)安裝了 Facelets。
安裝 Facelets
安裝 Facelets 的步驟很容易。請(qǐng)注意,我假設(shè)已經(jīng)下載并安裝了 示例應(yīng)用程序。
-
下載 Facelets 發(fā)行包 并解壓縮。
- 把 jsf-facelets.jar 拷貝到 WEB-INF/lib 目錄(在應(yīng)用程序部署時(shí),它最終必須放在 WEB-INF/lib 目錄中)。
- 把 Facelet 初始化參數(shù)添加到 web.xml 文件中。
- 把 FaceletViewHandler 添加到 faces-config.xml 文件中。
步驟 1 和 2 比較基本。我將詳細(xì)介紹其他兩個(gè)步驟。
添加初始化參數(shù)
這一步假設(shè)已經(jīng)安裝了工作正常的 JSF 應(yīng)用程序(例如 在線 CD 商店示例),然后編輯現(xiàn)有的 web.xml 頁(yè)面,添加以下參數(shù):
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>
|
這告訴 JSF 采用 xhtml 前綴,F(xiàn)acelet 渲染器能夠解釋這個(gè)前綴。
Facelets 有許多參數(shù),請(qǐng)參閱 參考資料 獲得完整清單。如果示例有問(wèn)題,請(qǐng)參考 DEVELOPMENT init
參數(shù),它適合調(diào)試。把 REFRESH_PERIOD
參數(shù)設(shè)置為 low
在開(kāi)發(fā)期間會(huì)有幫助。
添加 FaceletViewHandler
要讓 Facelets 模板生效,需要把 Facelets 視圖處理器告訴 JSF。JSF ViewHandler
是個(gè)插件,為不同的響應(yīng)生成技術(shù)(包括 Facelets)處理 JSF 請(qǐng)求處理生命周期的 “渲染器響應(yīng)和恢復(fù)視圖” 階段。(任何認(rèn)為 JSF 不能擴(kuò)展的人都是被誤導(dǎo)了!)通過(guò)添加以下視圖處理器到 faces-config.xml 中,就把 Facelets 插進(jìn)了 JSF 中:
<application>
<locale-config>
<default-locale>en</default-locale>
</locale-config>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application>
|
用 Facelets 進(jìn)行模板化
首先介紹 Facelets 模板化框架,因?yàn)樗鄬?duì)容易理解。創(chuàng)建和使用 Facelets 模板的步驟如下:
- 創(chuàng)建 layout.xhtml 頁(yè)面。
- 定義 Facelet 的命名空間,導(dǎo)入對(duì) Facelets 的使用。
- 用
ui:insert
標(biāo)記定義頁(yè)面的邏輯區(qū)域。
- 用純文本和
ui:include
標(biāo)記定義合理的默認(rèn)值。
我要逐步介紹這些步驟,用在線 CD 商店清單頁(yè)面作為我的布局示例。
步驟 1. 創(chuàng)建 layout.xhtml 頁(yè)面
layout.xhtml 頁(yè)面就是一個(gè)一般的 XHTML 文本文件,使用了以下文檔類(lèi)型聲明:
<!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>
|
不要求進(jìn)一步細(xì)節(jié)!
步驟 2. 定義 Facelets 的命名空間
為了用 Facelets 標(biāo)記進(jìn)行模板化,需要用 XML 命名空間像下面這樣導(dǎo)入它們:
<!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">
...
|
請(qǐng)注意 ui 命名空間的定義。
步驟 3. 用 ui:insert 標(biāo)記定義頁(yè)面的邏輯區(qū)域
下面,定義布局的邏輯區(qū)域,例如頁(yè)面標(biāo)題、小標(biāo)題、導(dǎo)航、內(nèi)容等等。下面是定義頁(yè)面標(biāo)題的示例:
<!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>
...
|
請(qǐng)注意使用 ui:insert
標(biāo)記定義了標(biāo)題的邏輯區(qū)域。ui:insert
元素內(nèi)的文本 “Default title” 定義了模板用戶不傳遞標(biāo)題時(shí)顯示的文本。也可以像下面這樣編寫(xiě)上面的內(nèi)容:
步驟 4. 用純文本和 ui:includes 定義默認(rèn)值
可以傳遞更多的純文本作為默認(rèn)值。例如,請(qǐng)研究 layout.xhtml 中的以下代碼片段:
<div id="header">
<ui:insert name="header">
<ui:include src="header.xhtml"/>
</ui:insert>
</div>
|
在這里,我用了 ui:insert
標(biāo)記定義邏輯區(qū)域,用 ui:include
標(biāo)記插入默認(rèn)值。默認(rèn)情況下,使用布局的頁(yè)面會(huì)采用 header.xhtml 的內(nèi)容作為標(biāo)題文本,但是因?yàn)闃?biāo)題是 ui:insert
定義的邏輯區(qū)域,所以用這個(gè)模板也能傳遞不同的標(biāo)題。對(duì)于擁有前端(例如,帶有購(gòu)物車(chē)的目錄)和后端管理(例如添加新產(chǎn)品)的應(yīng)用程序,后端站點(diǎn)在標(biāo)題或?qū)Ш缴峡赡懿煌逆溄印?code>ui:include 標(biāo)記可以容易地用新標(biāo)題換掉默認(rèn)標(biāo)題。
清單 1 顯示了示例應(yīng)用程序的清單頁(yè)面 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>
|
現(xiàn)在已經(jīng)知道了如何定義布局,我將介紹如何使用布局!
使用 Facelets 模板
為了調(diào)用模板,要使用 ui:composition
標(biāo)記。為了把參數(shù)傳遞給模板,要使用 ui:define
標(biāo)記,它是 ui:composition
標(biāo)記的子元素。在清單 2 中,我調(diào)用了在線 CD 商店示例的布局頁(yè)面:
清單 2. 調(diào)用布局頁(yè)面
<!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>
|
請(qǐng)注意在上面的調(diào)用中包含了以下命名空間:
- xmlns:h="http://java.sun.com/jsf/html"
- xmlns:f="http://java.sun.com/jsf/core"
有了 Facelets,就不必依賴 JSF 標(biāo)記庫(kù),所以要使用核心和 HTML JSF 組件,必須通過(guò)以上命名空間導(dǎo)入它們。
html
標(biāo)記的使用看起來(lái)可能有些奇怪。畢竟,清單 2 所示的布局頁(yè)面要調(diào)用的模板已經(jīng)有了一個(gè) html
標(biāo)記;所以這是不是意味著會(huì)得到兩個(gè) html
標(biāo)記?如果真的這樣,那么在 ui:composition
標(biāo)記之外的內(nèi)容全部被忽略,所以 html
標(biāo)記所做的只是讓 HTML 編輯器能夠看到 HTML 片段。它不會(huì)影響運(yùn)行時(shí)行為。
位置是一切
當(dāng)頁(yè)面調(diào)用布局模板時(shí),只需指定模板的位置,如下所示:
<ui:composition template="/WEB-INF/layout/layout.xhtml">
|
這個(gè)標(biāo)記調(diào)用清單 1 所示的模板,所以我要做的全部工作就是把參數(shù)傳遞給模板。然后,在復(fù)合標(biāo)記內(nèi)部,可以傳遞像標(biāo)題這樣的簡(jiǎn)單文本:
<ui:define name="title">CD form</ui:define>
|
或者傳遞整個(gè)組件樹(shù):
<ui:define name="content">
<!-- use the form tag to setup this form -->
<h:form id="cdForm">
...
...
...
</h:form>
</ui:define>
|
請(qǐng)注意,在我定義和傳遞的許多邏輯區(qū)域中,cdForm.xhtml 只傳遞兩個(gè):內(nèi)容和標(biāo)題。
|
Tiles 與 Facelets
這篇文章帶了三個(gè)示例:第一個(gè)使用 Tiles,第二個(gè)使用 Facelets,第三個(gè)使用復(fù)合組件。之所以包含 Tiles 示例是為了可以比較和對(duì)比兩個(gè)框架中不同的模板化技術(shù)。請(qǐng)看看它,并看看自己有什么想法!
|
|
復(fù)合組件
如果只用 Facelets 定義和使用模板,那么可能會(huì)有點(diǎn)失敗。雖然 Facelets 的模板化特性完整而且豐富,但是它沒(méi)有 Tiles 之類(lèi)的框架那么多的特性,后者還擅長(zhǎng)定義默認(rèn)值、相關(guān)模板的層次結(jié)構(gòu)以及類(lèi)似的東西。
但模板化不是 Facelets 真正出色的地方:Facelets 把它的精華放在復(fù)合組件上。(有趣的是,復(fù)合組件也給 Facelets 模板化帶來(lái)了一些好處;例如,在 Facelets 中可以舍棄 f:verbatim
標(biāo)記和各種 h:outputText
標(biāo)記,因?yàn)樗械臇|西都被當(dāng)成組件樹(shù)中的組件。關(guān)于這方面的更多內(nèi)容稍后介紹。)
對(duì)于這篇文章余下的部分,我將重點(diǎn)放在創(chuàng)建和使用復(fù)合組件的步驟上。但在開(kāi)始之前,先要確保能夠清楚地理解是什么讓這些方便的小代碼段這么棒。
破壞 DRY 原則
您是否曾經(jīng)編寫(xiě)過(guò)像清單 3 所示的代碼片段?
清單 3. 復(fù)合組件之前的生活
<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>
|
這段來(lái)自 listing.xhtml 的代碼為示例應(yīng)用程序的清單頁(yè)面生成列標(biāo)題和升序/降序排列鏈接。請(qǐng)注意,必須在多個(gè)地方重復(fù)代碼,才能輸出多列。(在上面的示例中您還會(huì)注意到,我在 ${..}
和 #{..}
之間切換;這可能讓人迷惑,但它們做的是同樣的事!)
所有這些渲染標(biāo)題列和藝術(shù)家列的重復(fù)代碼都破壞了 DRY 原則 —— 即,不要重復(fù)自己。那么您說(shuō),這里錯(cuò)在哪兒呢?假設(shè)在清單中平均有 5 列,應(yīng)用程序中有 20 個(gè)不同的清單。使用清單 3 的方法,就不得不重復(fù)這 35 行代碼 100 次,合計(jì) 3,500 行代碼!維護(hù)所有這些代碼會(huì)是種痛苦,但是如果決定修改清單的表示或添加一種通用的清單過(guò)濾方式,會(huì)怎么樣?需要更多、更多的工作。
現(xiàn)在拿清單 3 和這個(gè)代碼比較:
清單 4. 創(chuàng)建字段的新方式
<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}"/>
|
看起來(lái)好像我只用 4 行代碼就替代了 70 行或更多行代碼!可以猜出,a:column
是個(gè)復(fù)合組件。在 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>
|
要點(diǎn)
在進(jìn)入更高級(jí)的示例之前,我想把您的注意力引到幾件事上。首先,請(qǐng)注意在清單 5 中,我如何用一種通用方式引用值綁定:
<h:outputText value="${entity[fieldName]}"/>
|
第二,當(dāng)調(diào)用這個(gè)復(fù)合組件時(shí),我把 entity
和 fieldName
作為屬性傳遞,如下所示:
<a:column entity="${cd}" fieldName="title" backingBean="${CDManagerBean}"/>
|
Facelets 使用的 EL 規(guī)范允許用圓點(diǎn)(.
)表示法或較少使用的映射表示法引用字段。例如,如果像上面那樣調(diào)用,那么 ${entity[fieldName]}
等價(jià)于 CDManager.title
。還請(qǐng)注意,我不需要 f:verbatim
標(biāo)記或輔助的 h:outputText
。對(duì)于您編寫(xiě)的任何 Facelets 頁(yè)面,都可以這樣。Facelets 知道 JSF 組件樹(shù),它的唯一目的就是構(gòu)建這個(gè)組件樹(shù)。這是使用 Facelets 與使用 JSP 和 Tiles 相比的另一個(gè)優(yōu)勢(shì)。
編寫(xiě)完成之后,就可以在其他許多地方使用 column.xhtml 復(fù)合組件。作為一個(gè)通用規(guī)則:如果正在破壞 DRY 原則,那么請(qǐng)考慮改用復(fù)合組件。
創(chuàng)建組件
現(xiàn)在通過(guò) column.xhtml 示例已經(jīng)快速查看了復(fù)合組件。下面一步一步地介紹創(chuàng)建復(fù)合組件的過(guò)程。以下是創(chuàng)建復(fù)合組件的步驟:
- 創(chuàng)建 Facelets 標(biāo)記庫(kù)。
- 在 web.xml 中聲明標(biāo)記庫(kù)。
- 用命名空間導(dǎo)入標(biāo)記文件。
步驟 1. 創(chuàng)建 Facelets 標(biāo)記文件
標(biāo)記文件 是符合 facelet_taglib_1_0.dtd 的文件。在概念上它與 JSP 中的 TLD 文件相似。清單 6 是一個(gè)示例標(biāo)記庫(kù)文件:
清單 6. 標(biāo)記庫(kù)文件 —— 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 文件聲明了三個(gè)標(biāo)記:field
、column
(已經(jīng)看過(guò)這個(gè)!)和 columnCommand
。需要做的只是用 tag-name
指定標(biāo)記的名稱(chēng)和實(shí)現(xiàn)文件的位置。實(shí)現(xiàn)文件的名稱(chēng)是相對(duì)的。可以在示例 Web 應(yīng)用程序下的 WEB-INF\facelets\tags 文件中找到所有這些代碼,包括 DTD。
請(qǐng)一定注意在上面的標(biāo)記元素之前聲明的 namespace
元素:稍后需要通過(guò)它在其他 Facelets 頁(yè)面中使用這個(gè)標(biāo)記庫(kù)。
步驟 2. 在 web.xml 中聲明標(biāo)記庫(kù)
有了一個(gè)標(biāo)記庫(kù)是很好,但是要讓它有用,還必須把它的存在告訴 Facelets。在 web.xml 文件中用 facelets.LIBRARIES init
參數(shù)做這件事,如下所示:
<context-param>
<param-name>facelets.LIBRARIES</param-name>
<param-value>
/WEB-INF/facelets/tags/arcmind.taglib.xml
</param-value>
</context-param>
|
將 facelets.LIBRARIES
以分號(hào)分隔的列表形式傳遞,就可以想定義多少就定義多少標(biāo)記文件。
步驟 3. 用命名空間導(dǎo)入標(biāo)記文件
創(chuàng)建了標(biāo)記文件并在 Facelets 標(biāo)記庫(kù)中定義了它之后,就可以使用它了。標(biāo)記文件的使用要求把它聲明為 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}"/>
|
請(qǐng)注意命名空間的定義如下所示:
xmlns:a="http://www.arc-mind.com/jsf"
|
命名空間的值與前面步驟 1 中標(biāo)記庫(kù)中聲明的命名空間元素一樣。
高級(jí)技巧
上面只介紹了復(fù)合組件的基礎(chǔ)知識(shí)。用我目前為止介紹的內(nèi)容能夠創(chuàng)建可重用組件。在我自己使用 Facelets 時(shí),發(fā)現(xiàn)了一些可以讓復(fù)合組件更有用的小技巧,而且在某些情況下能夠解決一些小問(wèn)題。例如,請(qǐng)考慮來(lái)自 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"/>
|
以上頁(yè)面在概念上與 清單 3 類(lèi)似,但它為 Facelets 和一個(gè)字段復(fù)合組件留出了空間,可以避免重復(fù)代碼。基于這個(gè)代碼,應(yīng)當(dāng)可以容易地創(chuàng)建顯示字段的復(fù)合組件,但是有一個(gè)小麻煩。不知道您看到?jīng)]有?請(qǐng)看表單的 price
字段:它包含一個(gè)驗(yàn)證器。
現(xiàn)在,如何把驗(yàn)證器傳遞給復(fù)合組件?
傳遞子元素
這里有一個(gè)關(guān)于 Facelets 的小秘密:復(fù)合組件基本上也是一類(lèi)模板。所以,可以用 ui:define
標(biāo)記傳遞模板參數(shù),帶上具體的 ui:insert
,或者也可以把體作為默認(rèn) ui:insert
傳遞。
清單 8 就是字段組件的這樣一種實(shí)現(xiàn)(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 的工作基本上是不出所料。請(qǐng)注意 h:inputText
內(nèi)部未命名 ui:insert
標(biāo)記的使用。理解了它之后,就可以像清單 9 所示那樣使用這個(gè)復(fù)合組件:
清單 9. 字段標(biāo)記復(fù)合組件
<!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
的字段標(biāo)記被傳遞給驗(yàn)證器,作為匿名插入。因?yàn)槠渌侄螞](méi)有定義體,所以匿名插入對(duì)于默認(rèn)值沒(méi)有影響。
傳遞動(dòng)作
在想傳遞動(dòng)作綁定來(lái)創(chuàng)建像工具欄或?qū)Ш搅斜磉@樣的元素時(shí),問(wèn)題就是使用標(biāo)準(zhǔn)的表達(dá)式語(yǔ)言,不能傳遞,但是有方法可以做到!使用從對(duì)象中引用字段的相同方式,可以引用對(duì)象中的方法。所以,要?jiǎng)?chuàng)建可以創(chuàng)建動(dòng)作綁定的組件,可以像下面這樣做(來(lái)自 columnCommand.xhtml):
<h:commandLink id="#{action}" value="#{label}"
action="#{backingBean[action]}"/>
|
請(qǐng)研究動(dòng)作屬性的 value
。請(qǐng)注意,我使用與前面從實(shí)體引用字段相同的方式,訪問(wèn)到了方法。可以用以下語(yǔ)法調(diào)用這個(gè)組件:
<a:columnCommand label="Edit" action="editCD"
backingBean="${CDManagerBean}"/>
|
這個(gè)調(diào)用把 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 的不足
我已經(jīng)清楚地演示了使用 Facelets 的好處:即組件復(fù)合和模板框架,它的核心 是組件,而不是 Servlets 輸出。但是采用 Facelets 也有些不足。其中之一就是,對(duì) Facelets 的 IDE 支持極少。只有一個(gè) Eclipse IDE 實(shí)現(xiàn)完全支持 Facelets(商業(yè)版的實(shí)現(xiàn),請(qǐng)參閱 參考資料),而且看起來(lái)還不支持代碼補(bǔ)足。
而且也沒(méi)對(duì) Facelets 調(diào)試的 IDE 支持(也就是說(shuō),設(shè)置斷點(diǎn)之類(lèi)的東西)。要想調(diào)試,需要閱讀 Facelets 手冊(cè),打開(kāi) JDK 1.4 樣式的日志,根據(jù)開(kāi)發(fā)情況設(shè)置它的 init
參數(shù)。
在有利方面來(lái)說(shuō),我發(fā)現(xiàn)使用 Facelets API 非常自然和直觀。調(diào)試在開(kāi)始的時(shí)候有些怪異,但是后來(lái)就適應(yīng)了。隨 Facelets 發(fā)行包提供的演示應(yīng)用程序沒(méi)有定制標(biāo)記或功能的示例,但是核心項(xiàng)目代碼中有,所以請(qǐng)用核心項(xiàng)目代碼作為指導(dǎo)。
如果要使用新的 JSF 組件庫(kù),必須有公開(kāi)這個(gè)庫(kù)的 Facelets 標(biāo)記庫(kù)。有些主要的組件庫(kù)(例如 Oracle 和 Tomahawk)的標(biāo)記庫(kù)存在,但是即使這些也需要調(diào)整。我必須調(diào)整 Tomahawk 標(biāo)記庫(kù)才能在應(yīng)用程序中得到 Tomahawk 的日歷組件。雖然編寫(xiě)導(dǎo)出組件的標(biāo)記庫(kù)文件比較容易,但是也是件麻煩事。如果想使用新的定制組件庫(kù),就必須編寫(xiě)標(biāo)記庫(kù)文件。
因?yàn)樵谄渌麑?shí)現(xiàn)中的問(wèn)題,F(xiàn)acelets 看起來(lái)只能用于 MyFaces 1.1.1 和 Sun 1.2 JSF 的參考實(shí)現(xiàn)(Sun 的 JSF RI 1.2 還沒(méi)有正式發(fā)布)。不能把 Facelets 用于 1.1 RI。雖然可以把 MyFaces 用于 IBM WebSphere,但不能把 Facelets 用于 IBM 的實(shí)現(xiàn)。(如果使用最新版本的 Facelets,必須使用最新構(gòu)建的 MyFaces 1.1.2,它現(xiàn)在還沒(méi)推出。)
還要注意的是,MyFaces 1.1 和 JSF RI 1.2 的底層機(jī)制不同。盡管如此,F(xiàn)acelets 試圖把這兩者的實(shí)現(xiàn)保持為它們當(dāng)前的形式(MyFaces 1.1.2 和 JSF RI 1.2),這看起來(lái)解釋了花在 Facelets 上的大量時(shí)間。如果雙方更團(tuán)結(jié)協(xié)調(diào)一點(diǎn),讓 Facelets 在兩個(gè)環(huán)境中做同樣的事上少花些時(shí)間,就可以把更多時(shí)間花在改進(jìn) Facelets 上。
結(jié)束語(yǔ)
雖然有些缺點(diǎn),我還是強(qiáng)烈推薦您下載 Facelets 并盡快開(kāi)始使用它。Facelets 是 JSF 的未來(lái),或者將會(huì)是,使用它可以使您在任何 JSF 暴風(fēng)雨中都干爽(DRY 的雙關(guān)語(yǔ),譯者注)。如果還不能在當(dāng)前項(xiàng)目中使用它,請(qǐng)?jiān)谙乱粋€(gè)項(xiàng)目中想著它。
我已經(jīng)用 Facelets 創(chuàng)建了復(fù)合組件、定制 Facelet 標(biāo)記和內(nèi)部 CRUD 框架功能的整個(gè)庫(kù)。我發(fā)現(xiàn)了許多構(gòu)建復(fù)合組件的技巧和技術(shù)(例如,自動(dòng)生成復(fù)選框、日歷組件的字段標(biāo)記,或者根據(jù)綁定到組件的值綁定類(lèi)型的文本字段),超過(guò)了在這樣一篇介紹性文章中能涉及的內(nèi)容。相反,我把重點(diǎn)放在讓您了解和運(yùn)行復(fù)合組件。通過(guò)在這里學(xué)到的知識(shí),只用最少的定制功能和定制 Facelets 標(biāo)記,就可以創(chuàng)建動(dòng)人的組件。
致謝
特別感謝 Jacob Hookom,他是 Facelets 的創(chuàng)造者,感謝他對(duì)本文的審閱和貢獻(xiàn),還要感謝 Athenz 細(xì)致入微的編輯。
下載
描述 |
名字 |
大小 |
下載方法 |
Facelets source code |
j-facelets_code.zip |
267KB |
HTTP
|
Facelets source code with jars and wars |
j-faceletsjarsandwars.zip |
47MB |
HTTP
|
參考資料
學(xué)習(xí)