級別:中級DavidGeary,總裁,ClarityTraining" />

亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

JSF 2 簡介,第 2 部分: 模板及復合組件

系統 1997 0

JSF 2 簡介,第 2 部分: 模板及復合組件

用 JavaServer Faces 2 實現可擴展 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-->

級別: 中級

David Geary , 總裁, Clarity Training, Inc.

2009 年 6 月 25 日

模板和復合組件是 Java?Server Faces (JSF) 2 的兩個功能強大的特性,借助這兩個特性,您就可以實現易于修改和擴展的用戶界面。在本文 — 共三部分的 系列文章 的第 2 部分 — 中,JSF 2 專家組成員 David Geary 將向您展示如何在您的 Web 應用程序中利用模板和復合組件。
<!-- 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,當我還是 JavaServer Pages(JSP)郵件列表中的一個活躍分子的時候,我遇到了 Craig McClanahan,當時他正忙著開發一個新的 Web 框架,稱為 Struts。在那時,我還正在從 Swing 轉向服務器端 Java 編程,所以我已經實現了一個很小的框架來分離 JSP 視圖布局及其內容,這非常類似于 Swing 布局管理器的理念。Craig 問我,是否愿意將我的 模板 庫包含在 Struts 內,我欣然同意了。這樣一來,與 Struts 1.0 捆綁的 Struts Template Library 遂成為了 Struts 流行的 Tiles 庫的基礎,而 Tiles 庫最終成為了一個頂級的 Apache 框架。

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

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

在標準化開源 Facelets 實現的同時,JSF 2 專家組還對底層的 Facelets API 進行了更改,但保留了與標記庫的后向兼容性。這意味著用開源 Facelets 所實現的現有視圖均應適用于 JSF 2。

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

技巧 1:遵守 DRY 原則

在我作為軟件開發人員從事的第一項工作中,我的任務是為基于 UNIX? 的計算機輔助設計和計算機輔助制造(CAD/CAM)系統實現一個 GUI。

最初,一切進行順利,但是一段時間后,我的代碼開始問題不斷。待到代碼發布的時候,系統已經相當脆弱,我甚至都害怕修復 bug,而這次的代碼發布自然也伴隨著一連串的 bug 報告。

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

每條知識都必須在系統內具有一個單一、清晰和權威的表示。

我的 CAD/CAM 應用程序并不符合 DRY 原則 — 它具有太多關注點之間的交叉 — 因此在一個地方所做的更改常常會在其他地方引起意想不到的更改。

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

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

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

在實現 UI 時,因大多數更改均在開發過程中發生,所以遵守 DRY 原則尤其重要。

JSF 2 模板

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

我在 第 1 部分 中所介紹的 places 應用程序具有三個視圖,如圖 1 所示:


圖 1. places 應用程序的視圖:Login、source viewer 和 places

places 應用程序的視圖 places 應用程序的圖標 places 應用程序的圖標

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


清單 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 中的模板為此應用程序的所有視圖提供了如下的基礎設施:

  • HTML <head> <body> <title>
  • 一個默認標題(可由使用此模板的那些組裝覆蓋)
  • 一個 CSS 樣式表
  • 某些實用 JavaScript
  • 一個布局,格式為 <div> ,以及對應的 CSS 類
  • 頭的默認內容(可被覆蓋)
  • 右菜單的默認內容(可被覆蓋)

正如 清單 1 所示,模板通過 <ui:insert> 標記將內容插入到布局中。

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


清單 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>

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

通過為窗口標題、頭或右菜單提供 <ui:define> 標記,我也可以覆蓋此模板的默認內容。比如,清單 3 顯示了這個 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 視圖定義了內容部分以及右菜單的內容。它還覆蓋了由 清單 1 中的模板定義的針對左菜單的默認內容。

清單 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 模板功能

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

當 JSF 創建視圖時,它加載組裝的模板,然后將由組裝所定義的內容插入模板。

請注意清單 2 3 4 之間的相似性。所有這三個視圖均指定模板并定義內容。另外,也請注意創建新視圖十分容易,因為大多數視圖的基礎設施都封裝在模板及所包含的文件內。

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

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





回頁首


技巧 2:使用組合的方式

在我的 CAD/CAM GUI 發布后不久,我花了幾個月的時間與另一位開發人員 Bob 致力于一個新的項目。我們以 Bob 的代碼為基礎,而且不可思議地是,我們還能輕松進行更改并修復 bug。

我很快意識到 Bob 的代碼和我的代碼之間的最大區別是他編寫了 方法 — 通常是在代碼的 5 至 15 行之間 — 并且他的整個系統都是由這些小方法拼接而成的。在我還在忙著修改我之前項目中具有很多關注點的長方法時,Bob 已經開始機敏地組合小方法和原子功能性了。Bob 的代碼和我的代碼在維護性和可擴展性方面自然也有著天壤之別,從那以后,我開始信服小方法。

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

在一個抽象級別,將軟件分成能執行單個任務的多個方法。

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

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


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

清單 5 顯示了定義該菜單內容的文件:


清單 5. login 視圖左菜單的實現
                    
<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 內的標記很簡單,這就使文件更易于閱讀、理解、維護和擴展。如果相同的代碼埋藏在一個很長的、包含實現 login 視圖所需的全部內容的 XHTML 頁面內,那么它更改起來將會很繁瑣。

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


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

places 視圖的左菜單的實現如清單 6 所示:


清單 6. places 視圖的左菜單的實現
                    
<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 實現了一個表單,并且此表單使用了一個圖標組件。(我隨后會在 圖標組件 一節對該圖標組件進行詳細討論。目前,只需知道頁面作者可以用一個圖標關聯圖像和方法。)這個 logout 圖標的圖像顯示在 圖 3 的底部,而此 logout 圖標的方法 — 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";
}
}

對我而言, 清單 6 — places 視圖的左菜單的實現 — 已經十分接近 30 行的代碼長度限制。此清單有點難于讀懂,并且該代碼片段內的表單和圖標可被重構成各自的文件。清單 8 顯示了 清單 6 的重構版,其中,表單和圖標被封裝進各自的 XHTML 文件:


清單 8. 重構 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>

在從多個小文件組裝視圖時,就可享受到 Smalltalk 的 Composed Method 模式的益處。您還可以組織這些文件以便更易于對更改做出反應。例如,圖 4 顯示了構成 places 應用程序內的這三個視圖的文件:


圖 4. places 應用程序的視圖
places 應用程序的視圖

我 所創建的這三個目錄 — views、sections 和 templates — 包含了用來實現 places 應用程序視圖的大多數 XHTML 文件。由于 views 和 templates 目錄內的文件很少更改,因此我更多關注的是 sections 目錄。例如,若我想要更改 login 頁面左菜單內的圖標,我就知道該到哪里去更改:sections/login/menuLeft.xhtml。

當然,您可以使用任何目錄結構來組織您的 XHTML 文件。如果組織得合理,定位想要修改的代碼就會非常容易。

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





回頁首


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

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

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

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

在本文的剩余部分,我將向您介紹如何為 places 應用程序實現三個定制組件:一個圖標、一個 login 面板和一個顯示了地址地圖和天氣信息的面板。但是首先,讓我先來概括介紹一下 JSF 2 復合組件。

實現定制組件

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

一般情況下,是在 resources 目錄下的 XHTML 內實現復合組件,并將它們完全通過約定鏈接到一個名稱空間和標記。圖 5 展示了我是如何為 places 應用程序組織這些復合組件的:


圖 5. places 應用程序的組件
places 應用程序的組件

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

                    <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 組件:一個簡單的復合組件

places 應用程序使用了圖 6 所示的這兩個圖標:


圖 6. places 應用程序的圖標

places 應用程序的圖標 places 應用程序的圖標

每個圖標都是一個鏈接。當用戶單擊 圖 6 左側的圖標時,JSF 就會顯示當前視圖的標記,而激活右側圖標則會使用戶登出此應用程序。

可以為鏈接指定一個 CSS 類名和圖像,并且還可以向鏈接附加方法。當用戶單擊一個被關聯的鏈接時,JSF 就會調用那些方法。

清單 11 給出了 icon 組件是如何被用來在 places 應用程序中顯示標記的:


清單 11. 使用 icon 組件顯示標記
                    
<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 組件執行登出:


清單 12. 使用 icon 組件執行登出
                    
<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>

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

<composite:implementation> 節包含組件的實現。它使用 #{cc.attrs. ATTRIBUTE_NAME } 表達式來訪問組件的界面內定義的屬性。( cc 是 JSF 2 表達式語言中的保留關鍵字,代表的是復合組件。)

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


清單 14. 重構后的 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 中,我已經向這個圖標組件的界面添加了一個屬性,名為 styleClass ,并已經在此組件的實現中引用了該屬性。有了這種更改,現在就可以為此圖標的圖像指定一個可選的 CSS 類,如下所示:

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

如果不能指定 styleClass 屬性,JSF 將使用默認值 icon

login 組件:一個完全可配置的組件

有了 JSF 2,就可以實現完全可配置的復合組件。例如,places 應用程序就包含了一個 login 組件,如圖 7 所示:


圖 7. places 應用程序的 login 組件
places 應用程序的 login 組件

清單 15 顯示了這個 places 應用程序是如何使用 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 不僅參數化 login 組件的屬性,比如名字和密碼提示,它還將一個動作偵聽器附加到了此組件的 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 組件的界面,我已經在 loginButton 名稱下公開了 Log In 按鈕。該名稱所針對的是位于 form 表單內的 Log In 按鈕,因此 targets 屬性的值為: form:loginButton

清單 16 內的 Log In 按鈕相關聯的動作偵聽器如清單 17 所示:


清單 17. Log In 按鈕的動作偵聽器
                    
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 內的動作偵聽器完全是為了展示的目的 — 當用戶登錄時,我只簡單地將一條消息寫出到 servlet 容器日志文件。但是我希望您能體會到這樣一個概念:有了 JSF 2,您可以實現完全可配置的組件,并且還可以向這些組件附加功能,所有這些均不需要任何 Java 代碼或 XML 配置。這才真正稱得上是功能強大。

place 組件:嵌套復合組件

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


圖 8. places 應用程序的 place 組件
place 組件

清單 18 給出了 places 應用程序是如何使用 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 組件使用了兩個嵌套組件: <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>

復合組件重構

清單 20 map 組件的標記 — 對我而言有些過長。它初看上去多少有些難于理解,而且其復雜性也很可能給今后帶來問題。

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

請注意 清單 20 中表達式 #{cc.parent.attrs.location. ATTRIBUTE_NAME } 的使用。您可以使用一個復合組件的 parent 屬性來訪問父組件的屬性,這一點極大地方便了組件的嵌套。

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

清單 21 顯示了這個 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 服務的天氣 HTML )和一個特定于組件的屬性(標題)。(參見 第 1 部分 來了解此應用程序是如何獲得某個地區的地圖和天氣信息的。)

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

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





回頁首


結束語

在本文中,我向您展示了如何使用 JSF 2 的模板和復合組件特性來實現易于維護和擴展的 UI。在本系列的最后一篇文章,我將探討如何在復合組件中使用 JavaScript、如何使用 JSF 2 的新事件模型以及如何利用 JSF 2 對 Ajax 的內置支持。






回頁首


下載

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


參考資料

學習

獲得產品和技術
  • JSF :下載 JSF 2.0。

討論


關于作者

David Geary

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

JSF 2 簡介,第 2 部分: 模板及復合組件


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

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

【本文對您有幫助就好】

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

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 国产永久在线 | 夜夜橹| 国产主播专区 | 狠狠干.com| 日日狠狠太爽爽 | 精品亚洲一区二区 | 九九视频在线观看视频6偷拍 | 中文字幕精品1在线 | 亚洲区欧美中文字幕久久 | 狠狠操女人 | 亚洲线精品久久一区二区三区 | 久久久久激情免费观看 | 图片区亚洲 | 大ji吧快给我别停受不了视频 | 成人a毛片在线看免费全部播放 | 中文字幕一区二区三区视频在线 | 色偷偷88888欧美精品久久久 | 欧美在线一级片 | 亚洲成人综合在线 | 四虎一影院区永久精品 | 久久精品国产乱子伦多人 | 亚洲综合色秘密影院秘密影院 | 国产亚洲欧美日韩综合综合二区 | 欧美一区二区三 | 国产精品12p | 欧美 日韩 国产 在线 | 五月婷婷六月综合 | 国产日韩一区二区三区 | 久久婷婷综合中文字幕 | 99热久久这里只有精品2010 | 在线观看自拍视频 | 99热精品6| 久久精品免视国产 | 私人影院免费 | 亚洲国产精品a在线 | 99在线精品国产不卡在线观看 | 欧美性xxxxxx爱| 亚洲综合五月天婷 | 国产资源在线播放 | 欧美亚洲国产一区二区三区 | 99精品热线在线观看免费视频 |