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 から汲み取れる雰囲気ではそうなっていないようですが...。