kagamihogeの日記

kagamihogeの日記です。

イベントのバブリングを利用したカスタムコンポーネント間の連携

イベントのバブリングの基礎学習 - kagamihogeのblog の続き。

そもそも、俺がなぜイベントのバブリングについて調べるにいたったかですが。Flex のカスタムコンポーネント間で、データの受け渡しやメソッドの呼び出しをさせるなどして、どうやって連携させるのだろうか? という疑問が発生したわけです。

MXML のファイルは放置するとすぐに肥大化していくので、テキトーなところで分割してやらないと見通しが死ぬほど悪くなります。個人的には、小クラス主義を踏襲して、小さめ小さめを意識してどんどん分割するべきだと思ってます。具体的には、一ファイル 100-200 行・タグのネストの深さ 4,5 ぐらいがいいかなぁ、と感じています。

でまぁ、例えば下みたいな感じにカスタムコンポーネント化したとして。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
	layout="horizontal">

	<customcomponent:LeftBox id="leftBox" />
	<customcomponent:RightBox id="rightBox" />
</mx:Application>

leftBox の下で発生したイベントに呼応して、rightBox の方で何かしら処理をしたい……という場合はどう記述すればいいものか? leftBox が rightBox の参照を持つのは一つの解かもしれない。が、ちょっと考えずともスマートではない。それに、leftBox の子の子の子のコンポーネントがイベントの発生元だったりしたら……などを考えるとやっぱり良い手には思えない。

というわけで、イベントのバブリングを使用した手を考えてみる。

サンプルのディレクトリ構成はこんな感じ。

実行の様子はこんな感じ。左側のボックス内のテキストボックスで編集した文字列が、右側のボックスのラベルに連動して表示される、というものを考える。

まずは LeftBox のソースから。

<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"
    width="100%" height="100%"
    borderStyle="solid" cornerRadius="9" borderColor="#FF0000"
    xmlns:left="customcomponent.left.*" >

    <left:LeftInnerBox />
</mx:VBox>

バブリングしてるのを強調するために、更に子のカスタムコンポーネントを持ってます。
LeftInnerBox はこんな感じ。

<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml"
    width="100%" height="100%" borderStyle="solid"
    cornerRadius="9" borderColor="#0000FF">
    
    <mx:Script>
        <![CDATA[
            import customcomponent.event.LeftTextChangeEvent;
            private function change():void {
                var e:LeftTextChangeEvent = new LeftTextChangeEvent(
                    LeftTextChangeEvent.LEFT_TEXT_CHANGE_EVENT, true);
                e.text = t.text;
                this.dispatchEvent(e);
            }
        ]]>
    </mx:Script>
    
    <mx:TextInput id="t" change="change()" />
</mx:HBox>

テキストの変更を拾って、カスタムイベントの LeftTextChangeEvent をバブリングを true にして投げてます。

LeftTextChangeEvent はこんな感じ。

package customcomponent.event
{
    import flash.events.Event;

    public class LeftTextChangeEvent extends Event
    {
        public static var LEFT_TEXT_CHANGE_EVENT:String = "leftTextChangeEvent";
        public var text:String;
        public function LeftTextChangeEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false)
        {
            super(type, bubbles, cancelable);
        }
        
    }
}

このサンプルぐらい短ければ独自のイベントクラス作らなくてもいいんだけども。ただ、イベントハンドラ側で必要な情報はこのクラスに載せることになるんで、そこそこのケースでクラス作る必要になるんじゃないかなーと思います。

main となる MXML の中身。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="horizontal"
    xmlns:customcomponent="customcomponent.*"
    applicationComplete="init()">
    <mx:Script>
        <![CDATA[
            import customcomponent.event.LeftTextChangeEvent;
            private function init():void {
                leftBox.addEventListener(
                    LeftTextChangeEvent.LEFT_TEXT_CHANGE_EVENT,
                    rightBox.leftTextChangeEventHandler);
            }
        ]]>
    </mx:Script>

    <customcomponent:LeftBox id="leftBox" />
    <customcomponent:RightBox id="rightBox" />
</mx:Application>

leftBox に対してイベントリスナを設定してます。いま見てきたように、leftBox の子どもの方から LeftTextChangeEvent がバブリングで昇って来る。なのでそのイベントに対応する rightBox のリスナを登録しておくわけです。

これをもってして「LeftBox の子の LeftInnerBox で起きたイベントを RightBox に伝達する」という連携を実現します。

最後に RightBox のコード。

<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"
    width="100%" height="100%"
    borderStyle="solid" cornerRadius="9" borderColor="#FF0000">
    
    <mx:Script>
        <![CDATA[
            import customcomponent.event.LeftTextChangeEvent;
            public function leftTextChangeEventHandler(e:LeftTextChangeEvent):void {
                l.text = e.text;
            }
        ]]>
    </mx:Script>
    
    <mx:Label id="l" />
    
</mx:VBox>

ここは単純にイベントハンドラ書いてるだけですね。

もしくは、連携先コンポーネントのメソッド呼び出したい、だけならこういう形でもいいかなーと考えてます。

leftBox.addEventListener(
    LeftTextChangeEvent.LEFT_TEXT_CHANGE_EVENT,
    function():void {rightBox.leftTextChangeEventHandler()});

実際には MXML に ActionScript のコード置きたくないんでもちょっと複雑になるだろうけど。その辺は IMXMLObject でぐぐってもらうなどしてもらうということで。

そういえば Flexフレームワークってこの辺どうやってんだろうなぁ。果たしてこのやり方がクレバーなやり方かどうかわからんのでとりあえず blog に書いてみた、ってトコもあるんですよね。俺もまだまだ研究中の身なので、なんか良いやり方とか情報源とか知ってる人がいたら教えて頂きたいです。

カスタムコンポーネントのカスタムイベントをMXML内でリスナー登録する方法

[Flex]カスタムコンポーネントのカスタムイベントをMXML内でリスナー登録する方法 | _level0 | Kayac Front End Engineer's Blog

今回のエントリ書く途中で見つけたのでついでに。

Flex が標準で備えてるイベントに対するリスナって、MXML だと みたいに書けるじゃないですか。アレを自前でやれる方法が書いてあった。

せっかくなので、今回のサンプルにこのやり方を適用させてみる。

LeftBox はこうなる。

<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"
    width="100%" height="100%"
    borderStyle="solid" cornerRadius="9" borderColor="#FF0000"
    xmlns:left="customcomponent.left.*" >
    
    <left:LeftInnerBox />
    <mx:Metadata>
        [Event(name="leftTextChangeEvent",type="customcomponent.event.LeftTextChangeEvent")]
    </mx:Metadata>
</mx:VBox>

そうすると、main.mxml はこんな感じに書けるようになる。

<customcomponent:LeftBox id="leftBox" leftTextChangeEvent="rightBox.leftTextChangeEventHandler(event)" />