2010年7月23日金曜日

inline表示したブラウザでjavascriptを動作させる

別ウィンドウにPDF等の帳票をinlineでを出力することはよくあるが、その場合inlineで表示した別ウィンドウではjavascriptが動作しなくなる。
前回エントリの子ウィンドウを閉じる制御などを組み込みたい場合にどうすればよいか。

frameは使いたくないので、色々試行錯誤した結果とりあえずiframeとcssで頑張ることで解決。
ブラウザのbodyいっぱいにiframeを広げて、iframeのリクエストでファイルをダウンロード。
ダウンロードをGETで行わなければならないが、今回はPRGパターンのPOSTでファイル操作してGETでダウンロードという作りなのでOK



function writeOpenWin(win, requestPath) {
 
 // header各種生成
 var docType = 'xxx'; //xhtml定義など
 var htmlTagStart = 'xxx'; // htmlタグ namespaceスペースなど
 var contentTypeCharset = '<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />';
 var contentTypeJs = '<meta http-equiv="Content-Script-Type" content="text/javascript" />';
 var contentTypeCss = '<meta http-equiv="Content-Style-Type" content="text/css" />';
 
 // スタイル生成
 var wincss = '<style type="text/css">';
  wincss += 'html,body{';
  wincss += 'height:100%;';
  wincss += 'margin:0px;';
  wincss += 'padding:0px;';
  wincss += 'overflow: hidden;';   
  wincss += '}';
  wincss += '</style>';  
 
 // javascript生成
 var winJs = '<script type="text/javascript">';
  winJs += "function hogeFunc(){ alert("xxx"); }"; // onloadに登録する処理など
  winJs += '</script>';

 // ドキュメント書き込み
 win.document.open();
 win.document.write(docType);
 win.document.write(htmlTagStart);
 win.document.write('<head>');
 win.document.write('<title>' + dlFileName + '</title>');
 win.document.write(contentTypeCharset + contentTypeJs + contentTypeCss);
 win.document.write(wincss + winJs);
 win.document.write('</head>');
 win.document.write('<body onload="hogeFunc();" >');
 win.document.write('<iframe src="'+ requestPath + '" width="100%" height="100%" scrolling="auto" name="side" frameborder="0" allowtransparency="true"></iframe>');
 win.document.write('</body>');
 win.document.write('<html>');
 win.document.close();
}

使用例
// requestPath生成処理など
 ・・・
 var win = window.open("../dummy.htm" ,"winname", "width=800,height=600");
 writeOpenWin(win, requestPath);
 ・・・


※IE6,7しか確認してないす

ウィンドウを自動的に閉じるjavascript

親ウィンドウが閉じられた場合や特定の画面で、子ウィンドウを閉じるためのjavascript。
親ウィンドウから子ウィンドウを閉じるのでなく、子ウィンドウが親ウィンドウや条件を元に自分自身を閉じるアプローチ。
子ウィンドウをオープンした場合に、子ウィンドウのonloadに登録して使用する。
1秒おきにチェックするのでパフォーマンスに注意

function setOpenWindowCloser(){
        var count = 0;
        setInterval(function() {
            try {
                //親ウィンドウが閉じられた場合
                if(!window.opener || window.opener.closed){ 
                    window.close(); 
                }
                //エラー画面やログアウト画面等に遷移した場合に閉じる
                //別ウィンドウを閉じる特定の画面にhiddenで埋め込んでおく
                if (window.opener.document) {
                    if(window.opener.document.getElementById('xxx')){ 
                        window.close(); 
                    } 
                }
            }catch(e){
                // 親画面にアクセスできない場合 何故かたまに発生するので5回試してクローズ
                // 出せるとしてalertぐらいかな。。
                count++;
                if (count == 5) {
                    window.close();    
                }
            }            
        },1000);
}


※IE6,7しか確認してないす
   

2010年7月21日水曜日

ファイルダウンロードダイアログの日本語ファイル名クロスブラウザ対応

はまったのでメモ

ファイルダウンロードダイアログでブラウザによってファイル名の設定方法が統一されていない。asciiのみのファイル名ならなら問題ないが日本語の場合困ったことに。

色々調べた結果、以下の対応でOKぽい(IE8はやってない)
・IE6/IE7はUTF-8でURLエンコード
・Chrome/firefoxはISO-2022-JPでbase64エンコード
・opera/safariは全角スペース2ついれてISO-8859-1で復号化した文字列

Content-Dispositionのfilenameに以下のようなメソッドで取得したファイル名を設定

public static String getFilename(String filename) {
        if (filename == null || "".equals(filename)) {
            return "";
        }
        String displayName = null;
        try {
            if (isIE6() || isIE7()) {
                // IE6 IE7はUTF-8でURLエンコード
                displayName = URLEncoder.encode(filename, "UTF-8");
            } else if (isChrome() || isFirefox()) {
                // googlechrome firefox はISO-2022-JPでbase64エンコード
                displayName = MimeUtility.encodeWord(filename, "ISO-2022-JP",
                        "B");
            } else if (isSafari() || isOpera()) {
                // opera safariはlatin1で
                String encodedName = "  " + filename;
                displayName = new String(encodedName.getBytes(), "ISO-8859-1");
            } else {
                // サポート対象外はとりあえずISO-2022-JPでbase64エンコード
                displayName = MimeUtility.encodeWord(filename, "ISO-2022-JP",
                        "B");
            }
        } catch (UnsupportedEncodingException e) {
            // エラー処理
        }
        return displayName;
    }
    


2010年7月20日火曜日

Teedaカスタムコンポーネント作成手順

Teedaカスタムコンポーネント作成手順

今回はTeedaの拡張部品を作成したいと思います。
TeedaはSeasarプロジェクト発のJSFを拡張したWebアプリケーションフレームワークで、
慣れるとなかなか便利で使いやすいフレームワークなんですが、拡張するとなると
色々調べないといけないのでちょっと手間がかかります。
ただカスタムコンポーネントの拡張は、基本的にはJSFのカスタムコンポーネント作成と
流れは一緒なので、そんなに難しくないです。

Teedaに用意されている金額入力コンポーネントがありますが、
この部品を拡張してみます。
pageNameという属性の追加のみで、レンダラ実装はとりあえず空とします。

○やるべきこと
  ・tldファイルの作成
  ・カスタムUIコンポーネントクラスの作成
  ・カスタムファクトリークラスの作成
  ・カスタムタグクラスの作成
  ・カスタムレンダラーの作成
  ・faces-config.xmlにUIコンポーネントを登録
  ・diconにfactoryとrendererのコンポーネント登録
  ・xhtmlでのカスタムタグ使用


○tldファイルの作成
 作成したTLDファイルはアプリケーション内のフォルダならどこにでも配置できるが、通常はWEB-INF/tldなどのようにWEB-INFディレクトリの配下のフォルダに配置して隠ぺい。JAR形式にした場合にはMETA-INFディレクトリ以下に配置。
 何故かtldファイルをWEB-INF直下に配置しただけだと有効にならなかったので、tldファイルはjar化してクラスパスに追加。

 
○customext.taglib.tld
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib
  PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
  "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
      <tlib-version>1.0</tlib-version>
    <jsp-version>1.2</jsp-version>
    <short-name>dekatotoro</short-name>
    <uri>http://www.dekatotoro.co.jp/dekatotoro/teeda/extension</uri>
    <description>Teeda Extension Tag Library</description>

    <tag>
        <name>inputCommaText</name>
        <tag-class>
            jp.co.dekatotoro.ui.tag.ExtTInputCommaTextTag
        </tag-class>
        <body-content>JSP</body-content>
        <attribute>
            <name>id</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <name>rendered</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <name>pageName</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <name>binding</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
            <type>java.lang.String</type>
        </attribute>
        <attribute>
            <name>value</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
            <type>java.lang.String</type>
        </attribute>
        省略・・・
    </tag>
        
<taglib>




○カスタムUIコンポーネントクラスの作成
 THtmlInputCommaTextクラスを拡張してカスタムUIコンポーネントクラスを作成。

package jp.co.dekatotoro.ui.component;

import javax.faces.context.FacesContext;
import javax.faces.el.VariableResolver;
import org.seasar.framework.util.AssertionUtil;
import org.seasar.teeda.extension.component.html.THtmlInputCommaText;

public class ExtTHtmlInputCommaText extends THtmlInputCommaText {

    /** コンポーネントタイプ */
    public static final String COMPONENT_TYPE        = "jp.co.dekatotoro.ui.component.ExtTHtmlInputCommaText";

    /** レンダラータイプ */
    public static final String DEFAULT_RENDERER_TYPE = "jp.co.dekatotoro.ui.render.ExtTHtmlInputCommaTextRenderer";

    /** 追加したPageName属性 */
    private String             pageName;

    public ExtTHtmlInputCommaText() {
        setRendererType(DEFAULT_RENDERER_TYPE);
    }

    // pageName属性からPageクラスを取得
    public Object getPage(final FacesContext context) {
        AssertionUtil.assertNotNull("FacesContext", context);
        final VariableResolver variableResolver = context.getApplication().getVariableResolver();
        return variableResolver.resolveVariable(context, getPageName());
    }


    @Override
    public Object saveState(final FacesContext context) {

        final Object[] values = new Object[2];
        values[0] = super.saveState(context);
        values[1] = pageName;
        return values;
    }

    @Override
    public void restoreState(final FacesContext context, final Object state) {
        final Object[] values = (Object[])state;
        super.restoreState(context, values[0]);
        pageName = (String)values[1];
    }

    public String getPageName() {
        return pageName;
    }

    public void setPageName(String pageName) {
        this.pageName = pageName;
    }

}


○カスタムファクトリークラスの作成
 InputCommaTextFactoryクラスを拡張してカスタムUIコンポーネントクラスを作成。

package jp.co.dekatotoro.ui.factory;

import java.util.Map;
import org.seasar.teeda.extension.ExtensionConstants;
import org.seasar.teeda.extension.html.ActionDesc;
import org.seasar.teeda.extension.html.ElementNode;
import org.seasar.teeda.extension.html.PageDesc;
import org.seasar.teeda.extension.html.factory.InputCommaTextFactory;

public class ExtInputCommaTextFactory extends InputCommaTextFactory {


    public ExtInputCommaTextFactory() {}

    @Override
    protected void customizeProperties(Map properties, ElementNode elementNode, PageDesc pageDesc, ActionDesc actionDesc) {
        super.customizeProperties(properties, elementNode, pageDesc, actionDesc);
        if (!properties.containsKey(ExtensionConstants.PAGE_NAME_ATTR)) {
            properties.put(ExtensionConstants.PAGE_NAME_ATTR, pageDesc.getPageName());
        }
    }

    // tldファイルの取得先
    @Override
    protected String getUri() {
        return "http://www.dekatotoro.co.jp/dekatotoro/teeda/extension";
    }
    
    
    //  金額入力コンポーネントはclass属性にT_currencyを付加するが、
    //  変更したい場合は本メソッドを拡張する
    @Override
    public boolean isMatch(ElementNode elementNode, PageDesc pageDesc, ActionDesc actionDesc) {
        super.isMatch(elementNode, pageDesc, actionDesc);
    }

}



○カスタムタグクラスの作成
 TInputCommaTextTagクラスを拡張します。
package jp.co.dekatotoro.ui.tag;
import javax.faces.component.UIComponent;
import jp.co.dekatotoro.ui.component.ExtTHtmlInputCommaText;
import org.seasar.teeda.extension.ExtensionConstants;
import org.seasar.teeda.extension.taglib.TInputCommaTextTag;

public class ExtTInputCommaTextTag extends TInputCommaTextTag {

    /** Page名 */
    private String pageName;


    public ExtTInputCommaTextTag() {}

    /**
     * PageNameをプロパティへセット
     */
    @Override
    protected void setProperties(UIComponent component) {
        super.setProperties(component);
        setComponentProperty(component, "pageName", getPageName());
    }

    /**
     * コンポーネントタイプを取得
     */
    @Override
    public String getComponentType() {
        return ExtTHtmlInputCommaText.COMPONENT_TYPE;
    }

    public String getPageName() {
        return pageName;
    }

    public void setPageName(String pageName) {
        this.pageName = pageName;
    }

}




○カスタムレンダラーの作成
 THtmlInputCommaTextRendererを拡張
 拡張したいメソッドをオーバライドする。

package jp.co.dekatotoro.ui.render;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.seasar.teeda.extension.render.html.THtmlInputCommaTextRenderer;


public class ExtTHtmlInputCommaTextRenderer extends THtmlInputCommaTextRenderer {

    public ExtTHtmlInputCommaTextRenderer() {}

    /**
     * javascript読み込みキーを取得
     */
    @Override
    protected String getScriptKey() {
        return  "view/js/io/ExtTHtmlInputCommaText.js";
    }
    
    @Override
    protected void encodeHtmlHtmlInputTextPrepare(FacesContext context, THtmlInputText htmlInputText)
        throws IOException {
        //拡張処理
    }

    
    @Override
    protected void doEncodeEndStart(FacesContext context, THtmlInputText htmlInputText) throws IOException {
        //拡張処理
    }

    @Override
    protected void renderOnfocus(THtmlInputCommaText htmlInputCommaText, ResponseWriter writer, String groupingSeparator)
        throws IOException {
        //拡張処理
    }

    
    @Override
    protected String getValue(FacesContext context, UIComponent component) {
        //拡張処理
    }
}





○faces-config.xmlにUIコンポーネンの登録
    <component>
        <component-type>
            jp.co.dekatotoro.ui.component.ExtTHtmlInputCommaText
        </component-type>
        <component-class>
            jp.co.dekatotoro.ui.component.ExtTHtmlInputCommaText
        </component-class>
    </component>



○diconにコンポーネント登録
teeda-extension-1.0.13-spx.jarのteedaExtension.diconに
factoryとrendererの登録がされているので、同じようにfactoryと
rendererを登録します。

とりあえず、jarからteedaExtension.diconコピーしてきて
以下を追加。

※factoryはoriginalより前に定義する必要あり!?
    <!-- factoryはシングルトン -->
    <component name="extInputCommaTextFactory" class="jp.co.dekatotoro.ui.factory.ExtInputCommaTextFactory" />

    <!--
    レンダラはAutoRegisterで登録 一応prototype(毎回生成のスコープ)
    レンダラの実装にもよるけど、Teedaもシングルトンなんでパフォーマンス的にもシングルトンの方がよいのかな。。
    -->
    <component
        class="org.seasar.teeda.core.render.autoregister.TeedaRendererComponentAutoRegister">
        <property name="instanceDef">
            @org.seasar.framework.container.deployer.InstanceDefFactory@PROTOTYPE
        </property>
        <initMethod name="addReferenceClass">
            <arg>@jp.co.dekatotoro.ui.render.ExtTHtmlInputCommaTextRenderer@class</arg>
        </initMethod>
        <initMethod name="addClassPattern">
            <arg>"jp.co.dekatotoro.ui.render"</arg>
            <arg>".*Renderer"</arg>
        </initMethod>
    </component>



○xhtmlでのカスタムタグ使用
 ファクトリのisMatchメソッドを拡張していないので、指定方法はTHtmlInputCommaTextと同様で
 class属性に"T_currency"を指定するのみ

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:te="http://www.seasar.org/teeda/extension" xml:lang="ja" lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <title id="title">タイトル</title>
</head>
<body>

<form id="form" autocomplete="off">

<input type="text" id="aaa" class="T_currency" />

</form>





定義を纏めると
・ファクトリ     → diconファイル
・コンポーネント → faces-config.xml
・タグ           → tldファイル
・レンダラ       → ファイル

動き的には
 ファクトリがタグファイル特定
 →tldのtag定義からtagクラス特定
 →tagクラスからコンポーネントクラス特定
 →コンポーネントクラスからレンダラ特定
みたいな感じかな(適当)

javascriptの書き方 色々

javascriptの書き方は色々あるけど、こんな風にかくと
きれいになるのかなぁっと思う書き方を纏めてみた。

javascriptを分業して実装していると、変数名の重複に
注意しなければならないが、名前空間を用いれば気にする
必要がなくなる。クロージャの応用っぽい感じ。

また関数定義やオブジェクト定義、クラス定義など
用途によって使いわける。
prototype.jsやJQueryを使用する場合は、また
ちょっと変わってくるかな。。


▼ config.jsで名前空間と定数定義

var NMSP = {};
NMSP.common = {}; // 共通処理用
NMSP.config = {}; // 定数定義用
NMSP.xxx1 = {}; // 以下用途に合わせて menuやvalidateなど。
NMSP.xxx2 = {};
NMSP.xxx3 = {}; 

// 定数定義
NMSP.config.AAA      = "aaa";
NMSP.config.BBB      = "bbb";
NMSP.config.CCC      = "ccc";



▼ common.jsで関数定義 共通処理とか

(function() {
    
    var xxxArray = [];
    
    function hogefunc1(arg) {
        //処理
    }
        
    function hogefunc2(arg1, arg2) {
        //処理
    }    
    
    function hogefunc3(arg1, arg2) {
        //処理
    }
    
    // 名前空間へのエクスポート
    NMSP.common.hogefunc1 = hogefunc1;
    NMSP.common.hogefunc2 = hogefunc2;
    NMSP.common.hogefunc3 = hogefunc3;
    
})();


▼ hoge1.jsで関数定義

(function() {

    var initObj = {};
    var initWidth = 100;
    var initHeight = 100;
    
    function hogefunc1() {
        //処理
    }

    // 名前空間へのエクスポート
    NMSP.xxx1.hogefunc1 = hogefunc1;

})();


▼ hoge2.jsでクラス定義

// オブジェクト指向に向いている部品の場合はクラスを使用
(function() {

    /**
     *  コンストラクタ
     */
    function Hoge(arg1, arg2, arg3) {
        this.arg1 = arg1;
        this.arg2 = arg2;
        this.arg3 = arg3;
        this._initXxx();
    }

    /**
     *  初期処理
     */
    Hoge.prototype._initXxx = function() {
        // 処理
    }

    /**
     * 実行
     */
    Hoge.prototype._execXxx = function() {
       // 処理
    }
    
    // 名前空間へのエクスポート
    NMSP.xxx2.Hoge = Hoge;

})();


▼ hoge3.jsでオブジェクト定義

NMSP.xxx3 = {
  
  hogefunc1 : function(arg) {
      
  },
  
  hogefunc2 : function(arg) {
      
  },
  
  hogefunc3 : function(arg1, arg2) {
      
  },
  
};


▼ hoge4.jsでオブジェクト定義 別の名前空間を使用

if (typeof(NMSP2) == 'undefined') {
  NMSP2 = {};
}
if (typeof(NMSP2.xxx) == 'undefined') {
  NMSP2.xxx = {};
}

MSP2.xxx = {
  
  hogefunc1 : function(arg) {
      
  },
  
  
  hogefunc2 : function(arg) {
      
  },
  
  hogefunc3 : function(arg1, arg2) {
      
  },
  
};


▼ hoge5.jsでオブジェクト定義 別の名前空間を使用

// 上記とちょっと違う書き方だが好みの問題
if (typeof(NMSP3) == 'undefined') {
  NMSP3 = {};
}
if (typeof(NMSP3.xxx) == 'undefined') {
  NMSP3.xxx = {};
}

NMSP3.xxx.hogefunc1 = function (arg1, arg2) {

};

NMSP3.xxx.hogefunc2 = function (arg1, arg2) {

};

2010年7月16日金曜日

javascriptの初期処理を順番で実行するように制御する

init.js
( function() {

    // namespaceオブジェクト
    var NSOBJ = {};
    NSOBJ.common = {};
    
    var initFunc = [];
    
    function init() {
        initFunc.sort( function(a, b) {
            return a[1] - b[1];
        });
        for ( var i = 0, ilen = initFunc.length; i < ilen; i++) {
            initFunc[i][0]();
        }
    }
    
    // init登録用関数
    function setInit(f, order) {
        var a = [];
        a[0] = f;
        a[1] = (order != null ? parseInt(order) : 9999);
        initFunc.push(a);
    }
    
    // DOMContentLoadedでinit登録
    // 別にonloadでもよいけど
    jQuery(document).ready( function() {
        init();
    });
    
    
    NSOBJ.common.setInit = setInit;
    
    
})();


hoge.js
( function() {

    function hoge1func() {
       ・・・
    }

    function hoge2func() {
       ・・・
    }
    
    function hoge3func() {
       ・・・
    }

    // 関数登録
    NSOBJ.common.setInit(hoge1func, 10);
    NSOBJ.common.setInit(hoge2func, 30);
    NSOBJ.common.setInit(hoge3func, 100);    
})();

IE6でdiv要素をセレクトボックスに勝たせる

div要素でposition: absolute;など指定してポップアップ部品
などを作成することがありますが、
IE6だとdiv要素がセレクトボックスに負けてしまいます。

なのでiframeでかぶせてz-indexを指定することで
セレクトボックスに勝つようにする対応する必要があります。

また、上記対応をしてもブラウザ横スクロールが動作すると
何故かセレクトボックスに負けてしまうので
scrollイベントでスタイルの再割り当てを行うことで勝つ!
ちょっとちらつくけど。。他に方法ないかな~

function iframeCover() {
    //isIe6()はIE6の場合trueを返す関数
    if (isIe6()) {
        var targetObj =document.getElementById("divtarget");
        var html = "<iframe id=\"overlayframe\" src=\"javascript:false\" style=\"position: absolute; display: block; "
            + "z-index: -1; width: 100%; height: 100%; top: 0; left: 0;"
            + "filter: mask(); background-color: #ffffff; \"></iframe>";
        targetObj.innerHTML += html;
        
        // scrollイベントでstyleの再割り当て
        jQuery(window).bind("scroll", function(){
            var targetObj = document.getElementById("divtarget");
            // 何でもよいのでstyleの再割り当てを行う。
            var orignalBorder = targetObj.style.border;
            targetObj.style.border = 1;
            targetObj.style.border = orignalBorder;
        });
    }
}

faceletsカスタムコンポーネント作成

今回はfaceletsでJSFタグCommandLinkを拡張して部品を作成したいと思います。
※とりあえず"career"という属性を追加したレンダリングを行うのみ
faceletsを使用する場合、独自に作成したJSFカスタムコンポーネントは使用できまんせん。


○やるべきこと
  ・taglibファイルの作成
  ・web.xml登録
  ・カスタムUIコンポーネントクラスの作成
  ・カスタムレンダラーの作成
  ・faces-config.xmlにUIコンポーネントとレンダラーの登録
  ・xhtmlでのカスタムタグ使用


○taglibファイルの作成
 カスタムタグをtaglibファイルを作成してWEB-INF直下に配置します。
 以下を定義します。
  ・namespace
  ・tag-name
  ・component-type
  ・renderer-type
 
○custom.taglib.xml
 <?xml version="1.0"?> 
 <!DOCTYPE facelet-taglib PUBLIC 
   "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" 
   "http://java.sun.com/dtd/facelet-taglib_1_0.dtd"> 
 <facelet-taglib> 
       <namespace>http://www.arc-mind.com/jsf</namespace> 
 
     <tag> 
         <tag-name>extLink</tag-name> 
         <component> 
             <component-type>ui.component.ExtLink</component-type> 
             <renderer-type>ui.render.ExtLinkRender</renderer-type> 
         </component> 
     </tag> 
 
 </facelet-taglib> 


○web.xmlに登録
 web.xmlに作成したcustom.taglib.xmlを登録します。
 web.xmlに以下のcontext-paramを追加。
 ※faceletsのversionによってparam-nameが違うため、以下のどちらかを指定する。
   <context-param> 
        <param-name>javax.faces.FACELETS_LIBRARIES</param-name> 
        <param-value>/WEB-INF/custom.taglib.xml</param-value> 
   </context-param> 

 ※facelets-1.1.15-jsf1.2はこちらの定義で動作
   <context-param> 
         <param-name>facelets.LIBRARIES</param-name> 
         <param-value> 
         /WEB-INF/custom.taglib.xml 
         </param-value> 
  </context-param> 



○カスタムUIコンポーネントクラスの作成
 HtmlCommandLinkクラスを拡張してカスタムUIコンポーネントクラスを作成。
 オーバライドするべきメソッドは以下の通り。
  getFamily()    ファミリ名を返すメソッド
 package ui.component; 
 
 import javax.faces.component.html.HtmlCommandLink; 
 
 public class ExtLink extends HtmlCommandLink { 
 
 
  @Override
  public String getFamily() {
   return "GpsLinkFamily";
  }
 }



○faces-config.xmlにUIコンポーネントとレンダラーの登録
  <component> 
   <component-type>ui.component.ExtLink</component-type> 
   <component-class>ui.component.ExtLink</component-class> 
  </component> 
 
  <render-kit> 
   <render-kit-id>HTML_BASIC</render-kit-id> 
   <renderer> 
    <component-family>ExtLinkFamily</component-family> 
    <renderer-type>ui.render.ExtLinkRender</renderer-type> 
    <renderer-class>ui.render.ExtLinkRender</renderer-class> 
   </renderer> 
  </render-kit> 


○カスタムレンダラーの作成
 必要に応じてdecodeメソッドと各種encodeメソッドをオーバーライド。
 decodeメソッドはmanagedBean適用時(ブラウザ→サーバ)。
 encodeメソッドはレンダリング時(サーバ→ブラウザ)。

 赤字部分のみ追加であとはCommandLinkRendererのコピペ
 encode処理にて属性にcareerを追加してみる。decode処理はそのまま。
 ※ decodeでリクエストパラメータからコンポーネントに設定する場合はsetValue()ではなく、
  setSubmittedValue()で値を設定する。valueChangeListener()がsetValueだと呼ばれないため。
 package ui.render; 
 
 import java.io.IOException; 
 import java.util.Map; 
 
 import javax.faces.component.UIComponent; 
 import javax.faces.component.UIForm; 
 import javax.faces.context.FacesContext; 
 import javax.faces.context.ResponseWriter; 
 
 import com.sun.faces.renderkit.AttributeManager; 
 import com.sun.faces.renderkit.RenderKitUtils; 
 import com.sun.faces.renderkit.html_basic.CommandLinkRenderer; 
 import com.sun.faces.renderkit.html_basic.HtmlBasicRenderer; 
 
 public class ExtLinkRender extends CommandLinkRenderer { 
 
    public ExtLinkRender() 
      { 
     super(); 
      } 
     private static final String ATTRIBUTES[]; 
     private static final String SCRIPT_STATE = "com.sun.faces.scriptState"; 
 
     static 
     { 
         ATTRIBUTES = AttributeManager.getAttributes(com.sun.faces.renderkit.AttributeManager.Key.COMMANDLINK); 
     } 
 
 
  protected void renderAsActive(FacesContext context, UIComponent command) 
    throws IOException { 
   ResponseWriter writer = context.getResponseWriter(); 
   if (writer == null) 
    throw new AssertionError(); 
   String formClientId = getFormClientId(command, context); 
   if (formClientId == null) 
    return; 
   writer.startElement("a", command); 
   writeIdAttributeIfNecessary(context, writer, command); 
   writer.writeAttribute("href", "#", "href"); 
 
   Map attributeMap = command.getAttributes(); 
   writer.writeAttribute("career", "#", null); 
 
   RenderKitUtils.renderPassThruAttributes(writer, command, ATTRIBUTES); 
   RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, command); 
   String userOnclick = (String) command.getAttributes().get("onclick"); 
   StringBuffer sb = new StringBuffer(128); 
   boolean userSpecifiedOnclick = userOnclick != null 
     && !"".equals(userOnclick); 
   if (userSpecifiedOnclick) { 
    sb.append("var a=function(){"); 
    userOnclick = userOnclick.trim(); 
    sb.append(userOnclick); 
    if (userOnclick.charAt(userOnclick.length() - 1) != ';') 
     sb.append(';'); 
    sb.append("};var b=function(){"); 
   } 
   HtmlBasicRenderer.Param params[] = getParamList(command); 
   String commandClientId = command.getClientId(context); 
   String target = (String) command.getAttributes().get("target"); 
   if (target != null) 
    target = target.trim(); 
   else 
    target = ""; 
   sb.append(getOnClickScript(formClientId, commandClientId, target, 
     params)); 
   if (userSpecifiedOnclick) 
    sb.append("};return (a()==false) ? false : b();"); 
   writer.writeAttribute("onclick", sb.toString(), "onclick"); 
   writeCommonLinkAttributes(writer, command); 
   writeValue(command, writer); 
   writer.flush(); 
  } 
 
  private static boolean hasScriptBeenRendered(FacesContext context) { 
   return context.getExternalContext().getRequestMap().get( 
     com.sun.faces.scriptState) != null; 
  } 
 
  private static void setScriptAsRendered(FacesContext context) { 
   context.getExternalContext().getRequestMap().put( 
     com.sun.faces.scriptState, Boolean.TRUE); 
  } 
 
  private static String getFormClientId(UIComponent component, 
    FacesContext context) { 
   UIForm form = getMyForm(component); 
   if (form != null) 
    return form.getClientId(context); 
   else 
    return null; 
  } 
 
  private static UIForm getMyForm(UIComponent component) { 
   UIComponent parent; 
   for (parent = component.getParent(); parent != null 
     && !(parent instanceof UIForm); parent = parent.getParent()) 
    ; 
   return (UIForm) parent; 
  } 
 
 } 



○xhtmlでのカスタムタグ使用
 taglibファイルに定義したnamespaceでタグを使用する。

<%@ page contentType="text/html;charset=Shift_JIS" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="/WEB-INF/jsf_custum.tld" prefix="custom" %>
<html>
<head>
<title>HelloWorld</title>
</head>
<body>
<f:view>
<h:form>

文字を入力して下さい:<h:inputText id="string" value="#{HelloWorldBean.helloWorld}"/>
<h:commandButton value="送信"/>
<p><h:outputText id="output" value="#{HelloWorldBean.helloWorld}"/></p>


<h:commandButton id="btn1" action="ok" value="遷移" actionListener="#{HelloWorldBean.assembleMessage}" />
<br>
<custom:extlink id="link1" action="ok" actionListener="#{HelloWorldBean.assembleMessage}">カスタムリンク</custom:extlink> 
</h:form>
</f:view>
</body>
</html>




 注意点
  JBossToolでSeemプロジェクトを作成するとsrc/hotとsrc/mainが作成されるが、src/main側に入れないとClassが見つからない
  と怒られる。
   src/hot    エンティティクラス以外のソースディレクトリ
   src/main    エンティティクラスのソースディレクトリ