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

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

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

    詳細分析一個JSF組件的制作過程

    以下是一個JSF組件所需的幾個文件,涉及約5(包括js文件)個文件,主要文件兩個(針對JSF1.2)
    1.UIComponent 用于渲染組件自身,簡單一點就是渲染html代碼。(也可以使用Renderer類進行獨立渲染)
    2.UIComponentTag 組件的jsp標簽處理程序類,主要用于指示組件在頁面的顯示及相關的屬性等。
    3.Javascript文件(可有可無) 如果你的組件要使用到較多的javascript,還是建議單獨作為一個文件存在,可以使用一個servlet加載該js

    4.tld文件(很簡單) 這是一個標簽庫描述符文件,主要用于注冊該組件所用到的屬性,如:id,rendered,binding等
    5.faces-config.xml(很簡單)主要是用于注冊你的組件


    實際上制作一個JSF組件,基本上你只要處理好UIComponent及UIComponentTag即可,重點就是這兩個,其它文件只是簡單的收尾工作。
    下面以我制作的一個實現雙聯動的選擇框組件(Htmllinkage)為例進行說明,該組件在頁面渲染了兩個select,并可進行雙聯動選擇,使用方法及效果在我的上一篇文章中 http://www.tkk7.com/huliqing/archive/2008/03/06/184315.html#184353
    這里是介紹并分析隱藏在組件背后的默默無聞的代碼,篇幅會較長,如果你對組件原理不感興趣,那么你也可以只知道如何簡單使用它就可以。看代碼需要耐心,特別是看他人的代碼,還要學會分析,分析代碼給他人看也是一個鍛煉邏輯思維能力的過程。

    開始進入正題,下面是HtmlLinkage組件所需文件所對應的各個部分:
    1.UIComponent -> biz.tbuy.share.components.linkage.HtmlLinkage;
    2.UIComponentTag -> biz.tbuy.share.components.linkage.HtmlLinkageTag;
    3.Javascript -> biz/tbuy/share/components/linkage/linkage.js; (js文件放在了同一個包中)
    4.tld文件 -> Tcoco.tld
    5.faces-config.xml -> faces-config.xml (最好以這一個名稱命名,當你打為jar包后,JSF會自動加載jar內以faces-config.xml命名的文件)



    ================================================== 第一部分(UIComponent):HtmlLinkage
    package biz.tbuy.share.components.linkage;

    import biz.tbuy.share.components.TbuyExtensionsFilter;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import javax.faces.component.UIInput;
    import javax.faces.context.FacesContext;
    import javax.faces.context.ResponseWriter;

    /**
    *
    * @author huliqing
    */
    public class HtmlLinkage extends UIInput{
    // 當前文件所在的包路徑,及相關的資源文件,如圖片,JS等。以便于加載這些資源
    private static final String _PATH = "biz/tbuy/share/components/linkage/";
    private static final String _SCRIPT = "linkage.js";

    public HtmlLinkage() {
    setRendererType(null); // 因為組件自身渲染html代碼,所以不需要外部renderer
    }
    // ------------------------------------------------------------ encode

    // 覆蓋父類的渲染方法,這里就是渲染該組件在頁面中所顯示的html代碼了
    // 實際上就是將本來在JSP中顯示的代碼搬到了這里來。

    @Override
    public void encodeEnd(FacesContext fc) throws IOException {
    ResponseWriter rw = fc.getResponseWriter();
    String clientId = getClientId(fc);
    encodeScript(fc); // 渲染javascript請求,你可以使用自定義的servlet處理該js請求,
    encodeSelectA(rw, clientId, fc); // 渲染第一個選擇框
    encodeSelectB(rw, clientId, fc); // 渲染第二個選擇框
    encodeHidden(rw, clientId); // 瀉染一個隱藏的input框,后面說明
    encodeOnload(rw, clientId); // 主要用于初始化組件的狀態
    }

    // 渲染第一個選擇框
    private void encodeSelectA(ResponseWriter rw, String clientId, FacesContext fc)
    throws IOException {
    String id = clientId + ":selectA"; // 第一個select的名稱及id
    String value = (String) getAttributes().get("param1");

    rw.startElement("select", this);
    rw.writeAttribute("name", id, null);
    rw.writeAttribute("id", id, null);
    if (value != null) rw.writeAttribute("value", value, null);
    rw.writeAttribute("onchange", "javascript:tbuy_linkage_select(this.value);", null);
    rw.writeAttribute("size", "1", null);
    rw.startElement("option", this);
    if (value != null) {
    rw.writeAttribute("value", value, null);
    rw.writeAttribute("selected", Boolean.TRUE, null);
    rw.writeText(value, null);
    }
    rw.endElement("option");
    rw.endElement("select");
    }

    // 渲染第二個選擇框
    private void encodeSelectB(ResponseWriter rw, String clientId, FacesContext fc)
    throws IOException {
    String id = clientId + ":selectB"; // 第二個select的名稱及id
    String value = (String) getAttributes().get("param2");

    rw.startElement("select", this);
    rw.writeAttribute("name", id, null);
    rw.writeAttribute("id", id, null);
    if (value != null) rw.writeAttribute("value", value, null);
    rw.writeAttribute("size", "1", null);
    rw.writeAttribute("style", "margin-left:5px;", null);
    rw.startElement("option", this);
    if (value != null) {
    rw.writeAttribute("value", value, null);
    rw.writeAttribute("selected", Boolean.TRUE, null);
    rw.writeText(value, null);
    }
    rw.endElement("option");
    rw.endElement("select");
    }

    // 渲染一個隱藏字段,該隱藏字段主要用于在頁面存放所有可選項(有規律的字符串),
    // 即是將所有可用的選項組織成了一個有規律的字符串,存放于隱藏域中,以便js進行分析,最后的字符串的形式像這樣(objStr)
    //aa,aa==aa_1,aa_1;aa_2,aa_2;aa_3,aa_3;@@bb,bb==bb_1,bb_1;bb_2,bb_2;bb_3,bb_3;@@cc,cc==cc_1,cc_1;cc_2,cc_2;cc_3,cc_3;


    private void encodeHidden(ResponseWriter rw, String clientId) throws IOException {
    // 將頁面中綁定的Map類型的所有選頂轉化為字符串形式,頁面中的組件使用像是這樣的:
    // <hu:linkage ... obj="#{TestBean.val2}"/>

    Object obj = this.getAttributes().get("obj"); // 也就是獲取組件的obj屬性所綁定的值
    String objStr = getAsString(obj); // 將obj轉化為字符串形式存放于隱藏域中

    rw.startElement("input", this);
    rw.writeAttribute("id", clientId + ":obj", null);
    rw.writeAttribute("name", clientId + ":obj", null);
    rw.writeAttribute("type", "hidden", null);
    rw.writeAttribute("value", objStr, null);
    rw.endElement("input");
    }

    // 渲染一個js函數調用,在該組件渲染完畢之后調用tbuy_linkage_init對組件進行初始化

    private void encodeOnload(ResponseWriter rw, String clientId) throws IOException {
    rw.startElement("script", this);
    rw.writeAttribute("type", "text/javascript", null);
    rw.writeText("tbuy_linkage_init('" + clientId + "')", null);
    rw.endElement("script");
    }

    // 渲染一個JS文件的請求,實際上你也可以直接渲染所有js代碼,但那樣會很麻煩。

    private void encodeScript(FacesContext fc) throws IOException {
    ResponseWriter rw = fc.getResponseWriter();
    String contentPath = fc.getExternalContext().getRequestContextPath();
    rw.startElement("script", this);
    rw.writeAttribute("type", "text/javascript", null);
    rw.writeAttribute("src", contentPath +
    TbuyExtensionsFilter.TBUY_EXT_ID +
    TbuyExtensionsFilter.REQUEST_SCRIPT + "=" +
    _PATH + _SCRIPT, null);
    rw.endElement("script");
    }

    // -------------------------------------------------- process

    // 該方法主要是將組件的obj屬性所綁定的值(所有可選項)轉化為字符串,轉化后的字符串像是這樣的
    //aa,aa==aa_1,aa_1;aa_2,aa_2;aa_3,aa_3;@@bb,bb==bb_1,bb_1;bb_2,bb_2;bb_3,bb_3;@@cc,cc==cc_1,cc_1;cc_2,cc_2;cc_3,cc_3;

    private String getAsString(Object obj) {
    StringBuilder str = new StringBuilder();
    if (obj instanceof Map) {
    Map objm = (Map) obj;
    Set keys = objm.keySet();
    Iterator iter = keys.iterator();
    while (iter.hasNext()) {
    Object temp = iter.next();
    String[] keyArr = getAsStringArr(temp); // 處理第一維選頂,一律轉為String[]
    str.append(keyArr[0] + "," + keyArr[1] + "==");
    Object valueObj = objm.get(temp);
    List<String[]> values = getAsList(valueObj);// 處理第二維
    for (String[] val : values) {
    str.append(val[0] + "," + val[1] + ";");
    }
    str.append("@@");
    }
    }
    return str.toString();
    }

    // 這里相當于一個適配器,將String數組或String類型都轉化成String數組返回

    private String[] getAsStringArr(Object obj) {
    if (obj instanceof String[]) {
    return ((String[]) obj);
    } else if (obj instanceof String) {
    String tt = (String) obj;
    return (new String[]{tt,tt});
    }
    return null;
    }

    // 參照上面
    private List<String[]> getAsList(Object obj) {
    List<String[]> strArr = new ArrayList<String[]>();
    if (obj instanceof List) {
    List temp = (List) obj;
    Iterator iter = temp.iterator();
    while (iter.hasNext()) {
    Object tt = iter.next();
    String[] val = getAsStringArr(tt);
    strArr.add(val);
    }
    return strArr;
    }
    return null;
    }

    // -------------------------------------------------- decode

    // 組件的解碼,該方法在頁面有回傳(比如提交了表單form)的時候才會執行。

    @Override
    public void decode(FacesContext fc) {
    String clientId = getClientId(fc);// 獲取當前組件的頁面id
    Map<String, String> values = fc.getExternalContext().getRequestParameterMap();
    Map<String, String> val = new HashMap<String, String>(2);
    String valA = values.get(clientId + ":selectA"); // 獲取兩個select的當前所選的值
    String valB = values.get(clientId + ":selectB");
    this.setSubmittedValue(val);
    this.getValueExpression("param1").setValue(fc.getELContext(), valA);// 將值綁定到param1所綁定的后臺bean的屬性
    this.getValueExpression("param2").setValue(fc.getELContext(), valB);// 同上
    }
    }


    ================================================== 第二部分(UIComponentTag):HtmlLinkageTag

    HtmlLinkageTag類主要用于指定并綁定組件的各個屬性,并關聯到組件的核心類,如:HtmlLinkage

    package biz.tbuy.share.components.linkage;

    import javax.el.ValueExpression;
    import javax.faces.component.UIComponent;
    import javax.faces.webapp.UIComponentELTag;

    /**
    *
    * @author huliqing
    */
    public class HtmlLinkageTag extends UIComponentELTag{ // JSF1.2的時候建議擴展自UIComponentTag
    private ValueExpression param1; // 這里是組件在jsp頁面中的三個自定義屬性
    private ValueExpression param2;
    private ValueExpression obj;

    @Override
    public String getComponentType() {
    return "HtmlLinkage"; // 返回組件的類型,關聯到了上面的HtmlLinkage
    }

    @Override
    public String getRendererType() {
    return null; // 因為組件自身渲染自己,所以不需要外部渲染器
    }

    @Override
    protected void setProperties(UIComponent ui) {
    super.setProperties(ui); // 先調用父類處理組件的已有屬性如id,binding,redered...
    setValueBinding(ui, "param1", param1); // 處理并將三個自定義屬性綁定到組件的attribute中
    setValueBinding(ui, "param2", param2);
    setValueBinding(ui, "obj", obj);
    }

    private void setValueBinding(UIComponent ui, String displayName, ValueExpression value) {
    if (value != null) {
    ui.setValueExpression(displayName, value); // 綁定屬性
    }
    }

    // ------------------------------------------------------------ setter

    // 這幾個方法是必須的,否則你的jsp頁面中就無法使用相應的屬性,
    // 會出現找不到setter方法的錯誤。

    public void setParam1(ValueExpression param1) {
    this.param1 = param1;
    }

    public void setParam2(ValueExpression param2) {
    this.param2 = param2;
    }

    public void setObj(ValueExpression obj) {
    this.obj = obj;
    }

    @Override
    public void release() {
    super.release(); // 釋放資源
    this.param1 = null;
    this.param2 = null;
    this.obj = null;
    }

    }

    ================================================== 第三部分(Javascript):linkage.js
    這里是我的組件所用到的javascript,如果你的組件沒有用到javascript.就沒有必要這個部分了,
    另一個關于javascript及可能用到的圖片資源的裝載問題,我使用的是一個servlet進行處理:
    biz.tbuy.share.components.TbuyExtensionsFilter
    類似myfaces的擴展過濾器,該類主要用將請求的js或圖片資源處理并write到客戶端,如果直接在組件
    中渲染js會非常麻煩,也不容易修改。

    var tbuy_linkage_clientId;// 組件id,為了不與其它組件的變量沖突,我都把變量名取得較長
    var tbuy_linkage_selectA; // 第一個選擇框
    var tbuy_linkage_selectB; // 第二個選擇框
    var tbuy_linkage_params = new Array(); // 這里用于存放分析后的各個可選項

    function tbuy_linkage_init(clientId) { // 初始化各個選項
    if (tbuy_linkage_clientId == null) {
    tbuy_linkage_clientId = clientId;
    // 獲取隱藏域的值,該隱藏域在上面已經有說明,存放所有可選項的字符串
    var str = document.getElementById(tbuy_linkage_clientId + ":obj").value;
    // 獲取頁面中的兩個select
    tbuy_linkage_selectA = document.getElementById(tbuy_linkage_clientId + ":selectA");
    tbuy_linkage_selectB = document.getElementById(tbuy_linkage_clientId + ":selectB");
    // 分析字符串,并存放于tbuy_linkage_params中
    tbuy_linkage_parse(str);
    var valueA = tbuy_linkage_selectA.value; // 原始值A
    var valueB = tbuy_linkage_selectB.value; // 原始值B
    tbuy_linkage_clearSelectA(); // 清除A框
    tbuy_linkage_setSelectA(); // 重設A框
    // 如果頁面經過了回傳請求,則需要將selectA的值設置回上一次所選擇的值,否則每一次請求都會重置
    if (valueA != null) {
    tbuy_linkage_selectA.value = valueA;
    }
    // 根椐selectA框的值設置selectB框的值。

    tbuy_linkage_select(document.getElementById(tbuy_linkage_clientId + ":selectA").value);
    if (valueB != null) tbuy_linkage_selectB.value = valueB; // 同上
    }
    }

    // 初始化A選擇框的各選項
    function tbuy_linkage_setSelectA() {
    for (var i = 0; i < tbuy_linkage_params.length; i++) {
    var temp = tbuy_linkage_params[i];
    var opt = new Option();
    opt.value = temp.value;
    opt.text = temp.text;
    tbuy_linkage_selectA.options.add(opt);
    }
    }

    // 選擇selectA框后根據其值設置selectB的值
    function tbuy_linkage_select(val) {
    for (var i = 0; i < tbuy_linkage_params.length; i++) {
    var selA = tbuy_linkage_params[i];
    if (selA.value == val) {
    tbuy_linkage_clearSelectB(); // 清除selectB的值并重設
    tbuy_linkage_setSelectB(selA.objArr);
    }
    }
    }

    // 該方法用于清除selectA的所有選項,用于重設A框時所用

    function tbuy_linkage_clearSelectA() {
    while (tbuy_linkage_selectA.hasChildNodes()) {
    tbuy_linkage_selectA.removeChild(tbuy_linkage_selectA.firstChild);
    }
    }

    // 同上
    function tbuy_linkage_clearSelectB() {
    while (tbuy_linkage_selectB.hasChildNodes()) {
    tbuy_linkage_selectB.removeChild(tbuy_linkage_selectB.firstChild);
    }
    }

    // 設置selectB的各個選項

    function tbuy_linkage_setSelectB(objArr) {
    for (var i = 0; i < objArr.length; i++) {
    var obj = objArr[i];
    var opt = new Option();
    opt.value = obj.value;
    opt.text = obj.text;
    tbuy_linkage_selectB.options.add(opt);
    }
    }

    // 該方法用于分析隱藏域中的值,即所有可選類型的字符串形式.字符串規律像是這樣(可參照核心類HtmlLinkage中隱藏域的渲染):
    //aa,aa==aa_1,aa_1;aa_2,aa_2;aa_3,aa_3;@@bb,bb==bb_1,bb_1;bb_2,bb_2;bb_3,bb_3;@@cc,cc==cc_1,cc_1;cc_2,cc_2;cc_3,cc_3;@@

    function tbuy_linkage_parse(str) {
    var strArr = new Array();
    strArr = str.split("@@");
    for (var i = 0; i < strArr.length - 1; i++) {
    var lineArr = strArr[i].split("==");
    var objKVArr = lineArr[0].split(",");
    var param = new Object();
    param.value = objKVArr[0];
    param.text = objKVArr[1];
    // 分析"=="號后面的子選項,即第二維的選項
    param.objArr = tbuy_linkage_getAsArray(lineArr[1]);
    tbuy_linkage_params[i] = param;
    }
    }
    // 配合上面的分析
    function tbuy_linkage_getAsArray(valueStr) {
    var objArr = new Array();
    var valArr = new Array();
    valArr = valueStr.split(";");
    for (var i = 0; i < valArr.length - 1; i++) {
    var selArr = valArr[i].split(",");
    var param = new Object();
    param.value = selArr[0];
    param.text = selArr[1];
    objArr[i] = param;
    }
    return objArr;
    }
    ================================================== 第四部分(tld文件):Tcoco.tld
    為了讓組件能在jsp頁面中使用,還需要一個tld標簽描述符,下面是我的組件的標簽描述符,
    實際上只是簡單指明了該組件可用的屬性.對應了HtmlLinkageTag中的各個屬性(JSF1.2)

    <?xml version="1.0" encoding="UTF-8"?>
    <taglib xsi:schemaLocation="http://java.sun.com/xml/ns/javaee webjsptaglibrary_2_1.xsd"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.1">
    <description>This is the jsp tag library for my components Tcoco</description>

    <!-- 給自己指定一個組件庫名稱 -->

    <display-name>Tcoco</display-name>
    <short-name>Tcoco</short-name>

    <!-- 給組件庫指定一個版本 -->

    <tlib-version>0.9.1</tlib-version>

    <!-- 指定一個uri,該uri指定了使用該組件庫所需要引入到jsp頁面中的標簽,你可以指定其它的,
    但在jsp頁面中的uri必須與這里匹配,如:
    <%@taglib prefix="coco" uri="http://www.tbuy.biz/Tcoco"%>
    <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
    <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
    -->

    <uri>http://www.tbuy.biz/Tcoco</uri>

    <tag>
    <description>
    組件的名稱、所關聯的Tag類的全限定類名及各個可用屬性,
    只有在這里注冊了的屬性,在JSP頁面中才可以使用
    </description>
    <name>linkage</name>
    <tag-class>
    biz.tbuy.share.components.linkage.HtmlLinkageTag
    </tag-class>
    <attribute>
    <name>id</name>
    </attribute>
    <attribute>
    <name>binding</name>
    </attribute>
    <attribute>
    <name>rendered</name>
    </attribute>
    <attribute>
    <name>param1</name>
    <deferred-value>
    <type>java.lang.String</type>
    </deferred-value>
    </attribute>
    <attribute>
    <name>param2</name>
    <deferred-value>
    <type>java.lang.String</type>
    </deferred-value>
    </attribute>
    <attribute>
    <name>obj</name>
    <deferred-value>
    <type>java.lang.Object</type>
    </deferred-value>
    </attribute>
    </tag>
    </taglib>
    ================================================== 第五部分(faces-config.xml文件):faces-config.xml
    將組件注冊到faces-config.xml中,以下是faces-config.xml文件的1.2形式

    <?xml version='1.0' encoding='UTF-8'?>
    <faces-config version="1.2"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
    <!-- 主要就是將你的組件的核心類聲明一下就可以了,與tld文件同樣都是比較簡單的 -->
    <component>
    <component-type>HtmlLinkage</component-type>
    <component-class>
    biz.tbuy.share.components.linkage.HtmlLinkage
    </component-class>
    </component>
    </faces-config>

    --------------------------------------------------------------------

    好了,以上就是制作一個組件所需要用到的各個部分,也是一個完整示例,針對于JSF1.2。
    因為加入了一些javascript,所以看起來復雜了一些,可能還有一些說得不夠清楚的,暫時這樣吧,有需要再補充。
    關于組件的使用示例,可以查看我的其它文章。
    現在制作一些JSF組件還是比較繁瑣麻煩的,不過JSF2.0之后可能有較大簡化。期待吧!


    - huliqing@huliqing.name
    - http://www.huliqing.name

    posted on 2008-03-14 17:15 huliqing 閱讀(2213) 評論(5)  編輯  收藏 所屬分類: JSF

    評論

    # re: 詳細分析一個JSF組件的制作過程 2008-03-14 20:05 千里冰封

    應該弄個截圖更好一些
    這個可以更直觀的看到你自定義的組件是什么樣子了  回復  更多評論   

    # re: 詳細分析一個JSF組件的制作過程 2008-03-14 21:06 huliqing

    呵呵,你說得很對哦!  回復  更多評論   

    # re: 詳細分析一個JSF組件的制作過程 2008-03-18 14:41 Niko7

    謝謝了,學習了!  回復  更多評論   

    # re: 詳細分析一個JSF組件的制作過程 2008-03-19 10:16 mu

    <marquee>goo</marquee>  回復  更多評論   

    # re: 詳細分析一個JSF組件的制作過程 2008-03-19 10:16 mu

    @mu
    hahaha  回復  更多評論   

    導航

    統計

    公告

    文章原創,歡迎轉載
    ——轉載請注明出處及原文鏈接

    隨筆分類(60)

    隨筆檔案(33)

    最新評論

    評論排行榜

    主站蜘蛛池模板: 亚洲精品美女在线观看播放| 亚洲最大AV网站在线观看| 亚洲日本国产精华液| 1000部啪啪毛片免费看| 久久亚洲精品国产精品| 亚洲成人免费网站| 亚洲人成电影在线观看青青| 久久久久久精品成人免费图片| 亚洲网站在线观看| 18pao国产成视频永久免费| 亚洲免费观看在线视频| 免费国产成人高清在线观看网站| 老司机永久免费网站在线观看| 色噜噜亚洲男人的天堂| 免费观看一级毛片| 免费人成视频在线观看免费| 久久亚洲国产精品123区| 国产色无码精品视频免费| 日韩精品亚洲人成在线观看| 亚洲成人免费在线观看| 亚洲乱妇熟女爽到高潮的片| 亚洲av麻豆aⅴ无码电影| 日本高清不卡aⅴ免费网站| 亚洲国产日韩女人aaaaaa毛片在线| 啦啦啦www免费视频| 一级大黄美女免费播放| 亚洲av无码国产精品色午夜字幕| 五月亭亭免费高清在线| 亚洲AV无码一区二区大桥未久| 久久久久久久亚洲精品| 嘿嘿嘿视频免费网站在线观看| 亚洲国产成人AV网站| 亚洲国产精品无码久久久不卡| 精品一区二区三区免费毛片| 亚洲AV无码成人网站久久精品大| 国产免费不卡视频| 一级毛片免费全部播放| 亚洲综合激情另类小说区| 四虎国产精品免费视| 99re6在线精品视频免费播放| 亚洲AV无码AV男人的天堂不卡|