<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    lbom

    小江西

      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
      18 隨筆 :: 21 文章 :: 69 評論 :: 0 Trackbacks

    懷疑論者的 JSF: JSF 組件開發(fā)

    省時(shí)運(yùn)動使得構(gòu)建 JSF 組件輕而易舉

    developerWorks
    文檔選項(xiàng)
    將此頁作為電子郵件發(fā)送

    將此頁作為電子郵件發(fā)送

    未顯示需要 JavaScript 的文檔選項(xiàng)

    Discuss

    Sample code


    對此頁的評價(jià)

    幫助我們改進(jìn)這些內(nèi)容


    級別: 中級

    Rick Hightower , CTO, ArcMind

    2005 年 8 月 16 日

    在四部分的 懷疑論者的 JSF 系列的最后一期中,Rick Hightower 介紹了省時(shí)運(yùn)動,它可以一次或永遠(yuǎn)地說服您:JSF 組件開發(fā)要比您想像的更容易。

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

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

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

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

    JSF 組件模型

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

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

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

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

    因此,JSF 組件由兩部分構(gòu)成:組件和渲染器。JSF 組件 類定義 UI 組件的狀態(tài)和行為;渲染器 定義如何從請求讀取組件、如何顯示組件 —— 通常通過 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 組件
    JSF 組件和 JSF 生命周期
    提示!

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

    更多組件概念

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

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

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

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

    這里描述的許多組件的概念將會是接下來展示的示例的一部分,所以請記住它們!



    回頁首


    JSF 樣式的 Hello World!

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

    下面是我要采取的步驟:

    1. 擴(kuò)展 UIComponent
      • 創(chuàng)建一個(gè)類,擴(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
      • 返回渲染器類型
      • 返回組件類型
      • 設(shè)置可能使用 JSF 表達(dá)式的屬性

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

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

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

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


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

    第 1 步:擴(kuò)展 UIComponent

    第一步是創(chuàng)建一個(gè)組件,繼承 UIOutput,后者是 UIComponent 的子類。 除了繼承這個(gè)類之外,我還添加了組件將會顯示的 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;
    	}
    ...
    

    接下來要做的是保存組件狀態(tài)。JSF 通常通過會話、隱藏表單字段、cookies 等進(jìn)行實(shí)際的存儲和狀態(tài)管理。(這通常是用戶配置的設(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。我對編譯器進(jìn)行了設(shè)置,所以我必須指定 override 注釋,以便指明哪些方法要覆蓋基類的方法。這樣做可以更容易地標(biāo)識出 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)地定義渲染器的功能。稍后我會介紹如何創(chuàng)建獨(dú)立的渲染器。現(xiàn)在,先從編碼 Label 組件的輸出、顯示 label 開始,如清單 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)寫入器(javax.faces.context.ResponseWriter)可以容易地處理 HTML 這樣的標(biāo)記語言。清單 3 的代碼輸出 <label> 元素體內(nèi)的 label 的值。

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

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

    插曲:研究 JSF-RI

    如果正在使用來自 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 期望,在組件沒有渲染器的時(shí)候,渲染器會發(fā)送一個(gè)空指針異常。MyFaces 實(shí)現(xiàn)不要求處理這個(gè)需求,但是在代碼中包含以上方法依然是個(gè)好主意,這樣組件既可以在 MyFaces 環(huán)境中工作也可以在 JSF RI 環(huán)境中工作了。

    MyFaces 更好!

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

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

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


    圖 3. 連接 JSF 和 JSP
    連接 JSF 和 JSP

    注意,由于沒有獨(dú)立的渲染器,所以可以給 getRendererType() 返回 null 值。還請注意,必須已經(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
    綁定 JSF 和 JSP

    現(xiàn)在要做的全部工作就是創(chuàng)建一個(gè) TLD(標(biāo)記庫描述符)文件,以登記定制標(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 文件,就可以開始在 JSP 中使用標(biāo)記了,如下面示例所示:

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

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



    回頁首


    復(fù)合組件

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

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

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

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

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

    了解代碼

    由于我已經(jīng)介紹了創(chuàng)建組件、定義渲染器以及創(chuàng)建定制標(biāo)記的基本步驟,所以這次我讓代碼自己說話,我只點(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 組件
    Field 組件

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


    清單 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ú)立的渲染器中。我稍后會介紹它。

    值綁定和組件屬性

    雖然 Label 組件只有一個(gè)屬性(JSP 屬性),可是 Field 組件卻有多個(gè)屬性,即 labelerrorStyleerrorStyleClassvaluelabelvalue 屬性位于 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;
         }
    }
    

    從概念上說,在上面的代碼和 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 類會使用屬性映射去渲染類和樣式屬性。這個(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 屬性綁定到后臺 bean。出于示例的原因,我把 CDManagerBean 的 title 屬性綁定到 Field 組件,像下面這樣:value="#{CDManagerBean.title}。值綁定是用 Application 對象創(chuàng)建的。Application 對象是創(chuàng)建值綁定的工廠。這個(gè)組件擁有保存值綁定的特殊方法,即 setValueBinding;可以有不止一個(gè)值綁定。

    獨(dú)立渲染器

    最后介紹渲染器,但并不是說它不重要。獨(dú)立渲染器必須考慮的主要問題是解碼(輸入) 和編碼(輸出)。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");
        }
    
    }
    

    編碼和解碼

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

    
    @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);
    }
    

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

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

    
    @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();
    }
    

    encodeLabel 方法負(fù)責(zé)在出錯(cuò)的時(shí)候,把標(biā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í)!)。然后幫助器方法會檢查組件是不是必需的字段,如果是,就輸出星號。encodeMessagesencodeInput 方法做的就是這件事,即輸出出錯(cuò)消息并為 Field 組件生成 HTML 輸入的文本字段。

    注意,神秘方法!

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

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

    相反,需要使用方法 getConvertedValues 鎖定相關(guān)的轉(zhuǎn)換器并調(diào)用它。清單 9 顯示的方法根據(jù)值綁定的類型找到正確的轉(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)是自動執(zhí)行的功能,而實(shí)際上并不是。另一方面,請注意如果沒有 獨(dú)立的 Renderer,就不需要 以上(getConvertedValues)方法。UIComponentBase 類(Field 組件的超類)在直接渲染器的情況下提供了這個(gè)功能。請接受我的建議,只在特別想嘗試或者在編寫商業(yè)框架的時(shí)候,才考慮采用渲染器。在其他情況下,它們不值得額外的付出。

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


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

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



    回頁首


    結(jié)束語

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

    在編寫這篇文章的過程中,我遇到了 Renderer 的一個(gè)技術(shù)障礙,它使我發(fā)現(xiàn)了 getConvertedValues 方法的工作方式。盡管我以前遇到過 Converter 問題并處理過它,但是那時(shí)我是在一個(gè)緊張的(生產(chǎn))日程中做這件事的。在生產(chǎn)工作中進(jìn)行的研究,不必像在 how-to 文章中做得那么詳細(xì);所以這一次,我必須不僅學(xué)習(xí)如何修補(bǔ)問題,還要學(xué)習(xí)弄清如何 做對。通過這整個(gè)過程,我最終在非常深的層次上學(xué)會并體驗(yàn)了 JSF 組件處理工作的方式。所以,有時(shí)繞點(diǎn)彎路會看到優(yōu)美的風(fēng)景。

    我希望在這個(gè)由四部分組成的系列中,您已經(jīng)學(xué)到了關(guān)于使用 JSF 的優(yōu)勢的充足知識,以及它如何工作的基礎(chǔ)知識,還希望您會喜歡進(jìn)一步深入這項(xiàng)技術(shù)。而且當(dāng)您有時(shí)可能迷失方向的時(shí)候,請不要陷入 FUD。相反,請記住我說過的:彎路會看到優(yōu)美的風(fēng)景,請繼續(xù)前行。

    posted on 2006-01-10 11:26 lbom 閱讀(576) 評論(0)  編輯  收藏

    只有注冊用戶登錄后才能發(fā)表評論。


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 久久久久亚洲AV无码永不| 午夜影院免费观看| 美女视频黄免费亚洲| 亚洲精品国产成人片| 免费欧洲美女牲交视频| 美女网站免费福利视频| 久久免费看少妇高潮V片特黄| 男女超爽视频免费播放| 亚洲男人天堂2022| 亚洲一级毛片免费看| 久久亚洲春色中文字幕久久久| 国产亚洲?V无码?V男人的天堂| 国产成人精品免费视频大全五级| 久久精品无码一区二区三区免费| 一级毛片**不卡免费播| a级片在线免费看| 中文字幕永久免费| xxxxx做受大片视频免费| 老司机福利在线免费观看| 亚洲日韩乱码中文字幕| 亚洲最大无码中文字幕| 亚洲一区二区三区免费观看| 亚洲综合激情另类小说区| 色婷婷亚洲十月十月色天| 久久综合图区亚洲综合图区| 亚洲AV无码成人精品区天堂 | 亚洲AV日韩AV天堂一区二区三区| 亚洲色婷婷综合开心网| 亚洲男人天堂2020| 亚洲日韩中文字幕日韩在线| 国产黄色一级毛片亚洲黄片大全| 亚洲精品动漫人成3d在线| 亚洲日韩VA无码中文字幕| 国产中文在线亚洲精品官网| 中文字幕亚洲综合久久男男| 最新亚洲成av人免费看| 国产成人A人亚洲精品无码| 亚洲综合成人网在线观看| 亚洲欧洲精品一区二区三区| 亚洲一区二区三区四区视频 | 日本免费一区二区在线观看|