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クラスからコンポーネントクラス特定
 →コンポーネントクラスからレンダラ特定
みたいな感じかな(適当)

1 件のコメント: