Facelets是JSF更好的外衣

ouhennb發表於2009-06-16
是由 Sun 公司在 dev.java.net 上的一個開源專案,其主頁為:facelets.dev.java.net。為什麼說 Facelets 更適合JSF?筆者認為,主要是基於以下特性:
Facelets基於xml,它是元件樹更自然的一種描述方式(xml天生就是一種樹形結構描述語言)。

Facelets的模版技術,使它更適合網頁開發

Facelets支援複合元件,並且,元件的定義方式更簡單

Facelets的 jsfc 技術對 html 設計器更友好

與JSP相比,Facelets無需執行前編譯,並且,Facelets 還適合對生成的元件樹做cache,從而使執行期更輕量,效率更高

以下對Facelets的模版及複合元件技術做一個簡單介紹。

首先,我們舉一個場景:假設我們現在要做一個嚮導,該向導包含兩個頁面,分別是錄入使用者的姓名和其它資訊.

暫且不考慮 Facelets,這兩個頁面的程式碼分別如下:

dialog1.xhtml:

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
renderKitId="AJAX" xmlns:h="http://java.sun.com/jsf/html">
<w:page title="Dialog" style="font-family: Verdana; font-size: 10pt; margin: 0px; padding: 0px">
<w:form>
<layout:panelGrid columns="2" width="100%" style="padding-left: 20px">
<layout:cell colspan="1" rowspan="1" width="99%">
<div style="font-size: 11pt; font-weight: bold;">Input Name</div>
</layout:cell>
<layout:cell colspan="1" rowspan="2" style="align: right">
<img src="images/wizard.gif" />
</layout:cell>
<layout:cell colspan="1" rowspan="2">
<div style="font-size: 10pt; font-weight: normal;">Please input your name</div>
</layout:cell>
</layout:panelGrid>
<w:separator />
<layout:panelGrid columns="2" style="height: 250px">
<layout:cell style="vertical-align: top">
<h:outputLabel value="Name:"/>
</layout:cell>
<layout:cell style="vertical-align: top">
<w:textField id="name" width="250"/>
</layout:cell>
</layout:panelGrid>
<w:separator />
<layout:panelGrid columns="5" style="align: right" width="100%">
<layout:cell style="width: 90%"> </layout:cell>
<w:button value="Back"/>
<w:button value="Next"/>
<w:button value="Finished"/>
<w:button value="Cancel"/>
</layout:panelGrid>
</w:form>
</w:page>
</f:view>
dialog2.xhtml

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
renderKitId="AJAX" xmlns:h="http://java.sun.com/jsf/html">
<w:page title="Dialog" style="font-family: Verdana; font-size: 10pt; margin: 0px; padding: 0px">
<w:form>
<layout:panelGrid columns="2" width="100%" style="padding-left: 20px">
<layout:cell colspan="1" rowspan="1" width="99%">
<div style="font-size: 11pt; font-weight: bold;">Input Others</div>
</layout:cell>
<layout:cell colspan="1" rowspan="2" style="align: right">
<img src="images/wizard.gif" />
</layout:cell>
<layout:cell colspan="1" rowspan="2">
<div style="font-size: 10pt; font-weight: normal;">Please input your others information</div>
</layout:cell>
</layout:panelGrid>
<w:separator />
<layout:panelGrid columns="2" style="height: 250px">
<layout:cell style="vertical-align: top">
<h:outputLabel value="Others:"/>
</layout:cell>
<layout:cell style="vertical-align: top">
<w:combo id="others" width="250"/>
</layout:cell>
</layout:panelGrid>
<w:separator />
<layout:panelGrid columns="5" style="align: right" width="100%">
<layout:cell style="width: 90%"> </layout:cell>
<w:button value="Back"/>
<w:button value="Next"/>
<w:button value="Finished"/>
<w:button value="Cancel"/>
</layout:panelGrid>
</w:form>
</w:page>
</f:view>
首先非常抱歉的是,筆者對HTML/CSS並不是很懂,因此,上述的程式碼示例有許多可以改進的地方。但無庸置疑的是: 上述兩個頁面有太多的重複性程式碼。如果我們用純粹的JSP/Servlet技術來開發此應用,可以用 Tiles 來消除這種重複;如果用 AOM 來開發,則可以通過 Facelets 來達到同樣的目的。

模版技術
針對上述示例,我們首先定義一個模版檔案dialog_template.xhtml。

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
renderKitId="AJAX" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets">
<w:page title="Dialog" style="font-family: Verdana; font-size: 10pt; margin: 0px; padding: 0px">
<layout:panelGrid columns="2" width="100%" style="padding-left: 20px">
<layout:cell colspan="1" rowspan="1" width="99%">
<div style="font-size: 11pt; font-weight: bold;">
<ui:insert name="title">default title</ui:insert>
</div>
</layout:cell>
<layout:cell colspan="1" rowspan="2" style="align: right">
<img src="images/wizard.gif" />
</layout:cell>
<layout:cell colspan="1" rowspan="2">
<div style="font-size: 10pt; font-weight: normal;">
<ui:insert name="description">default description</ui:insert>
</div>
</layout:cell>
</layout:panelGrid>
<w:separator />
<ui:insert name="content">empty content</ui:insert>
<w:separator />
<layout:panelGrid columns="5" style="align: right" width="100%">
<layout:cell style="width: 90%"></layout:cell>
<w:button value="Back" />
<w:button value="Next" />
<w:button value="Finished" />
<w:button value="Cancel" />
</layout:panelGrid>
</w:page>
</f:view>
請注意,我們在上述模版檔案中,分別插入三個<ui:insert/>標記,每個標記都有一個name屬性,其值分別是:title、description、content。 這三個標記,本質上就相當於定義了三個“錨點”,至於具體的內容,隨時可以替換。

現在,我們再來看看 dialog1 應該怎麼使用這個模版。

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets">
This text should be skiped.
<ui:composition template="/dialog_template.xhtml">
This text should be skiped too.
<ui:define name="title">Input Name</ui:define>
<ui:define name="description">Please input your name</ui:define>

<ui:define name="content">
<layout:panelGrid columns="2" style="height: 250px">
<layout:cell style="vertical-align: top">
<h:outputLabel value="Name:" />
</layout:cell>
<layout:cell style="vertical-align: top">
<w:textField id="name" width="250" />
</layout:cell>
</layout:panelGrid>
</ui:define>
</ui:composition>
</html>
我們可以觀察:首先,<ui:composition>指定使用哪個模版檔案,然後通過<ui:define>對模版檔案中每個可供插入的“<ui:insert>錨點”進行定義。在 上述的dialog1.xhtml檔案中,我們針對模版檔案(dialog_template.xhtml)的三個錨點,分別定義了相應的具體內容。在執行期,這些具體的內容, 將會被插入到模版檔案中定義的錨點位置。 這裡需要提醒注意的是:<ui:composition>標記和<ui:define>標記以外的所有內容都會被忽略,並且,最外面的<html>標記, 只是為了定義一個根元素,以便在這個根元素中宣告名稱空間,至於你是用<html>還是<f:view>,甚至是其它的亂七八糟的 標記,如<abcde>等等,其實都無所謂。筆者建議各位同學使用<html>作為根元素,一是因為<html>所屬的名稱空間已經被宣告過了(xmlns="http://www.w3.org/1999/xhtml"), 二是因為它對設計器更友好(哪個網頁設計器不認識<html/>標記呢?)。

Facelets的相關知識畢竟不是本文的重點,讀者可以參考其它更加專業的文件,但為了方便對模版技術的理解,最後再把修訂後的 dialog2.xhtml 頁面原始碼附上:

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets">
<ui:composition template="/dialog_template.xhtml">
<ui:define name="title">Input Others</ui:define>
<ui:define name="description">Please input your others information</ui:define>
<ui:define name="content">
<layout:panelGrid columns="2" style="height: 250px">
<layout:cell style="vertical-align: top">
<h:outputLabel value="Others:" />
</layout:cell>
<layout:cell style="vertical-align: top">
<w:combo id="others" width="250" />
</layout:cell>
</layout:panelGrid>
</ui:define>
</ui:composition>
</html>
複合元件
事實上,筆者認為:複合元件才是 Facelets 的精華所在,而複合元件加上AOM的模型事件,則更是完美的搭配。

回顧Dialog的例子,我們發覺:back、next、finish、cancel這四個按鈕,其實經常組合在一起,在許多的場景下都可以重複使用。 那麼,我們希望把它組裝成一個自定義的“複合元件”,以達到程式碼重用的目的。

首先,我們準備一個buttons.xhtml作為該複合元件的模版:

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets">
<w:page>
This text above will not be displayed.
It's purpose is for design tools.
<ui:composition>
<layout:panelGrid columns="5" style="align: right" width="100%">
<layout:cell style="width: 90%" />
<w:button value="Back" action="#{onBack}" />
<w:button value="Next" action="#{onNext}" />
<w:button value="Finish" action="#{onFinish}" />
<w:button value="Cancel" action="#{onCancel}" />
</layout:panelGrid>
</ui:composition>
</w:page>
</f:view>
同樣需要注意的是:上述檔案中真正有用的東西是<ui:composition>標記之內的內容,至於外面的<f:view>、<w:page>等標記, 純粹是為了讓設計器更好的工作而準備的。

在buttons.xhtml中,我們定義了四個按鈕,並且,分別指定它們的 action為 #{onBack}、#{onNext}、#{onFinished}、#{onCancel}。然後我們再為這個元件準備一個描述檔案:buttons.taglib.xml。

<!DOCTYPE facelet-taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<facelet-taglib>
<namespace>http://www.mycompany.com/aom</namespace>
<tag>
<tag-name>buttons</tag-name>
<source>buttons.xhtml</source>
</tag>
</facelet-taglib>
在上面這個檔案中,我們宣告瞭一個新的tag,其名稱為"buttons",它所屬於的名稱空間是:"http://www.mycompany.com/aom",並且,這個自定義元件的內容來源自 source屬性指定的模版檔案buttons.xhtml。

怎麼告訴Facelets我們新做了一個複合元件呢?在 web.xml 中增加以下配置:

<context-param>
<param-name>facelets.LIBRARIES</param-name>
<param-value>/buttons.taglib.xml</param-value>
</context-param>
如果你有多個複合元件的描述檔案,就以分號隔開。至此,一個複合元件就準備完畢了,我們來看如何使用這個複合元件。我們準備一個 test_buttons.xhtml來呼叫新做的這個複合元件,如下所示:

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
xmlns:my="http://www.mycompany.com/aom" renderKitId="AJAX"
xmlns:ui="http://java.sun.com/jsf/facelets">
<w:page title="Test buttons component">
<w:form>
<h2>Test the buttons component.</h2>
<p/>
<my:buttons onBack="#{.onBack}" onNext="#{TestButtonsBean.onNext}"
onFinish="#{TestButtonsBean.onFinish}" onCancel="#{TestButtonsBean.onCancel}"/>
</w:form>
</w:page>
</f:view>
其中,TestButtonsBean如下所示:

@ManagedBean(name="TestButtonsBean", scope=ManagedBeanScope.SESSION)
public class TestButtonsBean {

public String onBack() {
System.out.println("onBack");
return null;
}

public String onNext() {
System.out.println("onNext");
return null;
}

public String onFinish() {
System.out.println("onFinish");
return null;
}

public String onCancel() {
System.out.println("onCancel");
return null;
}
}
通過上例可以看出:在 Facelets 下完成一個複合元件是比較簡單的一件事情,

但有的同學要問了:這種在 <my:buttons onBack="#{testButtonsBean.onBack}"> 中直接引入 EL 表示式的風格,好像不是那麼 IoVC ,有沒有更好的解決方案?

答案是:“有。通過模型事件,則是一個更加優雅的解決方案。”

我們將模版檔案buttons.xhtml修訂如下:

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout" renderKitId="AJAX"
xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets">
<w:page>
This text above will not be displayed.
It's purpose is for design tools.
<ui:composition>
<layout:panelGrid columns="5" style="align: right" width="100%">
<layout:cell style="width: 90%" />
<w:button value="Back" action="#{buttonsBean.onBack}" />
<w:button value="Next" action="#{buttonsBean.onNext}" />
<w:button value="Finish" action="#{buttonsBean.onFinish}" />
<w:button value="Cancel" action="#{buttonsBean.onCancel}" />
</layout:panelGrid>
</ui:composition>
</w:page>
</f:view>
請注意,我們在模版檔案中,直接將上述 Button 的 action 繫結在了一個 “buttonsBean” 上,由於這個 “buttonsBean” 是無狀態的,所以,它的生命週期宣告為none,以下是示例程式碼:

@ManagedBean(name = "buttonsBean", scope = ManagedBeanScope.NONE)
public class ButtonsBean {

private EventBroadcaster event = EventBroadcaster.getInstance();

public Object onBack() {
event.broadcast(this, "com.mycompany.buttons.onBack");
return null;
}

public Object onNext() {
event.broadcast(this, "com.mycompany.buttons.onNext");
return null;
}

public Object onFinish() {
event.broadcast(this, "com.mycompany.buttons.onFinish");
return null;
}

public Object onCancel() {
event.broadcast(this, "com.mycompany.buttons.onCancel");
return null;
}
}
換言之,當點選不同的按鈕時,buttonsBean 會傳送不同的模型事件,事件型別以不同的字串區分。

然後,我們再修訂一下test_buttons.xhtml,看上述這個複合元件,是如何使用的:

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
xmlns:my="http://www.mycompany.com/aom" renderKitId="AJAX"
xmlns:ui="http://java.sun.com/jsf/facelets">
<w:page title="Test buttons component">
<w:form>
<h2>Test the buttons component.</h2>
<p/>
<my:buttons/>
</w:form>
</w:page>
</f:view>
可以看到,此處,我們只是宣告一個<my:buttons/>,並沒有將其繫結任何EL表示式。那麼,如果我要響應 onBack 按鈕點選事件該怎麼辦呢?很簡單,只要有一個 EventListener,監聽 "com.mycompany.buttons.onBack"事件即可。以下是 TestButtonsBean 的示例程式碼:

@ManagedBean(name="TestButtonsBean", scope=ManagedBeanScope.SESSION)
public class TestButtons {

@EventListener("com.mycompany.buttons.onBack")
public void onBack() {
System.out.println("onBack event");
}
}
通過上例可以看出:通過模型事件,我們能夠設計一個更加優雅的複合元件。

相關文章