Data::AMF 0.03

| | Save This Page to del.icio.us このエントリーを含むはてなブックマーク

すっかりご無沙汰してしまいました。
書く習慣がなくなると駄目ですね。

というわけで、Data::AMF 0.03 がリリースされました。
http://search.cpan.org/dist/Data-AMF/

このバージョンから AMF3 がサポートされました。

あと、Data::AMF::Remoting というのが追加されていて、
NetConnection を使った Flash Remoting と、Flex の RemoteObject
を使った Flex RPC が簡単に出来るようになりました。

Plack でゲートウェイを実装するとこんな感じです。

use Data::AMF::Remoting;
use Plack::Request;
use UNIVERSAL::require;

sub
{
    my $env = shift;
    my $req = Plack::Request->new($env);
    my $res = $req->new_response(200);
	
    if ($req->path =~ /\/amf\/gateway$/)
    {
        my $remoting = Data::AMF::Remoting->new(
            source => $req->raw_body,
            message_did_process => sub
            {
                my $message = shift;
                my ($class_name, $method) = split '\.', $message->target_uri;
                $class_name->require;
                my $controller = $class_name->new;
                return $controller->$method($message->value);
            }
        );
        $remoting->run;
		
        $res->content_type('application/x-amf');
        $res->body($remoting->data);
    }
	
    return $res->finalize;
};

source に POSTデータを渡して、message_did_process というハンドラで、
メッセージ毎に処理しています。ヘッダー用には別途 header_did_process という
ハンドラが用意されています。

$message->target_uri に、Controller.method という形式の文字列が渡ってくるので、
これを使ってディスパッチします。

$message->value は Perl オブジェクトにデシリアライズされた引数です。


コントローラクラスの方は MooseX::Declare を使うと良い感じに書けます。

use MooseX::Declare;

class HelloController
{
    method echo(Str $text)
    {
        return $text;
    }
}


Flex 側はこんな感じです。

<mx:RemoteObject id="helloService"
    endpoint="http://localhost:5000/amf/gateway"
    destination="perlamf"
    source="HelloController"
    showBusyCursor="true"
    result="trace(event.result)"
    fault="trace(event.fault.faultDetail)"
/>
<mx:Button label="Hello" click="helloService.echo('Hello, world!')" />

destication は特に使われないのですが、空にするとランタイムエラーになるので、
適当に書いておきます。

source がサーバー側のクラス名になります。

これでボタンがクリックされると、HelloController クラスの echo メソッドが
コールされます。

Progression で Flex アプリケーションを作る

| | Save This Page to del.icio.us このエントリーを含むはてなブックマーク

Progression を使って Flex アプリケーションを作る場合、
自分ならどんな風に書くかな?と思って試してみました。

サンプル
ソース

--
まず、Progression と Flex を共存させるための基本的な考え方は、
コントローラを Progression で、ビューを Flex で書く、ということになります。

これを踏まえた、アーキテクチャは以下のようになります。

 
・View (MXML)

View は MXML で書きます。逆に考えると、MXML で書いた部分が View です。したがって、基本的に MXML にはビジュアルコンポーネントしか書きません。

・Delegate

Application、ItemRenderer など、View を生成するところで Flex の作法に逆らえないところは、View を操作するロジックを非ビジュアルコンポーネントとして View に記述します。Delegate は、後述する ViewController とは別のものになります。通常、View が ViewController を保持/参照することはありません。

・ViewController (Cast)

Progression の CastObject クラスを継承し、View オブジェクトを保持します。View の操作とイベントハンドリングを記述します。

・Scene

Progression の Scene です。Scene 毎に必要になる Model や View を生成し、それらを用いて ViewController を生成します。また、必要となるリソースは都度 Command を用いて取得し、生成した Model で処理/保持します。

・Service (Command)

外部サービスとの連携には Progression の Command を使います。これらの Command の生成と実行は、Scene が行います。

・Model

Model は ViewController の単位で生成します。ViewController が Model の値を View に適用します。値を保持する役割の他に、サービスから取得した値をデコードする処理なども担います。

--
次に、処理の流れを具体的に見て行きましょう。

1. ProgressionSample.mxml

Flex フレームワークを使う場合、メインクラスは Application クラス (MXML) になります。Application クラスは Flex のコンテナなので、直接 MXML を使って View を構築することも出来ますが、前述したアーキテクチャに従うために、ProgressionSampleDelegate コンポーネントのみが記述されています。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
	xmlns:mx="http://www.adobe.com/2006/mxml"
	xmlns:local="*"
	layout="absolute"
	viewSourceURL="srcview/index.html"
>
	<mx:Style source="ProgressionSample.css" />
	<local:ProgressionSampleDelegate />
</mx:Application>

2. ProgressionSampleDelegate

ProgressionSample の FlexEvent.INITIALIZE イベントで ProgressionSampleDelegate の initialize() が呼ばれます。initialize() が呼ばれるまでの処理は、親クラスの ApplicationDelegate に隠蔽しています。initialize() で Progression インスタンスを生成し、ここからの処理の流れは Progression に委ねます。

public class ProgressionSampleDelegate extends ApplicationDelegate
{
	public var view:ProgressionSample;
	
	override protected function initialize():void
	{
		SWFWheel.initialize(view.systemManager.stage);
		FilterShortcuts.init();
		
		var progression:Progression = new Progression("main", view.systemManager.stage, MainScene);
		progression.sync = true;
		progression.autoLock = false;
		progression.goto(progression.firstSceneId);	
	}
	
}

3. MainScene

MainScene は Page1Scene と Page2Scene を生成し、さらに Header/Navigation/Contents の View と ViewController を生成しています。スタティックなコンテンツなので難しいところは特にないと思います。これらは onLoad で Application に AddChild されます。さらに、onInit で Page1Scene に Goto するようになっています。

public function MainScene(initObject:Object = null)
{
	super("main", initObject);
	
	addScene(new Page1Scene());
	addScene(new Page2Scene());
	
	application = Application.application as Application;
	
	header = new HeaderController(null, new Header());
	navigation = new NavigationController(null, new Navigation());
	contents = new ContentsController(null, new Contents());
}
override protected function _onInit():void
{
	addCommand(
		new Goto(getSceneAt(0).sceneId)
	);
}

override protected function _onLoad():void
{
	addCommand(
		[
			new AddChild(application, header),
			new AddChild(application, navigation),
			new AddChild(application, contents)
		]
	);
}

4. Page1Scene

Page1Scene では、PageModel と Page1 を生成し、Page1Controller を生成しています。onLoad で外部 XML を読んで、PageModel に渡し、それから Contents に View1Controller を AddChild しています。

public function Page1Scene(initObject:Object = null)
{
	super("page1", initObject);
	
	addScene(new Page1DetailScene());

	controller = new Page1Controller(new PageModel(), new Page1());
}

public var controller:Page1Controller;

override protected function _onLoad():void
{
	addCommand(
		new LoadURL(new URLRequest("assets/data.en.xml")),
		
		function ():void
		{
			controller.model.parse(this.previous.data);
		},
		
		new AddChild(MainScene(parent).contents.view, controller)
	);
}

override protected function _onUnload():void
{
	addCommand(
		new RemoveChild(MainScene(parent).contents.view, controller)
	);
}

5. Page1Controller

Page1Controller は view プロパティに Page1 オブジェクトを保持しています。view の表示アニメーションを DoTweener コマンドで定義し、さらに view.dataGrid の MouseEvent.DOUBLE_CLICK イベントをハンドリングしています。データグリッドがダブルクリックされると、Page1DetailScene がダイナミックに生成する Scene に Goto しています。

public var model:PageModel;
public var view:Page1;

override protected function _onCastAdded():void
{
	view.alpha = 0;
	
	view.dataGrid.dataProvider = model.words;
	view.dataGrid.addEventListener(MouseEvent.DOUBLE_CLICK, dataGridDoubleClickHandler);
	
	addCommand(
		new DoTweener(
			view,
			{
				_Blur_blurX : .1,
				_Blur_blurY : .1,
				alpha: 1,
				time: 1
			}
		)
	);
}

override protected function _onCastRemoved():void
{
	view.dataGrid.removeEventListener(MouseEvent.DOUBLE_CLICK, dataGridDoubleClickHandler);
	
	addCommand(
		new DoTweener(
			view,
			{
				_Blur_blurX : .1,
				_Blur_blurY : .1,
				_Blur_quality : 3,
				alpha: 0,
				time: 1
			}
		)
	);
}

private function dataGridDoubleClickHandler(event:MouseEvent):void
{	
	new Goto(new SceneId("/main/page1/detail/" + view.dataGrid.selectedItem.@id)).execute();
}

6. Page1DetailScene

Page1DetailScene は通過専用の Scene で、ここで目的地となる Scene をダイナミックに生成しながら、遷移しています。この辺りは muraken さんの記事 を多いに参考(と言うより、ほぼそのまま...)にさせていただきました。遷移が終わったら、View を持たない Controller を生成し、Alert.show() でモーダルウィンドウを表示させています。

public function Page1DetailScene()
{
	super("detail");
	
	controller = new Page1DetailController();
}

public var controller:Page1DetailController;

override protected function _onLoad():void
{
	super._onLoad();
	
	var parentController:Page1Controller = Page1Scene(parent).controller;
	var item:XML = parentController.model.getItemById(current.name);
		
	controller.show(item, parentController.view);
}
public function show(item:XML, target:Sprite):void
{
	if (item == null)
		Alert.show("Couldn't find.", "Error", 4, target, closeHandler);
	else
		Alert.show(item.@value, "No." + item.@id, 4, target, closeHandler);		
}

private function closeHandler(event:CloseEvent):void
{
	new Goto(new SceneId('/main/page1')).execute()
}

--
基本的な流れは以上です。

DataGrid の DataGridColumn に無駄に豪華な ItemRenderer を当てていますが、ItemRenderer と ItemRendererDelegate はこんな具合に書くよ、というサンプルのつもりで敢えてそうしてあります。

html-template は Flex Builder でデプロイするのに最適なようにカスタマイズしてあります。

--
以下は余談ですが、今回の検証で本格的な Flex アプリケーションを Progression を使って実装出来ることがわかりました。

Flex フレームワークはページ管理、状態管理といった部分の機能が非常に貧弱なので、大規模な開発を管理するのが難しいという側面があります。それを補うために、Cairngorm や PureMVC といった Flex アプリケーション向けのフレームワークが開発されていますが、Progression もそれらと比較しても遜色はないと思いました。

特に Scene という概念が秀逸で、Scene が Facade になって内部の MVC を隠蔽してくれるため、Scene 単位でアプリケーションを分けることが出来ます。Scene 毎に分散して開発することで、大規模な開発もしやすいと思いました。

さらに余談ですが、MXML に非ビジュアルコンポーネントを記述することは、Flash Professional でタイムラインにスクリプトを書くのと同じ感覚なので、実際のプロジェクトでは使用されていないスタイルだと思います。

個人的には Cocoa + Interface Builder のように、View に相当する部分は GUI で構築してしまって、中のソースコードは意識しなくて済むように隠蔽されている、というのが案外望ましいのかな、と思っているので、Flash Catalyst + Flash Builder の開発はそこを目指して欲しいなと思っています。残念ながら Beta から汲み取れる雰囲気ではそうなっていないようですが...。

AMF と Perl について

| | Save This Page to del.icio.us このエントリーを含むはてなブックマーク

Flex や Flash で言うところの「Remoting」を行いたい場合、
サーバー側を Perl で実装しようとすると、どんな手段があるでしょうか。

古くから CPAN に存在するのは、AMF::Perl というモジュールです。
これは、AMFPHP からの移植されたモジュールらしいのですが、
残念ながら 2004 年でメンテナンスが止まってしまっていることで、
なかなか使いにくいという状況でした。

そんななか、先日 Data::AMF という新しいモジュールが CPAN
登録されている気付いたので、これを試してみることにしました。

そして試してみる中で色々わかったことがあったのでまとめてみようと思います。

Genius Framework Version 1.5.0

| | Save This Page to del.icio.us このエントリーを含むはてなブックマーク

間に v1.4.0 もリリースしていますが、
v1.3.0 からの差分をまとめて書いてしまいます。

なお、v1.5.0 は 安定板としてタグを切ってあるので、
API の仕様変更などが嫌で手を出せなかった人も、
これを機にぜひ使ってみて下さい。

http://www.libspark.org/browser/as3/GeniusFramework/tags/v1.5

Genius Framework Version 1.3.0

| | Save This Page to del.icio.us このエントリーを含むはてなブックマーク

先日に引き続き、Genius Framework Version 1.3.0 をリリースしました。

ロードマップなどは前回と変わっていません。
Version 1.5.0 へ向けて、着々と開発が進んでおります。

各ドキュメントやサンプルも 1.3.0 に対応したものに更新してあります。
詳しくはプロジェクトページを参照して下さい。

http://www.libspark.org/wiki/seagirl/genius

以下、前回からの差分を記します。

Genius Framework Version 1.2.0

| | Save This Page to del.icio.us このエントリーを含むはてなブックマーク

Genius Framework 1.2.0 をリリースしました。

いつの間にか branches が出来ていたので、
今回 trunk と tags を作って、既存のファイルは全て trunk に移しました。

今後のロードマップとしては、
7月22日に行われる Spark project 勉強会 #01 を目処に
Version 1.5.0 をリリースし、これを安定版として、
tags にスナップショットを置く予定です。

それまでは、trunk の方が変わりやすい状況が続くと思いますので、
ご注意下さい。

Version 1.0.0 からの主な変更点をまとめておきます。

コンポーネントの初期化フロー

| | Save This Page to del.icio.us このエントリーを含むはてなブックマーク

今回は Flex におけるコンポーネントの初期化フローについて、
ソースを追いながら内部でどんなことが起きているのかを調べたので、
わかったことをまとめてみたいと思います。

誤解しやすいところなので、確認しておくと、
ここでの「コンポーネント」はビジュアルコンポーネント
呼ばれているものを指してします。

さらに、コンポーネントは大きく分けて2つに分類されます。
一つは、Label クラスのように、UIComponent を直接継承しているものです。
もう一つは、Canvas クラスのように、Container クラスを継承しているものです。

実際には、Container は UIComponent のサブクラスなので、
Container も UIComponent になるのですが、
ここでは UIComponet を直接継承しているか、
Container を挟んで継承しているか、を区別しています。

そして、前者を「コントロール」、後者を「コンテナ」と呼びたいと思います。

前置きが長くなりましたが、本題に入ります。

Adobe AIR Update Framework

| | Save This Page to del.icio.us このエントリーを含むはてなブックマーク

Adobe Labs にて Adobe AIR Update Framework がベータリリースされています。
http://labs.adobe.com/wiki/index.php/AdobeAIRUpdate_Framework

AIR アプリケーションをアップデートするための Framework みたいです。

Genius にも同じ目的で作ったクラスがあるのですが、
こういうクラスがもう少し早く出てきてくれていれば、
わざわざ自前で作る必要もなかったのにな、と。。。

元々何でないのだろうというところだったので、
そのうち出るんじゃないかなとは思っていたのですが。

ところでサンプルコードを見ていておっと思ったのですが、
ADF から情報を取得するのに、XML のネームスペースの扱いが
知らないと難しいのですが、XML クラスのnamespaceメソッドを使うと、
こんな風に書けるのねというのを今さら知ったので書いておきます。

private function setApplicationNameAndVersion():void {
    var appXML:XML = NativeApplication.nativeApplication.applicationDescriptor;
    var ns:Namespace = appXML.namespace();
    lblAppVersion.text = appXML.ns::version;
    lblAppName.text = appXML.ns::name;
}

Genius Framework 1.0.0

| | Save This Page to del.icio.us このエントリーを含むはてなブックマーク

Genius Framework Version 1.0.0
http://www.libspark.org/browser/as3/GeniusFramework

兼ねてから自分用に作って使っていたFlex用のフレームワークを
Spark projectのリポジトリにコミットしました。

ひとまず簡単な紹介を書いておきます。
詳しい紹介や使い方はまた追ってポストしていこうと思います。

AIRアプリでOS側からのドラッグ&ドロップを受け付ける方法

| | Save This Page to del.icio.us このエントリーを含むはてなブックマーク

 またしてもAIRネタです。ハイペースで情報を詰め込むと忘れやすいので、覚えているうちにネタ毎にまとめてblogに投げておくと、後々自分にとっても役に立ちそうなので書いてしまいます。

 さてさて、今回はMacのFinderやWindowsのExplorerのから直接ファイルをAIRアプリにドラッグ&ドロップして渡すためのAPIの使い方です。

AIRアプリで特定のファイルフォーマットのファイルを開く方法

| | Save This Page to del.icio.us このエントリーを含むはてなブックマーク

 社内でアルファチャンネル付きのFLVを開くアプリケーションはないかと聞かれ、よく考えたらそういうアプリケーションが見当たらないことに気付きました。

 FLVならばFlashで開けるだろう、ということでAdobe AIRを使って「FLV Player」を社内向けに作ることにしました。

 ローカルアプリなんだから、普通にFLVファイルをダブルクリックして開いた時にこの「FLV Player」で開けないものかな、と思って調べたら出来たのでまとめておきます。

AIRアプリの起動時の位置

| | Save This Page to del.icio.us このエントリーを含むはてなブックマーク

 いきなり脱線しますが、Adobe AIRで作られたアプリケーションは何と呼べば良いのでしょうか。とりあえずAIRアプリと呼ぶことにしましたが、どうもわかりずらいですね。

 というわけで、AIRアプリの起動時の位置について社内で質問を受けたのでまとめておこうと思います。

Adobe AIR 1.0、Flex Builder 3に移行する

| | Save This Page to del.icio.us このエントリーを含むはてなブックマーク

 Adobe AIR 1.0が正式にリリースされました。
 http://www.adobe.com/products/air/

 早速、AIR Beta 3からAIR 1.0に移行してみます。

E4Xを使ってXMLをマージする

| | Save This Page to del.icio.us このエントリーを含むはてなブックマーク

 最近は必要なデータをなるべくローカル側に持たせるようにしていて、検索などはローカル内だけで行ってしまう場合も少なくなりました。

 ローカル側に必要なデータを全て持たせようとすると結構なボリュームになるので、サーバーからローカルに新しいデータを渡す時は差分だけを渡すようにしています。

 さて、差分を受け取ったローカル側はこれを今あるデータにマージする必要があります。今回はこの処理をE4Xを使って書いてみることにします。

mx.binding.utils.BindingUtils

| | Save This Page to del.icio.us このエントリーを含むはてなブックマーク

 日頃からFlexライブラリのデータバインディングにはずいぶんお世話になっている。

 データバインディングとは、あるオブジェクトのデータを他のオブジェクトに結びつける機能のことで、主にModelの値をViewに反映するのに使う。