免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
JSF 組件開(kāi)發(fā)

組件模型的關(guān)鍵考驗(yàn)就是:能否從第三方供應(yīng)商購(gòu)買(mǎi)組件,并把它們插入應(yīng)用程序?與可購(gòu)買(mǎi)可視 Swing 組件一樣,也可以購(gòu)買(mǎi) Java ServerFaces (JSF) 組件!需要一個(gè)好玩的日歷?可以在開(kāi)源實(shí)現(xiàn)和商業(yè)組件之間選擇??梢赃x擇購(gòu)買(mǎi)一個(gè),而不是自行開(kāi)發(fā)復(fù)雜的基于 Web 的 GUI 組件。

JSF 擁有一個(gè)與 AWT 的 GUI 組件模型類(lèi)似的組件模型??梢杂?JSF 創(chuàng)建可重用組件。但不幸的是,存在一個(gè)誤解:用 JSF 創(chuàng)建組件很困難。不要相信這些從未試過(guò)它的人們的 FUD!開(kāi)發(fā) JSF 組件并不困難。由于不用一遍又一遍重復(fù)相同的代碼,可以節(jié)約時(shí)間。一旦創(chuàng)建了組件,就可以容易地把組件拖到任何 JSP、甚至任何 JSF 表單中,如果正在處理的站點(diǎn)有 250 個(gè)頁(yè)面,這就很重要了。JSF 的大多數(shù)功能來(lái)自基類(lèi)。因?yàn)樗械姆敝毓ぷ鞫加?API 和基類(lèi)完成,所以 JSF 把組件創(chuàng)建變得很容易。

貫穿這個(gè)系列,我一直在試圖幫助您克服造成許多 Java 開(kāi)發(fā)人員逃避使用 JSF 技術(shù)的 FUD。我討論了對(duì)這項(xiàng)技術(shù)的基本誤解,介紹了它的底層框架和它最有價(jià)值的開(kāi)發(fā)特性。有了這些基礎(chǔ)工作之后,我認(rèn)為您已經(jīng)可以采取行動(dòng),開(kāi)發(fā)自己的定制 JSF 組件了。使用 JSF 的東西,我敢保證要比您想像的要更加容易,而且從節(jié)約的時(shí)間和精力上來(lái)說(shuō),回報(bào)如此之多,多得不能忽略。

這篇文章中的示例是用 JDK 1.5 和 Tomcat 開(kāi)發(fā)的。請(qǐng)單擊頁(yè)面頂部的 示例代碼 下載示例源代碼。注意,與以前的文章不同,這篇文章沒(méi)有關(guān)聯(lián)的 build 文件,因?yàn)槲姨匾獍阉艚o您作為一個(gè)練習(xí)了。只要設(shè)置 IDE 或編譯器,把 /src 中的類(lèi)編譯到 /webapp/WEB-INF/classes,并在 /webapp/WEB-INF/lib 中包含所有 JAR 文件(以及 servlet-api.jarjsp-api.jar,它們包含在 Tomcat 中)。

JSF 組件模型

JSF 組件模型與 AWT GUI 組件模型類(lèi)似。它有事件和屬性,就像 Swing 組件模型一樣。它也有包含組件的容器,容器也是組件,也可以由其他容器包含。從理論上說(shuō),JSF 組件模型分離自 HTML 和 JSP。JSF 自帶的標(biāo)準(zhǔn)組件集里面有 JSP 綁定,可以生成 HTML 渲染。

JSF 組件的示例包括日歷輸入組件和 HTML 富文本輸入組件。您可能從來(lái)沒(méi)時(shí)間去編寫(xiě)這樣的組件,但是如果它們已經(jīng)存在,那會(huì)如何呢?通過(guò)把常用功能變成商品,組件模型降低了向 Web 應(yīng)用程序添加更多功能的門(mén)檻。

組件的功能通常圍繞著兩個(gè)動(dòng)作:解碼和編碼數(shù)據(jù)。解碼 是把進(jìn)入的請(qǐng)求參數(shù)轉(zhuǎn)換成組件的值的過(guò)程。編碼 是把組件的當(dāng)前值轉(zhuǎn)換成對(duì)應(yīng)的標(biāo)記(也就是 HTML)的過(guò)程。

JSF 框架提供了兩個(gè)選項(xiàng)用于編碼和解碼數(shù)據(jù)。使用直接實(shí)現(xiàn) 方式,組件自己實(shí)現(xiàn)解碼和編碼。使用委托實(shí)現(xiàn) 方式,組件委托渲染器進(jìn)行編碼和解碼。如果選擇委托實(shí)現(xiàn),可以把組件與不同的渲染器關(guān)聯(lián),會(huì)在頁(yè)面上以不同的方式渲染組件;例如多選列表框和一列復(fù)選框。

因此,JSF 組件由兩部分構(gòu)成:組件和渲染器。JSF 組件 類(lèi)定義 UI 組件的狀態(tài)和行為;渲染器 定義如何從請(qǐng)求讀取組件、如何顯示組件 —— 通常通過(guò) HTML 渲染。渲染器把組件的值轉(zhuǎn)換成適當(dāng)?shù)臉?biāo)記。事件排隊(duì)和性能驗(yàn)證發(fā)生在組件內(nèi)部。

在圖 1 中可以看到數(shù)據(jù)編碼和解碼出現(xiàn)在 JSF 生命周期中的什么階段(到現(xiàn)在,我希望您已經(jīng)熟悉 JSF 生命周期了)。


圖 1. JSF 生命周期和 JSF 組件

提示!

在許多情況下,可以在保持組件本身不變的情況下,通過(guò)改變渲染而簡(jiǎn)化開(kāi)發(fā)過(guò)程。在這些情況下,可以編寫(xiě)定制渲染器而不是定制組件。

更多組件概念

所有 JSF 組件的基類(lèi)是 UIComponent。在開(kāi)發(fā)自己的組件時(shí),需要繼承 UIComponentBase,它擴(kuò)展了 UIComponent 并提供了 UIComponent 中所有抽象方法的默認(rèn)實(shí)現(xiàn)。

組件擁有雙親和標(biāo)識(shí)符。每個(gè)組件都關(guān)聯(lián)著一個(gè)組件類(lèi)型,組件類(lèi)型用于在 face 的上下文配置文件(faces-config.xml)中登記組件??梢杂?JSF-EL (表達(dá)式語(yǔ)言)把 JSF 組件綁定到受管理的 bean 屬性??梢园驯磉_(dá)式關(guān)聯(lián)到組件上的任何屬性,這樣就允許用 JSF-EL 設(shè)置組件的屬性值。在創(chuàng)建使用 JSF-EL 綁定的組件屬性時(shí),需要?jiǎng)?chuàng)建值綁定表達(dá)式。在調(diào)用綁定屬性的 getter 方法時(shí),除非 setter 方法已經(jīng)設(shè)置了值,否則 getter 方法必須用值綁定獲得值。

組件可以作為 ValueHolderEditableValueHolder。ValueHolder 與一個(gè)或多個(gè) ValidatorConverter 相關(guān)聯(lián);所以 JSF UI 組件也與 ValidatorConverter 關(guān)聯(lián)(請(qǐng)參閱 參考資料 獲得更多關(guān)于 JSF 驗(yàn)證和轉(zhuǎn)換的內(nèi)容。)

像表單字段組件這樣的組件擁有一個(gè) ValueBinding,它必須綁定到 JavaBean 的讀寫(xiě)屬性。組件可以調(diào)用 getParent 方法訪(fǎng)問(wèn)它們的雙親,也可以調(diào)用 getChildren 方法訪(fǎng)問(wèn)它們的子女。組件也可以有 facet 組件,facet 組件是當(dāng)前組件的子組件,可以調(diào)用 getFacets 方法訪(fǎng)問(wèn)它,這個(gè)方法返回一個(gè)映射。Facets 是著名的子組件。

這里描述的許多組件的概念將會(huì)是接下來(lái)展示的示例的一部分,所以請(qǐng)記住它們!



回頁(yè)首


JSF 樣式的 Hello World!

我們用一個(gè)又好又容易的示例來(lái)開(kāi)始 JSF 組件的開(kāi)發(fā):我將展示如何渲染 Label 標(biāo)記(示例:<label>Form Test</label>)。

下面是我要采取的步驟:

  1. 擴(kuò)展 UIComponent
    • 創(chuàng)建一個(gè)類(lèi),擴(kuò)展 UIComponent
    • 保存組件狀態(tài)
    • 用 faces-config.xml 登記組件
  2. 定義渲染器或者內(nèi)聯(lián)地實(shí)現(xiàn)它
    • 覆蓋 encode
    • 覆蓋 decode
    • 用 faces-config.xml 登記渲染器
  3. 創(chuàng)建定制標(biāo)記,繼承 UIComponentTag
    • 返回渲染器類(lèi)型
    • 返回組件類(lèi)型
    • 設(shè)置可能使用 JSF 表達(dá)式的屬性

Label 示例將演示 JSF 組件開(kāi)發(fā)的以下方面:

  • 創(chuàng)建組件
  • 直接實(shí)現(xiàn)渲染器
  • 編碼輸出
  • 把定制標(biāo)記與組件關(guān)聯(lián)

返回 圖 1,可以看到在這個(gè)示例中會(huì)有兩個(gè)生命周期屬性在活動(dòng)。它們是 Apply Request ValueRender Response。

在圖 2 中,可以看到在 JSP 中如何使用 Label 標(biāo)記的(<label>Form Test</label>)。


圖 2. 在 JSP 中使用 JSF 標(biāo)記

第 1 步:擴(kuò)展 UIComponent

第一步是創(chuàng)建一個(gè)組件,繼承 UIOutput,后者是 UIComponent 的子類(lèi)。 除了繼承這個(gè)類(lèi)之外,我還添加了組件將會(huì)顯示的 label 屬性,如清單 1 所示:


清單 1. 繼承 UIComponent 并添加 label
import java.io.IOException;import javax.faces.component.UIOutput;import javax.faces.context.FacesContext;import javax.faces.context.ResponseWriter;public class LabelComponent extends UIOutput{	private String label;	public String getLabel() {		return label;	}	public void setLabel(String label) {		this.label = label;	}...

接下來(lái)要做的是保存組件狀態(tài)。JSF 通常通過(guò)會(huì)話(huà)、隱藏表單字段、cookies 等進(jìn)行實(shí)際的存儲(chǔ)和狀態(tài)管理。(這通常是用戶(hù)配置的設(shè)置)。要保存組件狀態(tài),需要覆蓋組件的 saveStaterestoreState 方法,如清單 2 所示:


清單 2. 保存組件狀態(tài)
    @Override    public Object saveState(FacesContext context) {        Object values[] = new Object[2];        values[0] = super.saveState(context);        values[1] = label;        return ((Object) (values));    }    @Override    public void restoreState(FacesContext context, Object state) {        Object values[] = (Object[])state;        super.restoreState(context, values[0]);        label = (String)values[1];    } 

可以注意到,我使用的是 JDK 1.5。我對(duì)編譯器進(jìn)行了設(shè)置,所以我必須指定 override 注釋?zhuān)员阒该髂男┓椒ㄒ采w基類(lèi)的方法。這樣做可以更容易地標(biāo)識(shí)出 JSF 的鉤子在哪。

創(chuàng)建組件的最后一步是用 faces-config.xml 登記它,如下所示:

<faces-config>   <component>      <component-type>simple.Label</component-type>      <component-class>         arcmind.simple.LabelComponent      </component-class>   </component>...

第 2 步:定義渲染器

下面要做的是內(nèi)聯(lián)地定義渲染器的功能。稍后我會(huì)介紹如何創(chuàng)建獨(dú)立的渲染器?,F(xiàn)在,先從編碼 Label 組件的輸出、顯示 label 開(kāi)始,如清單 3 所示:


清單 3. 編碼組件的輸出
public class LabelComponent extends UIOutput{	...	public void encodeBegin(FacesContext context) 					throws IOException {		ResponseWriter writer = 			context.getResponseWriter();		writer.startElement("label", this);        	            writer.write(label);        	            writer.endElement("label");        	            writer.flush();	}	...}

注意,響應(yīng)寫(xiě)入器(javax.faces.context.ResponseWriter)可以容易地處理 HTML 這樣的標(biāo)記語(yǔ)言。清單 3 的代碼輸出 <label> 元素體內(nèi)的 label 的值。

下面顯示的 family 屬性用來(lái)把 Label 組件與渲染器關(guān)聯(lián)。雖然目前 Label 組件還不需要這個(gè)屬性(因?yàn)檫€沒(méi)有獨(dú)立的渲染器),但是在這篇文章后面,在介紹如何創(chuàng)建獨(dú)立渲染器的時(shí)候,會(huì)需要它。

public class LabelComponent extends UIOutput{	...	public String getFamily(){		return "simple.Label";	}	...}

插曲:研究 JSF-RI

如果正在使用來(lái)自 Sun Microsystems 的 JSF 參考實(shí)現(xiàn)(不是 MyFaces 實(shí)現(xiàn)),那么就不得不在組件創(chuàng)建代碼中添加下面一段:

public void encodeEnd(FacesContext context) 			throws IOException {	return;}public void decode(FacesContext context) {	return;}

Sun 的 JSF RI 期望,在組件沒(méi)有渲染器的時(shí)候,渲染器會(huì)發(fā)送一個(gè)空指針異常。MyFaces 實(shí)現(xiàn)不要求處理這個(gè)需求,但是在代碼中包含以上方法依然是個(gè)好主意,這樣組件既可以在 MyFaces 環(huán)境中工作也可以在 JSF RI 環(huán)境中工作了。

MyFaces 更好!

如果正在使用 Sun JSF RI 或其他替代品,那么請(qǐng)幫自己一個(gè)忙,轉(zhuǎn)到 MyFaces。雖然 MyFaces 不總是 更好的實(shí)現(xiàn),但是目前它是。它的錯(cuò)誤消息要比 Sun JSF RI 的好,而這個(gè)框架相比之下更嚴(yán)格。

第 3 步:創(chuàng)建定制標(biāo)記

JSF 組件不是天生綁定到 JSP 上的。要連接起 JSP 世界和 JSF 世界,需要能夠返回組件類(lèi)型的定制標(biāo)記(然后在 faces-context 文件中登記)和渲染器,如圖 3 所示。


圖 3. 連接 JSF 和 JSP

注意,由于沒(méi)有獨(dú)立的渲染器,所以可以給 getRendererType() 返回 null 值。還請(qǐng)注意,必須已經(jīng)把 label 屬性的值從定制標(biāo)記設(shè)置到組件上,如下所示:

[LabelTag.java]public class LabelTag extends UIComponentTag {…protected void setProperties(UIComponent component) {	/* you have to call the super class */	super.setProperties(component);	((LabelComponent)component).setLabel(label);}

記住,Tag 設(shè)置從 JSP 到 Label 組件的綁定,如圖 4 所示。


圖 4. 綁定 JSF 和 JSP

現(xiàn)在要做的全部工作就是創(chuàng)建一個(gè) TLD(標(biāo)記庫(kù)描述符)文件,以登記定制標(biāo)記,如清單 4 所示:


清單 4. 登記定制標(biāo)記
[arcmind.tld]<taglib>   <tlib-version>0.03</tlib-version>   <jsp-version>1.2</jsp-version>   <short-name>arcmind</short-name>   <uri>http://arcmind.com/jsf/component/tags</uri>   <description>ArcMind tags</description>      <tag>      <name>slabel</name>      <tag-class>arcmind.simple.LabelTag</tag-class>      <attribute>          <name>label</name>          <description>The value of the label</description>      </attribute>    </tag>...

一旦定義了 TLD 文件,就可以開(kāi)始在 JSP 中使用標(biāo)記了,如下面示例所示:

[test.jsp]<%@ taglib prefix="arcmind"          uri="http://arcmind.com/jsf/component/tags" %>            ...	<arcmind:slabel label="Form Test"/>

現(xiàn)在就可以了 —— 開(kāi)發(fā)一個(gè)簡(jiǎn)單的 JSP 組件不需要更多了。但是如果想創(chuàng)建稍微復(fù)雜一些的組件,針對(duì)更復(fù)雜的使用場(chǎng)景時(shí)該怎么辦?請(qǐng)繼續(xù)往下看。



回頁(yè)首


復(fù)合組件

在下一個(gè)示例中,我將介紹如何創(chuàng)建這樣一個(gè)組件(和標(biāo)記),它可以記住最后一個(gè)人離開(kāi)的位置。Field 組件把多個(gè)組件的工作組合到一個(gè)組件中。復(fù)合組件是 JSF 組件開(kāi)發(fā)的重點(diǎn),會(huì)節(jié)約大量時(shí)間!

Field 組件把標(biāo)簽、文本輸入和消息功能組合到一個(gè)組件。Field 的文本輸入功能允許用戶(hù)輸入文本。如果有問(wèn)題(例如輸入不正確),它的標(biāo)簽功能會(huì)顯示紅色,還會(huì)顯示星號(hào)(*)表示必需的字段。它的消息功能允許它在必要的時(shí)候?qū)懗龀鲥e(cuò)消息。

Field 組件示例演示了以下內(nèi)容:

  • UIInput 組件
  • 處理值綁定和組件屬性
  • 解碼來(lái)自請(qǐng)求參數(shù)的值
  • 處理出錯(cuò)消息

與 Label 組件不同,F(xiàn)ield 組件使用獨(dú)立渲染器。如果為一個(gè)基于 HTML 的應(yīng)用程序開(kāi)發(fā)組件,那么不要費(fèi)力使用獨(dú)立渲染器。這么做是額外的無(wú)用功。如果正在開(kāi)發(fā)許多 JSF 組件,打算賣(mài)給客戶(hù),而針對(duì)的客戶(hù)又不止一個(gè),那么就需要獨(dú)立的渲染器了。簡(jiǎn)而言之,渲染器適用于商業(yè)框架的開(kāi)發(fā)人員,不適用于開(kāi)發(fā)內(nèi)部 Web 應(yīng)用程序的應(yīng)用程序開(kāi)發(fā)人員。

了解代碼

由于我已經(jīng)介紹了創(chuàng)建組件、定義渲染器以及創(chuàng)建定制標(biāo)記的基本步驟,所以這次我讓代碼自己說(shuō)話(huà),我只點(diǎn)出幾個(gè)重要的細(xì)節(jié)。在清單 5 中,可以看到在典型的應(yīng)用程序示例中如何使用 Field 標(biāo)記的:


清單 5. Field 標(biāo)記
<f:view>  <h2>CD Form</h2>        <h:form id="cdForm">            <h:inputHidden id="rowIndex" value="#{CDManagerBean.rowIndex}" />             	        <arcmind:field id="title"                           value="#{CDManagerBean.title}"                             label="Title:"                           errorStyleClass="errorText"                           required="true" /> <br />		        <arcmind:field id="artist"                           value="#{CDManagerBean.artist}"                             label="Artist:"                           errorStyleClass="errorText"                           required="true" /> <br />      	        <arcmind:field id="price"                           value="#{CDManagerBean.price}"                             label="CD Price:"                           errorStyleClass="errorText"                           required="true">           <f:validateDoubleRange maximum="1000.0" minimum="1.0"/>        </arcmind:field>

以上標(biāo)記輸出以下 HTML:

<label style="" class="errorText">Artist*</label><input type="text" id="cdForm:artist "        name=" cdForm:artist " />Artist is blank, it must contain characters

圖 5 顯示了瀏覽器中這些內(nèi)容可能顯示的效果。


圖 5. Field 組件

清單 6 顯示了創(chuàng)建 Field 組件的代碼。因?yàn)檫@個(gè)組件負(fù)責(zé)輸入文本而不僅僅是輸出它(像 Label 那樣),所以要從繼承 UIInput 開(kāi)始,而不是從繼承 UIOutput 開(kāi)始。


清單 6. Field 繼承 UIInput
package com.arcmind.jsfquickstart;import javax.faces.component.UIInput;import javax.faces.context.FacesContext;/** * @author Richard Hightower *   */public class FieldComponent extends UIInput {	private String label;    @Override     public Object saveState(FacesContext context) {        Object values[] = new Object[2];        values[0] = super.saveState(context);        values[1] = label;        return ((Object) (values));    }    @Override    public void restoreState(FacesContext context, Object state) {        Object values[] = (Object[])state;        super.restoreState(context, values[0]);        label = (String)values[1];    }    	public FieldComponent (){		this.setRendererType("arcmind.Field");	}	/**	 * @return Returns the label.	 */	public String getLabel() {		return label;	}	/**	 * @param label	 *  The label to set.	 */	public void setLabel(String label) {		this.label = label;	}		@Override	public String getFamily() {		return "arcmind.Field";	}	public boolean isError() {		return !this.isValid();	}}

可以注意到,代表片段中遺漏了編碼方法。這是因?yàn)榫幋a和解碼發(fā)生在獨(dú)立的渲染器中。我稍后會(huì)介紹它。

值綁定和組件屬性

雖然 Label 組件只有一個(gè)屬性(JSP 屬性),可是 Field 組件卻有多個(gè)屬性,即 labelerrorStyle、errorStyleClassvalue。labelvalue 屬性位于 Field 組件的核心,而 errorStyleerrorStyleClass 是特定于 HTML 的。因?yàn)檫@些屬性是特定于 HTML 的,所以不需要讓它們作為 Field 組件的屬性;相反,只是把它們作為組件屬性進(jìn)行傳遞,只有渲染器知道這些屬性。

像使用 Label 組件時(shí)一樣,需要用定制標(biāo)記把 Field 組件綁定到 JSP,如清單 7 所示:


清單 7. 為 FieldComponent 創(chuàng)建定制標(biāo)記
/* * Created on Jul 19, 2004 * */package com.arcmind.jsfquickstart;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;/** * @author Richard Hightower * */public class FieldTag extends UIComponentTag {     private String label;     private String errorStyleClass="";     private String errorStyle="";     private boolean required;     private String value="";          /**      * @return Returns the label.      */     public String getLabel() {          return label;     }     /**      * @param label The label to set.      */     public void setLabel(String label) {          this.label = label;     }     /**      * @see javax.faces.webapp.UIComponentTag#setProperties      * (javax.faces.component.UIComponent)      */     @Override     protected void setProperties(UIComponent component) {          /* You have to call the super class */          super.setProperties(component);          ((FieldComponent)component).setLabel(label);          component.getAttributes().put("errorStyleClass",            errorStyleClass);          component.getAttributes().put("errorStyle",errorStyle);          ((FieldComponent)component).setRequired(required);                   FacesContext context = FacesContext.getCurrentInstance();         Application application = context.getApplication();         ValueBinding binding = application.createValueBinding(value);         component.setValueBinding("value", binding);               }     /**      * @see javax.faces.webapp.UIComponentTag#getComponentType()      */     @Override     public String getComponentType() {          return "arcmind.Field";          }     /**      * @see javax.faces.webapp.UIComponentTag#getRendererType()      */     @Override     public String getRendererType() {          return "arcmind.Field";          }     /**      * @return Returns the errorStyleClass.      */     public String getErrorStyleClass() {          return errorStyleClass;     }     /**      * @param errorStyleClass The errorStyleClass to set.      */     public void setErrorStyleClass(String errorStyleClass) {          this.errorStyleClass = errorStyleClass;     }          /**      * @return Returns the errorStyle.      */     public String getErrorStyle() {          return errorStyle;     }     /**      * @param errorStyle The errorStyle to set.      */     public void setErrorStyle(String errorStyle) {          this.errorStyle = errorStyle;     }     /**      * @return Returns the required.      */     public boolean isRequired() {          return required;     }     /**      * @param required The required to set.      */     public void setRequired(boolean required) {          this.required = required;     }          /**      * @return Returns the value.      */     public String getValue() {          return value;     }     /**      * @param value The value to set.      */     public void setValue(String value) {          this.value = value;     }}

從概念上說(shuō),在上面的代碼和 Label 組件之間找不出太大區(qū)別。但是,在這個(gè)示例中,setProperties 方法有些不同:

protected void setProperties(UIComponent component) {    /* You have to call the super class */    super.setProperties(component);    ((FieldComponent)component).setLabel(label);    component.getAttributes().put("errorStyleClass",       errorStyleClass);    component.getAttributes().put("errorStyle",errorStyle);    ((FieldComponent)component).setRequired(required);

雖然 label 屬性傳遞時(shí)的方式與前面的示例相同,但是 errorStyleClasserrorStyle 屬性不是這樣傳遞的。相反,它們被添加到 JSF 組件的屬性映射 中。Renderer 類(lèi)會(huì)使用屬性映射去渲染類(lèi)和樣式屬性。這個(gè)設(shè)置允許特定于 HTML 的代碼從組件脫離。

這個(gè)修訂后的 setProperties 方法實(shí)際的值綁定代碼也有些不同,如下所示。

protected void setProperties(UIComponent component) {      ...		     FacesContext context = FacesContext.getCurrentInstance();     Application application = context.getApplication();     ValueBinding binding = application.createValueBinding(value);     component.setValueBinding("value", binding);

這個(gè)代碼允許 Field 組件的 value 屬性綁定到后臺(tái) bean。出于示例的原因,我把 CDManagerBean 的 title 屬性綁定到 Field 組件,像下面這樣:value="#{CDManagerBean.title}。值綁定是用 Application 對(duì)象創(chuàng)建的。Application 對(duì)象是創(chuàng)建值綁定的工廠(chǎng)。這個(gè)組件擁有保存值綁定的特殊方法,即 setValueBinding;可以有不止一個(gè)值綁定。

獨(dú)立渲染器

最后介紹渲染器,但并不是說(shuō)它不重要。獨(dú)立渲染器必須考慮的主要問(wèn)題是解碼(輸入) 和編碼(輸出)。Field 組件做的編碼比解碼多得多,所以它的渲染器有許多編碼方法,而只有一個(gè)解碼方法。在清單 8 中,可以看到 Field 組件的渲染器:


清單 8. FieldRenderer 擴(kuò)展自 Renderer
package com.arcmind.jsfquickstart;import java.io.IOException;import java.util.Iterator;import java.util.Map;import javax.faces.application.FacesMessage;import javax.faces.component.UIComponent;import javax.faces.component.UIInput;import javax.faces.context.FacesContext;import javax.faces.context.ResponseWriter;import javax.faces.convert.Converter;import javax.faces.convert.ConverterException;import javax.faces.el.ValueBinding;import javax.faces.render.Renderer;/** * @author Richard Hightower * */public class FieldRenderer extends Renderer {  @Override    public Object getConvertedValue(FacesContext facesContext, UIComponent component,      Object submittedValue) throws ConverterException {            //Try to find out by value binding    ValueBinding valueBinding = component.getValueBinding("value");    if (valueBinding == null) return null;    Class valueType = valueBinding.getType(facesContext);    if (valueType == null) return null;    if (String.class.equals(valueType)) return submittedValue;        if (Object.class.equals(valueType)) return submittedValue;        Converter converter = ((UIInput) component).getConverter();    converter =  facesContext.getApplication().createConverter(valueType);    if (converter != null ) {        return converter.getAsObject(facesContext, component, (String) submittedValue);    }else {        return submittedValue;     }		    }   @Override    public void decode(FacesContext context, UIComponent component) {        /* Grab the request map from the external context */       Map requestMap = context.getExternalContext().getRequestParameterMap();        /* Get client ID, use client ID to grab value from parameters */       String clientId = component.getClientId(context);       String value = (String) requestMap.get(clientId);		        FieldComponent fieldComponent = (FieldComponent)component;          /* Set the submitted value */        ((UIInput)component).setSubmittedValue(value);    }	   @Override    public void encodeBegin(FacesContext context, UIComponent component)        throws IOException {        FieldComponent fieldComponent = (FieldComponent) component;        ResponseWriter writer = context.getResponseWriter();        encodeLabel(writer,fieldComponent);        encodeInput(writer,fieldComponent);        encodeMessage(context, writer, fieldComponent);        writer.flush();    }		    private void encodeMessage(FacesContext context, ResponseWriter writer,       FieldComponent fieldComponent) throws IOException {        Iterator iter = context.getMessages(fieldComponent.getClientId(context));        while (iter.hasNext()){           FacesMessage message = (FacesMessage) iter.next();           writer.write(message.getDetail());        }    }    private void encodeLabel(ResponseWriter writer, FieldComponent       fieldComponent) throws IOException{        writer.startElement("label", fieldComponent);        if (fieldComponent.isError()) {            String errorStyleClass = (String) fieldComponent.getAttributes().get("errorStyleClass");            String errorStyle = (String) fieldComponent.getAttributes().get("errorStyle");            writer.writeAttribute("style", errorStyle, "style");            writer.writeAttribute("class", errorStyleClass, "class");        }        writer.write("" + fieldComponent.getLabel());        if (fieldComponent.isRequired()) {            writer.write("*");        }       writer.endElement("label");    }	    private void encodeInput(ResponseWriter writer, FieldComponent       fieldComponent) throws IOException{        FacesContext currentInstance = FacesContext.getCurrentInstance();        writer.startElement("input", fieldComponent);        writer.writeAttribute("type", "text", "type");        writer.writeAttribute("id", fieldComponent.getClientId(currentInstance), "id");		writer.writeAttribute("name", fieldComponent.getClientId(currentInstance), "name");        if(fieldComponent.getValue()!=null)            writer.writeAttribute("value", fieldComponent.getValue().toString(), "value");        writer.endElement("input");    }}

編碼和解碼

正如前面提到的,渲染器做的主要工作就是解碼輸入和編碼輸出。我先從解碼開(kāi)始,因?yàn)樗亲钊菀椎摹?FieldRenderer 的 decode 方法如下所示:

@Overridepublic void decode(FacesContext context, UIComponent component) {       /* Grab the request map from the external context */     Map requestMap = context.getExternalContext().getRequestParameterMap();       /* Get client ID, use client ID to grab value from parameters */     String clientId = component.getClientId(context);     String value = (String) requestMap.get(clientId);		     FieldComponent fieldComponent = (FieldComponent)component;       /* Set the submitted value */     ((UIInput)component).setSubmittedValue(value);}

Label 組件不需要進(jìn)行解碼,因?yàn)樗且粋€(gè) UIOutput 組件。Field 組件是一個(gè) UIInput 組件,這意味著它接受輸入,所以 必須 進(jìn)行解碼。decode 方法可以從會(huì)話(huà)、cookie、頭、請(qǐng)求等處讀取值。在大多數(shù)請(qǐng)問(wèn)下,decode 方法只是像上面那樣從請(qǐng)求參數(shù)讀取值。Field 渲染器的 decode 方法從組件得到 clientId,以標(biāo)識(shí)要查找的請(qǐng)求參數(shù)。給定組件容器的路徑,clientId 被計(jì)算成為組件的全限定名稱(chēng)。而且,因?yàn)槭纠M件在表單中(是個(gè)容器),所以它的 clientid 應(yīng)當(dāng)是 nameOfForm:nameOfComponent 這樣的,或者是示例中的 cdForm:artist、cdForm:price、cdForm:title。decode 方法的最后一步是把提交的值保存到組件(稍后會(huì)轉(zhuǎn)換并驗(yàn)證它,請(qǐng)參閱 參考資料 獲取更多關(guān)于驗(yàn)證和轉(zhuǎn)換的內(nèi)容)。

編碼方法沒(méi)什么驚訝的。它們與 Label 組件中看到的類(lèi)似。第一個(gè)方法 encodeBegin,委托給三個(gè)幫助器方法 encodeLabel、encodeInputencodeMessage,如下所示:

@Overridepublic void encodeBegin(FacesContext context, UIComponent component)       throws IOException {     FieldComponent fieldComponent = (FieldComponent) component;     ResponseWriter writer = context.getResponseWriter();     encodeLabel(writer,fieldComponent);     encodeInput(writer,fieldComponent);     encodeMessage(context, writer, fieldComponent);     writer.flush();}

encodeLabel 方法負(fù)責(zé)在出錯(cuò)的時(shí)候,把標(biāo)簽的顏色改成紅色(或者在樣式表中指定的其他什么顏色),并用星號(hào) (*) 標(biāo)出必需的字段,如下所示:

private void encodeLabel(ResponseWriter writer, FieldComponent fieldComponent) throws IOException{     writer.startElement("label", fieldComponent);     if (fieldComponent.isError()) {          String errorStyleClass = (String) fieldComponent.getAttributes().get("errorStyleClass");          String errorStyle = (String) fieldComponent.getAttributes().get("errorStyle");          writer.writeAttribute("style", errorStyle, "style");          writer.writeAttribute("class", errorStyleClass, "class");     }     writer.write("" + fieldComponent.getLabel());     if (fieldComponent.isRequired()) {          writer.write("*");     }     writer.endElement("label");}

首先,encodeLabel 方法檢查是否有錯(cuò)誤,如果有就輸出 errorStyleerrorStyleClass(更好的版本是只有在它們不為空的時(shí)候才輸出 —— 但是我把它留給您做練習(xí)!)。然后幫助器方法會(huì)檢查組件是不是必需的字段,如果是,就輸出星號(hào)。encodeMessagesencodeInput 方法做的就是這件事,即輸出出錯(cuò)消息并為 Field 組件生成 HTML 輸入的文本字段。

注意,神秘方法!

您可能已經(jīng)注意到,有一個(gè)方法我還沒(méi)有介紹。這個(gè)方法就是這個(gè)類(lèi)中的“黑馬”方法。如果您閱讀 Renderer(所有渲染器都要擴(kuò)展的抽象類(lèi))的 javadoc,您可能會(huì)感覺(jué)到這樣的方法是不需要的,現(xiàn)有的就足夠了:這就是我最開(kāi)始時(shí)想的。但是,您和我一樣,都錯(cuò)了!

實(shí)際上,基類(lèi) Renderer 并不 自動(dòng)調(diào)用 Renderer 子類(lèi)的相關(guān)轉(zhuǎn)換器 —— 即使 Renderer 的 javadoc 和 JSF 規(guī)范建議它這樣做,它也沒(méi)做。MyFaces 和 JSF RI 擁有為它們的渲染器執(zhí)行這個(gè)魔術(shù)的類(lèi)(特定于它們的實(shí)現(xiàn)),但是在核心 JSF API 中并沒(méi)有涉及這項(xiàng)功能。

相反,需要使用方法 getConvertedValues 鎖定相關(guān)的轉(zhuǎn)換器并調(diào)用它。清單 9 顯示的方法根據(jù)值綁定的類(lèi)型找到正確的轉(zhuǎn)換器:


清單 9. getConvertedValues 方法
@Override public Object getConvertedValue(FacesContext facesContext,    UIComponent component, Object submittedValue) throws ConverterException {             //Try to find out by value binding     ValueBinding valueBinding = component.getValueBinding("value");     if (valueBinding == null) return null;     Class valueType = valueBinding.getType(facesContext);     if (valueType == null) return null;     if (String.class.equals(valueType)) return submittedValue;         if (Object.class.equals(valueType)) return submittedValue;         Converter converter = ((UIInput) component).getConverter();     converter =  facesContext.getApplication().createConverter(valueType);     if (converter != null ) {          return converter.getAsObject(facesContext, component, (String) submittedValue);     }else {          return submittedValue;      }		}

清單 9 的代碼添加了 Render javadoc 和 JSF 規(guī)范都讓您相信應(yīng)當(dāng)是自動(dòng)執(zhí)行的功能,而實(shí)際上并不是。另一方面,請(qǐng)注意如果沒(méi)有 獨(dú)立的 Renderer,就不需要 以上(getConvertedValues)方法。UIComponentBase 類(lèi)(Field 組件的超類(lèi))在直接渲染器的情況下提供了這個(gè)功能。請(qǐng)接受我的建議,只在特別想嘗試或者在編寫(xiě)商業(yè)框架的時(shí)候,才考慮采用渲染器。在其他情況下,它們不值得額外的付出。

如果想知道如何把組件和渲染器關(guān)聯(lián),那么只要看看圖 6 即可。


圖 6. 把渲染器映射到組件

定制標(biāo)記有兩個(gè)方法,分別返回組件類(lèi)型和渲染器類(lèi)型。這些方法用于查找配置在 faces-config.xml 中的正確的渲染器和組件。請(qǐng)注意(雖然圖中沒(méi)有)組件必須返回正確的 family 類(lèi)型。



回頁(yè)首


結(jié)束語(yǔ)

通過(guò)這些內(nèi)容,您已經(jīng)切實(shí)地了解了 JSF 組件開(kāi)發(fā)的核心。當(dāng)然,在這個(gè)領(lǐng)域還有許多其他主題需要涉及 —— 包括發(fā)出組件事件、國(guó)際化組件、創(chuàng)建 UICommand 樣式的組件,以及更多。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶(hù)發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
自定義開(kāi)發(fā)具有Ajax功能的JSF組件
Matrix - 與 Java 共舞 - [翻譯]JSF(JavaServer Faces) 介紹(jsp/servlet 技術(shù))
JSF框架簡(jiǎn)介與實(shí)例
A first look at JavaServer Faces, Part 2 | JavaWorld
JSF問(wèn)題集錦
十一、 自訂轉(zhuǎn)換器
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服