2011年10月19日星期三

初探 Robotlegs

最近看了一下 robotlegs 的電子書,有興趣學習這項 framework 的人可以參考官網的資源:
http://www.robotlegs.org/

或以下這本電子書:
http://www.wowebook.pro/book/actionscript-developers-guide-to-robotlegs/



寫一個很簡單的 login 範例。

主畫面,包含了 login 與 logon 兩個 state;這份 view component 同時包含了簡單的事件 用來向 Mediator 傳達使用者的操作。

/HelloRobotlegs.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:ben="idv.ben.*"
      minWidth="955" minHeight="600">
 <s:layout>
  <s:VerticalLayout/>
 </s:layout>

 <fx:Script>
  <![CDATA[
   import idv.ben.event.ViewCompEvent;
  
   [Bindable]
   public var userName:String = "";
   
   protected function btnSubmit_clickHandler(event:MouseEvent):void
   {
    this.dispatchEvent(new ViewCompEvent(ViewCompEvent.LOGIN_CLICKED));
   }

   protected function btnLogout_clickHandler(event:MouseEvent):void
   {
    this.dispatchEvent(new ViewCompEvent(ViewCompEvent.LOGOUT_CLICKED));
   }
  ]]>
 </fx:Script>

 <s:states>
  <s:State name="State1"/>
  <s:State name="login"/>
  <s:State name="logon"/>
 </s:states>
 
 <fx:Declarations>
  <ben:AppContext contextView="{this}" />
 </fx:Declarations>
 
 <s:Label text="Robotlegs Example" fontSize="24"/>
 
 <mx:Form width="300" includeIn="login">
  <mx:FormHeading label="login"/>
  <mx:FormItem label="UID:">
   <s:TextInput id="txtUID"/>
  </mx:FormItem>
  <mx:FormItem label="PWD:">
   <s:TextInput id="txtPWD"/>
  </mx:FormItem>
  <mx:FormItem>
   <s:Button label="Submit" id="btnSubmit" click="btnSubmit_clickHandler(event)"/>
  </mx:FormItem>
 </mx:Form>
 <s:Label includeIn="login" text="error message" id="txtErrorMessage" color="#FF0000"/>
 
 <s:Label includeIn="logon" text="Hello, {userName}" fontSize="36"/>
 <s:Button includeIn="logon" label="Logout" id="btnLogout" click="btnLogout_clickHandler(event)"/>
</s:Application>



Context,類似 PureMVC 的 Facade,在此註冊、或建立一些對照表。

/idv/ben/AppContext.as

package idv.ben
{
 import flash.display.DisplayObjectContainer;
 
 import idv.ben.controller.LoginCommand;
 import idv.ben.controller.LoginSuccessCommand;
 import idv.ben.controller.LogoutCommand;
 import idv.ben.event.MediatorLoginEvent;
 import idv.ben.event.MediatorLogoutEvent;
 import idv.ben.event.ServiceLoginedEvent;
 import idv.ben.model.AppModel;
 import idv.ben.view.AppMediator;
 
 import org.robotlegs.mvcs.Context;
 import idv.ben.service.AppService;
 
 public class AppContext extends Context
 {
  public function AppContext(contextView:DisplayObjectContainer=null, autoStartup:Boolean=true)
  {
   super(contextView, autoStartup);
   trace(this, "AppContext()", "contextView=" + contextView);
  }
  
  override public function startup():void{
   trace(this, "startup()");
   
   //service
   this.injector.mapSingleton(AppService);
   
   //model
   this.injector.mapSingleton(AppModel);
   
   //view
   this.mediatorMap.mapView(HelloRobotlegs, AppMediator);
   
   //controller
   this.commandMap.mapEvent(MediatorLoginEvent.LOGIN, LoginCommand);
   this.commandMap.mapEvent(MediatorLogoutEvent.LOGOUT, LogoutCommand);
   this.commandMap.mapEvent(ServiceLoginedEvent.SERVICE_LOGINED, LoginSuccessCommand);
   
   super.startup();
  }
 }
}



先談到 Model,僅僅用來存放一些資料,這裡我用來存放 "是否登錄" 與 "登錄成功後得到的 user name"。

/idv/ben/model/AppModel.as

package idv.ben.model
{
 import idv.ben.event.ModelEvent;
 
 import org.robotlegs.mvcs.Actor;

 public class AppModel extends Actor
 {
  private var _loginStatus:Boolean = false;

  public function get loginStatus():Boolean
  {
   return _loginStatus;
  }

  public function set loginStatus(value:Boolean):void
  {
   _loginStatus = value;
   
   this.dispatch(new ModelEvent(ModelEvent.LOGIN_STATUS_CHANGED));
  }
  
  
  
  private var _userName:String = "";

  public function get userName():String
  {
   return _userName;
  }

  public function set userName(value:String):void
  {
   _userName = value;
  }
  

 }
}



再談到 Service,主要是用來與外界溝通資料用的。

/idv/ben/service/AppService.as

package idv.ben.service
{
 import flash.utils.setTimeout;
 
 import idv.ben.event.ServiceLoginErrorEvent;
 import idv.ben.event.ServiceLoginedEvent;
 
 import org.robotlegs.mvcs.Actor;

 public class AppService extends Actor
 {
  public function login(uid:String, pwd:String):void{
   
   if(uid == "" || pwd == "")
    fooLoginError("input error");
   else if(uid != "abc")
    setTimeout(fooLoginError, 2000, "wrong uid");
   else if(uid == "abc" && pwd != "123")
    setTimeout(fooLoginError, 2000, "wrong pwd");
   else if(uid == "abc" && pwd == "123")
    setTimeout(fooLogined, 2000, uid);
  }
  
  private function fooLogined(uid:String):void{
   this.dispatch(new ServiceLoginedEvent("USER_" + uid));
  }
  
  private function fooLoginError(errMsg:String):void{
   this.dispatch(new ServiceLoginErrorEvent(errMsg));
  }
  
 }
}



Mediaotr,用來與 view component 溝通,接收感興趣的 "事件" 然後讓 view component 做出反應。

/idv/ben/view/AppMediator.as

package idv.ben.view
{
 import idv.ben.event.MediatorLoginEvent;
 import idv.ben.event.MediatorLogoutEvent;
 import idv.ben.event.ModelEvent;
 import idv.ben.event.ServiceLoginErrorEvent;
 import idv.ben.event.ServiceLoginedEvent;
 import idv.ben.event.ViewCompEvent;
 import idv.ben.model.AppModel;
 
 import mx.events.StateChangeEvent;
 import mx.logging.Log;
 
 import org.robotlegs.mvcs.Mediator;
 
 public class AppMediator extends Mediator
 {
  [Inject]
  public var viewComp:HelloRobotlegs;
  
  [Inject]
  public var appModel:AppModel;
  
  override public function onRegister():void{
   trace(this, "onRegister()");
   
   this.addViewListener(ViewCompEvent.LOGIN_CLICKED, onLoginClicked);
   this.addViewListener(ViewCompEvent.LOGOUT_CLICKED, onLogoutClicked);
   
   this.addContextListener(ServiceLoginErrorEvent.SERVICE_LOGIN_ERROR, onServiceLoginError);
   
   this.addContextListener(ModelEvent.LOGIN_STATUS_CHANGED, onModeLoginStatusChanged);
   
   currentState = "login";
  }

  override public function onRemove():void{
   trace(this, "onRemove()");
   
   this.removeViewListener(ViewCompEvent.LOGIN_CLICKED, onLoginClicked);
   this.removeViewListener(ViewCompEvent.LOGOUT_CLICKED, onLogoutClicked);

   this.removeContextListener(ServiceLoginErrorEvent.SERVICE_LOGIN_ERROR, onServiceLoginError);

   this.removeContextListener(ModelEvent.LOGIN_STATUS_CHANGED, onModeLoginStatusChanged);
  }
  
  private function set currentState(v:String):void{
   viewComp.currentState = v;
   
   if(v == "login"){
    unluck();
    viewComp.txtUID.text = "";
    viewComp.txtPWD.text = "";
    viewComp.txtErrorMessage.text = "";
   }else if(v == "logon"){
    viewComp.userName = appModel.userName;
   }
  }
  
  private function onLoginClicked(e:ViewCompEvent):void{
   trace(this, "onLoginClicked()");
   
   luck();
   
   var uid:String = viewComp.txtUID.text;
   var pwd:String = viewComp.txtPWD.text;
   
   this.dispatch(new MediatorLoginEvent(uid, pwd));
  }
  
  private function luck():void{
   viewComp.txtUID.enabled = false;
   viewComp.txtPWD.enabled = false;
   viewComp.txtErrorMessage.text = "";
   viewComp.btnSubmit.enabled = false;
  }
  
  private function unluck():void{
   viewComp.txtUID.enabled = true;
   viewComp.txtPWD.enabled = true;
   viewComp.btnSubmit.enabled = true;
  }
  
  private function onLogoutClicked(e:ViewCompEvent):void{
   trace(this, "onLogoutClicked()");
   
   this.dispatch(new MediatorLogoutEvent());
   
   currentState = "login";
  }
  
  private function onServiceLoginError(e:ServiceLoginErrorEvent):void{
   trace(this, "onServiceLoginError()");
   
   unluck();
   
   viewComp.txtErrorMessage.text = e.errMsg;
  }

  private function onModeLoginStatusChanged(e:ModelEvent):void{
   trace(this, "onModeLoginStatusChanged()");
   
   if(appModel.loginStatus){
    currentState = "logon";
   }
  }
 }
}

接下來,準備了幾個 Command,可用來決定流程、決定收到的資料如何處理、通知下一個流程的開始......

/idv/ben/controller/LoginCommand.as

package idv.ben.controller
{
 import idv.ben.service.AppService;
 import idv.ben.event.MediatorLoginEvent;
 
 import org.robotlegs.mvcs.Command;
 
 public class LoginCommand extends Command
 {
  [Inject]
  public var appService:AppService;
  
  [Inject]
  public var event:MediatorLoginEvent;
  
  override public function execute():void{
   trace(this, "execute()", "uid=" + event.uid, "pwd=" + event.pwd);
   
   appService.login(event.uid, event.pwd);
  }
 }
}

/idv/ben/controller/LogoutCommand.as

package idv.ben.controller
{
 import idv.ben.model.AppModel;
 
 import org.robotlegs.mvcs.Command;
 
 public class LogoutCommand extends Command
 {
  [Inject]
  public var appModel:AppModel;
  
  override public function execute():void{
   trace(this, "execute()");
   
   appModel.loginStatus = false;
   appModel.userName = "";
  }
 }
}

/idv/ben/controller/LoginSuccessCommand.as

package idv.ben.controller
{
 import idv.ben.event.ServiceLoginedEvent;
 import idv.ben.model.AppModel;
 
 import org.robotlegs.mvcs.Command;
 
 public class LoginSuccessCommand extends Command
 {
  [Inject]
  public var appModel:AppModel;
  
  [Inject]
  public var event:ServiceLoginedEvent;
  
  override public function execute():void{
   trace(this, "execute()");
   
   appModel.userName = event.userName;
   appModel.loginStatus = true;
  }
 }
}

剩下一些串通各角色之間的 VO (Value Object) 或 DTO (Data Transfer Object),以 Event 的形式存在,以下我將 source code 直接貼在一起:

package idv.ben.event
{
 import flash.events.Event;
 
 public class ViewCompEvent extends Event
 {
  static public const LOGIN_CLICKED:String = "loginClicked";
  
  static public const LOGOUT_CLICKED:String = "logoutClicked";
  
  public function ViewCompEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false)
  {
   super(type, bubbles, cancelable);
  }
 }
}


package idv.ben.event
{
 import flash.events.Event;
 
 public class MediatorLoginEvent extends Event
 {
  static public const LOGIN:String = "login";
  
  public var uid:String;
  public var pwd:String;
  
  public function MediatorLoginEvent(uid:String, pwd:String)
  {
   super(LOGIN);
   
   this.uid = uid;
   this.pwd = pwd;
  }
 }
}


package idv.ben.event
{
 import flash.events.Event;
 
 public class MediatorLogoutEvent extends Event
 {
  static public const LOGOUT:String = "logout";
  
  public function MediatorLogoutEvent()
  {
   super(LOGOUT);
  }
 }
}


package idv.ben.event
{
 import flash.events.Event;
 
 public class ServiceLoginErrorEvent extends Event
 {
  static public const SERVICE_LOGIN_ERROR:String = "serviceLoginError";
  
  public var errMsg:String;
  
  public function ServiceLoginErrorEvent(errMsg:String)
  {
   super(SERVICE_LOGIN_ERROR);
   
   this.errMsg = errMsg;
  }
 }
}


package idv.ben.event
{
 import flash.events.Event;
 
 public class ServiceLoginedEvent extends Event
 {
  static public const SERVICE_LOGINED:String = "serviceLogined";
  
  public var userName:String;
  
  public function ServiceLoginedEvent(userName:String)
  {
   super(SERVICE_LOGINED);
   
   this.userName = userName;
  }
 }
}



package idv.ben.event
{
 import flash.events.Event;
 
 public class ModelEvent extends Event
 {
  static public const LOGIN_STATUS_CHANGED:String = "loginStatusChanged";
  
  public function ModelEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false)
  {
   super(type, bubbles, cancelable);
  }
 }
}



執行畫面:

沒輸入資料就送出:

輸入錯誤 UID:

輸入錯誤 PWD:

登錄成功:


以上過程的輸出訊息:

[SWF] E:\works_test\HelloRobotlegs\bin-debug\HelloRobotlegs.swf\[[DYNAMIC]]\1 - 370,463 bytes after decompression
[SWF] E:\works_test\HelloRobotlegs\bin-debug\HelloRobotlegs.swf\[[DYNAMIC]]\2 - 650,777 bytes after decompression
[SWF] E:\works_test\HelloRobotlegs\bin-debug\HelloRobotlegs.swf - 262,669 bytes after decompression
[SWF] E:\works_test\HelloRobotlegs\bin-debug\HelloRobotlegs.swf\[[DYNAMIC]]\3 - 2,311,734 bytes after decompression
[SWF] E:\works_test\HelloRobotlegs\bin-debug\HelloRobotlegs.swf\[[DYNAMIC]]\4 - 1,432,382 bytes after decompression
[SWF] E:\works_test\HelloRobotlegs\bin-debug\HelloRobotlegs.swf\[[DYNAMIC]]\5 - 441,362 bytes after decompression
[SWF] E:\works_test\HelloRobotlegs\bin-debug\HelloRobotlegs.swf\[[DYNAMIC]]\6 - 323,354 bytes after decompression
[object AppContext] AppContext() contextView=null
[object AppContext] startup()
[object AppMediator] onRegister()
[object AppMediator] onLoginClicked()
[object LoginCommand] execute() uid= pwd=
[object AppMediator] onServiceLoginError()
[object AppMediator] onLoginClicked()
[object LoginCommand] execute() uid=aaa pwd=fff
[object AppMediator] onServiceLoginError()
[object AppMediator] onLoginClicked()
[object LoginCommand] execute() uid=abc pwd=fff
[object AppMediator] onServiceLoginError()
[object AppMediator] onLoginClicked()
[object LoginCommand] execute() uid=abc pwd=123
[object LoginSuccessCommand] execute()
[object AppMediator] onModeLoginStatusChanged()
[object AppMediator] onLogoutClicked()
[object LogoutCommand] execute()
[object AppMediator] onModeLoginStatusChanged()

0 意見: