かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

カスタムコンポーネント(表示されない奴)を作る

デザイナ上からobjectTypeやlistを設定できないObjectListDataProviderや、エラーメッセージが融通が利かないConverterやValidator。
目には見えないけど大事な役割をしてくれるこの人たち。
あまり使えない。

ObjectListDataProviderは、粘着力の無い糊みたい。


と、愚痴ってても始まらないので少し作ってみることに。


とりあえず、おもむろにEclipse起動!(ぉ
だってコーディングするならこっちがいいもの。

普通のJavaProjectをこさえたらクラスパスに

  • $NetBeansのインストールフォルダ$\rave2.0\modules\ext\dataprovider.jar
  • $NetBeansのインストールフォルダ$\rave2.0\modules\ext\jsf-api.jar

を追加する。

適当なパッケージをこさえて(仮にokazuki.dataprovider)ObjectListDataProviderを継承したクラスを作成。
名前は何のひねりも無くObjectListDataProviderEx

package okazuki.dataprovider;

import java.util.List;

import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;

import com.sun.data.provider.impl.ObjectListDataProvider;

public class ObjectListDataProviderEx extends ObjectListDataProvider {

	private static final long serialVersionUID = 1L;

	private String listExpression;

	/**
	 * objectTypeプロパティ代替用。文字列でクラス名を指定する。
	 * 
	 * @param name
	 */
	public void setObjectName(String name) {
		try {
			setObjectType(Class.forName(name));
		} catch (ClassNotFoundException e) {
			throw new IllegalArgumentException(e);
		}
	}

	public String getObjectName() {
		Class<?> objectType = getObjectType();
		if (objectType == null) {
			return null;
		}
		return objectType.getName();
	}

	/**
	 * listプロパティ代替用。EL式でlistプロパティにバインドするlistを設定する。
	 * 
	 * @param listExpression
	 */
	public void setListExpression(String listExpression) {
		this.listExpression = listExpression;
	}

	public String getListExpression() {
		return listExpression;
	}

	/**
	 * listExpressionプロパティに設定されたリストをlistプロパティに代入する。
	 * 
	 */
	public void applyExpression() {
		if (this.listExpression == null || "".equals(listExpression)) {
			return;
		}

		FacesContext ctx = FacesContext.getCurrentInstance();
		ValueBinding binding = ctx.getApplication().createValueBinding(
				listExpression);
		setList((List) binding.getValue(ctx));
	}
}

コード内のコメントにもあるように使えないobjectTypeプロパティとlistプロパティのかわりにobjectNameとlistExpressionプロパティを追加している。
文字列ならデザイナ上から設定可能だから!


ここまで出来たらjarに固めます。
EclipseからならExportでjar fileを選んでおいてsrcフォルダだけを入れるようにしておけばOK。
余計なものは入れないように。
ついでに、jardescとして保存しておくと次回からjarにするときに便利。


後は、次にcomplibを作るだけ!!
ここに、どれがどのコンポーネントか書いていく。
とりあえずマニフェストファイルから。


manifest.mf

Manifest-Version: 1.0
X-Rave-API-Compatibility-Version: 2.0
X-Rave-Complib-Configuration: META-INF/okazukiComponents-config.xml

二行目と三行目がポイント。
二行目は、2.0にしておけば問題ないらしい。1.0か2.0以外にするとNGっぽい。
三行目は、これから書くもの。ここに、どのクラスをパレットの何処に表示するのかとかを記述する。
META-INFというフォルダをこさえてokazukiComponents-config.xmlを作成!中身はこんな感じで。

<?xml version="1.0" encoding="UTF-8"?>

<complibConfiguration version="1.0" resourceBundleBaseName="ComplibBundle">
	<identifier>
		<uri>http://d.hatena.ne.jp/okazuki/sample</uri>
		<version>0.0.1</version>
	</identifier>
	<titleKey>okazuki components</titleKey>
	<runtimePath>
		<pathElement>okazuki.jar</pathElement>
	</runtimePath>
	<designTimePath prependRuntimePath="true">
		<!--       <pathElement>必要ならば書くらしい</pathElement>     -->
	</designTimePath>
	<javadocPath>
		<!--       <pathElement>必要ならば書くらしい</pathElement>     -->
	</javadocPath>
	<initialPalette>
		<folder key="okazuki DataProvider">
			<item
				className="okazuki.dataprovider.ObjectListDataProviderEx" />
		</folder>
	</initialPalette>
</complibConfiguration>

色々書いてあるけど、resourceBundleBaseNameはパレットとかに表示する文字列を指定するプロパティファイル名。
今回は使わないから適当に!

identifierタグの中は、このコンポーネントライブラリが一意になるようなものにしてあげる。
versionだけが違うものは、VisualWebPackがうまいこと管理してくれるっぽい。

titleKeyは、コンポーネントの名前と思ってOK
runtimePathは、その名の通り実行時に必要になるjarファイルを指定。
今回の場合は、最初に作ったokazuki.jarが必要になる。

designTimePathは、デザイン時に必要なものらしいけど今回使わないので空。
ここらへん使うようになってくると、ちょっとメンドクサソウ。
javadocPathも、よくわからんがドキュメントだろう。今回は無し。

initialPaletteタグの下が重要!
folderが、パレットの1塊。
itemにパレットに登録するクラス名を指定する。

後は、これらのファイルを下のような感じで拡張子をcomplibにしてjarに固めれば完成。

+META-INF
|   |--manifest.mf
|   |--okazukiComponents-config.xml
|
|--okazuki.jar

ここからは、NetBeansに移動。
作成したcomplibをインポートする。
インポートは、ツールのコンポーネントライブラリマネージャから。
インポートに成功すると、下のような感じになる。

適当にテスト用のVisual Webアプリのプロジェクトをこさえる。
さっきの追加したものとプロジェクトで使うように設定!
(やり方は、コンポーネントライブラリで右クリックしたらわかるはず!)
追加すると、パレットに項目が増えるはず!
増えない人は右クリックしてパレットの再表示してみてください。

あとは、ページにぽとぺた。
適当なBeanを作ってobjectNameプロパティに設定。

…なんてこった!
デザイナのせいか何のせいか知らないけど、独自に作ったクラスがなかなか認識されない。
objectNameプロパティに値を入れても、内部のClass.forNameあたりで例外が出てるんだろう…。
要改造だ…

とりあえずページを閉じてクリーンアンドビルド。そして、プロジェクトを閉じて開きなおすと使えるようになる。
objectNameプロパティに値を設定!
テーブルをぽとぺたして、データプロバイダを選択。
いい感じ。

SessionBean1あたりに適当にList型のプロパティを追加したらlistExpressionプロパティに

#{SessionBean1.employeesList}

を設定しておく。

後はボタンクリックなりお好きな所で

List employees = getSessionBean1().getEmployeesList();

if (employees == null) {
    employees = new ArrayList();
    getSessionBean1().setEmployees(employees);
}

employees.add(new Employee((Integer) idTextField.getValue(), 
        nameTextField.getValue().toString()));

employeeDataProvider.applyExpression();

こんなコードを書いてあげれば完成。


一応動いたけど、テーブルのカラムの中身をテキストボックスにして〜とかってなってくると、まだもう一ひねりくらいしないと駄目っぽいかな?
今度考えてみよう。