<tr valign="top"><td width="8"><img alt="" height="1" width="8" src="//www.ibm.com/i/c.gif"/></td><td width="16"><img alt="" width="16" height="16" src="//www.ibm.com/i/c.gif"/></td><td class="small" width="122"><p><span class="ast">需要JavaScript的文件選項無法顯示</span></p></td></tr>
Rick Hightower , CTO
2005 年 3 月 01 日
本系列 懷疑論者的 JSF 一共包含 4 篇文章,本文是其中的第 2 篇。在本文中,Rick Hightower 對 Java™Server Faces (JSF)請求處理生命週期的主要階段進行了介紹。作者使用一個範例程式,詳細介紹了請求處理的 5 個階段。隨著介紹的深入,作者將向您示範如何在 JSF 中採用 JavaScript 技術對即時事件進行處理,最後簡要介紹一下 JSF 中提供的眾多元件來結束對 JSF 元件模型的介紹。
與很多流行的觀點不同,我們無需瞭解技術工作原理的所有細節,就可以編寫 JSF 應用程式。您只需要給自己設置一個專案,並從頭到尾不斷修修補補,這樣就可以學習到大量的知識。另一方面,理解必要的基礎知識可以使您的開發工作更加有效 —— 而且會節省很多時間。
在本系列 懷疑論者的 JSF 的第 2 篇文章中,我們將逐一介紹一下 JSF 請求處理生命週期的 5 個階段。我們將介紹在每個階段中會發生什麼,以及這些階段是如何相互連接在一起的,然後使用一個範例程式來示範實際的生命週期。隨著學習的深入,我們還將介紹如何使用在 第 1 部分 中簡要提及的一些內置的 JSF 元件。我們還將向您介紹如何在 JSF 開發中採用 Struts Tiles,以及如何組合使用 JSF 和 JavaScript 技術進行即時事件的處理。
正如上一篇文章中介紹的一樣,範例程式的預設編譯環境是 Maven。您可以透過點擊頁面頂部或底部的 Code 圖示下載原始碼。為了簡單起見,您會發現與上一篇文章中一樣的範例設置。有關編譯環境的設置的詳細說明,請參閱 參考資料 ,其中包括使用 Ant(而不是 Maven)來編譯並執行範例程式的說明。
JSF 生命週期:概述
JSF 程式生命週期的 5 個階段如下(注意每個階段的事件處理):
恢復視圖
應用請求的值;處理驗證
更新模型值;處理事件
呼叫程式;處理事件
進行回應;處理事件
這 5 個階段顯示了 JSF 通常處理 GUI 的順序。雖然這個清單列出了每個階段中事件處理的可能執行順序,但是 JSF 的生命週期很難是固定一成不變的。您可以透過忽略某個階段或合併整個生命週期從而對執行順序進行修改。例如,如果一個無效的請求值被拷貝到一個元件中,那 麼當前的視圖就會重新顯示,而有些階段就可能不會執行。在這種情況中,您可以執行一個 FacesContext.responseComplete
方法呼叫,將用戶重新導向到一個不同的頁面上,然後使用請求分發器(從 FacesContext
中的請求物件中獲得)將其轉導到一個適當的 Web 資源上。另外,您可以呼叫 FacesContext.renderResponse
重新顯示原來的視圖。(詳細資訊請參看下面的範例程式。)
關於本系列文章
這個 4 部分的系列文章 專門用來消除那些懷疑論者對 JavaServer Faces(JSF)技術的恐懼、懷疑或疑慮(FUD),方法是給您一個機會自己透過一個一步步的、容易學習的格式來瞭解這種技術。透過這 4 篇文章的導覽介紹,我們將提供一系列的例子簡要介紹 JSF 的基本架構、特性和功能。一旦熟悉 JSF 處理事情的方法之後,我想您將發現很難再回傳 Struts Model 2 風格的開發了。畢竟,在體驗過 JSF 的事件驅動的 GUI 元件模型之後,誰還會希望重新經歷 XML 配置的惡夢呢?
要掌握本系列導覽的內容,您應該熟悉 Java 程式設計、JavaBeans(即事件模型和屬性)、JavaServer Pages(JSP)、JSP Standard Tag Library Expression Language 以及 Web 開發的所有基本概念。
關 鍵是讓生命週期構成您的開發專案,而不完全依賴於生命週期。在需要時,您可以修改生命週期,而不用擔心破壞您的程式。在大部分情況中,您會發現 JSF 的生命週期是值得遵守的,因為它的邏輯非常好。表單必須在任何應用程式邏輯執行之前進行驗證,並且在進行驗證之前,必須對域中的資料進行轉換。遵守生命週 期的規定,可以讓您更自由地考慮有關驗證和轉換的問題,而不是請求處理本身的階段。有一點非常重要:其他 Web 框架也都具有類似的生命週期;它們只不過是沒有很好地進行宣傳。
專注
有 些使用 JSF 的開發者可能從來都不會編寫一個元件,也不會對框架進行任何擴展;而另外一些人則專注於這種任務的開發。儘管 JSF 的生命週期與大部分那其他項目都是相同的,但是根據在專案中的角色您可以採用不同的階段。如果您更專注於通用的應用程式開發,就可能會關注請求處理生命週 期的中間階段:
如果您專注於 JSF 元件的開發,就可能會關注於整個生命週期中的第一個階段和最後一個階段:
在接下來的幾節中,我們將遍曆 JSF 請求處理生命週期的每個步驟,包括事件處理和驗證。瞭解了每個步驟的基本知識之後,我們將簡要介紹一個範例程式,它可以示範這些步驟如何一起使用。在開始之前,首先來看一下圖 1,這是一個有關 JSF 生命週期的圖。
圖 1. JSF 生命週期
階段 1:恢復視圖
在 JSF 生命週期的第一個階段 ——恢復視圖 —— 中,會有一個來自 FacesServlet
控制器的請求。控制器會對請求進行考察,並提取出視圖的 ID,這是由 JSP 頁面的名字來確定的。
JSF 框架控制器使用這個視圖 ID 來為當前的視圖查找元件。如果這個視圖尚未存在,那麼 JSF 控制器就會建立它。如果這個視圖早已存在,那麼 JSF 控制器就會使用它。這個視圖包含了所有的 GUI 元件。
生命週期的這個階段表示為三個視圖實例:新視圖、原始視圖和後視圖,每個視圖的處理方式都不相同。在 新視圖 的情況中,JSF 會建構 Faces
頁面的視圖,並將事件處理程式和驗證程式繫結到元件上。這個視圖被保存在一個 FacesContext
物件中。
FacesContext
物件包含了 JSF 用來管理當前 session 中當前請求的 GUI 元件狀態所需要的所有狀態資訊。FacesContext
將視圖保存在自己的 viewRoot
屬性中;viewRoot
包含了當前視圖 ID 的所有 JSF 元件。
在 原始視圖 的情況中(第一次載入的是一個頁面),JSF 會建立一個空視圖。這個空視圖會在用戶事件產生時進行填充。JSF 可以直接從原始視圖過渡到進行回應的階段。
在 後視圖(postback) 的情況中(用戶回傳之前存取過的頁面),包含頁面的視圖早已經存在了,因此只需要進行恢復就可以了。在這種情況中,JSF 就使用現有視圖的狀態資訊來重構狀態。後視圖的下一個階段是應用請求值。
階段 2:應用請求值
應用請求值 階段的目的是讓每個元件檢索自己當前的狀態資訊。這些元件必須首先透過 FacesContext
物件進行檢索或建立(使用其值)。雖然元件值也可以從 cookie 或頭檔中進行檢索,但是它們通常是透過請求參數進行檢索的。
如果一個元件的即時事件處理屬性 沒有 設置為 true
,那麼就會對這些值進行轉換。因此,如果 域
被繫結到一個 Integer
屬性上,那麼該值就會被轉換為一個 Integer
類型。如果值的轉換失敗了,那麼就會產生一個錯誤消息,並在 FacesContext
中進行排隊,在產生回應的階段會顯示其中的消息,同時還會顯示所有的驗證錯誤。
如果一個元件的即時事件處理屬性 的確 被設置為 true
,那麼這些值就會被轉換為適當的類型,並進行有效性驗證。然後轉換後的值會被保存到元件中。如果值轉換或值的有效性驗證失敗了,就會產生一個錯誤消息,並在 FacesContext
中進行排隊,在產生回應的階段會顯示其中的消息,同時還會顯示所有的驗證錯誤。
即時事件處理
JSF 的即時事件處理屬性用來處理那些通常不必要對整個表單進行有效性驗證的事件。例如,假設一個雇員表單中有一個單選按鈕來說明他是否是經理。當他選中 Manager 選項時,應用程式就會為經理產生一些內容。由於單選按鈕只用來產生一個列表,而不需要用戶填寫整個表單,因此不需要對整個表單進行有效性驗證。在這種情況 中,您就可以使用即時事件處理。有關這個主題的更詳細內容,請參閱 即時事件處理 。
處理驗證
生 命週期中的第一個事件處理發生在應用請求值階段之後。在這個階段中,每個元件都有一些值需要根據應用程式的驗證規則進行有效性驗證。這些驗證規則可以是預 先進行定義的(JSF 中提供的),也可以由開發者進行定義。用戶所輸入的值會與這些驗證規則進行比較。如果說輸入的值無效,就會向 FacesContext
中添加一個錯誤消息,並且該元件會被表示為無效的。如果一個元件被表示為無效的,那麼 JSF 就會轉到產生回應的階段,在這個階段中會顯示當前的視圖,以及驗證錯誤消息。如果沒有有效性驗證錯誤,那麼 JSF 就會轉到更新模型值的階段。
階段 3:更新模型值
JSF 應用程式生命週期中的第三個階段 ——更新模型值 —— 負責更新伺服器端模型的實際值,通常來講,這都是透過更新後臺 bean(稱為管理 bean)的屬性實作的。只有那些與元件值繫結在一起的 bean 屬性才會被更新。注意這個階段發生在有效性驗證之後,因此可以確保拷貝到 bean 屬性的值都是有效的(至少在表單域一級都是有效的;在業務規則一級仍可能無效)。
階段 4:呼叫程式
在生命週期的第四個階段 ——呼叫程式 —— 中,JSF 控制程式會呼叫程式來處理 表單
的提交操作。元件值已經經過了類型轉換和有效性驗證,並被應用到模型物件中了,因此您現在可以使用它們來執行應用程式的業務邏輯了。
在這個階段,您還可以為一個給定的序列或很多可能的序列指定後面的邏輯視圖,這可以透過為一次成功的表單提交定義一個特定的結果並回傳這個結果來實作。例如:在成功輸出時,將用戶重新導向到下一頁中 。要讓這種導向工作能夠起作用,您需要在 faces-config.xml 檔中建立一個到 成功輸出 的映射作為一條導向規則。一旦導向發生之後,您就轉換到生命週期的最後一個階段了。
階段 5:進行回應
在生命週期的第五個階段 ——進行回應 —— 中,您可以在視圖中顯示當前狀態中的所有元件。
圖 2 是 JSF 生命週期的第五個階段的一個物件狀態圖,包括時間有效性驗證和處理。
圖 2. JSF 生命週期的五階段
範例
現 在您已經對 JSF 生命週期的階段有了基本的瞭解,下面我們將向您介紹在一個範例 Web 應用程式中,這些階段是如何協同工作的。除了示範 JSF 生命週期的基本功能之外,這個應用程式還會利用一些通用的 JSF GUI 元件,例如 Radio List, List, Text Field, Label, Panel 等等,這樣您就可以親自體驗一下在 第 1 部分 中曾經簡要討論過的這些元件。
這個範例程式還會示範在 JSF 中使用其他 Java 技術的兩種方法。它將組合使用 JSF 和 JavaScript 來啟用即時事件處理(在那些對整個表單進行驗證是多餘的情況中),其佈局是由 Struts Tiles 進行管理的。 雖然 Struts Tiles 並不是 JSF 的一個必要部分,但是 tiles 通常用來為一個程式中的所有 JSF 頁面提供一致的外觀。要學習更多有關 Struts Tiles 的內容,請參閱 參考資料 。
程式設置
這 個範例 Web 程式實際上是一個非常簡單的建立、閱讀、更新並刪除(CRUD)一個線上 CD 倉庫中庫存的程式。它包括一個表單,讓用戶可以向系統中輸入新 CD;有一些單選按鈕,讓用戶選擇音樂的分類。當用戶選擇一個分類時,就啟動一些 JavaScript 腳本將這個表單立即發回伺服器。程式組合採用 JSF 和 JavaScript 技術來處理一個元件,而不是整個頁面,這種技術稱為 即時事件處理 。在這種情況中,您可以實作一個子類別清單,而不用驗證表單的其他內容。
這個範例程式還包括一個 CD 清單,它將示範如何使用 JSF 的 dataTable
。從這個頁面中,最終用戶可以根據標題或者藝術家對 CD 清單進行排序。
類別和方法
圖 3 列出了這個範例程式的類別。圖中列出了 4 個類別,我們只關注其中的 3 個:StoreManagerDelegate
、CD
和 StoreController
。
圖 3. 範例程式類別
StoreManagerDelegate
類別是這個程式的業務代表。它為整個模型呈現了主介面。CD
類別是一個資料轉換物件(DTO)。如果這是一個真實的程式,那麼 StoreManagerDelegate
類別就會為添加、刪除和編輯 CD 實作所有的業務規則,還會負責使用一個資料存取物件(DAO)將 CD
儲存到一個永久的儲存介質中。StoreManagerDelegate
和 CD
包含了一些用於這個 MVC 程式的 模型 。
StoreController
類別是本例中的主要後臺 bean。StoreController
類別是 GUI 世界和模型世界之間的黏合劑。它將自己的很多行為都委派給 StoreManagerDelegate
進行處理。StoreController
是這個 MVC 程式的 控制程式 。
StoreController
類別示範了如何建構一個可排序的 CRUD 清單。它具有以下與 CRUD 相關的方法:editCD
、addNew
、addCD
以及 updateCD
。StoreController
還負責將模型物件呈現給表單。在這種情況中,它使用 cd
屬性將當前的 CD
物件呈現給 CD 表單,該屬性的類型就是 CD
。
開始撰碼
開始編寫這個範例程式的最好方法是遍曆它的使用例子:
新增 CD
編輯現有的 CD
根據標題對 CD 進行排序
根據藝術家對 CD 進行排序
第三個使用例子和第四個使用例子的程式碼基本上是相同的,因此我將向您示範如何根據標題進行排序,並將第四個使用例子留作練習,請您自行完成。我們很快就會對使用例子進行程式碼編寫,但是首先讓我們來瞭解一下完成後的應用程式的頁面將是什麼樣子。
並非真正的 CRUD
注意這個程式並不是一個真正的 CRUD 清單程式。它實際上只是一個 CRU 清單,因為我將 D 留給您自己實作了。但是無須擔心,它非常簡單。刪除操作的步驟與編輯操作的步驟非常類似,後者在本文中已經實作了。您可以完成 CRUD 嗎?
圖 4 顯示了具有可排序列的 CD 清單頁面。
圖 4. 具有可排序列的 CD 清單頁面
圖 5 顯示了具有分類元件的 CD 表單頁面。
圖 5. 尚未選擇分類的 CD 表單頁面
圖 6 顯示了具有分類和子類元件的 CD 表單頁面。
圖 6. 已經選擇了分類和子類元件的 CD 表單頁面
使用例子 1:新增 CD
在該程式的第一個使用例子中,用戶將添加一個新 CD:切換到 CD 清單頁面上,點擊 Add CD 鏈結(這是在 listing.jsp 檔中定義的),如清單 1 所示。
清單 1. 在 listing.jsp 中定義的 Add CD 按鈕
<h:commandLink action="#{CDManagerBean.addNew}"> <f:verbatim>Add CD</f:verbatim> </h:commandLink>
這個鏈結被繫結到 CDManagerBean
的 addNew
方法上。這個 addNew
方法在 JSF 生命週期的呼叫程式階段(最後一個階段)被呼叫的。操作被使用 JSF 繫結運算式 #{CDManagerBean.addNew}
繫結到這個方法上。CDManagerBean
是這個程式的儲存控制器的一個別名。CDManagerBean
是這個控制器的邏輯名。控制器類是一個在 faces-config.xml 檔中定義的管理 bean,如清單 2 所示。
清單 2. 在 faces-config.xml 中定義的 StoreController 類
<managed-bean> <description>The "backing file" bean that backs up the CD application</description> <managed-bean-name>CDManagerBean</managed-bean-name> <managed-bean-class>com.arcmind.jsfquickstart.controller.StoreController</managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean>
準備表單
addNew()
方法透過建立一個空 CD 來準備表單,如清單 3 所示。
清單 3. addNew() 建立一個空 CD 表單
[StoreController.java] /** * Prepare the cdForm to add a new CD. * This gets executed before we prompt * the user to add a new CD. * * @return success */ public String addNew() { if (subCategoryList == null) { subCategoryList = new HtmlSelectOneListbox(); } subCategoryList.setRendered(false); this.cd = new CD(); return "success"; }
addNew()
方法透過建立一個新的 CD 來清空 CD 表單域。這個 CD 表單的域被繫結到 cd
屬性的屬性中。這個方法還會將正在顯示的子類別清單清空。
回傳成功結果
接下來,addNew()
方法會被呼叫,控制權被重新導向到成功映射頁面,即 cdForm.jsp 文件。cdForm.jsp 文件是在 faces-config.xml 檔中定義的,如清單 4 所示。
清單 4. cdForm.jsp 是 addNew() 的成功映射
<navigation-rule> <from-view-id>/listing.jsp</from-view-id> ... <navigation-case> <from-action>#{CDManagerBean.addNew}</from-action> <from-outcome>success</from-outcome> <to-view-id>/cdForm.jsp</to-view-id> </navigation-case> </navigation-rule>
清單 4 表明如果用戶從清單切換到 addNew (#{CDManagerBean.addNew})
操作,並且 addNew
操作成功回傳,那就會切換到 cdForm.jsp 頁面。
設置 cdForm 和 panelGrid
cdForm.jsp 是包含 CD 表單的表單。其中具有 ID、Title、Artist、Price、Category 和 Subcategory 的域。這些域被放到一個名為 panelGrid
的容器中。JSF 元件,例如 AWT 元件,具有一些容器和元件。容器 是一個包含其他元件的元件。這是一個 混合設計模式 的例子。panelGrid
有 3 列。每個域都各在一行中,還會有一個標籤和消息用於顯示該域的錯誤消息。cdForm
和 panelGrid
是在清單 5 中定義的。
清單 5. 定義 cdForm 和 panelGrid
<f:view> <h2>CD Form</h2> <h:form id="cdForm"> <h:inputHidden id="cdid" value="#{CDManagerBean.cd.id}"/> <h:panelGrid columns="3" rowClasses="row1, row2"> <h:outputLabel for="title" styleClass="label"> <h:outputText value="Title"/> </h:outputLabel> <h:inputText id="title" value="#{CDManagerBean.cd.title}" required="true"/> <h:message for="title" styleClass="errorText"/> <h:outputLabel for="artist" styleClass="label"> <h:outputText value="Artist"/> </h:outputLabel> <h:inputText id="artist" value="#{CDManagerBean.cd.artist}" required="true"/> <h:message for="artist" styleClass="errorText"/> <h:outputLabel for="price" styleClass="label"> <h:outputText value="Price"/> </h:outputLabel> <h:inputText id="price" value="#{CDManagerBean.cd.price}" required="true"/> <h:message for="price" styleClass="errorText"/> <h:outputLabel for="category" styleClass="label"> <h:outputText value="Category"/> </h:outputLabel> <h:selectOneRadio id="category" value="#{CDManagerBean.cd.category}" immediate="true" onclick="submit()" valueChangeListener="#{CDManagerBean.categorySelected}"> <f:selectItems value="#{CDManagerBean.categories}"/> </h:selectOneRadio> <h:message for="category" styleClass="errorText"/> <h:outputLabel for="subcategory" styleClass="label"> <h:outputText value="Subcategory"/> </h:outputLabel> <h:selectOneListbox id="subcategory" value="#{CDManagerBean.cd.subCategory}" binding="#{CDManagerBean.subCategoryList}"> <f:selectItems value="#{CDManagerBean.subCategories}"/> </h:selectOneListbox> <h:message for="subcategory" styleClass="errorText"/> </h:panelGrid> <br /> <h:commandButton id="submitAdd" action="#{CDManagerBean.addCD}" value="Add CD" rendered="#{not CDManagerBean.editMode}"/> <h:commandButton id="submitUpdate" action="#{CDManagerBean.updateCD}" value="Update CD" rendered="#{CDManagerBean.editMode}"/> </h:form> </f:view>
關於程式碼的註釋
每個輸入域都將該域繫結到控制器的 cd
屬性的一個屬性上。例如,標題的輸入文字域被使用下面的 JSF 繫結運算式繫結到 cd
屬性上:value="#{CDManagerBean.cd.title}"
。
您可能會注意到在清單 5 中幾乎沒有什麼 HTML 語句。這是由於 panelGrid
會產生大部分的 HMTL 語句。注意實際的外觀是由與 panelGrid
相關的樣式表決定的。屬性 rowClasses="row1, row2"
會為正在修改的行設置 CSS 類。第一行是白色的,第二行是灰色的。您還可以為列或其他內容指定 CSS 類。JSF panelGrid
元件可以方便地快速設置表單的佈局。如果您希望實作 panelGrid
沒有提供的功能,就不能使用它:不過可以使用 HTML 自己設置元件的佈局。然而,如果您發現自己在很多頁面上都使用了定制的 HTML,那麼就可能會考慮編寫自己的定制元件。這種想法可以讓您盡可能 DRY 地重用 HTML 語句(DRY 是 don't repeat yourself 的縮寫,這個術語來自於 Dave Thomas 的 Pragmatic Programmer 一書)。
關於清單 5 另外需要注意的是控制器有一個 editMode 屬性,由 cdForm.jsp 用於有選擇地顯示 submitAdd
按鈕或 submitUpdate
按鈕;submitAdd
按鈕是在表單不處於編輯模式時顯示的。submitUpdate
按鈕是在表單處於編輯模式時顯示的。這可以簡化為編輯和添加模式使用相同的 JSP。(預設情況下,表單不處於編輯模式。)這種功能是由 cdForm.jsp 中的每個按鈕上的呈現運算式實作的。例如,清單 6 列出了 submitAdd button rendered="#{not CDManagerBean.editMode}"
上的呈現運算式。submitAdd
按鈕被使用運算式 (action="#{CDManagerBean.addCD}"
) 繫結到 addCD
方法上。
清單 6. 使用 addCD() 方法添加一個 CD
[StoreController.java] /** * Add a cd to the store. * * @return outcome */ public String addCD() { store.addCD(this.cd); return "success"; }
對域進行有效性驗證
在 addCD
方法被呼叫之前,JSF 必須對 GUI 中的域進行有效性驗證。這實際上非常簡單,因為您還沒有為域關聯任何有效性驗證條件。在應用請求值階段,這些值被從請求參數拷貝到元件值中(這是由元件本 身進行的)。現在,價格從一個字串轉換為一個浮點類型。如果用戶為價格輸入的是“abc”,那麼轉換為浮點類型的操作就會失敗,控制權將被重新定向到 cdForm.jsp 頁面上,供最終用戶進行修正。與價格相關的 h:message
將顯示一個轉換錯誤消息。如果所有的值都可以正常進行類型轉換,並且現在都可以使用了(如果需要的話),那麼您就可以進行有效性驗證的處理了。由於這個範 例程式並沒有與元件關聯任何有效性驗證規則(在下一篇文章中我們將介紹這種特性),因此您可以繼續進入更新模型值的階段了。
在更新模型值的階段中,會使用保存在 GUI 元件中的經過轉換和有效性驗證的值來呼叫 CD 的賦值方法。addCD()
方法是在 呼叫程式 階段中被呼叫的。addCD()
方法使用一個業務代理(store
物件)來執行這個操作。addCD
方法在系統中使用 store
物件來儲存 CD。由於 addCD
方法會回傳成功,因此接下來會顯示這個清單,這是在 faces-config.xm 中定義的。在 faces-config.xml 中定義的導向規則如清單 7 所示。
清單 7. addCD 成功輸出的導向規則
<navigation-rule> <from-view-id>/cdForm.jsp</from-view-id> <navigation-case> <from-action>#{CDManagerBean.addCD}</from-action> <from-outcome>success</from-outcome> <to-view-id>/listing.jsp</to-view-id> </navigation-case> ... </navigation-rule>
使用例子 2:編輯 CD
這個範例程式的第二個使用例子也會在這個清單頁面(listing.jsp)中啟動。除了向您介紹如何編輯 JSF 頁面中的資料之外,這個使用例子還將向您介紹 JSF dataTable
元件。
這個清單頁面使用一個 dataTable
元件來顯示 CD 的清單。dataTable
的值被繫結到控制程式類別 StoreController
的 cds
屬性。cds
屬性的定義如清單 8 所示。
清單 8. 在 StoreController.java 中定義的 cds 屬性
[StoreController.java] /** List of cds for CD listing. */ private DataModel cdModel = new ListDataModel(); { cdModel.setWrappedData(store.findTitleAsc()); } /** * List of CDs in the system. * * @return Returns the cds. */ public DataModel getCds() { return cdModel; }
cds
屬性是基於從儲存物件 StoreManagerDelegate
回傳的 java.util.List
的,這個物件是該程式的業務代理。cdModel
對從 DataModel
中的儲存物件回傳的清單進行了封裝。DataModel
是一個用於 dataTable
的模型。
dataTable
的定義如清單 9 所示。
清單 9. listing.jsp 中的 dataTable 定義
<f:view> <h:form> <h:dataTable id="items" value="#{CDManagerBean.cds}" var="cd" rowClasses="oddRow, evenRow" headerClass="tableHeader">
注意該值被繫結到控制程式的 cds 屬性上。rowClasses
和 headerClass
屬性用來指定 CSS 類,後者用來定義 dataTable
的外觀。正如前面介紹的一樣,JSF 嚴重依賴於 CSS 來定義 GUI 的外觀。如果您並不瞭解 CSS(即您之前都是使用字體標籤和 HTML 表來設置外觀的),就可能會希望在靈活執行 JSF 之前首先來學習一下有關 CSS 的知識。
column 元件
Title
、Artist
和 Price
域都是使用 column
元件顯示的,如清單 10 所示(此處只顯示了 Title
域)。
清單 10. 在 column 元件中添加域
<h:column> <f:facet name="header"> ... <h:outputText value="Title"/> </f:facet> <h:commandLink action="#{CDManagerBean.editCD}"> <h:outputText value="#{cd.title}"/> </h:commandLink> </h:column>
column
元件是 dataTable
的一個子元件。column
元件使用一個子元件和一個 facet。facet 是一個有名的子元件;它並不是一個子元件。column
元件有一個名為 header 的 facet,它定義了在 header 中顯示的內容。對於本例來說,commandLink
是 column
元件的一個子元件。commandLink
在一個鏈結中顯示了 CD 的標題,該鏈結被繫結到操作 #{CDManagerBean.editCD}
上。這個操作屬性將 commandLink
繫結到控制程式類的 editCD()
方法上,如清單 11 所示。
清單 11. editCD commandLink 的後臺 bean 方法
[StoreController.java] /** * Edit the CD. This get executed before the edit cdForm * page gets loaded. * * @return outcome */ public String editCD() { this.cd = (CD) cdModel.getRowData(); this.cd = (CD) store.getCDById(cd.getId()); if ((cd.getCategory() != null) || !"".equals(cd.getCategory())) { this.subCategoryList.setRendered(true); this.subCategories = getSubcategoriesList(cd.getCategory()); } else { this.subCategoryList.setRendered(false); } this.editMode = true; return "success"; }
editCD() 方法
editCD()
方法是在 JSF 生命週期的呼叫程式階段呼叫的。editCD()
方法準備控制程式以使用編輯模式來顯示 cdForm.jsp 頁面。這是透過查看當前選定的 CD 來實作的,CD 是透過呼叫 cdModel.getRowData()
方法來選擇的。
注意 JSF DataModel
允許您從比傳統的 Web 應用程式更高的層次上使用資料。您並不需要對請求參數進行檢查:只需要呼叫 cdModel.getRowData()
方法向 DataModel
(cdModel
)查詢已經選擇了哪個 CD。這個更高級別的抽象對 Web 開發進行了相當程度的簡化。
一旦取得當前選擇的 CD 之後,就可以使用業務代理來載入該 CD 的最新拷貝了(store.getCDById()
)。在載入這個 CD 之後,store.getCDById()
會啟動 subCategory
清單(假設這個 CD 已經關聯了一個子目錄),然後將 editMode
屬性設置為 true
。回想一下,editMode
屬性是由 cdForm
用來顯示 Add 或 Update 按鈕。最後,store.getCDById()
方法回傳 success 。在清單 12 中重要的導向規則可以保證回傳成功之後,切換到 cdForm.jsp 頁面,如下所示。
清單 12. 一條重要的導向規則
<navigation-rule> <from-view-id>/listing.jsp</from-view-id> <navigation-case> <from-action>#{CDManagerBean.editCD}</from-action> <from-outcome>success</from-outcome> <to-view-id>/cdForm.jsp</to-view-id> </navigation-case> <navigation-case> <from-action>#{CDManagerBean.addNew}</from-action> <from-outcome>success</from-outcome> <to-view-id>/cdForm.jsp</to-view-id> </navigation-case> </navigation-rule>
updateCD() 方法
CD 表單會載入並顯示 CD 屬性的屬性設置。最終用戶可以根據需要編輯所得到的表單,並在完成時點擊 Update 按鈕。Update 按鈕是當用戶處於 Edit 模式時所顯示的唯一一個按鈕,它只會在 editMode
為 true
時顯示,如清單 13 所示。
清單 13. Update CD 按鈕
[cdForm.jsp] <h:commandButton id="submitUpdate" action="#{CDManagerBean.updateCD}" value="Update CD" endered="#{CDManagerBean.editMode}"/>
Update 按鈕被繫結到 updateCD()
方法上。在呼叫 update 方法之前,JSF 必須對 GUI 中的域進行有效性驗證。在應用請求值階段,這些值被從請求參數中拷貝到元件值中(這是由元件本身完成的)。現在,價格被從一個字串轉換成了一個浮點類型。 由於沒有為元件關聯任何有效性驗證規則,因此如果所有請求的值都已經存在並經過轉換了,就可以轉換到生命週期的下一個步驟了。
更新模型值
在更新模型值階段中,會使用保存在 GUI 元件中經過類型轉換和有效性驗證的值來呼叫 CD 的賦值函數。updateCD()
方法是在呼叫程式階段被呼叫的。updateCD()
方法如清單 14 所示。
清單 14. updateCD() 方法
[StoreController.java] /** * Update the CD loaded on the form. * * @return success */ public String updateCD() { store.updateCD(this.cd); this.editMode = false; return "success"; }
updateCD()
方法可以代理業務代理的大部分職責。它將 editMode
設置為 false
(這是預設值),並回傳成功。成功輸出將您重新導向回清單頁面中,在這個頁面中您可以查看根據清單 15 中顯示的導向規則新編輯的 CD。
清單 15. 成功的 UpdateCD 會將您帶回 listing.jsp
[faces-config.xml] <navigation-rule> <from-view-id>/cdForm.jsp</from-view-id> ... <navigation-case> <from-action>#{CDManagerBean.updateCD}</from-action> <from-outcome>success</from-outcome> <to-view-id>/listing.jsp</to-view-id> </navigation-case> </navigation-rule>
使用例子 3:對 CD 進行排序
我們要介紹的最後一個使用例子將向您示範如何對表進行排序。這個使用例子也是在 CD 清單頁面上啟動的。清單頁允許根據標題和藝術家對 CD 按照昇冪或降冪的順序進行排列。在本例中,我將向您示範如何根據標題進行排序,並將根據藝術家進行排序留作練習。
標題頭排序有一些到控制程式中排序方法的鏈結。清單 16 顯示了在 listing.jsp 中是如何顯示標題頭的。
清單 16. 對 commandLinks 進行排序
[listing.jsp] <h:column> <f:facet name="header"> <h:panelGroup> <h:outputText value="Title"/> <f:verbatim> [</f:verbatim> <h:commandLink styleClass="smallLink" action="#{CDManagerBean.sortTitleAsc}"> <h:outputText id="ascTitle" value="asc"/> </h:commandLink> <h:outputText value=","/> <h:commandLink styleClass="smallLink" action="#{CDManagerBean.sortTitleDec}"> <h:outputText id="decTitle" value="dec"/> </h:commandLink> <f:verbatim>]</f:verbatim> </h:panelGroup> </f:facet> <h:commandLink action="#{CDManagerBean.editCD}"> <h:outputText value="#{cd.title}"/> </h:commandLink> </h:column>
panelGroup 元件
注意一下清單 16,鏈結是在標題列的 header facet 中定義的。facet 只會關聯一個唯一名字的元件;這樣,要在 header facet 中放置一個多鏈結的元件,您需要使用 panelGroup
。panelGroup
(與 panelGrid
類似)是一個單獨的元件,其中包含了很多子元件。panelGroup
包含兩個鏈結,如清單 17 所示。
清單 17. panelGroup 元件鏈結
[listing.jsp] <h:commandLink styleClass="smallLink" action="#{CDManagerBean.sortTitleAsc}"> <h:outputText id="ascTitle" value="asc"/> </h:commandLink> ... <h:commandLink styleClass="smallLink" action="#{CDManagerBean.sortTitleDec}"> <h:outputText id="decTitle" value="dec"/> </h:commandLink>
第一個鏈結被繫結到控制程式的 sortTitleAsc
方法上,第二個鏈結被繫結到 sortTitleDec
上。這兩個方法如清單 18 所示。
清單 18. panelGroup 鏈結方法
[StoreController.java ] /** * Uses the store delegate to return * a sorted list of CDs by title (ascending). * * @return asc */ public String sortTitleAsc() { this.cdModel.setWrappedData(store.findTitleAsc()); return "asc"; } /** * Uses the store delegate to return * a sorted list of CDs by title (descending). * * @return dec */ public String sortTitleDec() { this.cdModel.setWrappedData(store.findTitleDec()); return "dec"; }
這兩個方法都依賴於業務代理回傳一個按照正確要求排序後的 java.util.List
。注意這個方法會回傳邏輯輸出 asc
和 dec
。這兩個輸出在 faces-config.xml 檔中都沒有映射。沒有映射的輸出會導致重新載入