kagamihogeの日記

kagamihogeの日記です。

yui-frameworks の Beta 版さわってみた

Flex/AIR開発でデザイナとの協業を楽にする「yui」(1/3) - 特集:デザイナとプログラマを“結”ぶオープンソース(前編)- @IT
yuiを使ったFlexアプリを作って、便利さを体感しよう(1/4) - 特集:デザイナとプログラマを“結”ぶオープンソース(後編)- @IT

個人的に注目している Flexフレームワーク、yui-frameworks が Beta 版をリリース。昔 yui-frameworks さわってみた - kagamihogeのblog で少し触った時とは、色々変更が加わっているっぽい。上記の記事をざっと読んだところ、割といい感じに変わっているように感じた。本エントリでは、yui-frameworks を使用して足し算を行う Flex アプリのサンプルコードと、前版との違い、雑感などを書いていきたいと思います。

yui-frameworks ライブラリの配置

まず Flex プロジェクトを作成しておく。

AKABANA - downloads から akabana.yui-frameworks 1.0.0-beta1(修正版) を DL する(具体的には コレ)。解凍してできたディレクトリ中の yui-frameworks/target/swc/yui-frameworks-1.0.0-beta1.swc を libs ディレクトリにコピーする。libs はライブラリパスのディレクトリ。Eclipse 上だと libs がデフォルトになってる。

前バージョンに存在していた yui-metadata-config.xml は必要なくなったみたい。

サンプルのディレクトリ構成

ディレクトリ名とクラス名の命名規約が重要なので、その部分は @IT の記事を参考にしてください。良くまとまっておりまする故。

本エントリで作成するサンプルのディレクトリ構成はこんな感じ。


log4yui.properties

このファイルにクラスパス通ってないと、下記のようなエラーが出る。

ロケール "ja_JP" のリソースバンドル "log4yui" を解決できません

自力で書くのめんどいので、今回は AKABANA - downloads のサンプル http://akabana.sandbox.seasar.org/downloads/2008-09-22/yui-examples-beta1.zip からパチってくることにする。具体的には、akabane サイトのサンプル ZIP ファイルの中にある yui-examples/src/main/flex/log4yui.properties を src 以下に放り込んでおく。

main.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application 
	xmlns:mx="http://www.adobe.com/2006/mxml"
	xmlns:akabana="http://akabana.seasar.org/yui/mxml"
	layout="absolute"
	xmlns:view="yuibetaadd.view.*">
	
	<mx:Style source="yuibetaadd/addClasses.css"/>
	
	<akabana:YuiFrameworkMixin>
		<akabana:conventions>
			yuibetaadd
		</akabana:conventions>
	</akabana:YuiFrameworkMixin>
	
	<view:AddView id="addView" />
	
</mx:Application>

yuibetaadd/addClasses.css は、yui-frameworks の制御対象とする ActionScript のクラスを列挙する。コレが無いと下記のようなコンパイルエラーが出る。中身は後述。

ReferenceError: Error #1065: 変数 AddAction は定義されていません。

akabana:YuiFrameworkMixin の部分は、Seasar2 的な言い方をすると convention.dicon のようなものだと思う。ここには、yui-frameworks の制御対象下とするパッケージ名をここに記述する。ここでは yuibetaadd というセンスの無いパッケージを対象にしています。このパッケージ下の、action, helper, logic, view などのパッケージが CoC の対象になるわけですな。

あと、mx:Appication を yui:YuiApplication に変更する必要がなくってますね。

yuibetaadd/addClasses.css

「変数 AddAction は定義されていません。」の解決方法がわかんなくてウンウン唸り続け、helloworld のサンプル見て、この css ファイルが必要なことに気付くまでに小一時間かかったw

AddView{
    action:ClassReference("yuibetaadd.action.AddAction");
    helper:ClassReference("yuibetaadd.helper.AddHelper");
    helper:ClassReference("yuibetaadd.logic.AddLogic");
}

yuibetaadd/view/AddView.mxml

コレは見たまんまなので、特に書く事ないです。

<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml"
	layout="absolute" width="400" height="300">
	
	<mx:TextInput id="argument1" width="70"  x="10" y="10"/>
	<mx:Label text="+" x="88" y="12"/>
	<mx:TextInput id="argument2" width="70" x="115" y="10"/>
	<mx:Button id="calculate" label="=" x="193" y="10"/>
	<mx:Text id="result"  text="" x="241" y="12"/>
	
	<mx:Text id="arg1validateResult"  text="" x="10" y="40" width="70"/>
	<mx:Text id="arg2validateResult"  text="" x="115" y="40" width="70"/>
	
</mx:Panel>

yuibetaadd/view/AddAction.as

main.mxml で設定書いたり、設定ファイル用意すれば、あとは前バージョンとやることはそれほど変わらない。パッケージ名やクラス名の命名規則がビミョウに変化しているけど、そこに気をつければ、まぁやることは変わらない。

package yuibetaadd.action
{
	import flash.events.Event;
	import flash.events.MouseEvent;
	
	import org.seasar.akabana.yui.framework.event.FrameworkEvent;
	
	import yuibetaadd.helper.AddHelper;
	import yuibetaadd.logic.AddLogic;
	import yuibetaadd.view.AddView;
	
	public class AddAction{
		
		public var helper:AddHelper;
		
		public var logic:AddLogic;
		
		//これは見えない
		public var view:AddView;
		
		public function onApplicationStartHandler( event:FrameworkEvent ):void{
			trace( "onApplicationStartHandler:"+event );
			helper.init();
		}
		
		public function argument1ChangeHandler(event:Event):void {
			helper.showArg1ValidateResult();
		}

		public function argument2ChangeHandler(event:Event):void {
			helper.showArg2ValidateResult();
		}
		
		public function calculateClickHandler(event:MouseEvent):void {
			trace("helper[" + helper + "] logic[" + logic + "] view[" + view);
			var r:int = logic.calculate(int(helper.arg1), int(helper.arg2));
			helper.showResult(r);
		}
	}
}

フィールド変数の helper, logic は、勝手にインスタンスが用意される。

「これは見えない」とコメントに書いたとおり、xxxAction からは xxxView は見えない*1。どのクラスからどのクラスが見えるのかは、@IT の記事にまとめリストがあるので詳細はそちらを参照。

クラス名は、xxxAction とか規約に沿う必要があるけど、変数名は何でもいいらしい。試しに下記のようなテキトーな変数名にしてみたが、ちゃんとインスタンスが用意されて動作する。

		public var hogec:AddLogic;

フィールドの変数名とは打って変わって、メソッド名は規約に従う必要がある。どんなネーミングルールに従う必要があるのかは @IT の記事を読みましょう。ここは前版からは変わってないですな。まー xxxAction は基本的にナンモしないとこだしね。

ちなみに onApplicationStartHandler は、初期化処理のスタートアップ用途に使えそーな感じですね。

yuibetaadd/view/AddLogic.as

ムリヤリ xxxLogic を切り出して足し算してます。

package yuibetaadd.logic
{
	public class AddLogic
	{
		public function calculate(arg1:int, arg2:int):int {
			return arg1 + arg2;
		}
	}
}

極々小規模な構成なら xxxLogic 作らずに、xxxAction にロジック含めてもいいだろうね。もしくは、あえて xxxLogic に頼らないのもアリかと。

……しかしアレだな、xxxLogic とか xxxAction とかでレイヤの種類を区別するのはなんかモニョモニョするんで、yui-frameworks 的な識別呼称が欲しいとこですな。

yuibetaadd/view/AddHelper.as

UI ヘルパーのレイヤ導入したのは、個人的にグッジョブ。前バージョンの構成に素直に従うと、アプリケーションのコードと UI(mxml で定義した UI コンポーネント)をいじくるコードが混在しちゃうからねぇ。最も、一手間加えてやれば解決する話ではあるんだけど、その一手間をフレームワーク側で吸収してくれるのが嬉しい。

package yuibetaadd.helper
{	
	import mx.binding.utils.BindingUtils;
	import mx.validators.NumberValidator;
	
	import yuibetaadd.view.AddView;
	
	public class AddHelper
	{
		public var view:AddView;
		
		public var arg1:String;
		
		public var arg2:String;
		
		public function init():void {
			trace("AddHelper init:" + view);
			BindingUtils.bindProperty(this, "arg1", view.argument1, "text");
			BindingUtils.bindProperty(this, "arg2", view.argument2, "text");
		}
		
		public function showArg1ValidateResult():void {
			view.arg1validateResult.text = "";
			if (NumberValidator.validateNumber(new NumberValidator(), arg1, "").length > 0) {
				view.arg1validateResult.text = "arg1 が数字じゃねっす";
			}
		}

		public function showArg2ValidateResult():void {
			view.arg2validateResult.text = "";
			if (NumberValidator.validateNumber(new NumberValidator(), arg2, "").length > 0) {
				view.arg2validateResult.text = "arg2 が数字じゃねっす";
			}
		}
		
		public function showResult(result:int):void {
			view.result.text = String(result);
		}
	}
}

データバインディングの機能がどうなったのかまだ調べてないので、Flex 標準 API の BindingUtils でフツーにバインド設定してます。

バインドする変数の型は int にしちまおうかと思ったけど、UI ヘルパーっぽい処理を何かしらやらせたかったので、文字列型にしてます。showArg1ValidateResult で、ソレっぽい validation とエラーメッセージ表示をやってます。NumberValidator ってこんな使い方であってんのかね?

足し算の結果も、データバインディングで設定するようにしてもよかったけど、何となくメソッドをいっこ作ってます。public プロパティ的なやり方がイヤな場合は、こんな感じにインタフェースを切ってやればいい、って認識でいいのかな?

とまぁ、こんな感じで足し算完成。ActionScript のコードが view.mxml からパーペキに追い出せてるのが、いちばんのミソですかね。

以下、足し算を試作っててきになったコトについてのメモ。

データバインディングはどこでやる?

mxml で UI を表現、ActionScript クラスでロジックを定義、って構成にした場合。mxml と ActionScript の境界をまたぐためのデータは、どこで定義するのが自然なのかな? ってのが疑問に思った。今回は xxxHelper で定義したけど、xxxLogic に置くべきかなぁ? とも感じたんだよね。でも、xxxLogic からは xxxView が見えないから、BindingUtils が書きにくいんだよねぇ。

うーん……でも、xxxLogic からは xxxService 呼ぶこと考えると、xxxLogic から先は状態持たない方が自然かな?

validation と type conversion はどこでやる?

……となると、こいつらをどうするかに頭を悩まされるわけで。もし、キメ細かくエラーメッセージ出したい場合、validation と type conversion の境界が曖昧になると思う。AddHelper#arg1,arg2 は文字列型か int 型、どちらが適切なんだろなー? って疑問に思ったのですよ。まーどんだけ細かい検証処理が求められるのかは要件次第でもあるし、実際のとこはゴッチャ煮して扱っちゃいそうだけどw

でまぁ 2008-09-20 - おおたに6号機blog を思い出したんだけど。

つまり、値の検証をしてそのまま値を返すのがValidator、値の検証をして変換という

付加機能をつけて値を返すのがConverterだ。つまりConverterはValidatorの特殊形態と

捉える感じ。自分的にもこの捉え方はかなりしっくりきた。

2008-09-20 - おおたに6号機blog より抜粋

なるほどねぇ「ConverterはValidatorの特殊形態」かぁ。うーん、Flex の検証クラスとうまいこと親和するやり方を研究する必要があるっぽいなー。

VHAL(View, Helper, Action, Logic)

http://akabana.info

……って、yui-frameworks の作者自身が、その辺について書いてたw なるほどなるほど、そーなのかー。





まぁなんだ、みんな Flex で yui-frameworks 使って遊ぼうぜー。

*1:xxxAction から xxxView が見れないのは素直だと思うけど、手抜きして xxxAction にコードまとめちゃいたい場合はちょっとメンドウになったねw まぁ、helper.view とかやっちゃえばいいんだろうけど。