MXML からスクリプトを分離する方法まとめ(4種類)
ここ数日 Flex をいじっていて、 MXML からスクリプトを分離する方法を調べてたらいろいろ出てきたのでまとめ*1。使用環境は FlashDevelop 4.0.1 RTM + Flex SDK 4.6.0 + Windows XP Pro 32bit 。
以下で紹介するのは以下の4種類。結論を先に書くと mx.core.IMXMLObject インターフェースを実装する方法がおすすめ。
<fx:Script>
の source 属性を使う or include する方法mx.core.IMXMLObject
インターフェース実装を使う方法mx.core.FlexGlobals.topLevelApplication
を参照する方法mx.core.Application
の継承クラスを使う方法
分離する前の状態
まず <fx:Script><![CDATA[ ... ]]></fx:Script>
内に処理を書いている状態を想定。ここからロジックを分離していく。
プロパティをビューにバインドしてコントロールにイベントをアタッチできれば大概のことはできるだろう、ということで処理的なものはバインドとイベントアタッチのみ。
HelloWorld/ ├── bin/ ├── lib/ └── src/ └── HelloWorld/ └── Main.mxml
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" creationComplete="onCreationComplete()"> <fx:Script> <![CDATA[ [Bindable] private var statusMsg:String = "initialized."; private function onCreationComplete():void { statusMsg = "Creation complete."; } private function onButtonClick():void { statusMsg = "Clicked!"; } ]]> </fx:Script> <s:HGroup> <s:Label text="Status: {statusMsg}"/> <s:Button label="Click me" click="onButtonClick()"/> </s:HGroup> </s:Application>
<fx:Script> の source 属性を使う or include する方法
一応ファイルだけは「分離」できるけど動作が変わるわけではない。
プロジェクトフォルダの構成
HelloWorld/ ├── bin/ ├── lib/ └── src/ └── HelloWorld/ ├── Main.mxml └── script.as
[Bindable] private var statusMsg:String = "initialized."; private function onCreationComplete():void { statusMsg = "Creation complete."; } private function onButtonClick():void { statusMsg = "Clicked!"; }
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" creationComplete="onCreationComplete()"> <fx:Script source="script.as" /> <s:HGroup> <s:Label text="Status: {statusMsg}"/> <s:Button label="Click me" click="onButtonClick()"/> </s:HGroup> </s:Application>
これは以下のように include するのと実質変わらない。
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" creationComplete="onCreationComplete()"> <fx:Script> <![CDATA[ include "script.as"; ]]> </fx:Script> <s:HGroup> <s:Label text="Status: {statusMsg}"/> <s:Button label="Click me" click="onButtonClick()"/> </s:HGroup> </s:Application>
mx.core.IMXMLObject インターフェース実装を使う方法
本命。 mx.core.IMXMLObject を実装すると初期化時に initialized
メソッドが呼び出されるので CREATION_COMPLETE
ハンドラを仕込み、そのハンドラ内で各コンポーネントにイベントを仕込む。これで MXML 内でイベントを設定する必要はなくなる。
この場合でも MXML 内でイベント時に実行するメソッドを指定することは可能だが、ロジックをできるだけ分離する方針にもとづき、あくまで AS ファイル内で addEventListener
する。
プロジェクトフォルダの構成
ViewHelper/ ├── bin/ ├── lib/ └── src/ └── ViewHelper/ ├── View.mxml └── ViewHelper.as
ViewHelper.as
package ViewHelper { import mx.core.IMXMLObject; import mx.events.FlexEvent; import flash.events.MouseEvent; public class ViewHelper implements IMXMLObject { // MXML 内から呼ぶ場合は internal 以上に [Bindable] internal var statusMsg:String; private var view:View; // IMXMLObject が要求するメソッドは initialized のみ public function initialized(document:Object, id:String):void { statusMsg = "initialized."; view = document as View; view.addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete); // ボタンなどにイベントリスナーをアタッチしようとしてもまだ null } private function onCreationComplete(event:FlexEvent):void { statusMsg = "Creation Complete."; // ボタンへのアタッチは CREATION_COMPLETE のタイミングで view.btn.addEventListener(MouseEvent.CLICK, onButtonClick); } private function onButtonClick(event:MouseEvent):void { statusMsg = "Clicked via View."; } } }
View.as
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:helper="ViewHelper.*"> <fx:Declarations> <helper:ViewHelper id="vh"/> </fx:Declarations> <s:HGroup> <s:Label text="Status: {vh.statusMsg}"/> <s:Button id="btn" label="View" /> </s:HGroup> </s:Application>
mx.core.FlexGlobals.topLevelApplication を参照する方法
mx.core.FlexGlobals.topLevelApplication を参照してアクセスする。
プロジェクトフォルダの構成
MyClass/ ├── bin/ ├── lib/ └── src/ └── MyClass/ ├── Main.mxml └── MyClass.as
MyClass.as
イベントハンドラしか持たないようなクラスを作成。
package MyClass { import mx.core.FlexGlobals; import mx.events.FlexEvent; import flash.events.MouseEvent; public class MyClass { [Bindable] internal var statusMsg:String; private var app:Main; public function MyClass() { statusMsg = "initialized."; } // メソッドも MXML 内から呼ぶなら internal 以上に internal function onCreationComplete():void { statusMsg = "Creation Complete."; app = FlexGlobals.topLevelApplication as View; app.btn.addEventListener(MouseEvent.CLICK, onButtonClick); } private function onButtonClick(event:MouseEvent):void { statusMsg = "Clicked via App."; } } }
Main.mxml
MXML 側に何か仕込まないとスクリプト側に処理が回らないので creationComplete
イベント時の実行メソッドを MXML 内で指定。
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mc="MyClass.*" creationComplete="mc.onCreationComplete()"> <fx:Declarations> <mc:MyClass id="mc"/> </fx:Declarations> <s:HGroup> <s:Label text="Status: {mc.statusMsg}"/> <s:Button id="btn" label="App" /> </s:HGroup> </s:Application>
IMXMLObject
実装の中で FlexGlobals.topLevelApplication
参照を同時に使ってみたり(あまり意味はない)。
package ViewHelper { import mx.core.IMXMLObject; import mx.core.FlexGlobals; import mx.events.FlexEvent; import flash.events.MouseEvent; public class ViewHelper implements IMXMLObject { [Bindable] internal var statusMsg:String; private var view:View; private var app:View; public function initialized(document:Object, id:String):void { statusMsg = "initialized."; view = document as View; app = FlexGlobals.topLevelApplication as View; view.addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete); } private function onCreationComplete(event:FlexEvent):void { statusMsg = "Creation Complete."; view.btn1.addEventListener(MouseEvent.CLICK, handleClickViaView); app.btn2.addEventListener(MouseEvent.CLICK, handleClickViaApp); } private function handleClickViaView(event:MouseEvent):void { statusMsg = "Clicked via View."; } private function handleClickViaApp(event:MouseEvent):void { statusMsg = "Clicked via App."; } } }
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:helper="ViewHelper.*"> <fx:Declarations> <helper:ViewHelper id="vh"/> </fx:Declarations> <s:HGroup> <s:Label text="Status: {vh.statusMsg}"/> <s:Button id="btn1" label="View" /> <s:Button id="btn2" label="App" /> </s:HGroup> </s:Application>
mx.core.Application の継承クラスを使う方法
MXML で作られるオブジェクトは mx.core.Application インスタンスなので、これを継承すれば自前のクラスを MXML で使える。ただしそのままでは *.as 側でコントロールにイベントをアタッチすることができないなどいくらか制限がある。
プロジェクトフォルダの構成
ExtendsApp/ ├── bin/ ├── lib/ └── src/ └── ExtendsApp/ ├── ExtendsApp.as └── Main.mxml
ExtendsApp.as
package ExtendsApp { import flash.events.MouseEvent; import mx.core.Application; public class ExtendsApp extends Application { [Bindable] protected var statusMsg:String; public function ExtendsApp() { statusMsg = "Hello world!!"; } protected function onCreationComplete():void { statusMsg = "Creation complete."; // このパターンを使う場合、このイベントアタッチはできない //btn2.addEventListener(MouseEvent.CLICK, onButtonClick); } protected function onButtonClick():void { statusMsg = "Clicked!"; } } }
Main.mxml
<?xml version="1.0" encoding="utf-8"?> <component:ExtendsApp xmlns:component="ExtendsApp.*" xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" creationComplete="onCreationComplete()"> <s:HGroup> <s:Label text="Status: {statusMsg}"/> <s:Button id="btn1" label="btn1" click="onButtonClick()"/> <s:Button id="btn2" label="btn2" /> </s:HGroup> </component:ExtendsApp>
参考サイト
以上の文章は以下のサイトで紹介・検討されている内容をもとにまとめたものです。感謝。
- デザインとロジックの分離について - フォーラム - Flex User Group
- view(MXML)とlogic(ActionScript)の分離 | AIRLife.net
- FlexでRIA開発入門【その3】〜ビューとロジックの分離〜 - フリップフラップ
- FlexアプリケーションのMXML内要素を外部から参照する | Oddwit
- flex - Separating MXML and Actionscript - Stack Overflow
- mx.core.FlexGlobals - Adobe® Flash® Platform 用 ActionScript® 3.0 リファレンスガイド
- Flex 3 - Adobe Flex 3 Help
*1:こういうのは Flash Builder 使うとバシバシ自動生成してくれるのかもしれないけど使ったことないのでわかりません><