作成 2004/4/27
更新 2004/4/28
カスタムコンポーネントに関するメモです。まだハローぐらい。
JSFではコンポーネントを作成して登録することができます。
カスタムコンポーネントに必要なクラスは以下です。
また、これらのクラスを次の設定ファイルに登録します。
UIコンポーネントがレンダラを兼ねることもできるようですが、 ここでは別のクラスとして作成します。
最初に作成するコンポーネントを利用するJSP側のコードを見てみます。
リスト cust1.jsp
<%@ taglib uri="WEB-INF/test.tld" prefix="myh" %> ... <myh:hello value="こんにちわ"/>
指定したvalue属性の値を表示するだけです。 JSFコンポーネントで作る必要はまったくありませんが、 何事もハローからということで。
ちなみに正常に実行されると以下のように表示されます(スクリーンショットにするだけアホらしいですが)。
実行結果
コンポーネントのソースは以下です。
リスト 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にも書かないといけません)。
お次はレンダラです。
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ファイルには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では、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はこんな感じ。 というか前と同じです(h:formでかこっただけ)。 ボタンは作るものとは関係ないです。 タグのvalue属性もあまりいらなかったかもしれません。
<%@ taglib uri="WEB-INF/test.tld" prefix="myh" %> ... <h:form> <myh:hello value="こんにちわ"/> <h:commandButton value="OK"/> </h:form>
実行結果
コンポーネントクラスは次のようになります。 相変わらずスカスカですが、継承元を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="前の値" />
まだあんまりよくわかってないので、説明はそのうち追記したいと思います
なんとなく、最初のカスタムコンポーネントがわかったところで、次は、value属性にEL式を書いて、 値をバッキングビーンからひろってくるようにしてみましょう。
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もValueBinding同様、タグクラスのsetProperties(...)で、 コンポーネントにリスナーメソッドをセットします。
こんな感じで使うときは。。。
<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に増やしたプロパティーを追加します。
<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も同様に行えます。
というような感じで、、、 そのうちもうちょっと楽しい例を作ってみようかと思います。
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/
(本のプレビュー)