Facelets是JSF更好的外衣
是由 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");
}
}
通過上例可以看出:通過模型事件,我們能夠設計一個更加優雅的複合元件。
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");
}
}
通過上例可以看出:通過模型事件,我們能夠設計一個更加優雅的複合元件。
相關文章
- JSF是思想的進步還是後退JS
- 懶 Redis 是更好的 RedisRedis
- 扒去Spring事件監聽機制的外衣,竟然是觀察者模式Spring事件模式
- 為我開發的API新增華麗的外衣API
- GO是更好的程式語言嗎?Go
- Linux 是更好的選擇(轉)Linux
- JSF的問題JS
- 破破的JSFJS
- 學Java還是前端更好?Java前端
- SQL是比GraphQL更好的API語言?SQLAPI
- 元宇宙只是未來技術演化的外衣元宇宙
- 白話 Tornado 原始碼(5):褪去模板的外衣原始碼
- JSF的框架相關JS框架
- JSF的中文問題JS
- 有jsf的應用嗎JS
- JSF中的設計模式JS設計模式
- JSF與Struts的異同JS
- ***** JSF專集 *****JS
- JSF和strutsJS
- JSF vs StrutsJS
- 原本只是單純的技術 卻被披上夢想的外衣
- 白話 Tornado 原始碼(4):褪去模板外衣的前戲原始碼
- 為什麼GPL是更好的開源許可證?
- Tita 是如何幫助企業更好推進 OKR 的?OKR
- JSF專案中seam的配置JS
- JSF的加減法與SeamJS
- jsf取值的簡單問題JS
- jsf與jspJS
- JSF效能問題JS
- Jsf國際化JS
- NetBeans能否承載JSF中興之重? JSF開發工具BeanJS
- 為什麼 APISIX Ingress 是比 Traefik 更好的選擇?API
- Seam中的JSF表單驗證JS
- PrettyFaces提供jSF的URL-rewritingJS
- 請教JSF和Struts的問題JS
- 關於JSF與Struts的區別JS
- JSF 中如何分模組??JS
- JSF技術介紹JS