アプリケーション構造見直しの手始めとしてまず履歴管理を PreviousLocations という外部クラスで行うことにしました。とりあえず動くようにはなりましたが少し気になる箇所が残っています。
「MyBrowser.mxml をよくみると back() と onDomInitialize() 両関数とも ”戻る” ボタンの状態を設定するために履歴の状況チェックを行っています」 という理由で、「履歴の状態を公開する手段を何かしら提供する必要がありそう」なため、PreviousLocations クラスに isPopEnabled() というメソッドを定義してみました。
ところが、このメソッドは履歴の状態が変わったら必ず呼ぶことが要求されるという種類のものです。そのため、呼び出す側からすると、いつ呼び出すのが適切かを把握したいと思ったら PreviousLocations 内部の動きを理解しなければなりません。
せっかく外部クラス化したのに、これでは意義も薄れてしまいます。そこで、PreviousLocations の内部ステータスが変わったら、PreviousLocations の側から通知するように変更してみたいと思います。
Bindable メタタグ
まず、履歴が2つ以上残っているかどうかを示すフラグ isPopEnabled を定義します。このフラグに新しく値が代入されたらイベントを発生するように変更したいと思います。あわせてフラグを設定するための setPopEnabled() メソッドも定義しておきます。
public var isPopEnabled:Boolean = false; private function setPopEnabled():void { isPopEnabled = history.length > 1; }
さて、これから isPopEnabled の変更を伝えるイベントを追加するわけですが、Flex にはこういった仕組みを簡単に実現する手段が提供されています。Bindable というメタタグです。
[Bindable] public var isPopEnabled:Boolean = false;
上のように変数の宣言の上の行に [Bindable] と記述すると isPopEnabled の値が変わったらそれを通知するイベントを発生させるというコンパイラに対する指示になります。この指定によってコンパイル時にイベント生成に必要なコードが勝手に追加されます。
このときに発生するイベント名は propertyChange という名前です。イベント名を指定することも可能です。
[Bindable(event="changePopEnabled")] public var isPopEnabled:Boolean = false;
Binding
さて、MyBrowser.mxml 側では PreviousLocations からのイベントを受け取るためにイベントリスナが必要になります。が、Flex はここでも便利な機能を提供しています。タグの属性に中括弧で囲んだ変数を代入する式があるとイベントリスナがコンパイル時に自動的に生成されるというものです。この機能をバインディングと呼んでいます。
<mx:Button label="戻る" enabled="{prevLocations.isPopEnabled}"/>
上のように記述することを enabled 属性に prevLocations.isPopEnabled をバインドすると言ったりします。これだけで対象 (ここでは prevLocations.isPopEnabled) の値が変更されたイベントで enabled 属性の値を再設定する仕組みまで記述したことになります。
以上で ”PreviousLocations の側から履歴の状況が変わったら通知” という目的は達成されました。あとはこの変更にあわせていくつかの修正です。
MyBrowser.mxml 内では isPopEnabled() を呼び出している2つの行を削除します。
代わりに PreviousLocations.as 内では、履歴を保持している配列の中身を変更したら setPopEnabled() の呼び出しを行うように直します。これにより popLocation() と pushLocation() で一箇所ずつ修正されることになります。
下が新しい PreviousLocations.as ファイルです。
package { public class PreviousLocations { private var history:Array = new Array(); private var isPop:int = 0; [Bindable] public var isPopEnabled:Boolean = false; private function setPopEnabled():void { isPopEnabled = history.length > 1; } public function popLocation():String { var prevLocation:String = null; if (history.length > 1) { history.pop(); setPopEnabled(); prevLocation = history[history.length - 1]; isPop++; } return prevLocation; } public function pushLocation(location:String):void { if (isPop > 0) { isPop--; } else { history.push(location); setPopEnabled(); } } } }
こちらは MyBrowser.mxml です。
<?xml version="1.0" encoding="utf-8"?> <mx:ApolloApplication xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*"> <mx:Script> <![CDATA[ private function back():void { var prevURL:String = prevLocations.popLocation(); if (prevURL != null) { setLocation(prevURL); } } private function setLocation(location:String):void { html.location = location; } private function onLocationChange(e:Event):void { html.alpha=0 } private function onDomInitialize(e:Event):void { prevLocations.pushLocation(html.location); inputTF.text = html.location; } private function onComplete(e:Event):void { fadeIn.target = html; fadeIn.play(); } ]]> </mx:Script> <mx:Style source="MyBrowser.css"/> <mx:Fade id="fadeIn" duration="1000" alphaFrom="0.0" alphaTo="1.0"/> <local:PreviousLocations id="prevLocations"/> <mx:HBox> <mx:TextInput id="inputTF" width="225" text="http://" enter="setLocation(inputTF.text)"/> <mx:Button label="移動" click="setLocation(inputTF.text)"/> <mx:Button label="戻る" click="back()" enabled="{prevLocations.isPopEnabled}"/> </mx:HBox> <mx:HTML id="html" width="100%" height="100%" locationChange="onLocationChange(event)" domInitialize="onDomInitialize(event)" complete="onComplete(event)"/> </mx:ApolloApplication>
コメントする