心魅 - cocoromi -

半角スペース時々全角

カスタムコンポーネントのデータバインディング

今AIRでP3のクローンを作っている。

【P3】P3:PeraPeraPrv - とかいろいろ
http://d.hatena.ne.jp/lynmock/20071107/p2


コンポーネントのべた書き

以下のコードでは最終的にP3のクローンを作ることを想定したコードになっていますが、例として使っているだけで、本質的には関係ありません。

クラス化と同じで、mxmlもある程度のまとまった部品群をまとめてコンポーネント化したほうが良い。

たとえばP3の上部にある、アイコン、ユーザ名、コメントなどが表示される部分はmxmlだとこんな感じになる。

<mx:HBox>
  <mx:VBox height="100%" width="70">
    <mx:Image height="50%" width="100%" />
    <mx:Button height="25%" width="100%" label="Web" />
    <mx:Button height="25%" width="100%" label="DM" />
  </mx:VBox>
  <mx:VBox height="100%" width="100%">
    <mx:HBox width="100%" height="20%">
      <mx:Label width="100%" height="100%" />
      <mx:Label height="100%"  />
    </mx:HBox>
    <mx:TextArea width="100%" height="80%" editable="false" />
  </mx:VBox>
</mx:HBox>


さて、このコンポーネントの各パーツにデータをバインディングするためには、ActionScriptからアクセスするために、適宜id属性を設定する必要がある。
必要な部分のidを設定すると以下のようになる。

<mx:HBox>
  <mx:VBox height="100%" width="70">
    <mx:Image id="userPicture" height="50%" width="100%" />
    <mx:Button height="25%" width="100%" label="Web" />
    <mx:Button height="25%" width="100%" label="DM" />
  </mx:VBox>
  <mx:VBox height="100%" width="100%">
    <mx:HBox width="100%" height="20%">
      <mx:Label id="userName" width="100%" height="100%" />
      <mx:Label id="postedAt" height="100%"  />
    </mx:HBox>
    <mx:TextArea id="status" width="100%" height="80%" editable="false" />
  </mx:VBox>
</mx:HBox>

ここで、それぞれに実際にデータをバインディングする部分のコードを考えるとこんな感じ。

userPicture.source = "imgae/icon.jpg";
userName.text = "Foo Bar";
postedAt.text = "200X/MM/DD";
status.text = "mogemogemogemoge";

ただデータを代入するだけの部分を4行も書くはめになります。
ここで、この部分をコンポーネント化してみます。

カスタムコンポーネント


上のコードをカスタムコンポーネント定義用に少し書き換えます。

<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" >
  <mx:VBox height="100%" width="70">
    <mx:Image height="50%" width="100%" source="{data.user.profileImageUrl}" />
    <mx:Button height="25%" width="100%" label="Web" />
    <mx:Button height="25%" width="100%" label="DM" />
  </mx:VBox>
  <mx:VBox height="100%" width="100%">
    <mx:HBox width="100%" height="20%">
      <mx:Label width="100%" height="100%" text="{data.user.screenName}/{data.user.name}" />
      <mx:Label height="100%" text="{data.createdAt.toLocaleString()}" />
    </mx:HBox>
    <mx:TextArea width="100%" height="80%" htmlText="{data.text}" editable="false" />
  </mx:VBox>
</mx:HBox>

まず変わったところを確認しておきましょう。
まずはカスタムコンポーネントの定義で単独のXML文章になったためxml宣言、そしてFlexコンポーネントを利用するために名前空間を定義しました。
そして、一番のポイントは先ほどid属性を設定していたコンポーネントのsourceやtextになにやら値が設定されています。

TextAreaコンポーネントを例に詳しく見てみましょう。

<mx:TextArea width="100%" height="80%" htmlText="{data.text}" editable="false" />

この部分の htmlText="{data.text}"という部分ですが、属性値の中で{}を使うと変数へのアクセスを行うことが出来、
"TextAreaのhtmlTextにはdata.textを設定する"という意味になっています。

コードで書くと

textarea.htmlText = data.text;

となります。
コードで書いた時との最大の違いは、"data.textが更新されると自動的に表示が変更される"というところにあります。
つまり、こう書いておくと、実際には上のような代入を行う必要がなくなります。


もう少し補足をしておきます。
"data.text"の部分ですが、この"data"というのは適当に使っているのではなく、Flexのコンポーネントが共通で持っているプロパティで表示に反映するための値を保持しておくための物です。


カスタムコンポーネントの利用


さてこのカスタムコンポーネントへ実際に値を設定する部分を考えましょう。
このあたりからファイルの配置まで考慮する必要があります。
まずは先ほどのカスタムコンポーネント

<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" >
  <mx:VBox height="100%" width="70">
    <mx:Image height="50%" width="100%" source="{data.user.profileImageUrl}" />
    <mx:Button height="25%" width="100%" label="Web" />
    <mx:Button height="25%" width="100%" label="DM" />
  </mx:VBox>
  <mx:VBox height="100%" width="100%">
    <mx:HBox width="100%" height="20%">
      <mx:Label width="100%" height="100%" text="{data.user.screenName}/{data.user.name}" />
      <mx:Label height="100%" text="{data.createdAt.toLocaleString()}" />
    </mx:HBox>
    <mx:TextArea width="100%" height="80%" htmlText="{data.text}" editable="false" />
  </mx:VBox>
</mx:HBox>

これをTwitterDetail.mxmlという名前のファイルで本体のmxmlファイル(ここではMain.mxml)と同じフォルダに置いておきます。

次はmxml本体です。

<?xml version="1.0"?>
<mx:Application
  xmlns:mx="http://www.adobe.com/2006/mxml" 
  xmlns:twitter="*"
  width="100%"
  height="100%"
>
  <mx:Script source="main.as"></mx:Script>
  <twitter:TwitterDetail id="detail" height="100" width="100%" />
</mx:Application>

ここで注意しなければならないのは、カスタムコンポーネントを利用するために新しく名前空間を定義する必要があるということです。

  xmlns:twitter="*"

この部分がそれを行っており、"デフォルトパッケージに存在するカスタムコンポーネントを"twitter"という名前空間で使う"という意味になっています。
この宣言をしてあるので

  <twitter:TwitterDetail id="detail" height="100" width="100%" />

このように書くことでカスタムコンポーネントを利用出来ます。

  <名前空間:ファイル名 属性>

ファイル名というところは正しく言うと間違っていますが、Main.mxmlと同じ階層においてあれば問題ないでしょう。
これでカスタムコンポーネントを利用したFlexアプリを作ることが出来ました。

カスタムコンポーネントへのデータバインディング


さて、いよいよデータのバインディングを考えましょう。

detail.data = someObject;

これでOK!!

さすがにこれは言い過ぎですね。
実際にはsomeObjectのプロパティが正しく設定されている必要があります。

someObject.user.screenName = "Foo Bar";
someObject.user.name = "foobar";
someObject.profileImageUrl = "http://mogemoge.com/user/foobar.jpg"
someObject.text = "mogemoge";
someObject.createdAt = new Date();
detail.data = someObject;

前より面倒くさい感じになってる!だまされた!!!!
もちろんsomeObjectをそのままベタに初期化するような場合は、よりコードの量が増えてしまいます。
しかし、"someObjectがしっかりクラス化されている"、"イベントハンドラの引数で初期化されたものが取得できる"、と言う場合には上の一行のコードで書くことが出来るようになります。

private function onUserTimeLine( event : TwitterEvent ) : void {
  userTimelineData = event.data as Array;
}

これは実際にP3クローンで使っているコードで、someObjectに当たる部分はあらかじめ初期化しevent.dataに格納してあります。
どうでしょうか、シンプルに書けているような気がしてきませんか?

まとめ


扱うデータ構造が決まっている場合はコンポーネントもちゃんと部品化した方が楽だ!