PureMVCの表現領域であるView層について記述していきます。
この層で利用されるクラスは以下の通りです。
・
MXML
・
Mediatorクラス
これらについても概要は既に述べていますので、
サンプルを見てみましょう。
まずはMXMLファイルからです。
このMXMLファイルは先に記述したMXMLファイルの中ではなく、
Viewコンポーネントとして作成したMXMLファイルです。
先に記述されたMXMLファイルの解説の中のDにあたります。
「CalclationPanel.mxml」
<?xml
version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" ・・・・・・・・・・・・・・・・・・・・・・・・・・・・@
layout="vertical"
width="400"
height="300"
creationComplete="{this.init()}"
>
<!-- このUIコンポーネントで発生するすべてのイベントをここで列挙 -->
<mx:Metadata> ・・・・・・・・・・・・・・・・・・・・・・・・・・・・A
[Event('click1')]
[Event('click2')]
[Event('click3')]
[Event('click4')]
[Event('click5')]
[Event('click6')]
[Event('click7')]
[Event('click8')]
[Event('click9')]
[Event('click0')]
[Event('clickPlus')]
[Event('clickMinus')]
[Event('clickEqual')]
[Event('clickClear')]
</mx:Metadata>
<mx:Script>
<![CDATA[
import example.model.vo.CalclationVO;
// このUIコンポーネントで発生するすべてのイベントの文字列を列挙(上のMetadataタグとここの文字列の内容は必ず一緒になる)
public static const
click1:String = "click1"; ・・・・・・・・・・・・・・・・・・・B
public static const
click2:String = "click2";
public static const
click3:String = "click3";
public static const
click4:String = "click4";
public static const
click5:String = "click5";
public static const
click6:String = "click6";
public static const
click7:String = "click7";
public static const click8:String
= "click8";
public static const
click9:String = "click9";
public static const
click0:String = "click0";
public static const clickPlus:String = "clickPlus";
public static const clickMinus:String = "clickMinus";
public static const clickEqual:String = "clickEqual";
public static const clickClear:String = "clickClear";
// 画面表示用VO
[Bindable]
public var calcVO:CalclationVO; ・・・・・・・・・・・・・・・・・・・・・・C
/**
* 初期化処理
*/
private function init():void ・・・・・・・・・・・・・・・・・・・・・・・・・・・・・D
{
calcVO = new CalclationVO(); ・・・・・・・・・・・・・・・・・・・・・・・・・・E
}
]]>
</mx:Script>
<mx:TextInput id="txtOutput" editable="false"
text="{calcVO.calcString}" /> ・・・・・・・・・・・・・・・F
<mx:HBox>
<mx:Button id="btn1"
label="1" click="{dispatchEvent(new Event(
click1 ) )}" /> ・・・・・・・・・・・・・・・・・G
<mx:Button id="btn2"
label="2" click="{dispatchEvent(new Event(
click2 ) )}" />
<mx:Button id="btn3"
label="3" click="{dispatchEvent(new Event(
click3 ) )}" />
<mx:Button id="btnPlus" label="+"
click="{dispatchEvent(new Event( clickPlus ) )}" />
</mx:HBox>
<mx:HBox>
<mx:Button id="btn4"
label="4" click="{dispatchEvent(new Event(
click4 ) )}" />
<mx:Button id="btn5"
label="5" click="{dispatchEvent(new Event(
click5 ) )}" />
<mx:Button id="btn6"
label="6" click="{dispatchEvent(new Event(
click6 ) )}" />
<mx:Button id="btnMinus" label="-"
click="{dispatchEvent(new Event( clickMinus ) )}" />
</mx:HBox>
<mx:HBox>
<mx:Button id="btn7"
label="7" click="{dispatchEvent(new Event(
click7 ) )}" />
<mx:Button id="btn8"
label="8" click="{dispatchEvent(new Event(
click8 ) )}" />
<mx:Button id="btn9"
label="9" click="{dispatchEvent(new Event(
click9 ) )}" />
<mx:Button id="btnEqual" label="="
click="{dispatchEvent(new Event( clickEqual ) )}" />
</mx:HBox>
<mx:HBox>
<mx:Button id="btn0"
label="0" click="{dispatchEvent(new Event(
click0 ) )}" />
<mx:Button id="btnClear" label="C"
click="{dispatchEvent(new Event( clickClear ) )}" />
</mx:HBox>
</mx:Panel>
@
Panelタグ
このタグも説明の必要はないでしょう。
Flexのパネルタグです。
このタグのプロパティとして注目すべきは以下の点です。
・
layout="vertical"
レイアウトについてです。
Verticalの設定なので「縦並び」ということですね。
取り立てて、大きく扱うプロパティではないのですが、Absorute(絶対位置)では都合が悪かったので、Verticalにしてみました。
・
width="400"
height="300"
このコンポーネントの大きさです。
縦400ピクセル、横300ピクセルです。
・
creationComplete="{this.init()}"
A
Metadataタグ
このコンポーネントがもつイベントのすべてを記述しています。
このタグの中に宣言することにより、このコンポーネントが当該イベントを送出することができるようになります。
ここでは各ボタンに対応するイベントを設定しました。
ボタン1を押したら「click1」イベントが起きるということです。
このイベントに対応するハンドラは次に出てくるMediatorクラスでハンドルされます。
B
public static const
click1:String = "click1";
イベントの名称です。
Metadataタグの中で記述されたイベント名をすべてpublic
static constとして宣言し、
送出されたイベント名を特定できるようにしています。
単純にいえば
public static const click1 =
[Event('click1')]
という意味です。
これは後述されるGとMediatorクラスで利用されています。
C
public var calcVO:CalclationVO;
このクラスが保持するインスタンス変数の宣言です。
VOとあるとおり、前回にありましたValueObjectのことで、
ここでは計算結果を表示するためのBindableな変数として扱っています。
このクラスもまた、Mediatorクラスに出てきます。
D
creationComplete時に呼び出される初期化メソッド
ここでは「init」としていますが、functionの名称は何でもOKです(初期化を示す単語であれば尚良しです)。
ここで重要なのは次のCのことを実行することです。
E
calcVO = new CalclationVO();
インスタンス変数であるcalcVOをインスタンス化しています。
ここでインスタンス化しないとこのcalcVO変数は永遠にインスタンス化されません
(誰がインスタンス化していいのかわからない)ので。
F
<mx:TextInput
id="txtOutput"
editable="false" text="{calcVO.calcString}" />
TextInputコンポーネントです。
ここではeditableプロパティをfalseとすることで、ユーザー入力をしないようにしています。
計算機なので、直接入力してもらうと何かと不便なので、こうしています。
またtextプロパティではインスタンス変数であるcalcVOのcalcStringというプロパティを参照しています。
ここで参照することにより、calcVOのcalcStringプロパティの中身がTextInputコンポーネントに表示されるわけです。
G
<mx:Button
id="btn1" label="1"
click="{dispatchEvent(new Event(
click1 ) )}" />
Buttonコンポーネントです。
いわゆるイベントを送出するためのものですね。
ご覧のようにclickイベントでは
dispatchEvent(new Event(
click1 ) )
としてclick1というイベントを送出しています。
これはAで述べたMetadataタグの中で指定している
[Event('click1')]
と関連付けされ、このCalclationPanel.mxmlという
ViewコンポーネントはMetadataタグに指定された
イベントを発生させるということがわかるでしょう。
さて、ここまでではMXMLファイルの解説(いわゆるViewコンポーネント)の解説をしてきました。
このMXMLが画面上に表示される直接的なインターフェースを表していることになります。
そして、このMXMLにMetadataとしてイベントが記述されていることがわかるでしょう。
CairngormではイベントはすべてFrontController(ApplicationController)と呼ばれている部分に
集中していました。
MXMLの中でMetadataタグを使ってのイベント記述は一切なく、
すべてがFrontControllerに集中していたわけです。
集中管理できるのは利点ですが、どのViewがどんなイベントを起こしているのかわからなくなってしまうことが多く、
この点を改善したのがPureMVCといえるのではないでしょうか。
MXMLを見ればそのコンポーネントがどんなイベントを起こすのかすぐにわかるというのは大きな利点ですね。
次にMediatorクラスです。
「CalclationPanelMediator.as」
package example.view
{
import org.puremvc.patterns.mediator.Mediator;
import org.puremvc.interfaces.INotification;
import org.puremvc.interfaces.IMediator;
import example.model.CalclationProxy;
import example.view.components.CalclationPanel;
import flash.events.Event;
import example.ApplicationFacade;
import example.model.vo.CalclationVO;
public class CalclationPanelMediator
extends Mediator implements IMediator ・・・・・・・・・・・・・・・・・・@
{
private var proxy:CalclationProxy; ・・・・・・・・・・・・・・・・・・・A
public static const NAME:String
= "CalclationPanelMediator"; ・・・・・・・・・・・・・・・・・・B
/**
* コンストラクタ
* ここではこのMediatorクラスで発生するイベントのリスナを登録
*/
public function CalclationPanelMediator(view:Object):void ・・・・・・・・・・・・・・・・・C
{
// 親クラスのコンストラクタの呼び出し
super(view); ・・・・・・・・・・・・・・・・・・D
// ハンドラの登録
calclationForm.addEventListener(CalclationPanel.click0,onClick); ・・・・・・・・・・・・・・・・・E
calclationForm.addEventListener(CalclationPanel.click1,onClick);
calclationForm.addEventListener(CalclationPanel.click2,onClick);
calclationForm.addEventListener(CalclationPanel.click3,onClick);
calclationForm.addEventListener(CalclationPanel.click4,onClick);
calclationForm.addEventListener(CalclationPanel.click5,onClick);
calclationForm.addEventListener(CalclationPanel.click6,onClick);
calclationForm.addEventListener(CalclationPanel.click7,onClick);
calclationForm.addEventListener(CalclationPanel.click8,onClick);
calclationForm.addEventListener(CalclationPanel.click9,onClick);
calclationForm.addEventListener(CalclationPanel.clickMinus,onClick);
calclationForm.addEventListener(CalclationPanel.clickPlus,onClick);
calclationForm.addEventListener(CalclationPanel.clickClear,onClick);
calclationForm.addEventListener(CalclationPanel.clickEqual,onClick);
// facadeインスタンスに登録されたproxyクラス(ロジック実装クラス)を取得
proxy
= facade.retrieveProxy( CalclationProxy.NAME
) as CalclationProxy; ・・・・・・・・・・・・・・F
}
/**
* プロパティアクセサ
*/
public function get calclationForm():CalclationPanel ・・・・・・・・・・・・・・・・G
{
return viewComponent
as CalclationPanel; ・・・・・・・・・・・・・H
}
/**
* イベントハンドラ
* ここではCalclationPanelのイベントであるclick0などの
* イベントを解析している
*/
private function onClick(e:Event):void ・・・・・・・・・・・・・・10
{
// VIEWコンポーネント(プロパティアクセサ)からVOを取得
var calc:CalclationVO
= calclationForm.calcVO; ・・・・・・・・・・・・・・11
// イベントタイプにより処理の振り分け
switch( e.type
) ・・・・・・・・・・・12
{
case CalclationPanel.click0: ・・・・・・・・・・・・13
proxy.setNumber("0"); ・・・・・・・・・・・・14
sendNotification(ApplicationFacade.APP_NUMBER_CLICK,
calc); ・・・・・・・・・・・・15
break;
case CalclationPanel.click1:
proxy.setNumber("1");
sendNotification(ApplicationFacade.APP_NUMBER_CLICK,
calc);
break;
case CalclationPanel.click2:
proxy.setNumber("2");
sendNotification(ApplicationFacade.APP_NUMBER_CLICK,
calc);
break;
case CalclationPanel.click3:
proxy.setNumber("3");
sendNotification(ApplicationFacade.APP_NUMBER_CLICK,
calc);
break;
case CalclationPanel.click4:
proxy.setNumber("4");
sendNotification(ApplicationFacade.APP_NUMBER_CLICK,
calc);
break;
case CalclationPanel.click5:
proxy.setNumber("5");
sendNotification(ApplicationFacade.APP_NUMBER_CLICK,
calc);
break;
case CalclationPanel.click6:
proxy.setNumber("6");
sendNotification(ApplicationFacade.APP_NUMBER_CLICK,
calc);
break;
case CalclationPanel.click7:
proxy.setNumber("7");
sendNotification(ApplicationFacade.APP_NUMBER_CLICK,
calc);
break;
case CalclationPanel.click8:
proxy.setNumber("8");
sendNotification(ApplicationFacade.APP_NUMBER_CLICK,
calc);
break;
case CalclationPanel.click9:
proxy.setNumber("9");
sendNotification(ApplicationFacade.APP_NUMBER_CLICK,
calc);
break;
case CalclationPanel.clickMinus:
sendNotification(ApplicationFacade.APP_MINUS_CLICK,
calc);
break;
case CalclationPanel.clickPlus:
sendNotification(ApplicationFacade.APP_PLUS_CLICK,
calc);
break;
case CalclationPanel.clickClear:
sendNotification(ApplicationFacade.APP_CLEAR_CLICK,
calc);
break;
case CalclationPanel.clickEqual:
sendNotification(ApplicationFacade.APP_EQUAL_CLICK,
calc);
break;
}
}
/**
* このMediatorクラスで発生するイベントの登録をする
*/
override public function listNotificationInterests():Array ・・・・・・・・・・・・・・16
{
// ここに列挙された文字列をこのクラスの中ではハンドルすることにより、
// 処理の振り分けを行う。
return [ ・・・・・・・・・・・・・・・17
ApplicationFacade.APP_NUMBER_CLICK,
ApplicationFacade.APP_PLUS_CLICK,
ApplicationFacade.APP_MINUS_CLICK,
ApplicationFacade.APP_CLEAR_CLICK,
ApplicationFacade.APP_EQUAL_CLICK
];
}
/**
* listNotificationInterestsメソッドで登録された文字列を
* 実際に処理に振り分ける
*/
override public function handleNotification(notification:INotification):void ・・・・・・・・・・・・・・・・18
{
switch ( notification.getName()
) ・・・・・・・・・・・・・・19
{
case ApplicationFacade.APP_NUMBER_CLICK: ・・・・・・・・・・・・・・・20
// 画面に表示している情報(VO)の取得
calclationForm.calcVO = notification.getBody()
as CalclationVO;
// 画面に表示している情報の書き換え
calclationForm.calcVO.calcString = proxy.calcString;
break;
case ApplicationFacade.APP_PLUS_CLICK:
proxy.plusNumber();
break;
case ApplicationFacade.APP_MINUS_CLICK:
proxy.minusNumber();
break;
case ApplicationFacade.APP_CLEAR_CLICK:
proxy.clear();
// 画面に表示している情報の書き換え
calclationForm.calcVO.calcString = proxy.calcString;
break;
case ApplicationFacade.APP_EQUAL_CLICK:
proxy.calcEqual();
// 画面に表示している情報の書き換え
calclationForm.calcVO.calcString = proxy.calcString;
break;
}
}
}
}
@
public class CalclationPanelMediator extends Mediator implements IMediator
Mediatorクラスの宣言部。
ここではMediatorクラスの継承とIMediatorインターフェースの実装を宣言しています。
Mediatorクラス(IMediatorインターフェース)にはhandleNotificationというメソッドを持っています。
後述しますが、このメソッドをオーバーライドすることにより、ビジネスロジックとの橋渡しをします。
またこのクラスがハンドルするイベントを登録するためのメソッドであるlistNotificationInterestsというメソッドもあります。
これもオーバーライドすることにより、このMediatorクラスがハンドルできるイベントを登録するわけです。
A private var proxy:CalclationProxy;
ビジネスロジックであるProxyクラスのインスタンスを保持するための変数です。
このMediatorクラスの中で共通的にProxyクラスを使用したいためにインスタンス変数としています。
B public static const NAME:String
= "CalclationPanelMediator";
Facadeクラスに登録するための名前をここで定義しています。
ここで定義された名称でFaçadeクラスの中にインスタンスが生成されます。
よって、この名前はアプリケーションの中でユニークになっている必要があります。
C public function CalclationPanelMediator(view:Object):void
コンストラクタです。
引数としてview:Objectを受け取るようになっていますが、
このViewの実態はViewコンポーネントそのものです。
今回の計算機サンプルでは上記MXMLの解説でお話した
「CalclationPanel.mxml」のインスタンスが対象のViewコンポーネントになります。
D super(view);
親クラスのコンストラクタを呼び出しています。
ここでは上記Cのコンストラクタで指定されたViewコンポーネントを引数として渡しています。
これを実行することで親であるMediatorクラスのインスタンス変数「viewComponent」に設定しています。
このview(さらにいえばCalclationPanel.mxmlのインスタンス)が設定されます。
以降、このクラスの中ではviewComponentというインスタンス変数で
Viewコンポーネントを扱っていきます。
が、直接的に扱うのではなくGetterメソッドを用いて利用しています(後述G)。
E calclationForm.addEventListener(CalclationPanel.click0,onClick);
イベントを登録しています。
ここでいうcalclationFormというのは後述Gで示している通り、
実態はviewComponentというインスタンス変数です。
これは上記Dでも出てきましたね。
そうです。CalclationPanel.mxmlのインスタンスをさしています。
ここでこのCalclationPanel.mxmlが送出するイベントのリスナとハンドラを登録しているわけです。
ここまで来てようやくCalclationPanel.mxmlで設定していた送出イベントの名前定義を利用できるわけです。
しかし、MXMLが送出するイベント名を使って、リスナとハンドラを登録しているわけですから、
Cairngormのように「(FrontControllerに)イベントがいっぱいでよくわからない」ということはないと思います。
このMediatorクラスでイベントを登録しているわけですから。
F proxy = facade.retrieveProxy(
CalclationProxy.NAME ) as CalclationProxy;
ここではこの中で利用するProxyクラスを取得しています。
Proxyクラスはビジネスロジックを実装しているクラスであることは前に述べました。
このProxyクラスのインスタンスを取得することにより、
Mediatorクラスの中で自由に利用できるようになります。
G public function get calclationForm():CalclationPanel
Getterメソッドです。後述のHを返却しています。
H return viewComponent
as CalclationPanel
Mediatorクラスのインスタンス変数であるviewComponentのインスタンスをCalclationPanelのインスタンスにキャストして
返却しています。
viewComponentというインスタンスをそのまま扱わないのは、viewComponentというのはオブジェクトであり、
CalclationPanelというキャストをしてあげなければ、機能として何もないのと同じだからです。
この辺り、
var obj:Object = new Object();
とプログラムを書いてみて、
この「obj」という変数のプロパティやイベント、メソッドを見てみるとよくわかると思います。
(EclipseやFlexBuilderではヘルプがでますので。そうでない方はFlexAPIドキュメントのObjectクラスをご覧ください。)
I private function onClick(e:Event):void
上記Eで登録していたハンドラです。
CalclationPanel.mxmlで起こったイベントを受け取るハンドラですね。
J var calc:CalclationVO
= calclationForm.calcVO
ここではValueObjectであるCalclationVOを取得しています。
ValueObjectクラスは画面に値を表示する際の器として使用しています。
K switch( e.type
)
イベントタイプの判定です。
L case CalclationPanel.click0:
Kで判定するケース文ですね。
ここではイベントタイプがclick0であったら、このCase文の中身を実行するようになっています。
Case文は多々ありますが、やっていることは基本的に同じなので、説明は省略しています。
M proxy.setNumber("0");
ビジネスロジックを持つproxyのsetNumberというメソッドを呼び出して、
proxyに入力された(ここではクリックされた)番号を通知しています。
ここで入力された値を設定しないと、設定する機会がないので、
このタイミングで設定しています。
このときの「ボタンを押した」というイベントはこの中でしか取れないからです。
N sendNotification(ApplicationFacade.APP_NUMBER_CLICK, calc);
イベント登録用クラスであるNotificationをイベントとして送出しているメソッドです。
ここで送出されたNotificationイベントは後述Pで登録されたイベントに対応しており、
これをQのメソッドhandleNotificationでハンドルします。
ここでNotificationを起こすことにより、ビジネスロジックとの完全分離を目指しているわけです。
今回の計算機ではビジネスロジックはたいしたことをやっていませんのであまり必要性は感じないかと思われますが。
O override public function listNotificationInterests():Array
MediatorクラスのメソッドlistNotificationInterestsをオーバーライドしています。
このメソッドでこのMediatorクラスが起こすNotificationイベントを登録します。
登録の仕方は後述のPの通りです。
このメソッドは親のコンストラクタの中で自動的に呼ばれます。
このメソッドが定義されていないと、
Nで呼び出しているメソッド「sendNotification」は意味がないので、きちんと定義しましょう。
P return [ApplicationFacade.APP_NUMBER_CLICK,
ApplicationFacade.APP_PLUS_CLICK, ApplicationFacade.APP_MINUS_CLICK,
ApplicationFacade.APP_CLEAR_CLICK, ApplicationFacade.APP_EQUAL_CLICK]
このMediatorクラスが起こすNotificationイベントをArray型として返却しています。
Q override public function handleNotification(notification:INotification):void
Nで送出させたNotificationイベントのハンドラです。
O、Pで設定したイベントのハンドラがこのメソッドになります。
この中でビジネスロジックを実装しています。
というよりはProxyクラスの呼び出しをコントロールしているといったほうが正しいでしょう。
今回は計算機なのでたいしたことはやっていませんが、
Proxyクラスのメソッドを使っての計算やValueObjectへの値の設定はここでやっていることがお分かりいただけると思います。
R switch ( notification.getName()
)
送出されたNotificationの名前の判定です。
Nで送出されたものの名前を判定しています。
S case ApplicationFacade.APP_NUMBER_CLICK
Nで送出された名前のCase文ですね。
このCase文の中でProxyクラスの呼び出しやValueObjectへの値の設定などを行っています。
ビジネスロジックというよりはProxyのコントロールとValueObjectの設定を主にやるところですね。
実際のビジネスロジックはProxyというクラスがいるわけですから、
ここではそのコントロールと画面に表示する部分のValueObjectの制御だけを行えばいいのです。
ここでサンプルの解説を終わります。
ここまで来て、結局、どうやって流れるの?
っていう素朴な疑問について、曖昧なままよくわかりませんので、
図化してみました。
PureMVCの初回起動時は動きは以下の図の通りです。
サンプルで利用した計算機アプリを元にしていますので、Commandなどの動きには
ほかにバリエーションがあるかもしれませんので、とりあえずはこんな動きなんだということでお願いします。
PureMVCのイベント駆動時の動きは以下の図の通りです。
こちらもサンプルの計算機を元におしていますので、あしからず。
いかがでしたでしょうか?
ここまでお送りしましたPureMVCについてをサンプルを交えて解説してきましたが、
全体の俯瞰図は以下の通りです。
かなり大きいです、ごめんなさい。
大体の概念はMVCと変わりないことがお分かりいただけたかと思います。
ただFlexはイベント駆動型のアプリケーションなので、
どうしてもイベント中心になりがちですが、その辺りを解決してくれるのがPureMVCではないでしょうか。
まだまだいろいろFrameworkはあると思いますので、折を見て触れていきたいと思います。
とりあえず、今回はここまでです。
お付き合いいただきありがとうございました。