JSF 標籤大全(非常詳細 有例子)

1. JSF入門

藉由以下的幾個主題,可以大致瞭解JSF的輪廓與特性,我們來看看網頁設計人員與應用程序設計人員各負責什麼。

1.1簡介JSF

Web應用程序的開發與傳統的單機程序開發在本質上存在着太多的差異,Web應用程序開發人員至今不可避免的必須處理 HTTP的細節,而HTTP無狀態的 (stateless)本質,與傳統應用程序必須維持程序運行過程中的信息有明顯的違背,再則Web應用程序面對網站上不同的使用者同時的存取,其執行緒 安全問題以及資料驗證、轉換處理等問題,又是複雜且難以解決的。

另一方面,本質上是靜態的HTML與本質上是動態的應用程序又是一項違背,這造成不可避免的,處理網頁設計的美術人員與 程序設計人員,必須被彼此加入至視圖組件中的邏輯互相干擾,即便一些視圖呈現邏輯以卷標的方式呈現,試圖展現對網頁設計美術人員的親切,但它終究必須牽涉 到相關的流程邏輯。

有很多方案試着解決種種的困境,而各自的着眼點各不相同,有的從程序設計人員的角度來解決,有的從網頁設計人員的角度來 解決,各種的框架被提出,所造成的是各種不統一的標籤與框架,爲了促進產能的整合開發環境(IDE)難以整合這些標籤與框架,另一方面,開發人員的學習負 擔也不斷的加重,他們必須一人瞭解多個角色的工作。

Java Server Faces的提出在試圖解決這個問題,它試圖在不同的角度上提供網頁設計人員、應用程序設計人員、組件開發人員解決方案,讓不同技術的人員可以彼此合作又 不互相干擾,它綜合了各家廠商現有的技術特點,由Java Community Process(JCP)團隊研擬出來的一套標準,並在2004年三月發表了Java ServerFaces 1.0實作成果。

從網頁設計人員的角度來看,Java Server Faces提供了一套像是新版本的HTML標籤,但它不是靜態的,而是動態的,可以與後端的動態程序結合,但網頁設計人員不需要理會後端的動態部份,網頁 設計人員甚至不太需要接觸JSTL這類的卷標,也可以動態的展現數據(像是動態的查詢表格內容),Java Server Faces提供標準的標籤,這可以與網頁編輯程序結合在一起,另一方面,Java Server Faces也允許您自訂標籤。

從應用程序設計人員的角度來看,Java Server Faces提供一個與傳統應用程序開發相類似的模型(當然因某些本質上的差異,模型還是稍有不同),他們可以基於事件驅動來開發程序,不必關切HTTP的 處理細節,如果必須處理一些視覺組件的屬性的話,他們也可以直接在整合開發環境上拖拉這些組件,點選設定組件的屬性,Java Server Faces甚至還爲應用程序設計人員處理了對象與字符串(HTTP傳送本質上就是字符串)間不匹配的轉換問題。

從UI組件開發人員的角度來看,他們可以設計通用的UI組件,讓應用程序的開發產能提高,就如同在設計Swing組件等,UI開發人員可以獨立開發,只要定義好相關的屬性選項來調整細節,而不用受到網頁設計人員或應用程序設計人員的干擾。

三個角色的知識領域原則上可以互不干擾,根據您的角色,您只要瞭解其中一個知識領域,就可以運用Java Server Faces,其它角色的知識領域您可以不用瞭解太多細節。

當然,就其中一個角色單獨來看,Java Server Faces隱藏了許多細節,若要全盤瞭解,其實Java Server Faces是複雜的,每一個處理的環境都值得深入探討,所以學習Java ServerFaces時,您要選擇的是通盤瞭解,還是從使用的角度來了解,這就決定了您學習時所要花費的心力。

要使用JSF,首先您要先取得Java Server Faces參考實作(Java Server Faces Reference Implementation),在將來,JSF會與Container整合在一起,屆時您只要下載支持的Container,就可以使用JSF的功能。

請至 JSF 官方網站的下載區下 載參考實作,在下載壓縮檔並解壓縮之後,將其 lib 目錄下的 jar 檔案複製至您的Web應用程序的/WEB-INF/lib目錄下,另外您還需要 jstl.jar 與 standard.jar 檔案,這些檔案您可以在sample目錄下,解壓縮當中的一個範例,在它的/WEB-INF/lib目錄下找到,將之一併複製至您的Web應用程序的 /WEB-INF/lib目錄下,您總共需要以下的檔案:

* jsf-impl.jar

    *jsf-api.jar

    *commons-digester.jar

    *commons-collections.jar

    *commons-beanutils.jar

    *jstl.jar

* standard.jar

接下來配置Web應用程序的web.xml,使用JSF時,所有的請求都透過Faces Servlet來處理,您可以如下定義:

 

web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>

 

 <web-app xmlns="http://java.sun.com/xml/ns/j2ee"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee

    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"

    version="2.4">

 

    <description>

        JSF Demo

</description>

 

<display-name>JSF Demo</display-name>

 

    <servlet>

        <servlet-name>

                     Faces Servlet

              </servlet-name>

 

        <servlet-class>

            javax.faces.webapp.FacesServlet

        </servlet-class>

 

        <load-on-startup>1</load-on-startup>

    </servlet>

 

    <servlet-mapping>

        <servlet-name>Faces Servlet</servlet-name>

        <url-pattern>*.faces</url-pattern>

    </servlet-mapping>

 

    <welcome-file-list>

        <welcome-file>index.html</welcome-file>

</welcome-file-list>

 

 </web-app>

在上面的定義中,我們將所有.faces的請求交由FaceServlet來處理,FaceServlet會喚起相對的.jsp網頁,例如請求是/index.faces的話,則實際上會喚起/index.jsp網頁,完成以上的配置,您就可以開始使用JSF了。

1.2第一個JSF程序

現在可以開發一個簡單的程序了,我們將設計一個簡單的登入程序,使用者送出名稱,之後由程序顯示使用者名稱及歡迎訊息。

 

程序開發人員

先看看應用程序開發人員要作些什麼事,我們撰寫一個簡單的JavaBean:

UserBean.java

package onlyfun.caterpillar;

 

public class UserBean

{

 

    private String name;

 

public void setName(String name)

{

        this.name = name;

    }

 

public String getName()

{

        return name;

    }

}

這個Bean將儲存使用者的名稱,編譯好之後放置在/WEB-INF/classes下。接下來設計頁面流程,我們將先 顯示一個登入網頁/pages/index.jsp,使用者填入名稱並送出窗體,之後在/pages/welcome.jsp中顯示Bean中的使用者名 稱與歡迎訊息。爲了讓JSF知道我們所設計的Bean以及頁面流程,我們定義一個/WEB-INF/faces-config.xml:

faces-config.xml

<?xml version="1.0"?>

 <!DOCTYPE faces-config PUBLIC

 "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"

 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 

 <faces-config>

    <navigation-rule>

        <from-view-id>/pages/index.jsp</from-view-id>

        <navigation-case>

            <from-outcome>login</from-outcome>

            <to-view-id>/pages/welcome.jsp</to-view-id>

        </navigation-case>

    </navigation-rule>

 

    <managed-bean>

        <managed-bean-name>user</managed-bean-name>

         <managed-bean-class>

             onlyfun.caterpillar.UserBean

         </managed-bean-class>

        <managed-bean-scope>session</managed-bean-scope>

    </managed-bean>

 </faces-config>

在<navigation-rule>中,我們定義了頁面流程,當請求來自<from-view- id>中指定的頁面,並且指定了<navigation-case>中的<from-outcome>爲login時,則 會將請求導向至<to-view-id>所指定的頁面。在<managed-bean>中我們可以統一管理我們的Bean,我們 設定Bean對象的存活範圍是session,也就是使用者開啓瀏覽器與程序互動過程中都存活。接下來要告訴網頁設計人員的信息是,他們可以使用的 Bean名稱,即<managed-bean-name>中設定的名稱,以及上面所定義的頁面流程。

網頁設計人員

首先網頁設計人員撰寫index.jsp網頁:

index.jsp

<%@taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<%@taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@page contentType="text/html;charset=Big5"%>

<html>

 <head>

 <title>第一個JSF程序</title>

 </head>

<body>

    <f:view>

        <h:form>

            <h3>請輸入您的名稱</h3>

            名稱: <h:inputText value="#{user.name}"/><p>

            <h:commandButton value="送出" action="login"/>

        </h:form>

    </f:view>

 </body>

</html>

我們使用了JSF的core與html標籤庫,core是有關於UI組件的處理,而html則是有關於HTML的進階標籤。<f:view>與<html>有類似的作用,當您要開始使用JSF組件時,這些組件一定要在<f: view></f:view>之 間,就如同使用HTML時,所有的標籤一定要在<html>與< /html>之間。html卷標庫中幾乎都是與HTML卷標相關的進階卷標,<h:form>會產生一個窗體,我們使用<h: inputText>來顯示user這個Bean對象的name屬性,而<h:commandButton>會產生一個提交按鈕,我們 在action屬性中指定將根據之前定義的login頁面流程中前往welcome.jsp頁面。網頁設計人員不必理會窗體傳送之後要作些什麼,他只要設 計好歡迎頁面就好了:

welcome.jsp

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@page contentType="text/html;charset=Big5"%>

 <html>

 <head>

 <title>第一個JSF程序</title>

 </head>

 <body>

    <f:view>

        <h:outputText value="#{user.name}"/> 您好!

        <h3>歡迎使用 Java Server Faces</h3>

    </f:view>

 </body>

 </html>

這個頁面沒什麼需要解釋的了,如您所看到的,在網頁上沒有程序邏輯,網頁設計人員所作的就是遵照頁面流程,使用相關名稱 取出數據,而不用擔心實際上程序是如何運作的。接下來啓動Container,連接上您的應用程序網址,例 如:http://localhost:8080/jsfDemo/pages/index.faces,填入名稱並送出窗體,您的歡迎頁面就會顯示了。

1.3簡單的導航 Navigation

第一個JSF程序中,我們簡單的定義了頁面的流程由index.jsp 到 welcome.jsp,接下來我們擴充程序,讓它可以根據使用者輸入的名稱與密碼是否正確,決定要顯示歡迎訊息或是將使用者送回原頁面進行重新登入。首先我們修改一下UserBean:

 

UserBean.java

package onlyfun.caterpillar;

 

public class UserBean

{

 

    private String name;

    private String password;

    private String errMessage;

 

public void setName(String name)

{

        this.name = name;

    }

 

public String getName()

{

        return name;

    }

 

public void setPassword(String password)

{

        this.password = password;

    }

 

public String getPassword()

{

        return password;

    }

 

public void setErrMessage(String errMessage)

{

        this.errMessage = errMessage;

    }

 

public String getErrMessage()

{

        return errMessage;

    }

 

public String verify()

{

        if(!name.equals("justin") ||!password.equals("123456"))

              {

            errMessage = "名稱或密碼錯誤";

            return "failure";

        }

        else

              {

            return "success";

        }

    }

 }

在UserBean中,我們增加了密碼與錯誤訊息屬性,在verify()方法中,我們檢查使用者名稱與密碼,它傳回一 個字符串,"failure"表示登入錯誤,並會設定錯誤訊息,而"success"表示登入正確,這個傳回的字符串將決定頁面的流程。接下來我們修改一 下faces-config.xml 中的頁面流程定義:

faces-config.xml

<?xml version="1.0"?>

 <!DOCTYPE faces-config PUBLIC

 "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"

 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 

 <faces-config>

    <navigation-rule>

        <from-view-id>/pages/index.jsp</from-view-id>

        <navigation-case>

            <from-outcome>success</from-outcome>

            <to-view-id>/pages/welcome.jsp</to-view-id>

        </navigation-case>

        <navigation-case>

            <from-outcome>failure</from-outcome>

            <to-view-id>/pages/index.jsp</to-view-id>

        </navigation-case>

    </navigation-rule>

 

    <managed-bean>

        <managed-bean-name>user</managed-bean-name>

        <managed-bean-class>

            onlyfun.caterpillar.UserBean

        </managed-bean-class>

        <managed-bean-scope>session</managed-bean-scope>

    </managed-bean>

 </faces-config>

 

根據上面的定義,當傳回的字符串是"success"時,將前往 welcome.jsp,如果是"failure"的話,將送回 index.jsp。接下來告訴網頁設計人員Bean名稱與相關屬性,以及決定頁面流程的verify名稱,我們修改 index.jsp 如下:

index.jsp

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@page contentType="text/html;charset=Big5"%>

 <html>

 <head>

 <title>第一個JSF程序</title>

 </head>

 <body>

    <f:view>

        <h:form>

            <h3>請輸入您的名稱</h3>

            <h:outputText value="#{user.errMessage}"/><p>

           名稱: <h:inputText value="#{user.name}"/><p>

           密碼: <h:inputSecret value="#{user.password}"/><p>

            <h:commandButton value="送出"action="#{user.verify}"/>

        </h:form>

    </f:view>

 </body>

 </html>

當要根據verify運行結果來決定頁面流程時,action屬性中使用JSF Expression Language"#{user.verify}",如此JSF就知道必須根據verify傳回的結果來導航頁 面。<h:outputText>可以取出指定的Bean之屬性值,當使用者因驗證錯誤而被送回原頁面時,這個錯誤訊息就可以顯示在頁面上。

1.4導航規則設置

在JSF中是根據faces-config.xml中<navigation-rule>設定,以決定在符合的條件成立時,該連結至哪一個頁面,一個基本的設定如下:

<navigation-rule>

        <from-view-id>/pages/index.jsp</from-view-id>

        <navigation-case>

            <from-outcome>success</from-outcome>

            <to-view-id>/pages/welcome.jsp</to-view-id>

        </navigation-case>

        <navigation-case>

            <from-outcome>failure</from-outcome>

            <to-view-id>/pages/index.jsp</to-view-id>

        </navigation-case>

</navigation-rule>

對於JSF,每一個視圖(View)都有一個獨特的識別(identifier),稱之爲View ID,在JSF中的View ID是從Web應用程序的環境相對路徑開始計算,設定時都是以“/”作爲開頭,如果您請求時的路徑是/pages/index.faces,則JSF會將 擴展名改爲/pages/index.jsp,以此作爲view-id。在<navigation-rule>中的<from- view-id>是個選擇性的定義,它規定了來源頁面的條件,<navigation-case>中定義各種導覽條 件,<from-outcome>定義當窗體結果符合的條件時,各自改導向哪一個目的頁面,目的頁面是在<to-view- id>中定義。您還可以在<navigation-case>中加入<from-action>,進一步規範窗體結果必須 根據哪一個動作方法(action method),當中是使用 JSF Expression Language 來設定,例如:

<navigation-rule>

        <from-view-id>/pages/index.jsp</from-view-id>

        <navigation-case>

            <from-action>#{user.verify}</from-action>

            <from-outcome>success</from-outcome>

            <to-view-id>/pages/welcome.jsp</to-view-id>

        </navigation-case>

</navigation-rule>

在導航時,預設都是使用forward的方式,您可以在<navigation-case>中加入一個<redirect/>,讓JSF發出讓瀏覽器重新導向(redirect)的header,讓瀏覽器主動要求新網頁,例如:

<navigation-rule>

        <from-view-id>/pages/index.jsp</from-view-id>

        <navigation-case>

            <from-outcome>success</from-outcome>

            <to-view-id>/pages/welcome.jsp</to-view-id>

            <redirect/>

        </navigation-case>

</navigation-rule>

您的來源網頁可能是某個特定模塊,例如在/admin/下的頁面,您可以在<from-view-id>中使用wildcards(通配符),也就是使用“*”字符,例如:

<navigation-rule>

        <from-view-id>/admin/*</from-view-id>

        <navigation-case>

            <from-action>#{user.verify}</from-action>

            <from-outcome>success</from-outcome>

            <to-view-id>/pages/welcome.jsp</to-view-id>

       </navigation-case>

</navigation-rule>

在上面的設定中,只要來源網頁是從/admin來的,都可以開始測試接下來的<navigation-case>。

  <from-view-id>如果沒有設定,表示來源網頁不作限制,您也可以使用 * 顯式的在定義檔中表明,例如:

<navigation-rule>

        <from-view-id>/*</from-view-id>

</navigation-rule>

或者是這樣:

<navigation-rule>

        <from-view-id>*</from-view-id>

</navigation-rule>

1.5 JSF Expression Language

JSF Expression Language搭配JSF標籤來使用,是用來存取數據對象的一個簡易語言。JSF EL(ExpressionLanguage)是以“#”開始,將變量或表達式放置在Unknown macro:“{”與“}”之間,例如:

 

#{someBeanName}

變量名稱可以是faces-config.xml中定義的名稱,如果是Bean的話,可以透過使用“.” 運算子來存取它的屬性,例如:

<f:view>

    <h:outputText value="#{userBean.name}"/>

</f:view>

在JSF卷標的屬性上,“"”與“"”(或“'”與“'”)之間如果含有EL,則會加以運算,您也可以這麼使用它:

<f:view>

    名稱,年齡:<h:outputTextvalue="#{userBean.name}, #{userBean.age}"/>

</f:view>

一個執行的結果可能是這樣顯示的:名稱,年齡:Justin, 29

EL的變量名也可以程序執行過程中所宣告的名稱,或是JSF EL預設的隱含對象,例如下面的程序使用param隱含對象來取得使用者輸入的參數:

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@page contentType="text/html; charset=Big5"%>

 <html>

   <head>

     <title></title>

   </head>

   <body>

     <f:view>

        <b> 您好, <h:outputText value="#{param.name}"/> </b>

     </f:view>

   </body>

 </html>

param是JSF EL預設的隱含對象變量,它代表request所有參數的集合,實際是一個java.util.Map型態對象,JSF所提供的隱含對象,大致上對應於 JSP隱含物件,不過JSF隱含對象移除了pageScope與pageContext,而增加了facesContext與view,它們分別對應於 javax.faces.context.FacesContext與javax.faces.component.UIViewRoot。

對於Map型態對象,我們可以使用“.”運算子指定key值來取出對應的value,也可以使用“[”與“]”來指定,例如:

<f:view>

    <b> 您好, <h:outputText value="#{param['name']}"/> </b>

</f:view>

在“[”與“]”之間,也可以放置其它的變量值,例如:

<f:view>

    <h:outputText value="#{someBean.someMap[user.name]}"/>

 </f:view>

如果變量是List型態或數組的話,則可以在“[]”中指定索引,例如:

<f:view>

     <h:outputText value="#{someBean.someList[0]}"/>

     <h:outputText value="#{someBean.someArray[1]}"/>

     <h:outputTextvalue="#{someBean.someListOrArray[user.age]}"/>

</f:view>

您也可以指定字面常數,對於true、false、字符串、數字,JSF EL會嘗試進行轉換,例如:

<h:outputText value="#{true}"/>

<h:outputText value="#{'This is a test'}"/>

如果要輸出字符串,必須以單引號 ' 或雙自變量"括住,如此纔不會被認爲是變量名稱。在宣告變量名稱時,要留意不可與JSF的保留字或關鍵詞同名,例如不可取以下這些名稱:

true false null div mod and or not eq ne lt gtle ge instanceof empty

使用EL,您可以直接實行一些算術運算、邏輯運算與關係運算,其使用就如同在一般常見的程序語言中之運算。算術運算子有:加法 (+), 減法 (-), 乘法 (*), 除法 (/ or div) 與餘除 (% or mod) 。下面是算術運算的一些例子:

表達式

結果

#{1}

1

#{1 + 2}

3

#{1.2 + 2.3}

3.5

#{1.2E4 + 1.4}

12001.4

#{-4 - 2}

-6

#{21 * 2}

42

#{3/4}

0.75

#{3 div 4}

0.75,除法

#{3/0}

Infinity

#{10%4}

2

#{10 mod 4}

2,也是餘除

#{(1==2) ? 3 : 4}

4

如同在Java語法一樣 (expression ? result1 : result2)是個三元運算,expression爲true顯示result1,false顯示result2。邏輯運算 有:and(或&&)、or(或!!)、not(或!)。一些例子爲:

表達式

結果

#{true and false}

false

#{true or false}

true

#{not true}

false

關係運算有:小於Less-than (< or lt)、大於Greater-than (> or gt)、小於或等於Less-than-or-equal (<= or le)、大於或等於Greater-than-or-equal (>= or ge)、等於Equal (== or eq)、不等於Not Equal (!= or ne),由英文名稱可以得到lt、gt等運算子之縮寫詞,以下是Tomcat的一些例子:

表達式

結果

#{1 < 2}

true

#{1 lt 2}

true

#{1 > (4/2)}

false

#{1 > (4/2)}

false

#{4.0 >= 3}

true

#{4.0 ge 3}

true

#{4 <= 3}

false

#{4 le 3}

false

#{100.0 == 100}

true

#{100.0 eq 100}

true

#{(10*10) != 100}

false

#{(10*10) ne 100}

false

左邊是運算子的使用方式,右邊的是運算結果,關係運算也可以用來比較字符或字符串,按字典順序來決定比較結果,例如:

表達式

結果

#{'a' < 'b'}

true

#{'hip' > 'hit'}

false

#{'4' > 3}

true

EL運算子的執行優先級與Java運算子對應,如果有疑慮的話,也可以使用括號()來自行決定先後順序。

1.6國際化訊息

JSF的國際化(Internnationalization)訊息處理是基於Java對國際化的支持,您可以在一個訊息資源文件中統一管理訊息資源,資源文件的名稱是.properties,而內容是名稱與值的配對,例如:

messages.properties

titleText=JSF Demo

 hintText=Please input your name and password

 nameText=name

 passText=password

 commandText=Submit

資源文件名稱由basename加上語言與地區來組成,例如:

* basename.properties

    *basename_en.properties

    *basename_zh_cn.properties

沒有指定語言與地區的basename是預設的資源檔名稱,JSF會根據瀏覽器送來的Accept-Languageheader中的內容來決定該使用哪一個資源檔名稱,例如:

Accept-Language: zh_cn, en-US, en

如果瀏覽器送來這些header,則預設會使用繁體中文,接着是美式英文,再來是英文語系,如果找不到對應的訊息資源文件,則會使用預設的訊息資源文件。

由於訊息資源文件必須是ISO-8859-1編碼,所以對於非西方語系的處理,必須先將之轉換爲Java UnicodeEscape格式,例如您可以先在訊息資源文件中寫下以下的內容:

messages_zh_cn.txt

titleText=JSF示範

 hintText=請輸入名稱與密碼

 nameText=名稱

 passText=密碼

 commandText=送出

然後使用JDK的工具程序native2ascii來轉換,例如:
native2ascii -encoding Big5messages_zh_cn.txt messages_zh_cn.properties

轉換後的內容會如下:

messages_zh_cn.properties

titleText=JSF\u793a\u7bc4

hintText=\u8acb\u8f38\u5165\u540d\u7a31\u8207\u5bc6\u78bc

nameText=\u540d\u7a31

passText=\u5bc6\u78bc

commandText=\u9001\u51fa

接下來您可以使用<f:loadBundle>卷標來指定加載訊息資源,一個例子如下:

index.jsp

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@page contentType="text/html;charset=UTF8"%>

 

 <f:view>

 <f:loadBundle basename="messages" var="msgs"/>

 <html>

 <head>

 <title><h:outputText value="#{msgs.titleText}"/></title>

 </head>

 <body>

    <h:form>

        <h3><h:outputText value="#{msgs.hintText}"/></h3>

        <h:outputText value="#{msgs.nameText}"/>:

                <h:inputText value="#{user.name}"/><p>

        <h:outputText value="#{msgs.passText}"/>:

                <h:inputSecret value="#{user.password}"/><p>

        <h:commandButton value="#{msgs.commandText}"actionListener="#{user.verify}"

                        action="#{user.outcome}"/>

   </h:form>

 </body>

 </html>

 </f:view>

如此一來,如果您的瀏覽器預設接受zh_cn語系的話,則頁面上就可以顯示中文,否則預設將以英文顯示,也就是 messages.properties的內容,爲了能顯示多國語系,我們設定網頁編碼爲UTF-8。<f:view>可以設定locale 屬性,直接指定所要使用的語系,例如:

<f:view locale="zh_cn">

<f:loadBundle basename="messages" var="msgs"/>

直接指定以上的話,則會使用繁體中文來顯示,JSF會根據<f:loadBundle>的basename 屬性加上<f:view>的locale屬性來決定要使用哪一個訊息資源文件,就上例而言,就是使用 messages_zh_cn.properties,如果設定爲以下的話,就會使用messages_en.properties:

<f:view locale="en">

<f:loadBundle basename="messages" var="msgs"/>

您也可以在faces-config.xml中設定語系,例如:

<faces-config>

    <application>

        <local-config>

            <default-locale>en</default-locale>

            <supported-locale>zh_cn</supported-locale>

        </local-config>

    </application>

</faces-config>

在<local-config>一定有一個<default-locale>, 而<supported-locale>可以有好幾個,這告訴JSF您的應用程序支持哪些語系。當然,如果您可以提供一個選項讓使用者選擇自 己的語系會是更好的方式,例如根據user這個Bean的locale屬性來決定頁面語系:

<f:view locale="#{user.locale}">

<f:loadBundle basename="messages" var="msgs"/>

在頁面中設定一個窗體,可以讓使用者選擇語系,例如設定單選按鈕:

<h:selectOneRadio value="#{user.locale}">

     <f:selectItem itemValue="zh_cn"itemLabel="#{msgs.zh_cnText}"/>

     <f:selectItem itemValue="en"itemLabel="#{msgs.enText}"/>

</h:selectOneRadio>

2.  Managed Beans

JSF使用Bean來達到邏輯層與表現層分離的目的,Bean的管理集中在組態檔案中,您只要修改組態檔案,就可以修改Bean之間的相依關係。

2.1 Backing Beans

JSF使用JavaBean來達到程序邏輯與視圖分離的目的,在JSF中的Bean其角色是屬於Backing Bean,又稱之爲Glue Bean,其作用是在真正的業務邏輯Bean及UI組件之間搭起橋樑,在Backing Bean中會呼叫業務邏輯Bean處理使用者的請求,或者是將業務處理結果放置其中,等待UI組件取出當中的值並顯示結果給使用者。JSF將Bean的管 理集中在faces-config.xml中,一個例子如下:

 <managed-bean>
    <managed-bean-name>user</managed-bean-name>
        <managed-bean-class>
            onlyfun.caterpillar.UserBean
        </managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
 </managed-bean>

 

這個例子我們在第一個JSF程序看過,<managed-bean-class>設定所要使用的Bean類別,<managed-bean-name>設定之名稱,可供我們在JSF頁面上使用Expression Language來取得或設定Bean的屬性,例如:

<h:inputText value="#{user.name}"/>

  

<managed-bean-scope>設定Bean的存活範圍,您可以設定爲request、session與 application,設定爲request時,Bean的存活時間爲請求階最,設定爲session則在使用者應用程序交互開始,直到關閉瀏覽器或顯 式的結束會話爲止(例如註銷程序),設定爲application的話,則Bean會一直存活,直到應用程序關閉爲止。

  您還可以將存活範圍設定爲none,當設定爲none時會在需要的時候生成一個新的Bean,例如您在一個method中想要生成一個臨時的Bean,就可以將之設定爲none。

  在JSF頁面上要取得Bean的屬性,是使用JSF表示語言 (Expression Language),要注意到的是,JSF表示語言是寫成 #{expression},而 JSP表示語言是寫成 ${expression},因爲表示層可能是使用JSP,所以必須特別區分,另外要注意的是,JSF的卷標上之屬性設定時,只接受JSF表示語言。

2.2Beans 的組態與設定

JSF預設會讀取faces-config.xml中關於Bean的定義,如果想要自行設置定義檔的名稱,我們是在web.xml中提供javax.faces.CONFIG_FILES參數,例如:

<web-app>
   <context-param>
      <param-name>javax.faces.CONFIG_FILES</param-name>
      <param-value>/WEB-INF/beans.xml</param-value>
   </context-param>   

</web-app>

 

定義檔可以有多個,中間以“,”區隔,例如:

/WEB-INF/navigation.xml,/WEB-INF/beans.xml

 

一個Bean最基本要定義Bean的名稱、類別與存活範圍,例如:

<managed-bean>
    <managed-bean-name>user</managed-bean-name>
        <managed-bean-class>
            onlyfun.caterpillar.UserBean
        </managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
< /managed-bean>

 

 如果要在其它類別中取得Bean對象,則可以先取得javax.faces.context.FacesContext,它代表了JSF目前的執行環境對象,接着嘗試取得javax.faces.el.ValueBinding對象,從中取得指定的Bean對象,例如:

FacesContext context = FacesContext.getCurrentInstance();
ValueBinding binding = context.getApplication().createValueBinding("#{user}");
UserBean user = (UserBean) binding.getValue(context);

 

如果只是要嘗試取得Bean的某個屬性,則可以如下:

FacesContext context = FacesContext.getCurrentInstance();
ValueBinding binding = context.getApplication().createValueBinding("#{user.name}");
String name = (String) binding.getValue(context);

 

如果有必要在啓始Bean時,自動設置屬性的初始值,則可以如下設定:

<managed-bean>
    <managed-bean-name>user</managed-bean-name>
        <managed-bean-class>
            onlyfun.caterpillar.UserBean
        </managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
    <managed-property>
        <property-name>name</property-name>
        <value>caterpillar</value>
    </managed-property>
    <managed-property>
        <property-name>password</property-name>
        <value>123456</value>
    </managed-property>
< /managed-bean>

  

如果要設定屬性爲null值,則可以使用<null-value/>標籤,例如:

    <managed-property>
        <property-name>name</property-name>
        <null-value/>
    </managed-property>
    <managed-property>
        <property-name>password</property-name>
        <null-value/>
    </managed-property>

  

當然,您的屬性不一定是字符串值,也許會是int、float、boolean等等型態,您可以設定<value> 值時指定這些值的字符串名稱,JSF會嘗試進行轉換,例如設定爲true時,會嘗試使用Boolean.valueOf()方法轉換爲boolean的 true,以下是一些可能進行的轉換:

型態

轉換

shortintlongfloatdoublebyte,或相應的Wrapper類別

嘗試使用WrappervalueOf()進行轉換,如果沒有設置,則設爲0

boolean Boolean

嘗試使用Boolean.valueOf()進行轉換,如果沒有設置,則設爲false

char Character

取設置的第一個字符,如果沒有設置,則設爲0

String Object

即設定的字符串值,如果沒有設定,則爲空字符串new String("")

  

您也可以將其它產生的Bean設定給另一個Bean的屬性,例如:

 <managed-bean>
    <managed-bean-name>user</managed-bean-name>
        <managed-bean-class>
            onlyfun.caterpillar.UserBean
        </managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
 </managed-bean>

 

 <managed-bean>
    <managed-bean-name>other</managed-bean-name>
        <managed-bean-class>
            onlyfun.caterpillar.OtherBean
        </managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
    <managed-property>
      <property-name>user</property-name>
      <value>#{user}</value>
    </managed-property>
 </managed-bean>

  

在上面的設定中,在OtherBean中的user屬性,接受一個UserBean型態的對象,我們設定爲前一個名稱爲user的UserBean對象。

2.3 Beans上的 List和Map

如果您的Bean上有接受List或Map型態的屬性,則您也可以在組態檔案中直接設定這些屬性的值,一個例子如下:

<managed-bean>
    <managed-bean-name>someBean</managed-bean-name>
      <managed-bean-class>
          onlyfun.caterpillar.SomeBean
      </managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>

 

    <managed-property>
        <property-name>someProperty</property-name>
        <list-entries>
            <value-class>java.lang.Integer</value-class>
            <value>1</value>
            <value>2</value>
            <value>3</value>
        </list-entries>
    </managed-property>
< /managed-bean>

 

 這是一個設定接受List型態的屬性,我們使用<list-entries>卷標指定將設定一個List對象,其 中<value-class>指定將存入List的型態,而<value>指定其值,如果是基本型態,則會嘗試使用指定 的<value-class>來作Wrapper類別。

 

設定Map的話,則是使用<map-entries>標籤,例如:

 <managed-bean>
    <managed-bean-name>someBean</managed-bean-name>
    <managed-bean-class>
       onlyfun.caterpillar.SomeBean
    </managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>

 

    <managed-property>
        <property-name>someProperty</property-name>
        <map-entries>
            <value-class>java.lang.Integer</value-class>
            <map-entry>
                <key>someKey1</key>
                <value>100</value>
            </map-entry>
            <map-entry>
                <key>someKey2</key>
                <value>200</value>
            </map-entry>
        </map-entries>
    </managed-property>
 </managed-bean>

  

由於Map對象是以key-value對的方式來存入,所以我們在每一個<map-entry>中使用<key> 與<value>標籤來分別指定。您也可以直接像設定Bean一樣,設定一個List或Map對象,例如在JSF附的範例中,有這樣的設定:

   <managed-bean>
        <description>
            Special expense item types
        </description>
      <managed-bean-name>specialTypes</managed-bean-name>
      <managed-bean-class>
          java.util.TreeMap
      </managed-bean-class>
      <managed-bean-scope>application</managed-bean-scope>
      <map-entries>
         <value-class>java.lang.Integer</value-class>
         <map-entry>
            <key>Presentation Material</key>
            <value>100</value>
         </map-entry>
         <map-entry>
            <key>Software</key>
            <value>101</value>
         </map-entry>
         <map-entry>
            <key>Balloons</key>
            <value>102</value>
         </map-entry>
        </map-entries>
  </managed-bean>

  

而範例中另一個設定List的例子如下:

  <managed-bean>
    <managed-bean-name>statusStrings</managed-bean-name>
    <managed-bean-class>
        java.util.ArrayList
    </managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    <list-entries>
      <null-value/>
      <value>Open</value>
      <value>Submitted</value>
      <value>Accepted</value>
      <value>Rejected</value>
    </list-entries>
  </managed-bean>

3. 數據轉換與驗證

轉換器(Converter)協助模型與視圖之間的數據轉換,驗證器(Validator)協助進行語意檢驗(Semantic Validation)。

3.1標準轉換器

Web應用程序與瀏覽器之間是使用HTTP進行溝通,所有傳送的數據基本上都是字符串文字,而Java應用程序本身基本上則是對象,所以對象數據必須經由轉換傳送給瀏覽器,而瀏覽器送來的數據也必須轉換爲對象才能使用。

  JSF定義了一系列標準的轉換器(Converter),對於基本數據型態(primitive type)或是其Wrapper類別,JSF會使用javax.faces.Boolean、javax.faces.Byte、 javax.faces.Character、javax.faces.Double、javax.faces.Float、 javax.faces.Integer、javax.faces.Long、javax.faces.Short等自動進行轉換,對於 BigDecimal、BigInteger,則會使用javax.faces.BigDecimal、javax.faces.BigInteger自 動進行轉換。

  至於DateTime、Number,我們可以使 用<f:convertDateTime>、<f:convertNumber>標籤進行轉換,它們各自提供有一些簡單的屬性, 可以讓我們在轉換時指定一些轉換的格式細節。來看個簡單的例子,首先我們定義一個簡單的Bean:

UserBean.java

package onlyfun.caterpillar;

 

 import java.util.Date;

 public class UserBean

{
    private Date date = new Date();
   
    public Date getDate()

{
        return date;
    }

 

public void setDate(Date date)

{
        this.date = date;
    }
 }

  

這個Bean的屬性接受Date型態的參數,按理來說,接收到HTTP傳來的數據中若有相關的日期信息,我們必須剖析這個信息,再轉換爲Date對象,然而我們可以使用JSF的標準轉換器來協助這項工作,例如:

index.jsp

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
 <%@page contentType="text/html;charset=Big5"%>

 <f:view>

 <html>
 <head>
 <title>
轉換器示範</title>
 </head>
 <body>

 設定的日期是:
           <b>
             <h:outputText value="#{user.date}">
                 <f:convertDateTime pattern="dd/MM/yyyy"/>
             </h:outputText>
           </b>

    <h:form>
        <h:inputText id="dateField" value="#{user.date}">
            <f:convertDateTime pattern = "dd/MM/yyyy"/>
        </h:inputText>
        <h:message for="dateField" style="color:red"/>
        <br>
        <h:commandButton value="
送出" action="show"/>
    </h:form>
 </body>
 </html>
< /f:view>

  

在<f:convertDateTime>中,我們使用pattern指定日期的樣式爲dd/MM/yyyy,即「日/月/公元」格 式,如果轉換錯誤,則<h:message>可以顯示錯誤訊息,for屬性參考至<h:inputText> 的id屬性,表示將有關dateField的錯誤訊息顯示出來。假設faces-config.xml是這樣定義的:

faces-config.xml

<?xml version="1.0"?>
 <!DOCTYPE faces-config PUBLIC
 "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 

 <faces-config>
    <navigation-rule>
        <from-view-id>/*</from-view-id>
        <navigation-case>
            <from-outcome>show</from-outcome>
            <to-view-id>/pages/index.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>
      
    <managed-bean>
        <managed-bean-name>user</managed-bean-name>
        <managed-bean-class>
            onlyfun.caterpillar.UserBean
        </managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
    </managed-bean>
 </faces-config>

  

首次連上頁面時顯示的畫面如下:

 

  

如您所看到的,轉換器自動依pattern設定的樣式將Date對象格式化了,當您依格式輸入數據並送出後,轉換器也會自動將您輸入的數據轉換爲Date對象,如果轉換時發生錯誤,則會出現以下的訊息:

 

   <f:convertDateTime>卷標還有幾個可用的屬性,您可以參考Tag LibraryDocumentation 的說明,而依照類似的方式,您也可以使用<f:convertNumber>來轉換數值。

3.2自訂轉換器

除了使用標準的轉換器之外,您還可以自行定製您的轉換器,您可以實作javax.faces.convert.Converter接口,這個接口有兩個要實作的方法:

public Object getAsObject(FacesContext context, UIComponent component, String str);
public String getAsString(FacesContext context, UIComponent component, Object obj);

  

簡單的說,第一個方法會接收從客戶端經由HTTP傳來的字符串數據,您在第一個方法中將之轉換爲您的自訂對象,這個自訂對象將會自動設定給您指定的 Bean對象;第二個方法就是將從您的Bean對象得到的對象轉換爲字符串,如此才能藉由HTTP傳回給客戶端。直接以一個簡單的例子來作說明,假設您有 一個User類別:

User.java

package onlyfun.caterpillar;

 

 public class User {
    private String firstName;
    private String lastName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
 }

  

這個User類別是我們轉換器的目標對象,而您有一個GuestBean類別:

GuestBean.java

package onlyfun.caterpillar;

 

 public class GuestBean {
    private User user;
   
    public void setUser(User user) {
        this.user = user;
    }
   
    public User getUser() {
        return user;
    }
 }

  

這個Bean上的屬性直接傳回或接受User型態的參數,我們來實作一個簡單的轉換器,爲HTTP字符串與User對象進行轉換:

UserConverter.java

package onlyfun.caterpillar;

 

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;

public class UserConverter implements Converter {

public Object getAsObject(FacesContext context, UIComponent component, String str) throws ConverterException {


            String[] strs = str.split(",");
            User user = new User();
       
            try {
                user.setFirstName(strs[0]);
                user.setLastName(strs[1]);
            }
            catch(Exception e) {
                //
轉換錯誤,簡單的丟出例外
                throw new ConverterException();
            }
       
            return user;
    }

 

    public String getAsString(FacesContext context, UIComponent component,Object obj)
                                 throws ConverterException {
            String firstName = ((User) obj).getFirstName();
            String lastName = ((User) obj).getLastName();
       
            return firstName + "," + lastName;
    }
 }

  

實作完成這個轉換器,我們要告訴JSF這件事,這是在faces-config.xml中完成註冊:

faces-config.xml

<?xml version="1.0"?>
 <!DOCTYPE faces-config PUBLIC
 "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 

 <faces-config>
    <navigation-rule>
        <from-view-id>/*</from-view-id>
        <navigation-case>
            <from-outcome>show</from-outcome>
            <to-view-id>/pages/index.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>
      
    <converter>
        <converter-id>onlyfun.caterpillar.User</converter-id>
        <converter-class>
            onlyfun.caterpillar.UserConverter
        </converter-class>
    </converter>

    <managed-bean>
        <managed-bean-name>guest</managed-bean-name>
        <managed-bean-class>
            onlyfun.caterpillar.GuestBean
        </managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
    </managed-bean>
 </faces-config>

  

註冊轉換器時,需提供轉換器識別(Converter ID)與轉換器類別,接下來要在JSF頁面中使用轉換器的話,就是指定所要使用的轉換器識別,例如:

index.jsp

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
< %@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
< %@page contentType="text/html;charset=Big5"%>

 

 <f:view>

 <html>
 <head>
 <title>
自訂轉換器</title>
 </head>
 <body>

 Guest名稱是:<b>
           <h:outputText value="#{guest.user}" converter="onlyfun.caterpillar.User"/>
           </b>

    <h:form>
        <h:inputText id="userField" value="#{guest.user}" converter="onlyfun.caterpillar.User"/>
        <h:message for="userField" style="color:red"/>
        <br>
        <h:commandButton value="
送出" action="show"/>
    </h:form>
 </body>
 </html>
 </f:view>

 

您也可以<f:converter>卷標並使用converterId屬性來指定轉換器,例如:

<h:inputText id="userField" value="#{guest.user}">
     <f:converter converterId="onlyfun.caterpillar.User"/>
< /h:inputText>

  

除了向JSF註冊轉換器之外,還有一個方式可以不用註冊,就是直接在Bean上提供一個取得轉換器的方法,例如:

GuestBean.java

package onlyfun.caterpillar;

import javax.faces.convert.Converter;

 public class GuestBean {


    private User user;
    private Converter converter = new UserConverter();
   
    public void setUser(User user) {
        this.user = user;
    }
   
    public User getUser() {
        return user;
    }
   
    public Converter getConverter() {
        return converter;
    }
 }

  

之後可以直接結合JSFExpression Language 來指定轉換器:

<h:inputText id="userField" value="#{guest.user}" converter="#{guest.converter}"/>

3.3標準驗證器

當應用程序要求使用者輸入數據時,必然考慮到使用者輸入數據之正確性,對於使用者的輸入必須進行檢驗,檢驗必要的兩種驗證是語法檢驗(Synatic Validation)與語意檢驗(Semantic Validation)。

  語法檢驗是要檢查使用者輸入的數據是否合乎我們所要求的格式,最基本的就是檢查使用者是否填入了字段值,或是字段值的長度、大小值等等是否符合 要求。語意檢驗是在語法檢驗之後,在格式符合需求之後,我們進一步驗證使用者輸入的數據語意上是否正確,例如檢查使用者的名稱與密碼是否匹配。

在簡單的導航 (Navigation) 中,我們對使用者名稱與密碼檢查是否匹配,這是語意檢驗,我們可以使用JSF所提供的標準驗證器,爲其加入語法檢驗,例如:

index.jsp

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
< %@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
< %@page contentType="text/html;charset=Big5"%>
 <html>
 <head>
 <title>
驗證器示範</title>
 </head>
 <body>
    <f:view>
        <h:messages layout="table" style="color:red"/>
        <h:form>
            <h3>
請輸入您的名稱</h3>
            <h:outputText value="#{user.errMessage}"/><p>
          
名稱: <h:inputText value="#{user.name}" required="true"/><p>
          
密碼: <h:inputSecret value="#{user.password}" required="true">
             <f:validateLength minimum="6"/>
         </h:inputSecret><p>
            <h:commandButton value="
送出" action="#{user.verify}"/>
        </h:form>
    </f:view>
 </body>
 </html>

  

在<h:inputText>、</h:inputSecret>中,我們設定了required屬性爲true,這表示這個字段一定要輸入值,我們也在</h:inputSecret>設定了<f: validateLength>,並設定其minimum屬性爲6,這表示這個字段最少需要6個字符。

  這一次在錯誤訊息的顯示上,我們使用<h:messages>標籤,當有驗證錯誤發生時,相關的錯誤訊息會收集起來,使用<h:messages>卷標可以一次將所有的錯誤訊息顯示出來。下面是一個驗證錯誤的訊息顯示:

 

  

JSF提供了三種標準驗證 器:<f:validateDoubleRange>、<f:validateLongRange>、<f:validateLength>, 您可以分別查詢它們的 Tag LibraryDocumentation,瞭解他們有哪些屬性可以使用,或者是參考Using the Standard Validators 這篇文章中有關於標準驗證器的說明。

3.4自訂驗證器

您可以自訂自己的驗證器,所需要的是實作javax.faces.validator.Validator接口,例如我們實作一個簡單的密碼驗證器,檢查字符長度,以及密碼中是否包括字符與數字:

PasswordValidator.java

package onlyfun.caterpillar;

 

 import javax.faces.application.FacesMessage;
 import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 import javax.faces.validator.Validator;
 import javax.faces.validator.ValidatorException;

 

 public class PasswordValidator implements Validator {


    public void validate(FacesContext context,UIComponent component,Object obj)
            throws ValidatorException {


        String password = (String) obj;

       
        if(password.length() < 6) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
                 "
字符長度小於6", "字符長度不得小於6");
            throw new ValidatorException(message);
        }
       
        if(!password.matches(".+[0-9]+")) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
                   "
密碼必須包括字符與數字", "密碼必須是字符加數字所組成");
            throw new ValidatorException(message);
        }
    }
 }

  

您要實作javax.faces.validator.Validator接口中的validate()方法,如果驗證錯誤,則丟出一個 ValidatorException,它接受一個FacesMessage對象,這個對象接受三個參數,分別表示訊息的嚴重程度(INFO、WARN、 ERROR、FATAL)、訊息概述與詳細訊息內容,這些訊息將可以使用<h:messages>或<h: message>卷標顯示在頁面上。接下來要在faces-config.xml中註冊驗證器的識別(Validater ID),要加入以下的內容:

faces-config.xml

<?xml version="1.0"?>
 <!DOCTYPE faces-config PUBLIC
 "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 

 <faces-config>
    <validator>
        <validator-id>
            onlyfun.caterpillar.Password
        </validator-id>
        <validator-class>
            onlyfun.caterpillar.PasswordValidator
        </validator-class>
    </validator>
 </faces-config>

  

要使用自訂的驗證器,我們可以使用<f:validator>卷標並設定validatorId屬性,例如:

 <h:inputSecret value="#{user.password}" required="true">
    <f:validator validatorId="onlyfun.caterpillar.Password"/>
 </h:inputSecret><p>

  

您也可以讓Bean自行負責驗證的工作,可以在Bean上提供一個驗證方法,這個方法沒有傳回值,並可以接收FacesContext、UIComponent、Object三個參數,例如:

UserBean.java

package onlyfun.caterpillar;

 import javax.faces.application.FacesMessage;
 import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 import javax.faces.validator.ValidatorException;

 public class UserBean {
     public void validate(FacesContext context,
                         UIComponent component,
                         Object obj)
            throws ValidatorException {
        String password = (String) obj;
       
        if(password.length() < 6) {
            FacesMessage message = new FacesMessage(
                 FacesMessage.SEVERITY_ERROR,
                 "
字符長度小於6",
                 "
字符長度不得小於6");
            throw new ValidatorException(message);
        }
        if(!password.matches(".+[0-9]+")) {
            FacesMessage message = new FacesMessage(
                   FacesMessage.SEVERITY_ERROR,
                   "
密碼必須包括字符與數字",
                   "
密碼必須是字符加數字所組成");
            throw new ValidatorException(message);
        }
    }
 }

  

接着可以在頁面下如下使用驗證器:

 <h:inputSecret value="#{user.password}"  required="true"  validator="#{user.validate}"/>

3.5錯誤訊息處理

在使用標準轉換器或驗證器時,當發生錯誤時,會有一些預設的錯誤訊息顯示,這些訊息可以使用<h:messages>或<h:message>卷標來顯示出來,而這些預設的錯誤訊息也是可以修改的,您所要作的是提供一個訊息資源文件,例如:
messages.properties

javax.faces.component.UIInput.CONVERSION=Format Error.
javax.faces.component.UIInput.REQUIRED=Please input your data.

  

javax.faces.component.UIInput.CONVERSION是用來設定當轉換器發現錯誤時顯示的訊息,而 javax.faces.component.UIInput.REQUIRED是在標籤設定了required爲true,而使用者沒有在字段輸入時顯 示的錯誤訊息。您要在faces-config.xml中告訴JSF您使用的訊息文件名稱,例如:
faces-config.xml

<?xml version="1.0"?>
< !DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
< faces-config>
  <application>
    <local-config>
      <default-locale>en</default-locale>
      <supported-locale>zh_TW</supported-locale>
    </local-config>
    <message-bundle>messages</message-bundle>
  </application>
< /faces-config>

  

在這邊我們設定了訊息檔案的名稱爲messages_xx_YY.properties,其中xx_YY是根據您的Locale來決定,轉換器或驗證器的錯誤訊息如果有設定的話,就使用設定值,如果沒有設定的話,就使用默認值。

驗證器錯誤訊息,除了上面的javax.faces.component.UIInput.REQUIRED之外,還有以下的幾個:

訊息識別

預設訊息

用於

javax.faces.validator.NOT_IN_RANGE

Validation Error: Specified attribute is not between the expected values of {0} and {1}.

DoubleRangeValidator LongRangeValidator{0}{1}分別代表minimummaximum所設定的屬性

javax.faces.validator.DoubleRangeValidator.MAXIMUMjavax.faces.validator.LongRangeValidator.MAXIMUM

Validation Error: Value is greater than allowable maximum of '{0}'.

DoubleRangeValidatorLongRangeValidator{0}表示maximum屬性

javax.faces.validator.DoubleRangeValidator.MINIMUMjavax.faces.validator.LongRangeValidator.MINIMUM

Validation Error: Value is less than allowable minimum of '{0}'.

DoubleRangeValidatorLongRangeValidator{0}代表minimum屬性

javax.faces.validator.DoubleRangeValidator.TYPEjavax.faces.validator.LongRangeValidator.TYPE

Validation Error: Value is not of the correct type.

DoubleRangeValidatorLongRangeValidator

javax.faces.validator.LengthValidator.MAXIMUM

Validation Error: Value is greater than allowable maximum of ''{0}''.

LengthValidator{0}代表maximum

javax.faces.validator.LengthValidator.MINIMUM

Validation Error: Value is less than allowable minimum of ''{0}''.

LengthValidator{0}代表minimum屬性

  

在您提供自訂訊息的時候,也可以提供{0}或{1}來設定顯示相對的屬性值,以提供詳細正確的錯誤提示訊息。訊息的顯示有概述訊息與詳述訊息,如果是詳述訊息,則在識別上加上 "_detail",例如:

javax.faces.component.UIInput.CONVERSION=Error.
javax.faces.component.UIInput.CONVERSION_detail= Detail Error.

  

除了在訊息資源文件中提供訊息,您也可以在程序中使用FacesMessage來提供訊息,例如在 自訂驗證器 中我們就這麼用過:

if(password.length() < 6)

{
FacesMessage message = new FacesMessage(
FacesMessage.SEVERITY_ERROR,
"
字符長度小於6",
"
字符長度不得小於6");
throw new ValidatorException(message);

}

  

最好的方法是在訊息資源文件中提供訊息,這麼一來如果我們要修改訊息,就只要修改訊息資源文件的內容,而不用修改程序,來看一個簡單的例子,假設我們的訊息資源文件中有以下的內容:

onlyfun.caterpillar.message1=This is message1.
onlyfun.caterpillar.message2=This is message2 with \{0} and \{1}.

  

則我們可以在程序中取得訊息資源文件的內容,例如:

package onlyfun.caterpillar;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.faces.context.FacesContext;
improt javax.faces.component.UIComponent;
import javax.faces.application.Application;
import javax.faces.application.FacesMessage;
public void xxxMethod(FacesContext context,
UIComponent component,
Object obj) {
//
取得應用程序代表對象
Application application = context.getApplication();
//
取得訊息檔案主名稱
String messageFileName =
application.getMessageBundle();
//
取得當前 Locale對象
Locale locale = context.getViewRoot().getLocale();
//
取得訊息綁定 ResourceBundle對象
ResourceBundle rsBundle =
ResourceBundle.getBundle(messageFileName, locale);
String message = rsBundle.getString(
"onlyfun.caterpillar.message1");
FacesMessage facesMessage = new FacesMessage(
FacesMessage.SEVERITY_FATAL, message, message);
}

  

接下來您可以將FacesMessage對象填入ValidatorException或ConverterException後再丟 出,FacesMessage建構時所使用的三個參數是嚴重程度、概述訊息與詳述訊息,嚴重程度有SEVERITY_FATAL、 SEVERITY_ERROR、SEVERITY_WARN與SEVERITY_INFO四種。 如果需要在訊息資源文件中設定{0}、{1}等參數,則可以如下:

String message = rsBundle.getString( "onlyfun.caterpillar.message2");
Object[] params = {"param1", "param2"};
message = java.text.MessageFormat.format(message, params);
FacesMessage facesMessage = new FacesMessage(
FacesMessage.SEVERITY_FATAL, message, message);

  

如此一來,在顯示訊息時,onlyfun.caterpillar.message2的{0}與{1}的位置就會被"param1"與"param2"所取代。

3.6自訂轉換和驗證標籤

在自訂驗證器中,我們的驗證器只能驗證一種pattern(+[0-9]+),我們希望可以在JSF頁面上自訂匹配的pattern,然而由於我們 使用<f: validator>這個通用的驗證器標籤,爲了要能提供pattern屬性,我們可以使用<f:attribute>標籤來設置,例 如:

<h:inputSecret value="#{user.password}" required="true">
< f:validator validatorId="onlyfun.caterpillar.Password"/>
< f:attribute name="pattern" value=".+[0-9]+"/>
< /h:inputSecret><p>

  

使用<f:attribute>卷標來設定屬性,接着我們可以如下取得所設定的屬性:

public void validate(FacesContext context,
UIComponent component,
Object obj)
throws ValidatorException {  
String pattern = (String)
component.getAttributes().get("pattern");
}

  

您也可以開發自己的一組驗證卷標,並提供相關屬性設定,這需要了解JSPTag Library的撰寫,所以請您先參考JSP/Servlet 中有關於JSP TagLibrary的介紹。

要開發驗證器轉用標籤,您可以直接繼承javax.faces.webapp.ValidatorTag,這個類別可以 幫您處理大部份的細節,您所需要的,就是重新定義它的createValidator()方法,我們以改寫 自訂驗證器 中的PasswordValidator爲例:

PasswordValidator.java
PasswordValidator.java
package onlyfun.caterpillar;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

public class PasswordValidator implements Validator {
private String pattern;

    public void setPattern(String pattern)

 {
        this.pattern = pattern;
    }

    public void validate(FacesContext context, UIComponent component, Object obj)

throws ValidatorException

{
String password = (String) obj;

if(password.length() < 6)

 {
FacesMessage message = new FacesMessage(
FacesMessage.SEVERITY_ERROR,
"
字符長度小於6", "字符長度不得小於6");
throw new ValidatorException(message);

}

if(pattern != null && !password.matches(pattern))

{
FacesMessage message = new FacesMessage(
FacesMessage.SEVERITY_ERROR,
"
密碼必須包括字符與數字",
"
密碼必須是字符加數字所組成");
throw new ValidatorException(message);

}

}

}

主要的差別是我們提供了pattern屬性,在validate()方法中進行驗證時,是根據我們所設定的pattern屬性,接着我們繼承javax.faces.webapp.ValidatorTag來撰寫自己的驗證標籤:

PasswordValidatorTag.java

package onlyfun.caterpillar;

import javax.faces.application.Application;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.webapp.ValidatorTag;

public class PasswordValidatorTag extends ValidatorTag

{
    private String pattern;

    public void setPattern(String pattern)

{
this.pattern = pattern;

}

 

protected Validator createValidator()

 {
Application application =
FacesContext.getCurrentInstance().
getApplication();
PasswordValidator validator =
(PasswordValidator) application.createValidator(
"onlyfun.caterpillar.Password");
validator.setPattern(pattern);
return validator;

}

}

  

application.createValidator()方法建立驗證器對象時,是根據在faces-config.xml中註冊驗證器的識別(Validater ID):
faces-config.xml

<?xml version="1.0"?>
< !DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

< faces-config>
    <validator>
        <validator-id>
            onlyfun.caterpillar.Password
        </validator-id>
        <validator-class>
            onlyfun.caterpillar.PasswordValidator
        </validator-class>
    </validator>
< /faces-config>

 

剩下來的工作,就是佈署tld描述檔了,我們簡單的定義一下:
taglib.tld

<?xml version="1.0" encoding="UTF-8" ?>

< taglib xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
web-jsptaglibrary_2_0.xsd"
version="2.0">

< description>PasswordValidator Tag</description>
< tlib-version>1.0</tlib-version>
< jsp-version>2.0</jsp-version>
< short-name>co</short-name>
< uri>http://caterpillar.onlyfun.net</uri>

< tag>
< description>PasswordValidator</description>
< name>passwordValidator</name>
< tag-class>
onlyfun.caterpillar.PasswordValidatorTag
< /tag-class>
< body-content>empty</body-content>
< attribute>
< name>pattern</name>
< required>true</required>
< rtexprvalue>false</rtexprvalue>
< /attribute>
< /tag>

< /taglib>

 

而我們的index.jsp改寫如下:
index.jsp

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
< %@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
< %@ taglib uri="/WEB-INF/taglib.tld" prefix="co" %>
< %@page contentType="text/html;charset=Big5"%>
< html>
< head>
< title>
驗證器示範</title>
< /head>
< body>
< f:view>
< h:messages layout="table" style="color:red"/>
< h:form>
< h3>
請輸入您的名稱</h3>
< h:outputText value="#{user.errMessage}"/><p>
名稱: <h:inputText value="#{user.name}"
required="true"/><p>
密碼: <h:inputSecret value="#{user.password}"
required="true">
< co:passwordValidator pattern=".+[0-9]+"/>
< /h:inputSecret> <p>
< h:commandButton value="
送出"
action="#{user.verify}"/>
< /h:form>
< /f:view>
< /body>
< /html>

  

主要的差別是,我們使用了自己的驗證器標籤:

<co:passwordValidator pattern=".+[0-9]+"/>

  

如果要自訂轉換器標籤,方法也是類似,您要作的是繼承javax.faces.webapp.ConverterTag,並重新定義其createConverter()方法。

4. 事件處理

JSF的事件模型提供一個近似的桌面GUI事件模式,讓熟悉GUI設計的人員也能快速上手Web程序設計。

4.1動作事件

JSF支持事件處理模型,雖然由於HTTP本身無狀態(stateless)的特性,使得這個模型多少有些地方仍不太相同,但JSF所提供的事件處理模型已足以讓一些傳統GUI程序的設計人員,可以用類似的模型來開發程序。

在 簡單的導航 中,我們根據動作方法(action method)的結果來決定要導向的網頁,一個按鈕繫結至一個方法,這樣的作法實際上即使JSF所提供的簡化的事件處理程序,在按鈕上使用action系 結至一個動作方法(action method),實際上JSF會爲其自動產生一個「預設的ActionListener」來處理事件,並根據其傳回值來決定導向的頁面。

如果您需要使用同一個方法來應付多種事件來源,並想要取得事件來源的相關訊息,您可以讓處理事件的方法接收一個javax.faces.event.ActionEvent事件參數,例如:

UserBean.java

package onlyfun.caterpillar;

import javax.faces.event.ActionEvent;

public class UserBean {
private String name;
private String password;
private String errMessage;
private String outcome;

public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setPassword(String password) {
this.password = password;
}

public String getPassword() {
return password;
}

public void setErrMessage(String errMessage) {
this.errMessage = errMessage;
}

public String getErrMessage() {
return errMessage;
}

public void verify(ActionEvent e) {
if(!name.equals("justin") ||
!password.equals("123456")) {
errMessage = "
名稱或密碼錯誤" + e.getSource();
outcome = "failure";
}
else {
outcome = "success";
}
}

public String outcome() {
return outcome;
}
}

  

在上例中,我們讓verify方法接收一個ActionEvent對象,當使用者按下按鈕,會自動產生ActionEvent對象代表事件來源,我 們故意在錯誤訊息之後如上事件來源的字符串描述,這樣就可以在顯示錯誤訊息時一併顯示事件來源描述。爲了提供ActionEvent的存取能力,您的 index.jsp可以改寫如下:
index.jsp

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
< %@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
< %@page contentType="text/html;charset=Big5"%>
< html>
< head>
< title>
第一個JSF程序</title>
< /head>
< body>
< f:view>
< h:form>
< h3>
請輸入您的名稱</h3>
< h:outputText value="#{user.errMessage}"/><p>
名稱: <h:inputText value="#{user.name}"/><p>
密碼: <h:inputSecret value="#{user.password}"/><p>
< h:commandButton value="
送出" actionListener="#{user.verify}" action="#{user.outcome}"/>
< /h:form>
< /f:view>
< /body>
< /html>

  

主要改變的是按鈕上使用了actionListener屬性,這種方法可以使用一個 ActionListener,JSF會先檢查是否有指定的 actionListener,然後再檢查是否指定了動作方法併產生預設的ActionListener,並根據其傳回值導航頁面。

如果您要註冊多個ActionListener,例如當使用者按下按鈕時,順便在記錄文件中增加一些記錄訊息,您可以實作javax.faces.event.ActionListener,例如:

LogHandler.java

package onlyfun.caterpillar;

import javax.faces.event.ActionListener;
public class LogHandler implements ActionListener {
public void processAction(ActionEvent e) {
//
處理Log
}
}
VerifyHandler.java
package onlyfun.caterpillar;

import javax.faces.event.ActionListener;
....

public class VerifyHandler implements ActionListener {
public void processAction(ActionEvent e) {
//
處理驗證
}
}

  

這麼一來,您就可以使用<f:actionListener>卷標向組件註冊事件,例如:

<h:commandButton value="送出" action="#{user.outcome}">
< f:actionListener type="onlyfun.caterpillar.LogHandler"/>
< f:actionListener type="onlyfun.caterpillar.VerifyHandler"/>
< /h:commandButton>

  <f:actionListener>會自動產生type所指定的對象,並呼叫組件的addActionListener()方法註冊Listener。

4.2實時事件

所謂的實時事件(Immediate Events),是指JSF視圖組件在取得請求中該取得的值之後,即立即處理指定的事件,而不再進行後續的轉換器處理、驗證器處理、更新模型值等流程。

在JSF的事件模型中會有所謂實時事件,導因於Web應用程序的先天特性不同於GUI程序,所以JSF的事件模式與 GUI程序的事件模式仍有相當程度的不同,一個最基本的問題正因爲HTTP無狀態的特性,使得Web應用程序天生就無法直接喚起伺服端的特定對象。  所 有的對象喚起都是在伺服端執行的,至於該喚起什麼對象,則是依一個基本的流程:

•回覆畫面(Restore View)
  依客戶端傳來的session數據或伺服端上的session數據,回覆JSF畫面組件。

•套用請求值(Apply Request Values)
  JSF畫面組件各自獲得請求中的值屬於自己的值,包括舊的值與新的值。

•執行驗證(Process Validations)
  轉換爲對象並進行驗證。

•更新模型值(Update Model Values)
  更新Bean或相關的模型值。

•喚起應用程序(Invoke Application)
  執行應用程序相關邏輯。

•繪製迴應畫面(Render Response)
  對先前的請求處理完之後,產生畫面以響應客戶端執行結果。

 

對於動作事件(Action Event)來說,組件的動作事件是在套用請求值階段就生成ActionEvent對象了,但相關的事件處理並不是馬上進行,ActionEvent會先被排入隊列,然後必須再通過驗證、更新模式值階段,之後才處理隊列中的事件。

這樣的流程對於按下按鈕然後執行後端的應用程序來說不成問題,但有些事件並不需要這樣的流程,例如隻影響畫面的事件。

舉個例子來說,在窗體中可能有使用者名稱、密碼等字段,並提供有一個地區選項按鈕,使用者可以在不填下按鈕的情況下,就按下地區選項按鈕,如果依照正常的流程,則會進行驗證、更新模型值、喚起應用程序等流程,但顯然的,使用者名稱與密碼是空白的,這會引起不必要的錯誤。

您可以設定組件的事件在套用請求值之後立即被處理,並跳過後續的階段,直接進行畫面繪製以響應請求,對於JSF的input與command組件,都有一個immediate屬性可以設定,只要將其設定爲true,則指定的事件就成爲立即事件。一個例子如下:

index.jsp

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
< %@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
< %@page contentType="text/html;charset=UTF8"%>
< f:view locale="#{user.locale}">
< f:loadBundle basename="messages" var="msgs"/>

< html>
< head>
< title><h:outputText value="#{msgs.titleText}"/></title>
< /head>
< body>

< h:form>
< h3><h:outputText value="#{msgs.hintText}"/></h3>
< h:outputText value="#{msgs.nameText}"/>:
< h:inputText value="#{user.name}"/><p>
< h:outputText value="#{msgs.passText}"/>:
< h:inputSecret value="#{user.password}"/><p>
< h:commandButton value="#{msgs.commandText}"
action="#{user.verify}"/>
< h:commandButton value="#{msgs.Text}"
immediate="true"
actionListener="#{user.changeLocale}"/>
< /h:form>
< /body>
< /html>
< /f:view>

  

這是一個可以讓使用者決定使用語系的示範,最後一個commandButton組件被設定了immediate屬性,當按下這個按鈕後,JSF 套用請求值之後會立即處理指定的actionListener,而不再進行驗證、更新模型值,簡單的說,就這個程序來說,您在輸入字段與密碼字段中填入的 值,不會影響您的user.name與user.password。基於範例的完整起見,我們列出這個程序Bean對象及faces- config.xml:
UserBean.java

package onlyfun.caterpillar;

import javax.faces.event.ActionEvent;

public class UserBean {
private String locale = "en";
private String name;
private String password;
private String errMessage;

public void changeLocale(ActionEvent e) {
if(locale.equals("en"))
locale = "zh_TW";
else
locale = "en";
}

public String getLocale() {
if (locale == null) {
locale = "en";
}
return locale;
}

public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setPassword(String password) {
this.password = password;
}

public String getPassword() {
return password;
}

public void setErrMessage(String errMessage) {
this.errMessage = errMessage;
}

public String getErrMessage() {
return errMessage;
}

public String verify() {
if(!name.equals("justin") ||
!password.equals("123456")) {
errMessage = "
名稱或密碼錯誤";
return "failure";
}
else {
return "success";
}
}
}

 

faces-config.xml

<?xml version="1.0"?>
< !DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

< faces-config>
< navigation-rule>
< from-view-id>/pages/index.jsp</from-view-id>
< navigation-case>
< from-outcome>success</from-outcome>
< to-view-id>/pages/welcome.jsp</to-view-id>
< /navigation-case>
< navigation-case>
< from-outcome>failure</from-outcome>
< to-view-id>/pages/index.jsp</to-view-id>
< /navigation-case>
< /navigation-rule>

< managed-bean>
< managed-bean-name>user</managed-bean-name>
< managed-bean-class>
onlyfun.caterpillar.UserBean
< /managed-bean-class>
< managed-bean-scope>session</managed-bean-scope>
< /managed-bean>
< /faces-config>

 

訊息資源文件的內容則是如下:

messages_en.properties

titleText=JSF Demo
hintText=Please input your name and password
nameText=name
passText=password
commandText=Submit
Text=\u4e2d\u6587

  

Text中設定的是「中文」轉換爲Java Unicode Escape格式的結果,另一個訊息資源文件的內容則是英文訊息的翻譯而已,其轉換爲Java Unicode Escape格式結果如下:

messages_zh_TW.properties

titleText=JSF\u793a\u7bc4
hintText=\u8acb\u8f38\u5165\u540d\u7a31\u8207\u5bc6\u78bc
nameText=\u540d\u7a31
passText=\u5bc6\u78bc
commandText=\u9001\u51fa
Text=English

 

welcome.jsp就請自行設計了,程序的畫面如下:

4.3值變事件

如果使用者改變了JSF輸入組件的值後送出窗體,就會發生值變事件(Value ChangeEvent),這會丟出一個javax.faces.event.ValueChangeEvent對象,如果您想要處理這個事件,有兩種方 式,一是直接 設定JSF輸入組件的valueChangeListener屬性,例如:

<h:selectOneMenu value="#{user.locale}"
onchange="this.form.submit();"
valueChangeListener="#{user.changeLocale}">
< f:selectItem itemValue="zh_TW" itemLabel="Chinese"/>
< f:selectItem itemValue="en" itemLabel="English"/>
< /h:selectOneMenu>

  

爲了仿真GUI中選擇了選單項目之後就立即發生反應,我們在onchange屬性中使用了JavaScript,其作用是在選項項目發生改變之 後,立即送出窗體,而不用按下提交按鈕;而valueChangeListener屬性所綁定的user.changeLocale方法必須接受 ValueChangeEvent對象,例如:
UserBean.java

package onlyfun.caterpillar;

import javax.faces.event.ValueChangeEvent;

public class UserBean {
private String locale = "en";
private String name;
private String password;
private String errMessage;

public void changeLocale(ValueChangeEvent event) {
if(locale.equals("en"))
locale = "zh_TW";
else
locale = "en";
}

public void setLocale(String locale) {
this.locale = locale;
}

public String getLocale() {
if (locale == null) {
locale = "en";
}
return locale;
}

public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setPassword(String password) {
this.password = password;
}

public String getPassword() {
return password;
}

public void setErrMessage(String errMessage) {
this.errMessage = errMessage;
}

public String getErrMessage() {
return errMessage;
}

public String verify() {
if(!name.equals("justin") ||
!password.equals("123456")) {
errMessage = "
名稱或密碼錯誤";
return "failure";
}
else {
return "success";
}
}
}

  

另一個方法是實作javax.faces.event.ValueChangeListener接口,並定義其processValueChange()方法,例如:
SomeListener.java

package onlyfun.caterpillar;
public class SomeListener implements ValueChangeListener {
public void processValueChange(ValueChangeEvent event) {
}
}

  

然後在JSF頁面上使用<f:valueChangeListener>卷標,並設定其type屬性,例如:

{code:borderStyle=solid}
< h:selectOneMenu value="#{user.locale}"
onchange="this.form.submit();">
< f:valueChangeListener
type="onlyfun.caterpillar.SomeListener"/>
< f:selectItem itemValue="zh_TW" itemLabel="Chinese"/>
< f:selectItem itemValue="en" itemLabel="English"/>
< /h:selectOneMenu>

  

下面這個頁面是對 立即事件 中的範例程序作一個修改,將語言選項改以下拉式選單的選擇方式呈現,這必須配合上面提供的UserBean類別來使用:
index.jsp

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
< %@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
< %@page contentType="text/html;charset=UTF8"%>

< f:view locale="#{user.locale}">
< f:loadBundle basename="messages" var="msgs"/>

< html>
< head>
< title><h:outputText value="#{msgs.titleText}"/></title>
< /head>
< body>

< h:form>
< h:selectOneMenu value="#{user.locale}"
immediate="true"
onchange="this.form.submit();"
valueChangeListener="#{user.changeLocale}">

< f:selectItem itemValue="zh_TW"
itemLabel="Chinese"/>
< f:selectItem itemValue="en"
itemLabel="English"/>
< /h:selectOneMenu>

< h3><h:outputText value="#{msgs.hintText}"/></h3>
< h:outputText value="#{msgs.nameText}"/>:
< h:inputText value="#{user.name}"/><p>
< h:outputText value="#{msgs.passText}"/>:
< h:inputSecret value="#{user.password}"/><p>
< h:commandButton value="#{msgs.commandText}"
action="#{user.verify}"/>
< /h:form>

< /body>
< /html>

< /f:view>

4.4 Phase事件

在實時事件 中我們提到,JSF的請求執行到響應,完整的過程會經過六個階段:

l  回覆畫面(Restore View)
  依客戶端傳來的session數據或伺服端上的session數據,回覆JSF畫面組件。

l  套用請求值(Apply Request Values)
  JSF畫面組件各自獲得請求中的值屬於自己的值,包括舊的值與新的值。

l  執行驗證(Process Validations)
  轉換爲對象並進行驗證。

l  更新模型值(Update Model Values)
  更新Bean或相關的模型值。

l  喚起應用程序(Invoke Application)
  執行應用程序相關邏輯。

l  繪製迴應畫面(Render Response)
  對先前的請求處理完之後,產生畫面以響應客戶端執行結果。

在每個階段的前後會引發javax.faces.event.PhaseEvent,如果您想嘗試在每個階段的前後捕捉 這個事件,以進行一些處理,則可以實作javax.faces.event.PhaseListener,並向 javax.faces.lifecycle.Lifecycle 登記這個Listener,以有適當的時候通知事件的發生。

PhaseListener有三個必須實作的方法getPhaseId()、beforePhase()與afterPhase(),其中getPhaseId()傳回一個PhaseId對象,代表Listener想要被通知的時機,可以設定的時機有:

• PhaseId.RESTORE_VIEW
• PhaseId.APPLY_REQUEST_VALUES
• PhaseId.PROCESS_VALIDATIONS
• PhaseId.UPDATE_MODEL_VALUES
• PhaseId.INVOKE_APPLICATION
• PhaseId.RENDER_RESPONSE
• PhaseId.ANY_PHASE

  

其中PhaseId.ANY_PHASE指的是任何的階段轉換時,就進行通知;您可以在beforePhase()與afterPhase()中撰寫階段前後撰寫分別想要處理的動作,例如下面這個簡單的類別會列出每個階段的名稱:
ShowPhaseListener.java

package onlyfun.caterpillar;

import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

public class ShowPhaseListener implements PhaseListener {

public void beforePhase(PhaseEvent event) {
String phaseName = event.getPhaseId().toString();
System.out.println("Before " + phaseName);
}

public void afterPhase(PhaseEvent event) {
String phaseName = event.getPhaseId().toString();
System.out.println("After " + phaseName);
}

public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}
}

  

撰寫好PhaseListener後,我們可以在faces-config.xml中向Lifecycle進行註冊:
faces-config.xml

<?xml version="1.0"?>
< !DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
< faces-config>
< lifecycle>
< phase-listener>
onlyfun.caterpillar.ShowPhaseListener
< /phase-listener>
< /lifecycle>
< /faces-config>

  

您可以使用這個簡單的類別,看看在請求任一個JSF畫面時所顯示的內容,藉此瞭解JSF每個階段的流程變化。

5. JSF 標籤

網頁設計人員要作的就是了解JSF的標籤的使用方式,這就像是學習進階的HTML標籤,另一件事就是與程序設計人員溝通好各個Bean的名稱綁定。卷標的相關屬性查詢,您可以參考 Tag LibraryDocumentation,這邊的介紹只是一些簡單的入門實例。

5.1簡介JSF標準標籤

JSF提供了標準的HTMLRenderer Kit,可以讓您搭配JSF組件輸出HTML文件,標準的HTML Renderer Kit主要包括了幾個類別:

l  輸出(Outputs)
  其名稱以output作爲開頭,作用爲輸出指定的訊息或綁定值。

l  輸入(Inputs)
  其名稱以input作爲開頭,其作用爲提供使用者輸入字段。

l  命令(Commands)
  其名稱以command作爲開頭,其作用爲提供命令或連結按鈕。

l  選擇(Selections)
  其名稱以select作爲開頭,其作用爲提供使用者選項的選取。

l  其它
  包括了form、message、messages、graphicImage等等未分類的標籤。

JSF標準HTML標籤包括了幾個共通的屬性,整理如下:

屬性名稱

適用

說明

id

所有組件

可指定id名稱,以讓其它卷標或組件參考

binding

所有組件

綁定至UIComponent

rendered

所有組件

是否顯示組件

styleClass

所有組件

設定Cascading stylesheet (CSS)

value

輸入、輸出、命令組件

設定值或綁定至指定的值

valueChangeListener

輸入組件

設定值變事件處理者

converter

輸入、輸出組件

設定轉換器

validator

輸入組件

設定驗證器

required

輸入組件

是否驗證必填字段

immediate

輸入、命令組件

是否爲立即事件

  

除了共通的屬性之外,您還可以在某些組件上設定卷標HTML 4.01的屬性,像是size、alt、width等屬性,或者是設定DHTML事件屬性,例如onchange、onclick等等。

除了JSF的標準HTML標籤之外,您還需要一些標準核心卷標,這些卷標是獨立於Renderer Kit的,JSF並不限制在HTML輸出表示層,核心標籤可以搭配其它的Renderer Kit來使用。詳細的HTML卷標或核心卷標的使用與屬性說明可以查詢Tag Library Documentation 文件。

5.2輸出類標籤

輸出類的標籤包括了outputLabel、outputLink、outputFormat與 outputText,分別舉例說明如下:

l  outputLabel

產生<label>HTML卷標,使用for屬性指定組件的client ID,例如:

<h:inputText id="user" value="#{user.name}"/>

<h:outputLabel for="user" value="#{user.name}"/>

 

這會產生像是以下的標籤:

<input id="user" type="text" name="user" value="guest" />

<label for="user">

 

l  outputLink

產生<a>HTML標籤,例如:

<h:outputLink value="../index.jsp">

    <h:outputText value="Link to Index"/>

    <f:param name="name" value="MyName"/>

</h:outputLink>

 

你可搭配<f:param>幫鏈接加上參數,所有的參數都會變成 name=value 的型態附加在連結後。value所指定的內容也可以是JSFEL綁定。

 

l  outputFormat

產生指定的文字訊息,可以搭配<f:param>來設定訊息的參數以格式化文字訊息,例如:

<f:loadBundle basename="messages" var="msgs"/>

 <h:outputFormat value="#{msgs.welcomeText}">

     <f:param value="Hello"/>

     <f:param value="Guest"/>

 </h:outputFormat>

 

如果您的messages.properties包括以下的內容:

welcomeText={0}, Your name is {1}.

 

則{0}與{1}會被取代爲<f:param>設定的文字,最後顯示的文字會是:Hello, Your name isGuest.

 

另一個使用的方法則是:

<h:outputFormat value="{0}, Your name is {1}.">

     <f:param value="Hello"/>

     <f:param value="Guest"/>

 </h:outputFormat>

 

l  outputText

簡單的顯示指定的值或綁定的訊息,例如:

<h:outputText value="#{user.name}"/>

5.3輸入類標籤

輸入類標籤包括了inputText、inputTextarea、inputSecret、 inputHidden,分別舉例說明如下:

l  inputText

顯示單行輸入字段,即輸出<input>HTML卷標,其type屬性設定爲text,例如:

<h:inputText value="#{user.name}"/>

 

l  inputTextarea

顯示多行輸入文字區域,即輸出<textarea>HTML標籤,例如:

<h:inputTextarea value="#{user.command}"/>

 

l  inputSecret

顯示密碼輸入字段,即輸出<input>HTML卷標,其type屬性設定爲password,例如:

<h:inputSecret value="#{user.password}"/>

 

您可以設定redisplay屬性以決定是否要顯示密碼字段的值,預設是false。

 

l  inputHidden

隱藏字段,即輸出<input> HTML卷標,其type屬性設定爲hidden,隱藏字段的值用於保留一些訊息於客戶端,以在下一次發送窗體時一併送出,例如:

<h:inputHidden value="#{user.hiddenInfo}"/>

5.4命令類標籤

命令類標籤包括commandButton與commandLink,其主要作用在於提供一個命令按鈕或連結,以下舉例說明:

l  commandButton

  顯示一個命令按鈕,即輸出<input> HTML卷標,其type屬性可以設定爲button、submit或reset,預設是submit,按下按鈕會觸發javax.faces.event.ActionEvent,使用例子如下:

<h:commandButton value="送出" action="#{user.verify}"/>

  

您可以設定image屬性,指定圖片的URL,設定了image屬性的話,<input>卷標的type屬性會被設定爲image,例如:

<h:commandButton value="#{msgs.commandText}"image="images/logowiki.jpg"

action="#{user.verify}"/>

l  commandLink

   產生超級鏈接,會輸出<a> HTML卷標,而href屬性會有'#',而onclick屬性會含有一段JavaScript程序,這個JavaScript的目的是按下連結後自動提 交窗體,具體來說其作用就像按鈕,但外觀卻是超級鏈接,包括在本體部份的內容都會成爲超級鏈接的一部份,一個使用的例子如下:

<h:commandLink value="#{msgs.commandText}" action="#{user.verify}"/>

 

產生的HTML輸出範例如下:

<a href="#" onclick="document.forms['_id3']['_id3:_idcl'].value='_id3:_id13'; document.forms['_id3'].submit(); return false;">Submit</a>

 

如果搭配<f:param>來使用,則所設定的參數會被當作請求參數一併送出,例如:

<h:commandLink>

<h:outputText value="welcome"/>

   <f:param name="locale" value="zh_TW"/>

</h:commandLink>

5.5選擇類標籤

選擇類的標籤可略分爲單選卷標與多選卷標,依外型的不同可以分爲單選鈕(Radio)、複選框(CheckBox)、列示方塊(ListBox)與 選單(Menu),以下分別先作簡單的說明。<h:selectBooleanCheckbox>在視圖上呈現一個複選框,例如:

<h:selectBooleanCheckbox value="#\{user.aggree\}"/>

 

value所綁定的屬性必須接受與傳回boolean型態。這個組件在網頁上呈現的外觀如下:

<h:selectOneRadio>、<h:selectOneListbox>、<h: selectOneMenu>這三個標籤的作用,是讓使用者從其所提供的選項中選擇一個項目,所不同的就是其外觀上的差別,例如:

<h:selectOneRadio value="#{user.education}">
< f:selectItem itemLabel="
高中" itemValue="高中"/>
< f:selectItem itemLabel="
大學" itemValue="大學"/>
< f:selectItem itemLabel="
研究所以上" itemValue="研究所以上"/>
< /h:selectOneRadio>

 

value所綁定的屬性可以接受字符串以外的型態或是自訂型態,但記得如果是必須轉換的型態或自訂型態,必須搭配標準轉換器 或 自訂轉換器 來轉換爲對象,<h:selectOneRadio>的外觀如下:

您也可以設定layout屬性,可設定的屬性是lineDirection、pageDirection,預設是lineDirection,也就是由左到右來排列選項,如果設定爲pageDirection,則是由上至下排列選項,例如設定爲:

<h:selectOneRadio layout="pageDirection" value="#{user.education}">
< f:selectItem itemLabel="
高中" itemValue="高中"/>
< f:selectItem itemLabel="
大學" itemValue="大學"/>
< f:selectItem itemLabel="
研究所以上" itemValue="研究所以上"/>
< /h:selectOneRadio>

 

則外觀如下:

<h:selectOneListbox>、<h:selectOneMenu>的設定方法類似於<h: selectOneRadio>,以下分別列出<h:selectOneListbox>、<h: selectOneMenu>的外觀:

<h:selectManyCheckbox>、<h:selectManyListbox>、<h: selectManyMenu>這三個卷標提供使用者複選項目的功能,一個<h:selectManyCheckbox>例子如下:

<h:selectManyCheckbox layout="pageDirection" value="#{user.preferColors}">
< f:selectItem itemLabel="
" itemValue="false"/>
< f:selectItem itemLabel="
" itemValue="false"/>
< f:selectItem itemLabel="
" itemValue="false"/>
< /h:selectManyCheckbox>

 

value所綁定的屬性必須是數組或集合(Collection)對象,在這個例子中所使用的是boolean數組,例如:
UserBean.java

package onlyfun.caterpillar;
public class UserBean {
private boolean[] preferColors;
public boolean[] getPreferColors() {
return preferColors;
}
public void setPreferColors(boolean[] preferColors) {
this.preferColors = preferColors;
}
}

 

如果是其它型態的對象,必要時必須搭配轉換器(Converter)進行字符串與對象之間的轉換。

 

下圖是<h:selectManyCheckbox>的外觀,這是將layout設定爲pageDirection的外觀:  <h:selectManyListbox>的設定方法類似,其外觀如下:

<h:selectManyMenu>在不同的瀏覽器中會有不同的外觀,在Mozilla Firefox中是這樣的:

在InternetExplorer則是這樣的:

 

選擇類標籤可以搭配<f:selectItem>或<f:selectItems>卷標來設定選項,例如:

<f:selectItem itemLabel="高中"
itemValue="
高中"
itemDescription="
學歷"
itemDisabled="true"/>

 

itemLabel屬性設定顯示在網頁上的文字,itemValue設定發送至伺服端時的值,itemDescription 設定文字描述,它只作用於一些工具程序,對HTML沒有什麼影響,itemDisabled設定是否選項是否作用,這些屬性也都可以使用JSF ExpressionLanguage來綁定至一個值。<f:selectItem>也可以使用value來綁定一個傳回 javax.faces.model.SelectItem的方法,例如:

<f:selectItem value="#{user.sex}"/>

 

則綁定的Bean上必須提供下面這個方法:

public SelectItem getSex() {
return new SelectItem("
");
}

 

如果要一次提供多個選項,則可以使用<f:selectItems>,它的value綁定至一個提供傳回SelectItem的數組、集合,或者是Map對象的方法,例如:

<h:selectOneRadio value="#{user.education}">
< f:selectItems value="#{user.educationItems}"/>
< /h:selectOneRadio>

 

這個例子中<f:selectItems>的value綁定至user.educationItems,其內容如下:

private SelectItem[] educationItems;

public SelectItem[] getEducationItems() {
if(educationItems == null) {
educationItems = new SelectItem[3];
educationItems[0] =
new SelectItem("
高中", "高中");
educationItems[1] =
new SelectItem("
大學", "大學");
educationItems[2] =
new SelectItem("
研究所以上", "研究所以上");
}
return educationItems;
}

 

在這個例子中,SelectItem的第一個建構參數用以設定value,而第二個參數用以設定label,SelectItem還提供有數個建構函式,記得可以參考一下線上API文件。

您也可以提供一個傳回Map對象的方法,Map的key-value會分別作爲選項的label-value,例如:

<h:selectManyCheckbox layout="pageDirection"  value="#{user.preferColors}">
< f:selectItems value="#{user.preferColorItems}"/>
< /h:selectManyCheckbox>

 

您要提供下面的程序來搭配上面這個例子:

private Map preferColorItems;

public Map getPreferColorItems() {
if(preferColorItems == null) {
preferColorItems = new HashMap();
preferColorItems.put("
", "Red");
preferColorItems.put("
", "Yellow");
preferColorItems.put("
", "Blue");
}
return preferColorItems;
}

5.6其它標籤

<h:messages>或<h:message>標籤的介紹,在錯誤訊息處理中已經有介紹了。

l  <h:graphicImage>

這個卷標會繪製一個HTML <img>卷標,value可以指定路徑或圖片URL,路徑可以指定相對路徑或絕對路徑,例如:

<h:graphicImage value="/images/logowiki.jpg"/>
< h:panelGrid>


  這個卷標可以用來作簡單的組件排版,它會使用HTML表格卷標來繪製表格,並將組件置於其中,主要指定columns屬性,例如設定爲 2:

<h:panelGrid columns="2">
< h:outputText value="Username"/>
< h:inputText id="name" value="#{userBean.name}"/>
< h:outputText value="Password"/>
< h:inputText id="password" value="#{userBean.password}"/>
< h:commandButton value="submit" action="login"/>
< h:commandButton value="reset" type="reset"/>
< /h:panelGrid>

 

則自動將組件分作 2 個 column來排列,排列出來的樣子如下:

 

<h:panelGrid>的本體間只能包括JSF組件,如果想要放入非JSF組件,例如簡單的樣版(template)文字,則要使用 <f:verbatim>包括住,例如:

<h:panelGrid columns="2">
< f:verbatim>Username</f:verbatim>
< h:inputText id="name" value="#{userBean.name}"/>
< f:verbatim>Password</f:verbatim>
< h:inputText id="password" value="#{userBean.password}"/>
< h:commandButton value="submit" action="login"/>
< h:commandButton value="reset" type="reset"/>
< /h:panelGrid>
< h:panelGroup>

 

這個組件用來將數個JSF組件包裝起來,使其看來像是一個組件,例如:

<h:panelGrid columns="2">
< h:outputText value="Username"/>
< h:inputText id="name" value="#{userBean.name}"/>
< h:outputText value="Password"/>
< h:inputText id="password" value="#{userBean.password}"/>
< h:panelGroup>
< h:commandButton value="submit" action="login"/>
< h:commandButton value="reset" type="reset"/>
< /h:panelGroup>
< /h:panelGrid>

 

在<h:panelGroup>中包括了兩個<h:commandButton>,這使得< h:panelGrid>在處理時,將那兩個<h:commandButton>看作是一個組件來看待,其完成的版面配置如下所示:

6. 表格處理

對於必須使用表格方式呈現的數據,JSF 的 <h:dataTable> 卷標協助您進行動態表格數據的輸出。

6.1簡單的表格

很多數據經常使用表格來表現,JSF提供<h:dataTable>卷標讓您得以列舉數據並使用表格方式來呈現,舉個實際的例子來看,假設您撰寫了以下的兩個類別:

UserBean.java

package onlyfun.caterpillar;

public class UserBean {
private String name;
private String password;

public UserBean() {
}

public UserBean(String name, String password) {
this.name = name;
this.password = password;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

 

TableBean.java

package onlyfun.caterpillar;
import java.util.*;
public class TableBean {
private List userList;

public List getUserList() {
if(userList == null) {
userList = new ArrayList();
userList.add(new UserBean("caterpillar", "123456"));
userList.add(new UserBean("momor", "654321"));
userList.add(new UserBean("becky", "7890"));
}

return userList;
}
}


  在TableBean中,我們假設getUserList()方法實際上是從數據庫中查詢出UserBean的內容,之後傳回List對象,若我們的 faces-config.xml如下:
faces-config.xml

<?xml version="1.0" encoding="UTF-8"?>
< !DOCTYPE faces-config PUBLIC "-//Sun Microsystems,
Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

< faces-config>
< managed-bean>
< managed-bean-name>tableBean</managed-bean-name>
< managed-bean-class>
onlyfun.caterpillar.TableBean
< /managed-bean-class>
< managed-bean-scope>request</managed-bean-scope>
< /managed-bean>
< managed-bean>
< managed-bean-name>userBean</managed-bean-name>
< managed-bean-class>
onlyfun.caterpillar.UserBean
< /managed-bean-class>
< managed-bean-scope>request</managed-bean-scope>
< /managed-bean>
< /faces-config>

  

我們可以如下使用<h:dataTable>來產生表格數據:
index.jsp

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
< %@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
< html>
< body>
< f:view>
< h:dataTable value="#{tableBean.userList}" var="user">
< h:column>
< h:outputText value="#{user.name}"/>
< /h:column>
< h:column>
< h:outputText value="#{user.password}"/>
< /h:column>
< /h:dataTable>
< /f:view>
< /body>
< /html>

 

<h:dataTable>的value值綁定tableBean的userList方法,它會一個一個取出 List中的數據並設定給var設定的user,之後在每一個column中我們可以顯示所列舉出的user.name與user.password,程 序的結果會如下所示:

所產生的HTML表格卷標如下:

<table>
< tbody>
< tr>
< td>caterpillar</td>
< td>123456</td>
< /tr>
< tr>
< td>momor</td>
< td>654321</td>
< /tr>
< tr>
< td>becky</td>
< td>7890</td>
< /tr>
< /tbody>
< /table>

 

  <h:dataTable>的value值綁定的對象可以是以下的型態:
• 數組
•java.util.List的實例
•java.sql.ResultSet的實例
•javax.servlet.jsp.jstl.sql.Result的實例
•javax.faces.model.DataModel的實例

6.2表頭和表尾

<h:dataTable>配合<h:column>來以表格的方式顯示數據,< h:column>中只能包括JSF組件或者是<f:facet>,JSF支援兩種facet:header與footer。分別用以 設定表格的表頭與表尾文字,一個設定的例子如下:

<h:dataTable value="#{tableBean.userList}" var="user">
< h:column>
< f:facet name="header">
< h:outputText value="Name"/>
< /f:facet>
< h:outputText value="#{user.name}"/>
< f:facet name="footer">
< h:outputText value="****"/>
< /f:facet>
< /h:column>
< h:column>
< f:facet name="header">
< h:outputText value="Password"/>
< /f:facet>
< h:outputText value="#{user.password}"/>
< f:facet name="footer">
< h:outputText value="****"/>
< /f:facet>
< /h:column>
< /h:dataTable>

 

所產生的表格如下所示:

另外,對於表頭、表尾仍至於每一行列,都可以分別設定CSS風格,例如下面這個styles.css摘錄自Core JSF一書:
styles.css

.orders {
border: thin solid black;
}

.ordersHeader {
text-align: center;
font-style: italic;
color: Snow;
background: Teal;
}

.evenColumn {
height: 25px;
text-align: center;
background: MediumTurquoise;
}

.oddColumn {
text-align: center;
background: PowderBlue;
}

  

可以在我們的頁面中如下加入:

<link href="styles.css" rel="stylesheet" type="text/css"/>
< h:dataTable value="#{tableBean.userList}" var="user"
styleClass="orders"
headerClass="ordersHeader"
rowClasses="evenColumn,oddColumn">
< h:column>
< f:facet name="header">
< h:outputText value="Name"/>
< /f:facet>
< h:outputText value="#{user.name}"/>
< f:facet name="footer">
< h:outputText value="****"/>
< /f:facet>
< /h:column>
< h:column>
< f:facet name="header">
< h:outputText value="Password"/>
< /f:facet>
< h:outputText value="#{user.password}"/>
< f:facet name="footer">
< h:outputText value="****"/>
< /f:facet>
< /h:column>
< /h:dataTable>

 

則顯示的表格結果如下:

6.3TableModel 類別

在簡單的表格中曾經提過,<h:dataTable>可以列舉以下幾種型態的數據:
• 數組
•java.util.List的實例
•java.sql.ResultSet的實例
•javax.servlet.jsp.jstl.sql.Result的實例
•javax.faces.model.DataModel的實例
  

對於前四種型態,JSF實際上是以javax.faces.model.DataModel加以包裝,DataModel是個抽象類別,其子類別都是位於 javax.faces.model這個package下:
•ArrayDataModel
•ListDataModel
•ResultDataModel
•ResultSetDataModel
•ScalarDataModel
  

如果您想要對錶格數據有更多的控制,您可以直接使用DataModel來設定表格數據,呼叫DataModel的setWrappedObject()方法可以讓您設定對應型態的數據,呼叫getWrappedObject()則可以取回數據,例如:
TableBean.java

package onlyfun.caterpillar;

import java.util.*;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;

public class TableBean {
private DataModel model;
private int rowIndex = -1;

public DataModel getUsers() {
if(model == null) {
model = new ListDataModel();
model.setWrappedData(getUserList());
}

return model;
}

private List getUserList() {
List userList = new ArrayList();
userList.add(new UserBean("caterpillar", "123456"));
userList.add(new UserBean("momor", "654321"));
userList.add(new UserBean("becky", "7890"));

return userList;
}

public int getSelectedRowIndex() {
return rowIndex;
}

public String select() {
rowIndex = model.getRowIndex();
return "success";
}
}

 

在這個Bean中,我們直接設定DataModel?,將userList設定給它,如您所看到的,我們還可以取得DataModel?的各個變 項,在這個例子中,select()將作爲點選表格之後的事件處理方法,我們可以藉由DataModel?的getRowIndex ()來取得所點選的是哪一row的資料,例如:
index.jsp

 

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
< %@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
< html>
< link href="styles.css" rel="stylesheet" type="text/css"/>
< body>
< f:view>
< h:form>
< h:dataTable value="#{tableBean.users}" var="user"
styleClass="orders"
headerClass="ordersHeader"
rowClasses="evenColumn,oddColumn">
< h:column>
< f:facet name="header">
< h:outputText value="Name"/>
< /f:facet>
< h:commandLink action="#{tableBean.select}">
< h:outputText value="#{user.name}"/>
< /h:commandLink>

< f:facet name="footer">
< h:outputText value="****"/>
< /f:facet>
< /h:column>
< h:column>
< f:facet name="header">
< h:outputText value="Password"/>
< /f:facet>
< h:outputText value="#{user.password}"/>
< f:facet name="footer">
< h:outputText value="****"/>
< /f:facet>
< /h:column>
< /h:dataTable>
< /h:form>
Selected Row: <h:outputText
value="#{tableBean.selectedRowIndex}"/>
< /f:view>
< /body>
< /html>

 

  DataModel的rowIndex是從0開始計算,當處理ActionEvent時,JSF會逐次遞增rowIndex的值,這讓您可以得知目前正在處理的是哪一個row的數據,一個執行的圖示如下:

7. 自訂組件

JSF 讓您可以自訂組件,每個組件都是可替換的,這使得組件在搭配時更有彈性,但相對的卻使開發組件的過程複雜的多,這邊對自訂JSF 組件只是個入門磚,更多有關自訂組件的細節可得要專書來說明。要開發 JSF 組件,您需要更深入瞭解JSF的一些處理細節,包括了JSF生命週期以及JSF框架。

7.1 JSF生命週期

JSF的每個組件基本上都是可替換的,像是轉換器(Converter)、驗證器(Validator)、組件 (Component)、繪製器 (Renderer)等等,每個組件都可以替換讓JSF在使用時更有彈性,但相對的所付出的就是組件組合時的複雜性,爲此,最基本的,如果您打算自訂一些 JSF組件,那麼您對於JSF處理請求的每個階段必須要有所瞭解。

下圖是JSF處理請求時的每個階段與簡單說明,起始狀態即使用者端發出請求時,終止狀態則相當於繪製器發出響應時:

 

扣除事件處理,JSF總共必須經過六個階段:

l  回覆畫面(Restore View)

對於選擇的頁面如果是初次瀏覽則建立新的組件樹。如果是會話階段,會從使用者端或服務器端的數據找尋數據以回覆每個組件的狀態並重建組件樹,如果不包括請求參數,則直接跳過接下來的階段直接繪製響應。

l  套用申請值(Apply Request Values)

每個組件嘗試從到來的請求中找尋自己的參數並更新組件值,在這邊會觸發ActionEvent,這個事件會被排入隊列中,然後在喚起應用程序階段之後纔會真正由事件處理者進行處理。

 

然而對於設定immeduate爲true的命令(Commamnd)組件來說,會立即處理事件並跳過之後的階段直接繪製響應,而對於設定immediate爲true的輸入(Input)組件,會馬上進行轉換驗證並處理值變事件,之後跳過接下來的階段,直接繪製響應。

l  執行驗證(Process Validations)
    進行轉換與驗證處理,如果驗證錯誤,則會跳過之後的階段,直接繪製響應,結果是重新呼叫同一頁繪製結果。

l  更新模型值(Update Model Values)
  更新每一個與組件綁定的backingbean或模型對象。

l  喚起應用程序(Invoke Application)
  處理動作事件,並進行後端應用程序邏輯。

l  繪製迴應(Render Response)
  使用繪製器繪製頁面。

 

如果您只是要使用JSF,則您最基本的只需要知道執行驗證、更新模型值與喚起應用程序這三個階段及中間的事件觸發,JSF參考實作將這三個階段之外的其它階段之複雜性隱藏起來了,您不需要知道這幾個階段的處理細節。

然而如果您要自訂組件,則您還必須知道回覆畫面、套用請求值與繪製響應這些階段是如何處理的,這幾個階段相當複雜,所幸的是您可以使用JSF 所提供的框架來進行組件自訂,JSF提供的框架已經很大程度上降低了組件製作的複雜性。

當然,即使JSF框架降低了複雜性,但實際上要處理JSF自訂組件還是很複雜的一件事,在嘗試開發自訂組件之前,您可以 先搜尋一些網站,像是 Apache MyFaces http://myfaces.apache.org/,看看是不是已經有相關類似的組件已經開發完成,省去您重新自訂組件的氣力。

7.2概述自訂組件

所謂的自訂JSF組件是一個概略的稱呼,事實上,一個JSF組件包括了三個部份:Tag、Component 與Renderer。

Tag即之前一直在使用的JSF卷標,類似於HTML卷標,JSF卷標主要是方便網頁設計人員進行版面配置與數據呈現的一種方式,實際的處理中,JSF標籤的目的在於設定Component屬性、設定驗證器、設定數據綁定、設定方法綁定等等。

Component的目的在於處理請求,當請求來到伺服端應用程序時,每一個Component都有機會根據自己的client id,從請求中取得屬於自己的值,接着Component可以將這個值作處理,然後設定給綁定的bean。

當請求來到Web應用程序時,HTTP中的字符串內容可以轉換爲JSF組件所需的值,這個動作稱之爲譯碼 (decode),相對的,將JSF 組件的值轉換爲HTTP字符串數據並送至客戶端,這個動作稱之爲編碼(encode),Component可自己處理編碼、譯碼的任務,也可以將之委託給 Renderer來處理。

當您要自訂Component時,您可以繼承UIComonent或其相關的子類別,這要根據您實際要自訂的組件而定, 如果您要自訂一個輸出元 件,可以繼承UIOutput,如果要自訂一個輸入組件,則可以繼承UIInput,每一個標準的JSF組件實際上都對應了一個 UIComponent的子類別,下圖爲一個大致的類別繼承架構圖:    實際上要自訂一個組件是複雜的一件工作,您首先要學會的是一個完整的自訂組件流程,實際上要自訂一個組件時,您可以參考一下網絡上的一些成品,例如 Apache MyFaces http://myfaces.apache.org/ ,接下來後面的幾個主題所要介紹的,將只是一個自訂組件的簡單流程。

Renderer是一個可替換的組件,您的Component可以搭配不同的Renderer,而不用自行擔任繪製響應 或譯碼的動作,這會讓您的 Component可以重用,當您需要將響應從HTML轉換爲其它的媒介時(例如行動電話網絡),則只要替換Renderer就可以了,這是一個好處,或 者您可以簡單的替換掉一個Renderer,就可以將原先簡單的HTML響應,替換爲有JavaScript功能的Renderer。

當您開始接觸自訂組件時,您會開始接觸到JSF的框架(Framework),也許有幾個類別會是您經常接觸的:

•javax.faces.component.UIComponent
   自訂Component所要繼承的父類別,但通常,您是繼承其子類別,例如UIInput、UIOutput等等。
•javax.faces.webapp.UIComponentTag
    自訂JSF標籤所要繼承的父類別,繼承它可以幫您省去許多JSF標籤處理的細節。
•javax.faces.context.FacesContext
   包括了JSF相關的請求信息,您可以透過它取得請求對象或請求參數,或者是 javax.faces.application.Application物件。
•javax.faces.application.Application
   包括了一個應用程序所共享的信息,像是locale、驗證器、轉換器等等,您可以透過一些工廠方法 取得相關的信息。

7.3簡單實例

在不考慮組件有子組件的情況下,這邊以實際的一個例子來說明開發組件的過程,至於考慮子組件的情況請參考專書介紹。

7.3.1編碼和譯碼

Component可以自己負責將對象數據編碼爲HTML文件或其它的輸出文件,也可以將這個任務委託給 Renderer,這邊先介紹的是讓Component自己負責編碼的動作。

這邊着重的是介紹完成自訂組件所必須的流程,所以我們不設計太複雜的組件,這邊將完成以下的組件,這個組件會有一個輸入文字字段以及一個送出按鈕:

您要繼承UIComponent或其子類別來自訂Component,由於文字字段是一個輸入字段,爲了方便,您可以繼 承UIInput類別,這可以讓您省去一些處理細節的功夫,在繼承UIComponent或其子類別後,與編碼相關的主要有三個方 法:encodeBegin()、encodeChildren()和encodeEnd()。其中encodeChildren()是在包括子組件時必 須定義,Component如果它的 getRendersChildren()方法傳回true時會呼叫encodeChildren()方法,預設上, getRendersChildren()方法傳回false。

由於我們的自訂組件相當簡單,所以將編碼的動作寫在encodeBegin()或是encodeEnd()都可以,我們這邊是定義encodeBegin ()方法:

UITextWithCmd.java

package onlyfun.caterpillar;

import java.io.IOException;
import java.util.Map;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

public class UITextWithCmd extends UIInput {
private static final String TEXT = ".text";
private static final String CMD = ".cmd";

public UITextWithCmd() {
setRendererType(null);
}

public void encodeBegin(FacesContext context)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
String clientId = getClientId(context);

encodeTextField(writer, clientId);
encodeCommand(writer, clientId);
}

public void decode(FacesContext context) {
// .....
}

private void encodeTextField(ResponseWriter writer,
String clientId) throws IOException {
writer.startElement("input", this);
writer.writeAttribute("name", clientId + TEXT, null);

Object value = getValue();
if(value != null) {
writer.writeAttribute("value",
value.toString(), null);
}

String size = (String) getAttributes().get("size");
if(size != null) {
writer.writeAttribute("size", size, null);
}

writer.endElement("input");
}

private void encodeCommand(ResponseWriter writer,
String clientId) throws IOException {
writer.startElement("input", this);
writer.writeAttribute("type", "submit", null);
writer.writeAttribute("name", clientId + CMD, null);
writer.writeAttribute("value", "submit", null);
writer.endElement("input");
}
}

 

 在encodeBegin()方法中,我們取得ResponseWriter對象,這個對象可以協助您輸出HTML卷 標、屬性等,我們使用 getClientId()取得組件的id,這個id是每個組件的唯一識別,預設上如果您沒有指定,則JSF會自動爲您產生id值。

 接着我們分別對輸入文字字段及送出鈕作HTML標籤輸出,在輸出時,我們將name屬性設成clientId與一個字符串值的結合(即TEXT或CMD),這是爲了方便在譯碼時,取得對應name屬性的請求值。

在encodeTextField中我們有呼叫getValue()方法,這個方法是從UIOutput繼承下來 的,getValue() 方法可以取得Component的設定值,這個值可能是靜態的屬性設定值,也可能是JSFExpression的綁定值,預設會先從組件的屬性設定值開始 找尋,如果找不到,再從綁定值(ValueBinding對象)中找尋,組件的屬性值或綁定值的設定,是在定義Tag時要作的事。

編碥的部份總結來說,是取得Component的值並作適當的HTML標籤輸出,再來我們看看譯碼的部份,這是定義在decode()方法中,將下面的內容加入至上面的類別定義中:

public void decode(FacesContext context) {
Map reqParaMap = context.getExternalContext().
getRequestParameterMap();
String clientId = getClientId(context);

String submittedValue =
(String) reqParaMap.get(clientId + TEXT);
setSubmittedValue(submittedValue);
setValid(true);
}

 

我們必須先取得RequestParameterMap,這個Map對象中填入了所有客戶端傳來的請求參數, Component在這個方法中有機會查詢這些請求參數中,是否有自己所想要取得的數據,記得我們之前譯碼時,是將輸入字段的name屬性譯碼爲 clientid加上一個字符串值(即TEXT設定的值),所以這時,我們嘗試從RequestParameterMap中取得這個請求值。

取得請求值之後,您可以將數據藉由setSumittedValue()設定給綁定的bean,最後呼叫setValid()方法,這個方法設定爲 true時,表示組件正確的獲得自己的值,沒有任何的錯誤發生。

由於我們先不使用Renderer,所以在建構函式中,我們設定RendererType爲null,表示我們不使用Renderer進行譯碼輸出:

public UITextWithCmd() {
setRendererType(null);
}

 

  在我們的例子中,我們都是處理字符串對象,所以這邊不需要轉換器,如果您需要使用轉換器,可以呼叫setConverter()方法加以設定,在不使用 Renderer的時候,Component要設定轉換器來自行進行字符串與對象的轉換。

7.3.2組件卷標

完成Component的自訂,接下來要設定一個自訂Tag與之對應,自訂Tag的目的,在於設定 Component屬性,取得Componenty型態,取得Renderer型態值等;屬性的設定包括了設定靜態值、設定綁定值、設定驗證器等等。要自 訂與Component對應的Tag,您可以繼承UIComponentTag,例如:

TextWithCmdTag.java

package onlyfun.caterpillar;

import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.webapp.UIComponentTag;

public class TextWithCmdTag extends UIComponentTag {
private String size;
private String value;

public String getComponentType() {
return "onlyfun.caterpillar.TextWithCmd";
}

public String getRendererType() {
return null;
}

public void setProperties(UIComponent component) {
super.setProperties(component);

setStringProperty(component, "size", size);
setStringProperty(component, "value", value);
}

private void setStringProperty(UIComponent component,
String attrName, String attrValue) {
if(attrValue == null)
return;

if(isValueReference(attrValue)) {
FacesContext context =
FacesContext.getCurrentInstance();
Application application =
context.getApplication();
ValueBinding binding =
application.createValueBinding(attrValue);
component.setValueBinding(attrName, binding);
}
else {
component.getAttributes().
put(attrName, attrValue);
}
}

public void release() {
super.release();
size = null;
value = null;
}

public String getSize() {
return size;
}

public void setSize(String size) {
this.size = size;
}

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}
}

  

首先看到這兩個方法:

public String getComponentType() {
return "onlyfun.caterpillar.TextWithCmd";
}

public String getRendererType() {
return null;
}

 

由於我們的Component目前不使用Renderer,所以getRendererType()傳回null值,而 getComponentType()在於讓JSF取得這個Tag所對應的Component,所傳回的值在faces-config.xml中要有定 義,例如:

<component>
< component-type>
onlyfun.caterpillar.TextWithCmd
< /component-type>
< component-class>
onlyfun.caterpillar.UITextWithCmd
< /component-class>
< /component>

 

藉由faces-config.xml中的定義,JSF可以得知 onlyfun.caterpillar.TextWithCmd的真正類別,而這樣的定義方式很顯然的,您可以隨時換掉<component- class>所對應的類別,也就是說,Tag所對應的Component是可以隨時替換的。
  在設定Component屬性值時,可以由component.getAttributes()取得Map對象,並將卷標屬性值存入Map 中,這個Map對象可以在對應的Component中使用getAttributes()取得,例如在上一個主題中的UITextWithCmd中可以如 下取得存入Map的size屬性:
UITextWithCmd.java

package onlyfun.caterpillar;
import java.io.IOException;
import java.util.Map;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
public class UITextWithCmd extends UIInput {
private void encodeTextField(ResponseWriter writer,
String clientId) throws IOException {
String size = (String) getAttributes().get("size");
if(size != null) {
writer.writeAttribute("size", size, null);
}
}
}

 

可以使用isValueReference()來測試是否爲JSF Expression Language的綁定語法,如果是的話,則我們必須建立ValueBinding對象,並設定值綁定:

private void setStringProperty(UIComponent component,
String attrName, String attrValue) {
if(attrValue == null)
return;

if(isValueReference(attrValue)) {
FacesContext context =
FacesContext.getCurrentInstance();
Application application =
context.getApplication();
ValueBinding binding =
application.createValueBinding(attrValue);
component.setValueBinding(attrName, binding);
}
else {
component.getAttributes().
put(attrName, attrValue);
}
}

 

如果是value屬性,記得在上一個主題中我們提過,從UIOutput繼承下來的getValue()方法可以取得 Component的value設定值,這個值可能是靜態的屬性設定值,也可能是JSF Expression的綁定值,預設會先從組件的屬性設定值開始找尋,如果找不到,再從綁定值(ValueBinding對象)中找尋。

最後,我們必須提供自訂Tag的tld檔:
textcmd.tld

<?xml version="1.0" encoding="UTF-8"?>
< taglib version="2.0"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd">
< tlib-version>1.0</tlib-version>
< jsp-version>2.0</jsp-version>
< short-name>textcmd</short-name>
< uri>http://caterpillar.onlyfun.net/textcmd</uri>
< tag>
< name>textcmd</name>
< tag-class>onlyfun.caterpillar.TextWithCmdTag</tag-class>
< body-content>empty</body-content>
< attribute>
< name>size</name>
< /attribute>
< attribute>
< name>value</name>
< required>true</required>
< /attribute>
< /tag>
< /taglib>

7.3.3使用自訂組件

在Component與Tag自訂完成後,這邊來看看如何使用它們,首先定義faces-config.xml:
faces-config.xml

<?xml version="1.0" encoding="UTF-8"?>
< !DOCTYPE faces-config PUBLIC "-//Sun Microsystems,
Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
< faces-config>
< component>
< component-type>
onlyfun.caterpillar.TextWithCmd
< /component-type>
< component-class>
onlyfun.caterpillar.UITextWithCmd
< /component-class>
< /component>
< managed-bean>
< managed-bean-name>someBean</managed-bean-name>
< managed-bean-class>
onlyfun.caterpillar.SomeBean
< /managed-bean-class>
< managed-bean-scope>session</managed-bean-scope>
< /managed-bean>
< /faces-config>

  

<component>中定義Component的型態與實際的類別對應,在您於自訂Tag中呼叫 getComponentType()方法所返回的值,就是尋找<component-type>設定的值對應,並由此得知真正對應的 Component類別。

 

我們所撰寫的SomeBean測試類別如下:

SomeBean

package onlyfun.caterpillar;
public class SomeBean {
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}

 

這邊寫一個簡單的網頁來測試一下我們撰寫的自訂組件:
index.jsp

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
< %@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
< %@ taglib uri="/WEB-INF/textcmd.tld" prefix="oc" %>
< html>
< link href="styles.css" rel="stylesheet" type="text/css"/>
< head>
< title></title>
< /head>
< body>
< f:view>
< h:form>
Input data: <oc:textcmd size="10"
value="#{someBean.data}"/>
< /h:form>
< h:outputText value="#{someBean.data}"/>
< /f:view>
< /body>
< /html>

7.3.4自訂 Renderer

Component可以將譯碼、編碼的動作交給Renderer,這讓您的表現層技術可以輕易的抽換,我們可以將之前的自訂組件的譯碼、編碼動作移 出至 Renderer,不過由於我們之前設計的Component是個很簡單的組件,事實上,如果只是要新增一個Command在輸入字段旁邊,我們並不需要 大費周章的自訂一個新的組件,我們可以直接爲輸入字段更換一個自訂的Renderer。要自訂一個Renderer,您要繼承 javax.faces.render.Renderer,我們的自訂Renderer如下:
TextCmdRenderer.java

package onlyfun.caterpillar;

import java.io.IOException;
import java.util.Map;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.Renderer;

public class TextCmdRenderer extends Renderer {
private static final String TEXT = ".text";
private static final String CMD = ".cmd";

public void encodeBegin(FacesContext context,
UIComponent component) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String clientId = component.getClientId(context);

encodeTextField(component, writer, clientId);
encodeCommand(component, writer, clientId);
}

public void decode(FacesContext context,
UIComponent component) {
Map reqParaMap = context.getExternalContext().
getRequestParameterMap();
String clientId = component.getClientId(context);

String submittedValue =
(String) reqParaMap.get(clientId + TEXT);
((EditableValueHolder) component).setSubmittedValue(
submittedValue);
((EditableValueHolder) component).setValid(true);
}

private void encodeTextField(UIComponent component,
ResponseWriter writer, String clientId)
throws IOException {
writer.startElement("input", component);
writer.writeAttribute("name", clientId + TEXT, null);

Object value = ((UIInput) component).getValue();
if(value != null) {
writer.writeAttribute("value",
alue.toString(), null);
}

String size =
(String) component.getAttributes().get("size");
if(size != null) {
writer.writeAttribute("size", size, null);
}

writer.endElement("input");
}

private void encodeCommand(UIComponent component,
ResponseWriter writer,
String clientId) throws IOException {
writer.startElement("input", component);
writer.writeAttribute("type", "submit", null);
writer.writeAttribute("name", clientId + CMD, null);
writer.writeAttribute("value", "submit", null);
writer.endElement("input");
}
}

 

這個自訂的Renderer其譯碼、編碼過程,與之前直接在Component中進行譯碼或編碼過程是類似的,所不同的是在譯碼與編碼的方法上,多 了UIComponent參數,代表所代理繪製的Component。接下來在自訂Tag上,我們的TextWithCmdTag與之前主題所介紹的沒什 麼差別,只不過在getComponentType()與 getRendererType()方法上要修改一下:
TextWithCmdTag.java

package onlyfun.caterpillar;

import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.webapp.UIComponentTag;

public class TextWithCmdTag extends UIComponentTag {
private String size;
private String value;

public String getComponentType() {
return "javax.faces.Input";
}

public String getRendererType() {
return "onlyfun.caterpillar.TextCmd";
}
}

 

getComponentType()取得的是"javax.faces.Input",它實際上對應至UIInput類別,而 getRendererType()取回的是"onlyfun.caterpillar.TextCmd",這會在faces-config.xml中定 義,以對應至實際的Renderer類別:
faces-config.xml

<faces-config>
< render-kit>
< renderer>
< component-family>
javax.faces.Input
< /component-family>
< renderer-type>
onlyfun.caterpillar.TextCmd
< /renderer-type>
< renderer-class>
onlyfun.caterpillar.TextCmdRenderer
< /renderer-class>
< /renderer>
< /render-kit>
< /faces-config>

 

爲Component定義一個Renderer,必須由component family與renderer type共同定義,這並不難理解,因爲一個Component可以搭配不同的Renderer,但它是屬於同一個component family,例如UIInput就是屬於javax.faces.Input這個組件家族,而我們爲它定義一個新的Renderer。接下未完成的範例 可以取之前主題介紹過的,我們雖然沒有自訂組件,但我們爲UIInput置換了一個新的Renderer,這個Renderer會在輸入字段上加入一個按 鈕。如果您堅持使用之前自訂的UITextWithCmd,則可以如下修改:
UITextWithCmd.java

package onlyfun.caterpillar;

import javax.faces.component.UIInput;

public class UITextWithCmd extends UIInput {
public UITextWithCmd() {
setRendererType("onlyfun.caterpillar.TextCmd");
}
}

 

我們只是單純的繼承UIInput,然後使用setRendererType()設 定"onlyfun.caterpillar.TextCmd",但並沒有爲組件加入什麼行爲,看來什麼事都沒有作,但事實上這是因爲繼承了 UIInput,它爲我們處理了大多數的細節。接下來同樣的,設定自訂Tag:
TextWithCmdTag.java

package onlyfun.caterpillar;

import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.webapp.UIComponentTag;

public class TextWithCmdTag extends UIComponentTag {
private String size;
private String value;

public String getComponentType() {
return "onlyfun.caterpillar.TextWithCmd";
}

public String getRendererType() {
return "onlyfun.caterpillar.TextCmd";
}
}

 

要使用自訂的Component,記得要在faces-config.xml中再加入:

<component>
< component-type>
onlyfun.caterpillar.TextWithCmd
< /component-type>
< component-class>
onlyfun.caterpillar.UITextWithCmd
< /component-class>
< /component>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章