2010年4月13日火曜日

Flex でカスタムコンポーネントを作成してみた

Flex でカスタムコンポーネントを作ってみました。


お題

今回は、ありがちな感じのログイン用の画面を題材に選んでみました。


オブジェクト指向っぽく考えてみた。

まずば、カスタムコンポーネントをブラックボックスと考えてみたときに、外部にどのような機能を公開すべきか考えてみました。

とりあえず、サンプルとしては以下の機能があればいいかな。

  • userName:String
    ユーザー名入力用コンポーネントとの双方向バインドされた bindable な変数
  • password:String
    パスワード入力用コンポーネントとの双方向バインドされた bindable な変数
  • loginBtn_clickHandler:Function
    ログインボタンをクリックしたときに呼び出される関数を外部からセットするための変数。
  • clear()
    ユーザー名入力用コンポーネントとパスワード入力用コンポーネントのクリア用関数
  • showErrorMessage(message:String)
    エラーメッセージ表示用関数

公開する変数については、変数自体が bindable でありながら、その変数自体もコンポーネントのテキストと双方向にバインドされているというのがミソですね。

関数のハンドラについては、Function 型の変数としました。本当は関数のシグニチャをインタフェースのように定義して厳密な型指定をしたかったのですが、できなさそうなので、、、。できるのかな?


サンプルコンポーネント(LoginBox)

ユーザー名とパスワードの入力フィールドとログインボタンを持つシンプルなログイン画面です。



LoginBox.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009" 
   xmlns:s="library://ns.adobe.com/flex/spark" 
   xmlns:mx="library://ns.adobe.com/flex/mx">
 <s:layout>
  <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
 </s:layout>
 <fx:Script>
  <![CDATA[
   import mx.controls.Alert;
   import mx.effects.Effect;
   import mx.effects.Fade;
   [Bindable]
   /**
    * user name.
    */
   public var userName:String;
   
   [Bindable]
   /**
    * password.
    */
   public var password:String;
   
   /**
    * Login handler.
    * 
    * This handler is invoked when login button is clicked.
    */
   public var loginBtn_clickHandler:Function;
   
   /**
    * Clear user name, password and error message.
    */
   public function clear():void {
    userNameTextInput.text = "";
    passwordTextInput.text = "";
    errorMessageLabel.visible = false;
   }
   
   /**
    * Show error message.// TODO: エラー失敗時のハンドラという名前のほうが正解?要検討。
    */
   public function showErrorMessage(message:String):void {
    errorMessageLabel.visible = true;
    errorMessageLabel.text = message;
   }
  ]]>
 </fx:Script>

 <!-- Bi-directional Bindings -->
 <fx:Binding source="userNameTextInput.text" destination="userName"/>
 <fx:Binding source="userName" destination="userNameTextInput.text"/>
 <fx:Binding source="passwordTextInput.text" destination="password"/>
 <fx:Binding source="password" destination="passwordTextInput.text"/>
 
 <!-- Visual Components -->
 <mx:Form fontFamily="Courier New" defaultButton="{loginBtn}">
  <mx:FormItem label="username">
   <mx:TextInput id="userNameTextInput" focusIn="IME.enabled = false;userNameTextInput.setSelection(0,userNameTextInput.text.length)"/>
  </mx:FormItem>
  <mx:FormItem label="password">
   <mx:TextInput displayAsPassword="true" id="passwordTextInput" focusIn="IME.enabled = false;passwordTextInput.setSelection(0,passwordTextInput.text.length)" keyDown="IME.enabled = false"/>
  </mx:FormItem>
 </mx:Form>
 <s:Label id="errorMessageLabel" text="Login failed" color="red" visible="false" hideEffect="{Fade}"/>
 <s:HGroup>
  <s:Button label="Login" id="loginBtn" click="loginBtn_clickHandler(event)"/>
  <s:Button label="Clear" id="clearBtn" click="clear()"/>
 </s:HGroup>
</s:Group>


サンプル用コンポーネントのテスト用アプリ(LoginBoxTest)

LoginBox コンポーネントを試すためのアプリです。


username = foo, password = bar の時以外はログイン失敗メソッドを呼び出すハンドラを設定しています。


LoginBoxTest.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:mx="library://ns.adobe.com/flex/mx" xmlns:component="component.*" width="100%" height="100%" creationComplete="init()">
 <s:layout>
  <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
 </s:layout>

 <fx:Script>
  <![CDATA[   
   import mx.controls.Alert;
   
   // test for loginBox.loginBtn_clickHandler
   private function init():void {
    loginBox.loginBtn_clickHandler = function(event:MouseEvent):void {
     Alert.show("Login button clicked.\nusername = "+loginBox.userName+"\npassword = "+loginBox.password+"\nPos = ("+event.localX+", "+event.localY+")", "Assigned Method from Outer Component");
     if (loginBox.userName != "foo" || loginBox.password != "bar") loginBox.showErrorMessage("Login Failed");
    };
   }
   
   // test for loginBox.clear()
   protected function button1_clickHandler(event:MouseEvent):void {
    loginBox.clear();
   }
  ]]>
 </fx:Script>
 
 <!-- bi-directional bind test for userName valiable. -->
 <fx:Binding source="userNameTextInput.text" destination="loginBox.userName"/>
 
 <!-- bi-directional bind test for password valiable. -->
 <fx:Binding source="passwordTextInput.text" destination="loginBox.password"/>
 
 <s:Label text="Test for LoginBox" fontSize="24"/>
 <mx:Form>
  
  <!-- bi-directional bind test for userName valiable. -->
  <mx:FormItem label="bi-directional bind test for userName valiable">
   <mx:TextInput id="userNameTextInput" focusIn="IME.enabled = false" text="{loginBox.userName}"/>
  </mx:FormItem>
  
  <!-- bi-directional bind test for password valiable. -->
  <mx:FormItem label="bi-directional bind test for password valiable">
   <mx:TextInput id="passwordTextInput" focusIn="IME.enabled = false" text="{loginBox.password}"/>
  </mx:FormItem>
  
  <!-- clear method test -->
  <mx:FormItem label="clear method test">
   <s:Button label="Clear" click="button1_clickHandler(event)"/>
  </mx:FormItem>
 </mx:Form>
 
 <component:LoginBox id="loginBox"/>
</s:Application>

0 件のコメント:

コメントを投稿