級(jí)別:中級(jí)DavidGeary,總裁,ClarityTraining" />

日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

JSF 2 簡(jiǎn)介,第 2 部分: 模板及復(fù)合組件

系統(tǒng) 2300 0

JSF 2 簡(jiǎn)介,第 2 部分: 模板及復(fù)合組件

用 JavaServer Faces 2 實(shí)現(xiàn)可擴(kuò)展 UI


<!-- START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- this content will be automatically generated across all content areas --><!-- END RESERVED FOR FUTURE USE INCLUDE FILES-->

級(jí)別: 中級(jí)

David Geary , 總裁, Clarity Training, Inc.

2009 年 6 月 25 日

模板和復(fù)合組件是 Java?Server Faces (JSF) 2 的兩個(gè)功能強(qiáng)大的特性,借助這兩個(gè)特性,您就可以實(shí)現(xiàn)易于修改和擴(kuò)展的用戶界面。在本文 — 共三部分的 系列文章 的第 2 部分 — 中,JSF 2 專家組成員 David Geary 將向您展示如何在您的 Web 應(yīng)用程序中利用模板和復(fù)合組件。
<!-- START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --> <!-- END RESERVED FOR FUTURE USE INCLUDE FILES-->

早在 2000,當(dāng)我還是 JavaServer Pages(JSP)郵件列表中的一個(gè)活躍分子的時(shí)候,我遇到了 Craig McClanahan,當(dāng)時(shí)他正忙著開發(fā)一個(gè)新的 Web 框架,稱為 Struts。在那時(shí),我還正在從 Swing 轉(zhuǎn)向服務(wù)器端 Java 編程,所以我已經(jīng)實(shí)現(xiàn)了一個(gè)很小的框架來分離 JSP 視圖布局及其內(nèi)容,這非常類似于 Swing 布局管理器的理念。Craig 問我,是否愿意將我的 模板 庫包含在 Struts 內(nèi),我欣然同意了。這樣一來,與 Struts 1.0 捆綁的 Struts Template Library 遂成為了 Struts 流行的 Tiles 庫的基礎(chǔ),而 Tiles 庫最終成為了一個(gè)頂級(jí)的 Apache 框架。

JSF 2 現(xiàn)在的默認(rèn)顯示技術(shù) — Facelets — 就是一個(gè)模板框架,在很大程度上基于的是 Tiles。JSF 2 還提供了一個(gè)功能強(qiáng)大的機(jī)制,稱為 復(fù)合組件 ,該機(jī)制構(gòu)建在 Facelets 的模板特性之上,因此,在無需任何 Java 代碼和 XML 配置的情況下就可以實(shí)現(xiàn)定制組件。在本文中,我將向您介紹模板和復(fù)合組件,并且還會(huì)給出如何充分利用 JSF 2 的三個(gè)技巧:

  • 技巧 1:遵守 DRY 原則
  • 技巧 2:使用組合的方式
  • 技巧 3:牢記 LEGO 拼裝玩具的理念
Facelets 和 JSF 2

在標(biāo)準(zhǔn)化開源 Facelets 實(shí)現(xiàn)的同時(shí),JSF 2 專家組還對(duì)底層的 Facelets API 進(jìn)行了更改,但保留了與標(biāo)記庫的后向兼容性。這意味著用開源 Facelets 所實(shí)現(xiàn)的現(xiàn)有視圖均應(yīng)適用于 JSF 2。

在 Rick Hightower 的這兩篇文章 “ Facelets 非常適合 JSF ” 和 “ 高級(jí) Facelets 編程 ” 中可以找到有關(guān) Facelets 眾多特性的更多信息。

技巧 1:遵守 DRY 原則

在我作為軟件開發(fā)人員從事的第一項(xiàng)工作中,我的任務(wù)是為基于 UNIX? 的計(jì)算機(jī)輔助設(shè)計(jì)和計(jì)算機(jī)輔助制造(CAD/CAM)系統(tǒng)實(shí)現(xiàn)一個(gè) GUI。

最初,一切進(jìn)行順利,但是一段時(shí)間后,我的代碼開始問題不斷。待到代碼發(fā)布的時(shí)候,系統(tǒng)已經(jīng)相當(dāng)脆弱,我甚至都害怕修復(fù) bug,而這次的代碼發(fā)布自然也伴隨著一連串的 bug 報(bào)告。

如果我在這個(gè)項(xiàng)目中遵循了 DRY 原則 — 不重復(fù)自己(Don't Repeat Yourself),我本可以讓自己不至于這么悲慘。DRY 原則最初由 Dave Thomas 和 Andy Huntprinciple 提出(參見 參考資料 ),它要求:

每條知識(shí)都必須在系統(tǒng)內(nèi)具有一個(gè)單一、清晰和權(quán)威的表示。

我的 CAD/CAM 應(yīng)用程序并不符合 DRY 原則 — 它具有太多關(guān)注點(diǎn)之間的交叉 — 因此在一個(gè)地方所做的更改常常會(huì)在其他地方引起意想不到的更改。

JSF 1 在幾個(gè)方面違背了 DRY 原則,比如,它強(qiáng)迫您提供托管 beans 的兩種表示 — 一個(gè)使用 XML,一個(gè)使用 Java 代碼。對(duì)多重表示的需求讓創(chuàng)建和更改托管 bean 更加困難。正如我在本系列 第 1 部分 中介紹的,JSF 2 讓您能夠使用注釋取代 XML 來配置托管 bean,這樣一來,托管 bean 就具有了一個(gè)單一、權(quán)威的表示。

除托管 beans 之外,就連一些看似有益的實(shí)踐 — 比如在所有視圖中包括相同的樣式表 — 也違背了 DRY 原則,并會(huì)導(dǎo)致混亂。比如,如果要更改樣式表的名字,就必須更改多個(gè)視圖。如果可能,最好是封裝此樣式表包含。

DRY 原則同樣適用于代碼設(shè)計(jì)。如果多個(gè)方法均包含遍歷樹的代碼,一種好的做法是(比如在一個(gè)子類中)封裝遍歷樹的算法。

在實(shí)現(xiàn) UI 時(shí),因大多數(shù)更改均在開發(fā)過程中發(fā)生,所以遵守 DRY 原則尤其重要。

JSF 2 模板

JSF 2 在很多方面都支持 DRY 原則,其中之一就是通過 模板 。模板能夠封裝在應(yīng)用程序視圖中十分常見的功能,因此該功能只需被指定一次。在 JSF 2 應(yīng)用程序中,一個(gè)模板可供多個(gè) 組裝(compositions) 用于創(chuàng)建視圖。

我在 第 1 部分 中所介紹的 places 應(yīng)用程序具有三個(gè)視圖,如圖 1 所示:


圖 1. places 應(yīng)用程序的視圖:Login、source viewer 和 places

places 應(yīng)用程序的視圖 places 應(yīng)用程序的圖標(biāo) places 應(yīng)用程序的圖標(biāo)

與很多 Web 應(yīng)用程序一樣,這個(gè) places 應(yīng)用程序包含多個(gè)具有相同布局的視圖。JSF 模板功能讓您能夠在一個(gè)模板內(nèi)封裝該布局 — 及其他共享工件,比如 JavaScript 和 Cascading Style Sheets(CSS)。清單 1 是 圖 1 中所示的這三個(gè)視圖的模板:


清單 1. places 模板:/templates/masterLayout.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:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">

<h:head>
<title>
< ui:insert name="windowTitle">
#{msgs.placesWindowTitle}
</ui:insert>
</title>
</h:head>

<h:body>
<h:outputScript library="javascript" name="util.js" target="head"/>
<h:outputStylesheet library="css" name="styles.css" target="body"/>

<div class="pageHeading">
< ui:insert name="heading">
#{msgs.placesHeading}
</ui:insert>
</div>

<div class="menuAndContent">
<div class="menuLeft">
< ui:insert name="menuLeft"/>
</div>

<div class="content" style="display: #{places.showContent}">
< ui:insert name="content"/>
</div>

<div class="menuRight">
< ui:insert name="menuRight">
<ui:include src="/sections/shared/sourceViewer.xhtml"/>
</ui:insert>
</div>
</div>
</h:body>
</html>

清單 1 中的模板為此應(yīng)用程序的所有視圖提供了如下的基礎(chǔ)設(shè)施:

  • HTML <head> <body> <title>
  • 一個(gè)默認(rèn)標(biāo)題(可由使用此模板的那些組裝覆蓋)
  • 一個(gè) CSS 樣式表
  • 某些實(shí)用 JavaScript
  • 一個(gè)布局,格式為 <div> ,以及對(duì)應(yīng)的 CSS 類
  • 頭的默認(rèn)內(nèi)容(可被覆蓋)
  • 右菜單的默認(rèn)內(nèi)容(可被覆蓋)

正如 清單 1 所示,模板通過 <ui:insert> 標(biāo)記將內(nèi)容插入到布局中。

如為 <ui:insert> 標(biāo)記指定了主體,正如我在 清單 1 中為窗口標(biāo)題、頭和右菜單所做的,JSF 會(huì)將此標(biāo)記的主體作為 默認(rèn)內(nèi)容 。借助 <ui:define> 標(biāo)記,使用此模板的那些封裝可以定義內(nèi)容或者覆蓋默認(rèn)內(nèi)容,如清單 2 所示,它給出了 login 視圖的標(biāo)記:


清單 2. login 視圖
                    
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
template="/templates/masterLayout.xhtml" >

< ui:define name="menuLeft">
<ui:include src="/sections/login/menuLeft.xhtml"/>
</ui:define>

< ui:define name="content">
<ui:include src="/sections/login/content.xhtml"/>
</ui:define>

</ui:composition>

這個(gè) login 視圖為窗口的標(biāo)題、頭和右菜單使用了模板的默認(rèn)內(nèi)容。它只定義了特定于此 login 視圖的功能:內(nèi)容部分和左菜單。

通過為窗口標(biāo)題、頭或右菜單提供 <ui:define> 標(biāo)記,我也可以覆蓋此模板的默認(rèn)內(nèi)容。比如,清單 3 顯示了這個(gè) source-viewer 視圖( 圖 1 中間的圖片):


清單 3. source-viewer 視圖
                    
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
template="/templates/masterLayout.xhtml" >

< ui:define name="content">
<ui:include src="/sections/showSource/content.xhtml"/>
</ui:define>

< ui:define name=" menuLeft ">
<ui:include src="/sections/showSource/menuLeft.xhtml"/>
</ui:define>

< ui:define name="menuRight">
<ui:include src="/sections/showSource/menuRight.xhtml"/>
</ui:define>

</ui:composition>

source-viewer 視圖定義了內(nèi)容部分以及右菜單的內(nèi)容。它還覆蓋了由 清單 1 中的模板定義的針對(duì)左菜單的默認(rèn)內(nèi)容。

清單 4 顯示了 places 視圖( 圖 1 底部的圖片):


清單 4. places 視圖
                    
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
template="/templates/masterLayout.xhtml" >

< ui:define name="menuLeft">
<ui:include src="/sections/places/menuLeft.xhtml"/>
</ui:define>

< ui:define name="content">
<ui:include src="/sections/places/content.xhtml"/>
</ui:define>

</ui:composition>

JSF 2 模板功能

模板功能背后的概念十分簡(jiǎn)單。定義一個(gè)模板來封裝在多個(gè)視圖中常見的功能。每個(gè)視圖由一個(gè)組裝和一個(gè)模板組成。

當(dāng) JSF 創(chuàng)建視圖時(shí),它加載組裝的模板,然后將由組裝所定義的內(nèi)容插入模板。

請(qǐng)注意清單 2 3 4 之間的相似性。所有這三個(gè)視圖均指定模板并定義內(nèi)容。另外,也請(qǐng)注意創(chuàng)建新視圖十分容易,因?yàn)榇蠖鄶?shù)視圖的基礎(chǔ)設(shè)施都封裝在模板及所包含的文件內(nèi)。

使用 JSF 模板功能的另一個(gè)有趣之處是類似清單 2 3 4 中的這些視圖并不會(huì)隨時(shí)間有太多變化,所以大部分視圖代碼基本不需要維護(hù)。

與使用模板的視圖類似,模板本身也更改甚少。由于大量常見功能都封裝在幾乎不用維護(hù)的代碼中,這樣一來,您就可以將精力集中于視圖的實(shí)際內(nèi)容 — 比如,login 頁面的左菜單應(yīng)該有些什么內(nèi)容。專心于視圖的實(shí)際內(nèi)容就是下一個(gè)技巧的主旨所在。





回頁首


技巧 2:使用組合的方式

在我的 CAD/CAM GUI 發(fā)布后不久,我花了幾個(gè)月的時(shí)間與另一位開發(fā)人員 Bob 致力于一個(gè)新的項(xiàng)目。我們以 Bob 的代碼為基礎(chǔ),而且不可思議地是,我們還能輕松進(jìn)行更改并修復(fù) bug。

我很快意識(shí)到 Bob 的代碼和我的代碼之間的最大區(qū)別是他編寫了 方法 — 通常是在代碼的 5 至 15 行之間 — 并且他的整個(gè)系統(tǒng)都是由這些小方法拼接而成的。在我還在忙著修改我之前項(xiàng)目中具有很多關(guān)注點(diǎn)的長(zhǎng)方法時(shí),Bob 已經(jīng)開始機(jī)敏地組合小方法和原子功能性了。Bob 的代碼和我的代碼在維護(hù)性和可擴(kuò)展性方面自然也有著天壤之別,從那以后,我開始信服小方法。

雖然 Bob 和我那時(shí)都沒有意識(shí)到,但是我們過去一直在使用 Smalltalk 的一種設(shè)計(jì)模式,稱為 Composed Method(參見 參考資料 ):

在一個(gè)抽象級(jí)別,將軟件分成能執(zhí)行單個(gè)任務(wù)的多個(gè)方法。

使用 Composed Method 模式的好處已經(jīng)有大量書面記載(詳細(xì)說明,請(qǐng)參見 Neal Ford 的 “ 演化架構(gòu)與緊急設(shè)計(jì):組合方法和 SLAP ” )。在這里,我將側(cè)重于介紹如何在 JSF 視圖中使用 Composed Method 模式。

JSF 2 鼓勵(lì)使用較小的視圖段組裝視圖。模板封裝了常見功能,進(jìn)而將視圖分成了更小的塊。JSF 2 還提供了一個(gè) <ui:include> 標(biāo)記,正如我在先前的代碼清單中所展示的,這個(gè)標(biāo)記可以讓您將視圖進(jìn)一步分成更小的功能塊。比如,圖 2 展示了 places 應(yīng)用程序的 login 頁面的左菜單:


圖 2. login 頁面的左菜單
login 視圖的左菜單

清單 5 顯示了定義該菜單內(nèi)容的文件:


清單 5. login 視圖左菜單的實(shí)現(xiàn)
                    
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">

<div class="menuLeftText">
#{msgs.welcomeGreeting}

<div class="welcomeImage">
<h:graphicImage library="images" name="cloudy.gif"/>
</div>
</div>

</html>

清單 5 內(nèi)的標(biāo)記很簡(jiǎn)單,這就使文件更易于閱讀、理解、維護(hù)和擴(kuò)展。如果相同的代碼埋藏在一個(gè)很長(zhǎng)的、包含實(shí)現(xiàn) login 視圖所需的全部?jī)?nèi)容的 XHTML 頁面內(nèi),那么它更改起來將會(huì)很繁瑣。

圖 3 顯示了 places 視圖的左菜單:


圖 3. places 視圖的左菜單
places 視圖的左菜單

places 視圖的左菜單的實(shí)現(xiàn)如清單 6 所示:


清單 6. places 視圖的左菜單的實(shí)現(xiàn)
                    
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:util="http://java.sun.com/jsf/composite/components/util">

<div class="placesSearchForm">
<div class="placesSearchFormHeading">
#{msgs.findAPlace}
</div>

<h:form prependId="false">
<h:panelGrid columns="2">

#{msgs.streetAddress}
<h:inputText value="#{place.streetAddress}" size="15"/>

#{msgs.city} <h:inputText value="#{place.city}" size="10"/>
#{msgs.state} <h:inputText value="#{place.state}" size="3"/>
#{msgs.zip} <h:inputText value="#{place.zip}" size="5"/>

<h:commandButton value="#{msgs.goButtonText}"
style="font-family:Palatino;font-style:italic"
action="#{place.fetch}"/>

</h:panelGrid>
</h:form>
</div>

<util:icon image ="#{resource['images:back.jpg']}"
actionMethod ="#{places.logout}"
style="border: thin solid lightBlue"/>

</ui:composition>

清單 6 實(shí)現(xiàn)了一個(gè)表單,并且此表單使用了一個(gè)圖標(biāo)組件。(我隨后會(huì)在 圖標(biāo)組件 一節(jié)對(duì)該圖標(biāo)組件進(jìn)行詳細(xì)討論。目前,只需知道頁面作者可以用一個(gè)圖標(biāo)關(guān)聯(lián)圖像和方法。)這個(gè) logout 圖標(biāo)的圖像顯示在 圖 3 的底部,而此 logout 圖標(biāo)的方法 — places.logout() — 則如清單 7 所示:


清單 7. Places.logout() 方法
                    
package com.clarity;
...
@ManagedBean()
@SessionScoped

public class Places {
private ArrayList<Place> places = null;
...
private static SelectItem[] zoomLevelItems = {
...
public String logout() {
FacesContext fc = FacesContext.getCurrentInstance();
ELResolver elResolver = fc.getApplication().getELResolver();

User user = (User)elResolver.getValue(
fc.getELContext(), null, "user");

user.setName("");
user.setPassword("");

setPlacesList(null);

return "login";
}
}

對(duì)我而言, 清單 6 — places 視圖的左菜單的實(shí)現(xiàn) — 已經(jīng)十分接近 30 行的代碼長(zhǎng)度限制。此清單有點(diǎn)難于讀懂,并且該代碼片段內(nèi)的表單和圖標(biāo)可被重構(gòu)成各自的文件。清單 8 顯示了 清單 6 的重構(gòu)版,其中,表單和圖標(biāo)被封裝進(jìn)各自的 XHTML 文件:


清單 8. 重構(gòu) places 視圖的左菜單
                    
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets">

<div class="placesSearchForm">
<div class="placesSearchFormHeading">
#{msgs.findAPlace}
</div>

<ui:include src=" addressForm.xhtml ">
<ui:include src=" logoutIcon.xhtml ">
</div>

</ui:composition>

清單 9 顯示了 addressForm.xhtml:


清單 9. addressForm.xhtml
                    
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">

<h:form prependId="false">
<h:panelGrid columns="2">

#{msgs.streetAddress}
<h:inputText value="#{place.streetAddress}" size="15"/>

#{msgs.city} <h:inputText value="#{place.city}" size="10"/>
#{msgs.state} <h:inputText value="#{place.state}" size="3"/>
#{msgs.zip} <h:inputText value="#{place.zip}" size="5"/>

<h:commandButton value="#{msgs.goButtonText}"
style="font-family:Palatino;font-style:italic"
action="#{place.fetch}"/>

</h:panelGrid>
</h:form>

</ui:composition>

清單 10 顯示了 logoutIcon.xhtml:


清單 10. logoutIcon.xhtml
                    
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:util="http://java.sun.com/jsf/composite/components/util">

<util:icon image="#{resource['images:back.jpg']}"
actionMethod="#{places.logout}"
style="border: thin solid lightBlue"/>

</ui:composition>

在從多個(gè)小文件組裝視圖時(shí),就可享受到 Smalltalk 的 Composed Method 模式的益處。您還可以組織這些文件以便更易于對(duì)更改做出反應(yīng)。例如,圖 4 顯示了構(gòu)成 places 應(yīng)用程序內(nèi)的這三個(gè)視圖的文件:


圖 4. places 應(yīng)用程序的視圖
places 應(yīng)用程序的視圖

我 所創(chuàng)建的這三個(gè)目錄 — views、sections 和 templates — 包含了用來實(shí)現(xiàn) places 應(yīng)用程序視圖的大多數(shù) XHTML 文件。由于 views 和 templates 目錄內(nèi)的文件很少更改,因此我更多關(guān)注的是 sections 目錄。例如,若我想要更改 login 頁面左菜單內(nèi)的圖標(biāo),我就知道該到哪里去更改:sections/login/menuLeft.xhtml。

當(dāng)然,您可以使用任何目錄結(jié)構(gòu)來組織您的 XHTML 文件。如果組織得合理,定位想要修改的代碼就會(huì)非常容易。

除了遵循 DRY 原則和使用 Composed Method 模式之外,還有一種好的做法是在定制組件內(nèi)封裝功能。組件是一種功能強(qiáng)大的重用機(jī)制,而且您應(yīng)該充分利用這種強(qiáng)大性。與 JSF 1 不同,使用 JSF 2 更易于實(shí)現(xiàn)定制組件。





回頁首


技巧 3:牢記 LEGO 拼裝玩具的理念

在我還是一個(gè)男孩的時(shí)候,我有兩個(gè)最喜歡的玩具:一個(gè)是化學(xué)組合(chemistry set),一個(gè)是 LEGO 拼裝玩具。這兩種玩具讓我能夠通過組合基本的構(gòu)建塊來創(chuàng)建東西,而這也成為了我一生的愛好,只不過現(xiàn)在是打著軟件開發(fā)的幌子。

JSF 的優(yōu)勢(shì)一直都在于其組件模型,但這種優(yōu)勢(shì)直到現(xiàn)在才完全實(shí)現(xiàn),因?yàn)橛?JSF 1 很難實(shí)現(xiàn)定制組件。您必須要編寫 Java 代碼、指定 XML 配置,并對(duì) JSF 的生命周期有深刻的理解。有了 JSF 2,您就能夠輕松實(shí)現(xiàn)定制組件:

  • 無需配置、XML 或其他。
  • 無需 Java 代碼。
  • 開發(fā)人員可以向其附加功能。
  • 修改后執(zhí)行熱部署。

在本文的剩余部分,我將向您介紹如何為 places 應(yīng)用程序?qū)崿F(xiàn)三個(gè)定制組件:一個(gè)圖標(biāo)、一個(gè) login 面板和一個(gè)顯示了地址地圖和天氣信息的面板。但是首先,讓我先來概括介紹一下 JSF 2 復(fù)合組件。

實(shí)現(xiàn)定制組件

JSF 2 綜合了 Facelets 模板 、資源處理(在 第 1 部分 中討論過)和一個(gè)簡(jiǎn)單的命名約定來實(shí)現(xiàn) 復(fù)合組件 。復(fù)合組件,正如其名字所示,讓您能夠從現(xiàn)有組件組裝一個(gè)新組件。

一般情況下,是在 resources 目錄下的 XHTML 內(nèi)實(shí)現(xiàn)復(fù)合組件,并將它們完全通過約定鏈接到一個(gè)名稱空間和標(biāo)記。圖 5 展示了我是如何為 places 應(yīng)用程序組織這些復(fù)合組件的:


圖 5. places 應(yīng)用程序的組件
places 應(yīng)用程序的組件

要使用復(fù)合組件,需要聲明一個(gè)名稱空間并使用標(biāo)記。此名稱空間通常為 http://java.sun.com/jsf/composite 外加目錄名,這個(gè)目錄就是 resources 目錄下組件所在之處。組件名本身是其 XHTML 文件的名字,只不過沒有 .xhtml 擴(kuò)展名。這種約定消除了對(duì)配置的需要。比如,要在 places 應(yīng)用程序中使用 login 組件,應(yīng)該這樣做:

                    <html xmlns="http://www.w3.org/1999/xhtml"
                    
...
xmlns:util="http://java.sun.com/jsf/composite /component/util ">
...
< util:login .../>
...
<html>

而要使用 icon 組件,則需要像下面這樣:

                    <html xmlns="http://www.w3.org/1999/xhtml"
                    
...
xmlns:util="http://java.sun.com/jsf/composite /components/util ">
...
< util:icon .../>
...
<html>

最后,若要使用 place 組件,則可按如下所示的這樣做:

                    <html xmlns="http://www.w3.org/1999/xhtml"
                    
...
xmlns:util="http://java.sun.com/jsf/composite /components/places ">
...
< places:place .../>
...
<html>

icon 組件:一個(gè)簡(jiǎn)單的復(fù)合組件

places 應(yīng)用程序使用了圖 6 所示的這兩個(gè)圖標(biāo):


圖 6. places 應(yīng)用程序的圖標(biāo)

places 應(yīng)用程序的圖標(biāo) places 應(yīng)用程序的圖標(biāo)

每個(gè)圖標(biāo)都是一個(gè)鏈接。當(dāng)用戶單擊 圖 6 左側(cè)的圖標(biāo)時(shí),JSF 就會(huì)顯示當(dāng)前視圖的標(biāo)記,而激活右側(cè)圖標(biāo)則會(huì)使用戶登出此應(yīng)用程序。

可以為鏈接指定一個(gè) CSS 類名和圖像,并且還可以向鏈接附加方法。當(dāng)用戶單擊一個(gè)被關(guān)聯(lián)的鏈接時(shí),JSF 就會(huì)調(diào)用那些方法。

清單 11 給出了 icon 組件是如何被用來在 places 應(yīng)用程序中顯示標(biāo)記的:


清單 11. 使用 icon 組件顯示標(biāo)記
                    
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:util="http://java.sun.com/jsf/composite/components/util">

<util:icon actionMethod ="#{ sourceViewer.showSource }"
image ="#{resource[' images:disk-icon.jpg ']}"/>
...
</html>


清單 12 給出了如何使用 icon 組件執(zhí)行登出:


清單 12. 使用 icon 組件執(zhí)行登出
                    
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:util="http://java.sun.com/jsf/composite/components/util">

<util:icon actionMethod ="#{ places.logout }"
image ="#{resource['images: back-arrow.jpg ']}"/>
...
</html>

清單 13 給出了 icon 組件的代碼:


清單 13. icon 組件
                    
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:composite="http://java.sun.com/jsf/composite">

<!-- INTERFACE -->
< composite:interface >
< composite:attribute name="image"/>
< composite:attribute name="actionMethod"
method-signature="java.lang.String action()"/>
</composite:interface>

<!-- IMPLEMENTATION -->
< composite:implementation >
<h:form>
<h:commandLink action="#{ cc.attrs.actionMethod }" immediate="true">

<h:graphicImage value="#{ cc.attrs.image }"
styleClass ="icon"/>

</h:commandLink>
</h:form>
</composite:implementation>
</html>

與其他復(fù)合組件類似, 清單 13 中的 icon 組件包含兩節(jié): <composite:interface> <composite:implementation> <composite:interface> 節(jié)定義了一個(gè)界面,可用來配置此組件。 icon 組件具有兩個(gè)屬性: image actionMethod ,前者定義了組件的外觀,后者定義了組件的行為。

<composite:implementation> 節(jié)包含組件的實(shí)現(xiàn)。它使用 #{cc.attrs. ATTRIBUTE_NAME } 表達(dá)式來訪問組件的界面內(nèi)定義的屬性。( cc 是 JSF 2 表達(dá)式語言中的保留關(guān)鍵字,代表的是復(fù)合組件。)

請(qǐng)注意, 清單 13 中的 icon 組件用 <h:graphicImage> styleClass 屬性為其圖像指定了一個(gè) CSS 類。該 CSS 類的名字被硬編碼為 icon , 所以您就能夠指定一個(gè)具有該名稱的 CSS 類,JSF 將為應(yīng)用程序中的所有圖標(biāo)使用該類。但是如果您想要覆蓋該 CSS 類名,又該如何呢?在這種情況下,我可以為該 CSS 添加另一個(gè)屬性并提供一個(gè)默認(rèn),可供 JSF 在未指定屬性的時(shí)候使用。清單 14 給出了該屬性:


清單 14. 重構(gòu)后的 icon 組件
                    
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
<html xmlns="http://www.w3.org/1999/xhtml"
...
xmlns:composite="http://java.sun.com/jsf/composite">

<composite:interface>
...
<composite:attribute name="styleClass" default="icon" required="false" />
...
</composite:interface>

<composite:implementation>
...
<h:graphicImage value="#{cc.attrs.image}"
styleClass=" #{cc.attrs.styleClass} "/>
...
</composite:implementation>
</html>

清單 14 中,我已經(jīng)向這個(gè)圖標(biāo)組件的界面添加了一個(gè)屬性,名為 styleClass ,并已經(jīng)在此組件的實(shí)現(xiàn)中引用了該屬性。有了這種更改,現(xiàn)在就可以為此圖標(biāo)的圖像指定一個(gè)可選的 CSS 類,如下所示:

                    <util:icon actionMethod="#{places.logout}" 
                    
image="#{resource['images:back-arrow.jpg']}"
styleClass ="customIconClass"/>

如果不能指定 styleClass 屬性,JSF 將使用默認(rèn)值 icon

login 組件:一個(gè)完全可配置的組件

有了 JSF 2,就可以實(shí)現(xiàn)完全可配置的復(fù)合組件。例如,places 應(yīng)用程序就包含了一個(gè) login 組件,如圖 7 所示:


圖 7. places 應(yīng)用程序的 login 組件
places 應(yīng)用程序的 login 組件

清單 15 顯示了這個(gè) places 應(yīng)用程序是如何使用 login 組件的:


清單 15. 使用 login 組件
                    
<!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:comp="http://java.sun.com/jsf/composite/ component/util ">

< util:login loginPrompt="#{msgs.loginPrompt}"
namePrompt="#{msgs.namePrompt}"
passwordPrompt="#{msgs.passwordPrompt}"
loginAction="#{user.login}"
loginButtonText="#{msgs.loginButtonText}"
managedBean="#{user}">

< f:actionListener for="loginButton"
type="com.clarity.LoginActionListener"/>

</util:login>
...
</html>

清單 15 不僅參數(shù)化 login 組件的屬性,比如名字和密碼提示,它還將一個(gè)動(dòng)作偵聽器附加到了此組件的 Log In 按鈕。該按鈕由 login 組件的界面公開,如清單 16 所示:


清單 16. login 組件
                    
<!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:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:composite="http://java.sun.com/jsf/composite">

<!-- INTERFACE -->
<composite:interface>
<composite:actionSource name="loginButton" targets="form:loginButton"/>
<composite:attribute name="loginButtonText" default="Log In" required="true"/>
<composite:attribute name="loginPrompt"/>
<composite:attribute name="namePrompt"/>
<composite:attribute name="passwordPrompt"/>
<composite:attribute name="loginAction"
method-signature="java.lang.String action()"/>
<composite:attribute name="managedBean"/>
</composite:interface>

<!-- IMPLEMENTATION -->
<composite:implementation>
<h:form id="form" prependId="false">

<div class="prompt">
#{cc.attrs.loginPrompt}
</div>

<panelGrid columns="2">
#{cc.attrs.namePrompt}
<h:inputText id="name" value="#{cc.attrs.managedBean.name}"/>

#{cc.attrs.passwordPrompt}
<h:inputSecret id="password" value="#{cc.attrs.managedBean.password}" />

</panelGrid>

<p>
<h:commandButton id="loginButton"
value="#{cc.attrs.loginButtonText}"
action="#{cc.attrs.loginAction}"/>
</p>
</h:form>

<div class="error" style="padding-top:10px;">
<h:messages layout="table"/>
</div>
</composite:implementation>
</html>

在 login 組件的界面,我已經(jīng)在 loginButton 名稱下公開了 Log In 按鈕。該名稱所針對(duì)的是位于 form 表單內(nèi)的 Log In 按鈕,因此 targets 屬性的值為: form:loginButton

清單 16 內(nèi)的 Log In 按鈕相關(guān)聯(lián)的動(dòng)作偵聽器如清單 17 所示:


清單 17. Log In 按鈕的動(dòng)作偵聽器
                    
package com.clarity;

import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;

public class LoginActionListener implements ActionListener {
public void processAction(ActionEvent e)
throws AbortProcessingException {
System.out.println("logging in ...........");
}
}

清單 17 內(nèi)的動(dòng)作偵聽器完全是為了展示的目的 — 當(dāng)用戶登錄時(shí),我只簡(jiǎn)單地將一條消息寫出到 servlet 容器日志文件。但是我希望您能體會(huì)到這樣一個(gè)概念:有了 JSF 2,您可以實(shí)現(xiàn)完全可配置的組件,并且還可以向這些組件附加功能,所有這些均不需要任何 Java 代碼或 XML 配置。這才真正稱得上是功能強(qiáng)大。

place 組件:嵌套復(fù)合組件

JSF 2 讓您能夠在無需任何 Java 代碼或配置的情況下實(shí)現(xiàn)完全可配置的組件。除此之外,您還可以嵌套復(fù)合組件,這樣一來,您就可以將復(fù)雜的組件拆分成更小的、更易于管理的塊。比如,圖 8 所示的 place 組件,它能顯示針對(duì)給定地址的地圖和天氣信息。


圖 8. places 應(yīng)用程序的 place 組件
place 組件

清單 18 給出了 places 應(yīng)用程序是如何使用 place 組件的:


清單 18. 使用 place 組件
                    
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns: places ="http://java.sun.com/jsf/composite/ components/places ">

<h:form id="form">
<ui:repeat value="#{places.placesList}" var="place">
<places:place location="#{place}"/>
</ui:repeat>
</h:form>
</ui:composition>

place 組件的代碼如清單 19 所示:


清單 19. place 組件
                    
<!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:composite="http://java.sun.com/jsf/composite"
xmlns: places ="http://java.sun.com/jsf/composite/ components/places ">

<!-- INTERFACE -->
<composite:interface>
<composite:attribute name="location" required="true"/>
</composite:interface>

<!-- IMPLEMENTATION -->
<composite:implementation>
<div class="placeHeading">

<places:map title="Map"/>
<places:weather title="Weather"/>


</div>
</composite:implementation>

</html>

清單 19 中, place 組件使用了兩個(gè)嵌套組件: <places:map> <places:weather> 。清單 20 給出了 map 組件:


清單 20. map 組件
                    
<!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:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:composite="http://java.sun.com/jsf/composite">

<!-- INTERFACE -->
<composite:interface>
<composite:attribute name="title"/>
</composite:interface>

<!-- IMPLEMENTATION -->
<composite:implementation>
<div class="map">
<div style="padding-bottom: 10px;">
<h:outputText value=" #{cc.attrs.title} "
style="color: blue"/>
</div>

<h:panelGrid columns="1">
<h:panelGroup>
<div style="padding-left: 5px;">
<i>
<h:outputText value="#{cc. parent.attrs .location.streetAddress}, "/>
</i>

<h:outputText value=" #{cc. parent.attrs .location.city}" />
<h:outputText value="#{cc. parent.attrs .location.state}"/><hr/>
</div>
</h:panelGroup>

<h:panelGrid columns="2">
<div style="padding-right: 10px;margin-bottom: 10px;font-size:14px">
#{msgs.zoomPrompt}
</div>

<h:selectOneMenu onchange="submit()"
value="#{cc. parent.attrs .location.zoomIndex}"
valueChangeListener="#{cc. parent.attrs .location.zoomChanged}"
style="font-size:13px;font-family:Palatino">

<f:selectItems value="#{cc. parent.attrs .location.zoomLevelItems}"/>

</h:selectOneMenu>
</h:panelGrid>

<h:graphicImage url="#{cc. parent.attrs .location.mapUrl}"
style="border: thin solid gray"/>

</h:panelGrid>
</div>
</composite:implementation>
</html>

復(fù)合組件重構(gòu)

清單 20 map 組件的標(biāo)記 — 對(duì)我而言有些過長(zhǎng)。它初看上去多少有些難于理解,而且其復(fù)雜性也很可能給今后帶來問題。

您可以很輕松地對(duì) 清單 20 進(jìn)行重構(gòu),將其分成多個(gè)更容易管理的文件,正如我之前在清單 8 9 10 中重構(gòu) places 視圖的左菜單時(shí)所做的那樣。在本例中,我將重構(gòu)的任務(wù)留給您作為練習(xí)。

請(qǐng)注意 清單 20 中表達(dá)式 #{cc.parent.attrs.location. ATTRIBUTE_NAME } 的使用。您可以使用一個(gè)復(fù)合組件的 parent 屬性來訪問父組件的屬性,這一點(diǎn)極大地方便了組件的嵌套。

但是,您無需嚴(yán)格依賴于嵌套組件中的父屬性,正如我在 清單 19 中對(duì) place 組件所做的那樣,您也可以將屬性(比如地圖的標(biāo)題)從父組件傳遞給其內(nèi)嵌套的組件,與向其他任何組件(不管嵌套與否)傳遞屬性無異。

清單 21 顯示了這個(gè) weather 組件:


清單 21. weather 組件
                    
<!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:h="http://java.sun.com/jsf/html"
xmlns:composite="http://java.sun.com/jsf/composite">

<!-- INTERFACE -->
<composite:interface>
<composite:attribute name="title"/>
</composite:interface>

<!-- IMPLEMENTATION -->
<composite:implementation>

<div class="weather">
<div style="padding-bottom: 10px;">
<h:outputText value="#{cc.attrs.title}"
style="color: blue"/>
</div>

<div style="margin-top: 10px;width:250px;">
<h:outputText style="font-size: 12px;"
value="#{cc. parent.attrs .location.weather}"
escape="false"/>
</div>
</div>

</composite:implementation>
</html>

weather 組件與 map 組件一樣,使用了父組件屬性(來自天氣 Web 服務(wù)的天氣 HTML )和一個(gè)特定于組件的屬性(標(biāo)題)。(參見 第 1 部分 來了解此應(yīng)用程序是如何獲得某個(gè)地區(qū)的地圖和天氣信息的。)

因此,在想要實(shí)現(xiàn)嵌套組件時(shí),您就有了選擇。您可以讓嵌套的組件依賴于其父組件的屬性,也可以要求父組件將屬性顯式地傳遞給其內(nèi)嵌套的組件。比如, 清單 19 中的 place 組件顯式地將標(biāo)題屬性傳遞給了其內(nèi)所嵌套的組件,但所嵌套的組件依賴于這個(gè)父組件的屬性,比如地圖 URL 和天氣 HTML。

是選擇實(shí)現(xiàn)組件-顯式屬性,還是選擇依賴于父屬性,這是耦合和方便性之間的權(quán)衡問題。在本例中, map weather 組件緊密耦合到它們的父組件( place 組件),因?yàn)樗鼈円蕾囉诟附M件的屬性。我本可以通過將 map weather 組件的屬性指定為組件-顯式屬性來去掉 map weather 組件與 place 組件間的耦合。但是如果那樣做的話,我就會(huì)犧牲一些方便性,因?yàn)? place 組件需要將所有屬性顯式地傳遞給 map weather 組件。





回頁首


結(jié)束語

在本文中,我向您展示了如何使用 JSF 2 的模板和復(fù)合組件特性來實(shí)現(xiàn)易于維護(hù)和擴(kuò)展的 UI。在本系列的最后一篇文章,我將探討如何在復(fù)合組件中使用 JavaScript、如何使用 JSF 2 的新事件模型以及如何利用 JSF 2 對(duì) Ajax 的內(nèi)置支持。






回頁首


下載

描述 名字 大小 下載方法 示例源代碼
jsf2fu2.zip 7.4MB HTTP
關(guān)于下載方法的信息


參考資料

學(xué)習(xí)

獲得產(chǎn)品和技術(shù)
  • JSF :下載 JSF 2.0。

討論


關(guān)于作者

David Geary

David Geary 是一名作家、演講家和顧問,也是 Clarity Training, Inc. 的總裁,他指導(dǎo)開發(fā)人員使用 JSF 和 Google Web Toolkit (GWT) 實(shí)現(xiàn) Web 應(yīng)用程序。他是 JSTL 1.0 和 JSF 1.0/2.0 專家組的成員,與人合作編寫了 Sun 的 Web Developer 認(rèn)證考試的內(nèi)容,并且為多個(gè)開源項(xiàng)目作出貢獻(xiàn),包括 Apache Struts 和 Apache Shale。David 的 Graphic Java Swing 一直是有關(guān) Java 的暢銷書籍,而 Core JSF (與 Cay Horstman 合著)是有關(guān) JSF 的暢銷書。David 經(jīng)常在各大會(huì)議和用戶組發(fā)表演講。他從 2003 年開始就一直是 NFJS tour 的固定演講人,并且在 Java University 教授課程,兩次當(dāng)選為 JavaOne 之星。

JSF 2 簡(jiǎn)介,第 2 部分: 模板及復(fù)合組件


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 格尔木市| 新密市| 平武县| 汪清县| 九江县| 伊金霍洛旗| 怀集县| 三原县| 保亭| 日喀则市| 娄烦县| 秦安县| 花垣县| 剑川县| 遂溪县| 江山市| 静海县| 六盘水市| 雷波县| 迭部县| 灵璧县| 溧阳市| 新乐市| 石柱| 平遥县| 渭源县| 东丽区| 崇明县| 铁岭县| 南澳县| 鄂托克旗| 德庆县| 乡宁县| 烟台市| 荆州市| 尼勒克县| 金川县| SHOW| 左权县| 宜川县| 永德县|