[Flex][Flex2開発ガイド] 4章 ActionScriptの使用

次はActionScriptですね。
ココで私が一番知りたいのは、MXMLActionScriptが互いをどのように意識して利用しているかと言うことです。
どうも、このあたりがしっかり書かれているブログとか見たことない(探せていないだけ?!)ので、
しっかり見ていきたいと思います。

FlexアプリケーションでのActionScriptの使用

ActionScriptECMAScript(ECMA-262)Edition 4言語仕様草案をベースにしているとのこと。
で、MXMLActionScriptは下記のような組み合わせができるらしい。

  • MXMLイベント属性の中でイベントリスナーを定義
  • タグを使用してスクリプトブロックを追加
  • 外部ActionScriptファイルをインクルード
  • ActionScriptクラスの読み込み
  • ActionScriptコンポーネントを作成

これを読んでいて、私は大きな思い違いをしていたみたいです。
私のActionScriptのイメージって、クラスがあって、
メソッドがあってとかだったのですが、
イベントのプロパティに各ちょこっとした記述もActionScriptって言ってるみたい。
違和感は残るのですが、ま、良しとしましょう。

これら一つ一つについては後ほど見ていきます。

ActionScriptのコンパイル

ここでは、どのようにコンパイルが行われるかをざっくり書かれているようです。
大雑把な流れは、下のような感じでしょうか?

  1. MXMLファイルとインクルードされたほかのファイルを1つのActionScriptに変換
  2. 変換したActionScript、読み込んだActionScriptクラスを最終的なSWFファイルへ追加
  3. ActionScriptコンポーネントをリンクし、最終的なSWFファイルにインクルード

なんだか、インクルードとか読み込むとかわけが分からないですね。
後でじっくり書いてあるのですが、下記のような使い分けっぽい?!

ActionScriptのインクルード
中に展開する
ActionScriptの読み込み
ActionScriptを参照する

このフローについては、個人的にはもっと深く知る必要もあるかなと思うのですが、
今はやめておきます。

生成されたActionScriptについて

生成されるActionScriptのクラス名はMXMLファイルの拡張子を覗いたものになるということです。
ただ、ここで出てきてる例がですね・・・

  • MyButton.mxml
<?xml version="1.0" encoding="UTF-8"?>

<mx:Button xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:click>var hoge:String = "hoge";</mx:click>
</mx:Button>

って、こんなことできるんですね。
ただ、こいつのメリットが何なのか分かりません。
scriptタグが埋め込めないんですよね。
この例のようにイベントに対して色々記述することはできるみたいなのですが、
それだけで何とかしろってことでしょうか?

でちなみに、これからできるActionScriptファイルは以下のとおりです。

実際のロジックはMyButton-generated.asに含まれています。

  • MyButton-generated.asの一部
/**
 * @private
 **/
public function ___MyButton_Button1_click(event:flash.events.MouseEvent):void
{
	var hoge:String = "hoge";
}

メソッド名にもルールがあるようですね。

ところでこいつをどう使うかですが、私には今のところ良く分かりませんでした。
*1
たぶん、自分で拡張したボタンとかはActionScriptで書いたほうがいいような気がするので
あえてMXMLで書く必要はないかな〜って思ってます。

MXMLイベントハンドラでのActionScriptの使用

タグの中で定義できる()よってことなのでスルー

MXMLファイル内でのActionScriptブロックの使用

ActionScriptタグ()の中で記述できるよってことです。
もちろんクラスを定義するのではなく、メソッドを定義するってことですよ。

このタグの外で記述手できるステートメントは下記のとおり

  • import
  • var
  • include
  • const
  • namespace
  • use namespace

あと注意するのは文字'<'とか'>'などのXMLで都合の悪いやつはCDATAで囲みましょうってことでしょうか。

ActionScriptマニュアルへのアクセス

そのままですね。『ActionScript3.0のプログラミング』を見ましょう

Flexコンポーネントの参照

idを振っておけばMXML内に書かれたActionScriptからはそのidで指定したものを頼りに参照できます。

  • CompRef1.mxml
<?xml version="1.0" encoding="UTF-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    width="100%"
    height="100%">
    
    <mx:Script>
        private function changeText1():void {
            text1.text = "ほげ";
        }
    </mx:Script>
    
    <mx:Text id="text1" text="hoge"/>
    <mx:Button id="button1" click="changeText1();"/>
    
</mx:Application>

じゃ、idがない場合はどうするのでしょうか?
getChildAt()とかgetChildByName()とかで強引にやっちゃうことはできるらしいが、
通常はやらないのかな?idはつけておくべきだしね。

それより、変更したいコンポーネント名が動的に変わる場合はありますよね。
その場合はthis["変更したいコンポーネントのid"]でアクセスすることはできます。

  • CompRef2.mxml
<?xml version="1.0" encoding="UTF-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    width="100%"
    height="100%">
    
    <mx:Script>
        private function changeText1(compId:String):void {
            this[compId].text = "ほげ";
        }
    </mx:Script>
    
    <mx:Text id="text1" text="hoge"/>
    <mx:Button id="button1" click="changeText1('text1');"/>
    
</mx:Application>

コンポーネントのメソッドの呼び出し

コンポーネントの参照ができれば、そのコンポーネントのメソッドも簡単に呼び出せます。
なのでスルー。

ActionScriptでのビジュアルFlexコンポーネントの作成

コンポーネントを動的に追加したいときのやり方ですね。
そんなに難しくなく、コンテナコンポーネントのaddChild()、addChildAt()を使えばいいとのこと。

  • DynamicComp1.swf
<?xml version="1.0" encoding="UTF-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    width="100%"
    height="100%">
    
    <mx:Script>
        import mx.controls.Text;
        private function createText1():void {
            var t1:Text = new Text();
            t1.text = "hoge";
            this.addChild(t1);
        }
    </mx:Script>
    
    <mx:Button id="button1" click="createText1()"/>
    
</mx:Application>

ただ、このやり方だと先ほどの参照がめんどいことになるので、
下記のように、MXMLに対応するActionScriptクラスのプロパティに自分でセットしてあげるのがいい気がします。
(別のやり方があったら教えてください)

  • DynamicComp2.swf
<?xml version="1.0" encoding="UTF-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    width="100%"
    height="100%">
    
    <mx:Script>
        import mx.controls.Text;
        public var text1:Text; // どこからでも参照できるように!
        private function createText1():void {
            text1 = new Text();
            text1.text = "hoge";
            this.addChild(text1); // addChildはコンテナへ追加する命令
        }
    </mx:Script>
    
    <mx:Button id="button1" click="createText1()"/>
    
</mx:Application>

あと削除はそのコンテナコンポーネントのremoveChild()、removeChildAt()、removeAllChildren()で
行うらしい。ただ、これで消してもオブジェクトがなくなるわけではなく、そのオブジェクトの参照が消えて
GC(ガベージコレクション)が発生したときにVMから消えるようです。

スコープについて

thisの話です。難しい話ではなく、thisが何であるかがちゃんと理解できてればOK。なのでスルーです。

ActionScriptファイルのインクルード

ActionScriptファイルのインクルードというのは、Scriptタグの中に記述するActionScriptを
外のファイルへ出してしまえ〜っていうことです。
なので、通常のクラスを前提にしたActionScriptファイルとは違います。

ここでは、CompRef2.mxmlのScriptタグに記述した部分を外に追い出しちゃいましょう。

  • AsInclude.mxml
<?xml version="1.0" encoding="UTF-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    width="100%"
    height="100%">
    
    <!-- MXMLからの相対パス -->
    <mx:Script source="AsInclude.as"/>    
    <mx:Text id="text1" text="hoge"/>
    <mx:Button id="button1" click="changeText1('text1');"/>
    
</mx:Application>
  • AsInclude.as
import mx.controls.Text;
/** 日本語もUTF-8ならOK */
private function changeText1(compId:String):void {
    this[compId].text = "ほげ";
}

MXMLの中からActionScriptoの要素がかなり少なくなりました。いいことです。
個人的にはMXMLのファイル名と一致させてActionScriptファイルを作るのが
開発しやすいような気もします。
ただ気になるのは、MXMLとActionScriptを行き来しなければならないことでしょうかね。
開発中はMXMLでどんどん進めて、メソッドが出来上がったら外に追い出しちゃうのがいいのかな?

あと、もちろんインクルードではthisはMXMLに対応したActionScriptクラスのインスタンスを指します。
もし外に出したActionScriptファイルが別のMXMLでも使用される場合は、thisはそれぞれのMXMLに対応したActionScriptクラスに
対応してしまうので注意です。(MXMLで共通のActionScriptのインクルードは行わないほうがいいですね。)

includeディレクティブの使用

Scriptタグの中にincludeってかけますよって話。
Scriptタグのsource属性で指定したほうが分かりやすいですよね。
ただ、複数指定したい場合はこちらの方がいいかもしれません。
あとincludeの場合、相対パスしか使えないので注意。

インクルードされた外部ファイルの参照

Scriptタグのsource属性とincludeは異なる方法でActionScriptファイルを見ているという話。
source属性の絶対パス指定はFlexデータサービスを使う場合のみだそうです。
ま、基本相対パスでってことですね。

クラスおよびパッケージの読み込み

こっちはimportの話。
使いたいクラスがあるならimportしましょう。
importにはワイルドカードも使えます。
そして、importしたのに使われてないクラスがあった場合でも、
そのクラスはSWFファイルには含まれないので、おもいっきりimportしちゃってください。
ってことです・・・。

ActionScriptをMXMLから分離するテクニック

今までやってきたインクルードをいくつかの例で見ているだけなのでスルーです。

ActionScriptコンポーネントの作成

独自コンポーネントを作っちゃいましょうってことで、ボタンの例で書かれているのでまねてみましょう。

  • CustomComp.mxml
<?xml version="1.0" encoding="UTF-8"?>

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:my="mycomp.*"
    width="100%"
    height="100%">
    
    <my:MyButton label="hoge"/>
    
</mx:Application>
  • MyButton.as
package mycomp {
	import mx.controls.Button;
	public class MyButton extends Button {
		// 何もしません
	}	
}

プログラムを書く上では難しくないですよね。
ただね、こいつをどのように配置してコンパイルしてってことは書いてあるところを見たところありません。

ちなみに私はAntを利用しているわけですが、下記のような配置にしています。

で、コンパイル時にはオプションsource-pathでsrc/main/asまで指定してあげればいいみたいです。
ここでのbuild.xmlも書いておきましょうかね。

  • build.xml
<project default="all">

    <property name="src.mxml.dir" value="src/main/mxml" />
    <property name="src.as.dir" value="src/main/as" />
    <property name="build.dir" value="build" />
    <property file="build.properties" />

    <path id="flexTasks">
        <fileset dir="${FLEX_HOME}">
            <include name="lib/*.jar" />
            <include name="ant/lib/*.jar" />
        </fileset>
    </path>

    <taskdef resource="flexTasks.tasks">
        <classpath refid="flexTasks" />
    </taskdef>

    <target name="all" depends="clean">
        <antcall target="mxmlc">
            <param name="target.name" value="${target.name.1}" />
        </antcall>
    </target>

    <target name="mxmlc">
        <mxmlc file="${src.mxml.dir}/${target.name}.mxml" output="${build.dir}/${target.name}.swf" debug="${mxmlc.debug}" keep-generated-actionscript="${mxml.keep-generated-actionscript}">
            <load-config filename="${mxmlc.load-config}" />
            <default-size width="${default.size.width}" height="${default.size.height}" />
            <source-path path-element="${FLEX_HOME}/frameworks" />
            <source-path path-element="${src.as.dir}" />
        </mxmlc>
    </target>

    <target name="clean">
        <delete dir="${build.dir}" />
    </target>

</project>
  • build.properties
FLEX_HOME=/Flex/flex3sdk_b1_061107

mxmlc.debug=false
mxmlc.load-config=/Flex/flex3sdk_b1_061107/frameworks/flex-config.xml
mxml.keep-generated-actionscript=false

default.size.width=800
default.size.height=600

target.name.1=custom/CustomComp

とりあえず、mxmlcでコンパイルするMXMLはちゃんと指定しなければならないのですが、
importするActionScriptはActionScriptが配置されているトップのディレクトリで問題ないみたいです。


カスタムコンポーネント

ActionScriptで作るコンポーネントにはユーザーインターフェイスコンポーネント(UIコンポーネント)と非ビジュアルコンポーネントがあるってこと。
で、UIコンポーネントはUIComponentを親に持つクラスということです。
更にこいつが重いので、ビジュアルが必用でないときは非ビジュアルコンポーネントで作りましょうって事らしい。
ま、よけいなもんは継承しちゃいかんってことですね。

オブジェクトイントロスペクションの実行

オブジェクトイントロスペクションはクラスのインスタンスからプロパティ、メソッドを調べる方法らしい。
リフレクションのこと?!
うーん、イントロスペクションって言葉は始めて聞いた気がする・・・。
ま、いいや。

で、いくつか方法があるようです。

for..in

動的追加されたプロパティは簡単にfor..inで簡単に取れるのでそれを利用した方法です。
ま、動的追加以外はできないので中途半端ですが、お手軽です。

<?xml version="1.0" encoding="UTF-8"?>

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    width="100%"
    height="100%">
    
    <mx:Script>
        private function showObject():void {
            var hoge:Object = new Object();
            hoge.a = "A";
            hoge.b = "B";
            
            var t:String = "";
            for( var p:Object in hoge ) {
                t += p + ":" + hoge[p] + "\n";
            }
            textArea.text = t;
        }
    </mx:Script>
    <mx:Button id="button1" click="showObject();"/>
    <mx:TextArea id="textArea" height="100" width="200"/>
</mx:Application>

これ以外にもただ表示したいだけなら、mx.utils.ObjectUtils.toString(obj:Object)でいいみたいですね。

イントロスペクションAPI

こちらは、非動的に追加されたプロパティでなくてもOKだそうです。
describeType()メソッドを利用して、その結果をE4X APIを使ってごにょごにょすればいいそうです。

<?xml version="1.0" encoding="UTF-8"?>

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    width="100%"
    height="100%">
    
    <mx:Script>
        public var hoge:String ="ほげ";
        
        private function showObject():void {
           var classInfo:XML = describeType(this);
           textArea.text = classInfo.toString();
        }
    </mx:Script>
    <mx:Button id="button1" click="showObject();"/>
    <mx:TextArea id="textArea" height="100" width="200"/>
</mx:Application>

まずはE4Xのお勉強ですかね。

*1:いつか調べます