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> |
這個例子我們在第一個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> |
定義檔可以有多個,中間以“,”區隔,例如:
/WEB-INF/navigation.xml,/WEB-INF/beans.xml |
一個Bean最基本要定義Bean的名稱、類別與存活范圍,例如:
<managed-bean> |
如果要在其它類別中取得Bean對象,則可以先取得javax.faces.context.FacesContext,它代表了JSF目前的執行環境對象,接着嘗試取得javax.faces.el.ValueBinding對象,從中取得指定的Bean對象,例如:
FacesContext context = FacesContext.getCurrentInstance(); |
如果只是要嘗試取得Bean的某個屬性,則可以如下:
FacesContext context = FacesContext.getCurrentInstance(); |
如果有必要在啟始Bean時,自動設置屬性的初始值,則可以如下設定:
<managed-bean> |
如果要設定屬性為null值,則可以使用<null-value/>標簽,例如:
<managed-property> |
當然,您的屬性不一定是字符串值,也許會是int、float、boolean等等型態,您可以設定<value> 值時指定這些值的字符串名稱,JSF會嘗試進行轉換,例如設定為true時,會嘗試使用Boolean.valueOf()方法轉換為boolean的 true,以下是一些可能進行的轉換:
型態 |
轉換 |
short、int、long、float、double、byte,或相應的Wrapper類別 |
嘗試使用Wrapper的valueOf()進行轉換,如果沒有設置,則設為0 |
boolean 或 Boolean |
嘗試使用Boolean.valueOf()進行轉換,如果沒有設置,則設為false |
char 或 Character |
取設置的第一個字符,如果沒有設置,則設為0 |
String 或 Object |
即設定的字符串值,如果沒有設定,則為空字符串new String("") |
您也可以將其它產生的Bean設定給另一個Bean的屬性,例如:
<managed-bean>
<managed-bean> |
在上面的設定中,在OtherBean中的user屬性,接受一個UserBean型態的對象,我們設定為前一個名稱為user的UserBean對象。
2.3 Beans上的 List和Map
如果您的Bean上有接受List或Map型態的屬性,則您也可以在組態檔案中直接設定這些屬性的值,一個例子如下:
<managed-bean>
<managed-property> |
這是一個設定接受List型態的屬性,我們使用<list-entries>卷標指定將設定一個List對象,其 中<value-class>指定將存入List的型態,而<value>指定其值,如果是基本型態,則會嘗試使用指定 的<value-class>來作Wrapper類別。
設定Map的話,則是使用<map-entries>標簽,例如:
<managed-bean>
<managed-property> |
由於Map對象是以key-value對的方式來存入,所以我們在每一個<map-entry>中使用<key> 與<value>標簽來分別指定。您也可以直接像設定Bean一樣,設定一個List或Map對象,例如在JSF附的范例中,有這樣的設定:
<managed-bean> |
而范例中另一個設定List的例子如下:
<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 { {
public void setDate(Date date) { |
這個Bean的屬性接受Date型態的參數,按理來說,接收到HTTP傳來的數據中若有相關的日期信息,我們必須剖析這個信息,再轉換為Date對象,然而我們可以使用JSF的標准轉換器來協助這項工作,例如:
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <f:view> <html> 設定的日期是: <h:form> |
在<f:convertDateTime>中,我們使用pattern指定日期的樣式為dd/MM/yyyy,即「日/月/公元」格 式,如果轉換錯誤,則<h:message>可以顯示錯誤訊息,for屬性參考至<h:inputText> 的id屬性,表示將有關dateField的錯誤訊息顯示出來。假設faces-config.xml是這樣定義的:
faces-config.xml
<?xml version="1.0"?>
<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); |
簡單的說,第一個方法會接收從客戶端經由HTTP傳來的字符串數據,您在第一個方法中將之轉換為您的自訂對象,這個自訂對象將會自動設定給您指定的 Bean對象;第二個方法就是將從您的Bean對象得到的對象轉換為字符串,如此才能藉由HTTP傳回給客戶端。直接以一個簡單的例子來作說明,假設您有 一個User類別:
User.java
package onlyfun.caterpillar;
public class User { public String getFirstName() { public void setFirstName(String firstName) { public String getLastName() { public void setLastName(String lastName) { |
這個User類別是我們轉換器的目標對象,而您有一個GuestBean類別:
GuestBean.java
package onlyfun.caterpillar;
public class GuestBean { |
這個Bean上的屬性直接傳回或接受User型態的參數,我們來實作一個簡單的轉換器,為HTTP字符串與User對象進行轉換:
UserConverter.java
package onlyfun.caterpillar;
import javax.faces.component.UIComponent; public class UserConverter implements Converter { public Object getAsObject(FacesContext context, UIComponent component, String str) throws ConverterException {
public String getAsString(FacesContext context, UIComponent component,Object obj) |
實作完成這個轉換器,我們要告訴JSF這件事,這是在faces-config.xml中完成注冊:
faces-config.xml
<?xml version="1.0"?>
<faces-config> <managed-bean> |
注冊轉換器時,需提供轉換器識別(Converter ID)與轉換器類別,接下來要在JSF頁面中使用轉換器的話,就是指定所要使用的轉換器識別,例如:
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<f:view> <html> Guest名稱是:<b> <h:form> |
您也可以<f:converter>卷標並使用converterId屬性來指定轉換器,例如:
<h:inputText id="userField" value="#{guest.user}"> |
除了向JSF注冊轉換器之外,還有一個方式可以不用注冊,就是直接在Bean上提供一個取得轉換器的方法,例如:
GuestBean.java
package onlyfun.caterpillar; import javax.faces.convert.Converter; public class GuestBean {
|
之后可以直接結合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" %> |
在<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;
public class PasswordValidator implements Validator {
|
您要實作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"?>
<faces-config> |
要使用自訂的驗證器,我們可以使用<f:validator>卷標並設定validatorId屬性,例如:
<h:inputSecret value="#{user.password}" required="true"> |
您也可以讓Bean自行負責驗證的工作,可以在Bean上提供一個驗證方法,這個方法沒有傳回值,並可以接收FacesContext、UIComponent、Object三個參數,例如:
UserBean.java
package onlyfun.caterpillar; import javax.faces.application.FacesMessage; public class UserBean { |
接着可以在頁面下如下使用驗證器:
<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.CONVERSION是用來設定當轉換器發現錯誤時顯示的訊息,而 javax.faces.component.UIInput.REQUIRED是在標簽設定了required為true,而使用者沒有在字段輸入時顯 示的錯誤訊息。您要在faces-config.xml中告訴JSF您使用的訊息文件名稱,例如:
faces-config.xml
<?xml version="1.0"?> |
在這邊我們設定了訊息檔案的名稱為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}分別代表minimum與maximum所設定的屬性 |
javax.faces.validator.DoubleRangeValidator.MAXIMUM、javax.faces.validator.LongRangeValidator.MAXIMUM |
Validation Error: Value is greater than allowable maximum of '{0}'. |
DoubleRangeValidator或LongRangeValidator,{0}表示maximum屬性 |
javax.faces.validator.DoubleRangeValidator.MINIMUM、javax.faces.validator.LongRangeValidator.MINIMUM |
Validation Error: Value is less than allowable minimum of '{0}'. |
DoubleRangeValidator或LongRangeValidator,{0}代表minimum屬性 |
javax.faces.validator.DoubleRangeValidator.TYPE、javax.faces.validator.LongRangeValidator.TYPE |
Validation Error: Value is not of the correct type. |
DoubleRangeValidator或LongRangeValidator |
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. |
除了在訊息資源文件中提供訊息,您也可以在程序中使用FacesMessage來提供訊息,例如在 自訂驗證器 中我們就這么用過:
if(password.length() < 6) { } |
最好的方法是在訊息資源文件中提供訊息,這么一來如果我們要修改訊息,就只要修改訊息資源文件的內容,而不用修改程序,來看一個簡單的例子,假設我們的訊息資源文件中有以下的內容:
onlyfun.caterpillar.message1=This is message1. |
則我們可以在程序中取得訊息資源文件的內容,例如:
package onlyfun.caterpillar; |
接下來您可以將FacesMessage對象填入ValidatorException或ConverterException后再丟 出,FacesMessage建構時所使用的三個參數是嚴重程度、概述訊息與詳述訊息,嚴重程度有SEVERITY_FATAL、 SEVERITY_ERROR、SEVERITY_WARN與SEVERITY_INFO四種。 如果需要在訊息資源文件中設定{0}、{1}等參數,則可以如下:
String message = rsBundle.getString( "onlyfun.caterpillar.message2"); |
如此一來,在顯示訊息時,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:attribute>卷標來設定屬性,接着我們可以如下取得所設定的屬性:
public void validate(FacesContext context, |
您也可以開發自己的一組驗證卷標,並提供相關屬性設定,這需要了解JSPTag Library的撰寫,所以請您先參考JSP/Servlet 中有關於JSP TagLibrary的介紹。
要開發驗證器轉用標簽,您可以直接繼承javax.faces.webapp.ValidatorTag,這個類別可以 幫您處理大部份的細節,您所需要的,就是重新定義它的createValidator()方法,我們以改寫 自訂驗證器 中的PasswordValidator為例:
PasswordValidator.java { throws ValidatorException { { } if(pattern != null && !password.matches(pattern)) { } } } |
主要的差別是我們提供了pattern屬性,在validate()方法中進行驗證時,是根據我們所設定的pattern屬性,接着我們繼承javax.faces.webapp.ValidatorTag來撰寫自己的驗證標簽:
PasswordValidatorTag.java
package onlyfun.caterpillar; { { }
protected Validator createValidator() { } } |
application.createValidator()方法建立驗證器對象時,是根據在faces-config.xml中注冊驗證器的識別(Validater ID):
faces-config.xml
<?xml version="1.0"?> |
剩下來的工作,就是布署tld描述檔了,我們簡單的定義一下:
taglib.tld
<?xml version="1.0" encoding="UTF-8" ?> |
而我們的index.jsp改寫如下:
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> |
主要的差別是,我們使用了自己的驗證器標簽:
<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; |
在上例中,我們讓verify方法接收一個ActionEvent對象,當使用者按下按鈕,會自動產生ActionEvent對象代表事件來源,我 們故意在錯誤訊息之后如上事件來源的字符串描述,這樣就可以在顯示錯誤訊息時一並顯示事件來源描述。為了提供ActionEvent的存取能力,您的 index.jsp可以改寫如下:
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> |
主要改變的是按鈕上使用了actionListener屬性,這種方法可以使用一個 ActionListener,JSF會先檢查是否有指定的 actionListener,然后再檢查是否指定了動作方法並產生預設的ActionListener,並根據其傳回值導航頁面。
如果您要注冊多個ActionListener,例如當使用者按下按鈕時,順便在記錄文件中增加一些記錄訊息,您可以實作javax.faces.event.ActionListener,例如:
LogHandler.java
package onlyfun.caterpillar; |
這么一來,您就可以使用<f:actionListener>卷標向組件注冊事件,例如:
<h:commandButton value="送出" action="#{user.outcome}"> |
<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" %> |
這是一個可以讓使用者決定使用語系的示范,最后一個commandButton組件被設定了immediate屬性,當按下這個按鈕后,JSF 套用請求值之后會立即處理指定的actionListener,而不再進行驗證、更新模型值,簡單的說,就這個程序來說,您在輸入字段與密碼字段中填入的 值,不會影響您的user.name與user.password。基於范例的完整起見,我們列出這個程序Bean對象及faces- config.xml:
UserBean.java
package onlyfun.caterpillar; |
faces-config.xml
<?xml version="1.0"?> |
訊息資源文件的內容則是如下:
messages_en.properties
titleText=JSF Demo |
Text中設定的是「中文」轉換為Java Unicode Escape格式的結果,另一個訊息資源文件的內容則是英文訊息的翻譯而已,其轉換為Java Unicode Escape格式結果如下:
messages_zh_TW.properties
titleText=JSF\u793a\u7bc4 |
welcome.jsp就請自行設計了,程序的畫面如下:
4.3值變事件
如果使用者改變了JSF輸入組件的值后送出窗體,就會發生值變事件(Value ChangeEvent),這會丟出一個javax.faces.event.ValueChangeEvent對象,如果您想要處理這個事件,有兩種方 式,一是直接 設定JSF輸入組件的valueChangeListener屬性,例如:
<h:selectOneMenu value="#{user.locale}" |
為了仿真GUI中選擇了選單項目之后就立即發生反應,我們在onchange屬性中使用了JavaScript,其作用是在選項項目發生改變之 后,立即送出窗體,而不用按下提交按鈕;而valueChangeListener屬性所綁定的user.changeLocale方法必須接受 ValueChangeEvent對象,例如:
UserBean.java
package onlyfun.caterpillar; |
另一個方法是實作javax.faces.event.ValueChangeListener接口,並定義其processValueChange()方法,例如:
SomeListener.java
package onlyfun.caterpillar; |
然后在JSF頁面上使用<f:valueChangeListener>卷標,並設定其type屬性,例如:
{code:borderStyle=solid} |
下面這個頁面是對 立即事件 中的范例程序作一個修改,將語言選項改以下拉式選單的選擇方式呈現,這必須配合上面提供的UserBean類別來使用:
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> |
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.ANY_PHASE指的是任何的階段轉換時,就進行通知;您可以在beforePhase()與afterPhase()中撰寫階段前后撰寫分別想要處理的動作,例如下面這個簡單的類別會列出每個階段的名稱:
ShowPhaseListener.java
package onlyfun.caterpillar; |
撰寫好PhaseListener后,我們可以在faces-config.xml中向Lifecycle進行注冊:
faces-config.xml
<?xml version="1.0"?> |
您可以使用這個簡單的類別,看看在請求任一個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}"> |
value所綁定的屬性可以接受字符串以外的型態或是自訂型態,但記得如果是必須轉換的型態或自訂型態,必須搭配標准轉換器 或 自訂轉換器 來轉換為對象,<h:selectOneRadio>的外觀如下:
您也可以設定layout屬性,可設定的屬性是lineDirection、pageDirection,預設是lineDirection,也就是由左到右來排列選項,如果設定為pageDirection,則是由上至下排列選項,例如設定為:
<h:selectOneRadio layout="pageDirection" value="#{user.education}"> |
則外觀如下:
<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}"> |
value所綁定的屬性必須是數組或集合(Collection)對象,在這個例子中所使用的是boolean數組,例如:
UserBean.java
package onlyfun.caterpillar; |
如果是其它型態的對象,必要時必須搭配轉換器(Converter)進行字符串與對象之間的轉換。
下圖是<h:selectManyCheckbox>的外觀,這是將layout設定為pageDirection的外觀: <h:selectManyListbox>的設定方法類似,其外觀如下:
<h:selectManyMenu>在不同的瀏覽器中會有不同的外觀,在Mozilla Firefox中是這樣的:
在InternetExplorer則是這樣的:
選擇類標簽可以搭配<f:selectItem>或<f:selectItems>卷標來設定選項,例如:
<f:selectItem itemLabel="高中" |
itemLabel屬性設定顯示在網頁上的文字,itemValue設定發送至伺服端時的值,itemDescription 設定文字描述,它只作用於一些工具程序,對HTML沒有什么影響,itemDisabled設定是否選項是否作用,這些屬性也都可以使用JSF ExpressionLanguage來綁定至一個值。<f:selectItem>也可以使用value來綁定一個傳回 javax.faces.model.SelectItem的方法,例如:
<f:selectItem value="#{user.sex}"/> |
則綁定的Bean上必須提供下面這個方法:
public SelectItem getSex() { |
如果要一次提供多個選項,則可以使用<f:selectItems>,它的value綁定至一個提供傳回SelectItem的數組、集合,或者是Map對象的方法,例如:
<h:selectOneRadio value="#{user.education}"> |
這個例子中<f:selectItems>的value綁定至user.educationItems,其內容如下:
private SelectItem[] educationItems; |
在這個例子中,SelectItem的第一個建構參數用以設定value,而第二個參數用以設定label,SelectItem還提供有數個建構函式,記得可以參考一下線上API文件。
您也可以提供一個傳回Map對象的方法,Map的key-value會分別作為選項的label-value,例如:
<h:selectManyCheckbox layout="pageDirection" value="#{user.preferColors}"> |
您要提供下面的程序來搭配上面這個例子:
private Map preferColorItems; |
5.6其它標簽
<h:messages>或<h:message>標簽的介紹,在錯誤訊息處理中已經有介紹了。
l <h:graphicImage>
這個卷標會繪制一個HTML <img>卷標,value可以指定路徑或圖片URL,路徑可以指定相對路徑或絕對路徑,例如:
<h:graphicImage value="/images/logowiki.jpg"/> |
這個卷標可以用來作簡單的組件排版,它會使用HTML表格卷標來繪制表格,並將組件置於其中,主要指定columns屬性,例如設定為 2:
<h:panelGrid columns="2"> |
則自動將組件分作 2 個 column來排列,排列出來的樣子如下:
<h:panelGrid>的本體間只能包括JSF組件,如果想要放入非JSF組件,例如簡單的樣版(template)文字,則要使用 <f:verbatim>包括住,例如:
<h:panelGrid columns="2"> |
這個組件用來將數個JSF組件包裝起來,使其看來像是一個組件,例如:
<h:panelGrid columns="2"> |
在<h:panelGroup>中包括了兩個<h:commandButton>,這使得< h:panelGrid>在處理時,將那兩個<h:commandButton>看作是一個組件來看待,其完成的版面配置如下所示:
6. 表格處理
對於必須使用表格方式呈現的數據,JSF 的 <h:dataTable> 卷標協助您進行動態表格數據的輸出。
6.1簡單的表格
很多數據經常使用表格來表現,JSF提供<h:dataTable>卷標讓您得以列舉數據並使用表格方式來呈現,舉個實際的例子來看,假設您撰寫了以下的兩個類別:
UserBean.java
package onlyfun.caterpillar; |
TableBean.java
package onlyfun.caterpillar; |
在TableBean中,我們假設getUserList()方法實際上是從數據庫中查詢出UserBean的內容,之后傳回List對象,若我們的 faces-config.xml如下:
faces-config.xml
<?xml version="1.0" encoding="UTF-8"?> |
我們可以如下使用<h:dataTable>來產生表格數據:
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> |
<h:dataTable>的value值綁定tableBean的userList方法,它會一個一個取出 List中的數據並設定給var設定的user,之后在每一個column中我們可以顯示所列舉出的user.name與user.password,程 序的結果會如下所示:
所產生的HTML表格卷標如下:
<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"> |
所產生的表格如下所示:
另外,對於表頭、表尾仍至於每一行列,都可以分別設定CSS風格,例如下面這個styles.css摘錄自Core JSF一書:
styles.css
.orders { |
可以在我們的頁面中如下加入:
<link href="styles.css" rel="stylesheet" type="text/css"/> |
則顯示的表格結果如下:
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; |
在這個Bean中,我們直接設定DataModel?,將userList設定給它,如您所看到的,我們還可以取得DataModel?的各個變 項,在這個例子中,select()將作為點選表格之后的事件處理方法,我們可以藉由DataModel?的getRowIndex ()來取得所點選的是哪一row的資料,例如:
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> |
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; |
在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) { |
我們必須先取得RequestParameterMap,這個Map對象中填入了所有客戶端傳來的請求參數, Component在這個方法中有機會查詢這些請求參數中,是否有自己所想要取得的數據,記得我們之前譯碼時,是將輸入字段的name屬性譯碼為 clientid加上一個字符串值(即TEXT設定的值),所以這時,我們嘗試從RequestParameterMap中取得這個請求值。
取得請求值之后,您可以將數據藉由setSumittedValue()設定給綁定的bean,最后呼叫setValid()方法,這個方法設定為 true時,表示組件正確的獲得自己的值,沒有任何的錯誤發生。
由於我們先不使用Renderer,所以在建構函式中,我們設定RendererType為null,表示我們不使用Renderer進行譯碼輸出:
public UITextWithCmd() { |
在我們的例子中,我們都是處理字符串對象,所以這邊不需要轉換器,如果您需要使用轉換器,可以呼叫setConverter()方法加以設定,在不使用 Renderer的時候,Component要設定轉換器來自行進行字符串與對象的轉換。
7.3.2組件卷標
完成Component的自訂,接下來要設定一個自訂Tag與之對應,自訂Tag的目的,在於設定 Component屬性,取得Componenty型態,取得Renderer型態值等;屬性的設定包括了設定靜態值、設定綁定值、設定驗證器等等。要自 訂與Component對應的Tag,您可以繼承UIComponentTag,例如:
TextWithCmdTag.java
package onlyfun.caterpillar; |
首先看到這兩個方法:
public String getComponentType() { |
由於我們的Component目前不使用Renderer,所以getRendererType()傳回null值,而 getComponentType()在於讓JSF取得這個Tag所對應的Component,所傳回的值在faces-config.xml中要有定 義,例如:
<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; |
可以使用isValueReference()來測試是否為JSF Expression Language的綁定語法,如果是的話,則我們必須建立ValueBinding對象,並設定值綁定:
private void setStringProperty(UIComponent component, |
如果是value屬性,記得在上一個主題中我們提過,從UIOutput繼承下來的getValue()方法可以取得 Component的value設定值,這個值可能是靜態的屬性設定值,也可能是JSF Expression的綁定值,預設會先從組件的屬性設定值開始找尋,如果找不到,再從綁定值(ValueBinding對象)中找尋。
最后,我們必須提供自訂Tag的tld檔:
textcmd.tld
<?xml version="1.0" encoding="UTF-8"?> |
7.3.3使用自訂組件
在Component與Tag自訂完成后,這邊來看看如何使用它們,首先定義faces-config.xml:
faces-config.xml
<?xml version="1.0" encoding="UTF-8"?> |
<component>中定義Component的型態與實際的類別對應,在您於自訂Tag中呼叫 getComponentType()方法所返回的值,就是尋找<component-type>設定的值對應,並由此得知真正對應的 Component類別。
我們所撰寫的SomeBean測試類別如下:
SomeBean
package onlyfun.caterpillar; |
這邊寫一個簡單的網頁來測試一下我們撰寫的自訂組件:
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> |
7.3.4自訂 Renderer
Component可以將譯碼、編碼的動作交給Renderer,這讓您的表現層技術可以輕易的抽換,我們可以將之前的自訂組件的譯碼、編碼動作移 出至 Renderer,不過由於我們之前設計的Component是個很簡單的組件,事實上,如果只是要新增一個Command在輸入字段旁邊,我們並不需要 大費周章的自訂一個新的組件,我們可以直接為輸入字段更換一個自訂的Renderer。要自訂一個Renderer,您要繼承 javax.faces.render.Renderer,我們的自訂Renderer如下:
TextCmdRenderer.java
package onlyfun.caterpillar; |
這個自訂的Renderer其譯碼、編碼過程,與之前直接在Component中進行譯碼或編碼過程是類似的,所不同的是在譯碼與編碼的方法上,多 了UIComponent參數,代表所代理繪制的Component。接下來在自訂Tag上,我們的TextWithCmdTag與之前主題所介紹的沒什 么差別,只不過在getComponentType()與 getRendererType()方法上要修改一下:
TextWithCmdTag.java
package onlyfun.caterpillar; |
getComponentType()取得的是"javax.faces.Input",它實際上對應至UIInput類別,而 getRendererType()取回的是"onlyfun.caterpillar.TextCmd",這會在faces-config.xml中定 義,以對應至實際的Renderer類別:
faces-config.xml
<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; |
我們只是單純的繼承UIInput,然后使用setRendererType()設 定"onlyfun.caterpillar.TextCmd",但並沒有為組件加入什么行為,看來什么事都沒有作,但事實上這是因為繼承了 UIInput,它為我們處理了大多數的細節。接下來同樣的,設定自訂Tag:
TextWithCmdTag.java
package onlyfun.caterpillar; |
要使用自訂的Component,記得要在faces-config.xml中再加入:
<component> |