作成 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/
(本のプレビュー)