JSF カスタムコンポーネント

作成 2004/4/27
更新 2004/4/28

カスタムコンポーネントに関するメモです。まだハローぐらい。

JSFではコンポーネントを作成して登録することができます。

最初のカスタムコンポーネント

作るもの

カスタムコンポーネントに必要なクラスは以下です。

また、これらのクラスを次の設定ファイルに登録します。

UIコンポーネントがレンダラを兼ねることもできるようですが、 ここでは別のクラスとして作成します。

JSP

最初に作成するコンポーネントを利用するJSP側のコードを見てみます。

リスト cust1.jsp

<%@ taglib uri="WEB-INF/test.tld" prefix="myh" %>

...

<myh:hello value="こんにちわ"/>

指定したvalue属性の値を表示するだけです。 JSFコンポーネントで作る必要はまったくありませんが、 何事もハローからということで。

ちなみに正常に実行されると以下のように表示されます(スクリーンショットにするだけアホらしいですが)。

実行結果

UIコンポーネント

コンポーネントのソースは以下です。

リスト UIHello.java

public class UIHello extends UIComponentBase{

    public String getFamily() {
        return "MyFamily";
    }
}

中身はスカスカです。getFamily()はUIファミリ名(?)を返します。 これはfaces-config.xmlにUIコンポーネントを登録するのに必要です。 UIComponentでなく、UIOutputなどを継承して作成する場合は、 メソッドをオーバーライドせず、定義済のjavax.faces.Outputなどを利用することも可能です。 ここでは、適当な名前"MyFamily"を返しています。

タグクラス

JSPとUIコンポーネントの間をとりもつのがタグクラスです。

リスト HelloTag.java

public class HelloTag extends UIComponentTag{

    private String value;

    public String getComponentType() {
        return "Hello";
    }

    public String getRendererType() {
        return "HelloRenderer";
    }


    protected void setProperties(UIComponent component) {

        super.setProperties(component);
        UIHello hello = (UIHello)component;
        hello.getAttributes().put("value", value);
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

getComponentType()はコンポーネントタイプ名、getRendererType()はレンダラタイプ名を返します。 このタイプ名はfaces-config.xmlに登録する名前です。 getRendererType()でnullを返すことで、レンダラをコンポーネント自信が行うこともできます。 setProperties(...)で、JSPから受け取った必要な値をコンポーネントにセットしています。 UIComponentBaseではgetAttributes()/setAttribute(...)でキー、バリューの値をセットできます。 タグライブラリの方はそういうのはないので、セッタ、ゲッタが必要です(TLDにも書かないといけません)。

レンダラ

お次はレンダラです。

リスト HelloRenderer.java

public class HelloRenderer extends Renderer {

    public void encodeBegin(FacesContext context, UIComponent component)
            throws IOException {
        
        ResponseWriter writer = context.getResponseWriter();
        UIHello hello = (UIHello)component;        
        String value = (String)hello.getAttributes().get("value");

        writer.write(value);
    }

}

encodeBegin(...)メソッドで、HTML出力コードを作成しています。 といっても、ここでは、valueを出力しているだけです。

TLDファイル

TLDファイルにはHelloTagを登録します。 value属性を記述しています。

リスト test.tld

<tag>
  <name>hello</name>
  <tag-class>ct.HelloTag</tag-class>
  <body-content>empty</body-content>
  <attribute>
    <name>value</name>
    <required>true</required>
  </attribute>
</tag>

faces-config.xml

faces-config.xmlでは、UIコンポーネントとレンダラを登録しています。

リスト faces-config.xml

<component>
    <component-type>Hello</component-type>
    <component-class>ct.UIHello</component-class>
</component>

<render-kit>
    <renderer>
        <component-family>MyFamily</component-family>
        <renderer-type>HelloRenderer</renderer-type>
        <renderer-class>ct.HelloRenderer</renderer-class>
    </renderer>
</render-kit>

component-type、renderer-typeに指定した値を、タグクラスで使います。 component-class、renderer-classは、それぞれのクラス名です。

ここまでで、最初のコンポーネントの作成は完了です。 実行し、JSPにアクセスすると、valueで指定した値が表示されます。

入力コンポーネント

これだけだと、あんまりなので、次はテキストフィールドに入力できる形のコンポーネントにしてみましょ う。

JSP

JSPはこんな感じ。 というか前と同じです(h:formでかこっただけ)。 ボタンは作るものとは関係ないです。 タグのvalue属性もあまりいらなかったかもしれません。

<%@ taglib uri="WEB-INF/test.tld" prefix="myh" %>

...

<h:form>

    <myh:hello value="こんにちわ"/>

    <h:commandButton value="OK"/>

</h:form>

実行結果

UIコンポーネント

コンポーネントクラスは次のようになります。 相変わらずスカスカですが、継承元をUIInputにしています。

public class Hello extends UIInput{

    public String getFamily() {
        return "MyFamily";
    }

}

タグクラス

前と同じ

レンダラ

レンダラは次のようになります。decode(...)メソッドで入力値を拾って、 encodeBegin(...)メソッドでHTMLを出力しています。

public class HelloRenderer extends Renderer {

    public void decode(FacesContext context, UIComponent component) {

        Map map = context.getExternalContext().getRequestParameterMap();
        String clientId = component.getClientId(context);
        UIHello hello = (UIHello)component;
        
        String value = (String)map.get(clientId);
        hello.setSubmittedValue(value);
    }

    
    public void encodeBegin(FacesContext context, UIComponent component)
        throws IOException {

        ResponseWriter writer = context.getResponseWriter();
        String clientId = component.getClientId(context);
        UIHello hello = (UIHello)component;        
        String value = (String)hello.getAttributes().get("value");

        //writer.write(value);
        writer.startElement("input", component);
        writer.writeAttribute("type", "text", null);
        writer.writeAttribute("name", clientId, "id");
        writer.writeAttribute("value", value, null);
        writer.endElement("input");
    }
}

TLDとfaces-config.xmlは前と同じです。 これで実行すると、入力値をひろって、UIコンポーネントに保持することができます。 出力するときは、毎度、HTMLのテキストフィールドを組み立てています。 HTMLソース上では以下のようなソースが出力されます。

<input type="text" name="_id0:_id1" value="前の値" />

まだあんまりよくわかってないので、説明はそのうち追記したいと思います

ValueBinding

なんとなく、最初のカスタムコンポーネントがわかったところで、次は、value属性にEL式を書いて、 値をバッキングビーンからひろってくるようにしてみましょう。

JSP

JSPでは、valueにバッキングビーンのEL式を書きます。

<myh:hello value="#{hoge.s}"/>

タグクラス

HelloTag.javaのsetProperties(...)メソッドで、valueをEL式の値参照かどうか判断して、 そうであれば、コンポーネントのsetValueBindeing(...)しています。

protected void setProperties(UIComponent component) {

    super.setProperties(component);
    UIHello hello = (UIHello)component;
    if(value != null){
        if(isValueReference(value)){
            ValueBinding bind = FacesContext.getCurrentInstance().getApplication().createValueBinding(value);
            hello.setValueBinding("value", bind);
        }else{
            hello.getAttributes().put("value", value);
        }
    }
}

レンダラ

HelloRenderer.javaでは、getValueBinding(...)した値を使っています。

public void encodeBegin(FacesContext context, UIComponent component)
        throws IOException {

    ResponseWriter writer = context.getResponseWriter();
    String clientId = component.getClientId(context);
    UIHello hello = (UIHello)component;        
    String value = (String)hello.getAttributes().get("value");
    if(value != null){
        ValueBinding bind =  hello.getValueBinding("value");
        if(bind != null){
            value = (String)bind.getValue(context);
        }
    }
            
    //writer.write(value);
    writer.startElement("input", component);
    writer.writeAttribute("type", "text", null);
    writer.writeAttribute("name", clientId, "id");
    writer.writeAttribute("value", value, null);
    writer.endElement("input");
}

これで、バッキングビーンとやりとりできます。

MethodBinding

MethodBindingもValueBinding同様、タグクラスのsetProperties(...)で、 コンポーネントにリスナーメソッドをセットします。

JSP

こんな感じで使うときは。。。

<myh:hello value="#{hoge.s}"
    valueChangeListener="#{hoge.valueChanged}"/>

タグクラス

メソッドのバインドは、Application#createMethodBinding(...)で行います。 引数にはメソッド名と引数のクラスの配列を指定します。 以下はValueChangeListenerの例です。

public class HelloTag extends UIComponentTag {

    private String valueChangeListener;

    //...

    protected void setProperties(UIComponent component) {

        //...

        if (valueChangeListener != null) {
            if (isValueReference(valueChangeListener)) {
                MethodBinding bind = FacesContext.getCurrentInstance()
                        .getApplication().createMethodBinding(
                                valueChangeListener,
                                new Class[]{ValueChangeEvent.class});
                hello.setValueChangeListener(bind);
            }
        }
    }

    //...

    public String getValueChangeListener() {
        return valueChangeListener;
    }

    public void setValueChangeListener(String valueChangeListener) {
        this.valueChangeListener = valueChangeListener;
    }
}

TLD

TLDに増やしたプロパティーを追加します。

<tag>
  <name>hello</name>
  <tag-class>ct.HelloTag</tag-class>
  <body-content>empty</body-content>
  <attribute>
    <name>value</name>
    <required>true</required>
  </attribute>
  <attribute>
    <name>valueChangeListener</name>
    <required>false</required>
  </attribute>
</tag>

メソッドValidatorも同様に行えます。

まとめ(てない)

というような感じで、、、 そのうちもうちょっと楽しい例を作ってみようかと思います。

@TODO

参考

Sun J2EE 1.4 Tutorial - Chapter 20: Creating Custom UI Components
http://java.sun.com/j2ee/1.4/docs/tutorial/doc/index.html

Core JSF - Custom Components
http://www.horstmann.com/corejsf/
(本のプレビュー)


Back Top