Mate-Tag Base Framework
Mate will become another hot pot in Flex area . It use configuration to call the service and handle the event. It also uses configuration to update the Bindable object. Somehow The Mate is The “springframework” in Flex Area.
There are two architecture level diagrams of Mate. One is from Yakov Fain of Farata Systems, Another is from Mate funder company ASFusion. I like the later one:
Lets use mate to develop the buddyList application
1.Create the core component of mate —-EventMap.
The EventMap is the heart of mate it glues all the other component and control the process:
BuddyListEventMap.mxml:
<?xml version=”1.0″ encoding=”utf-8″?>
<EventMap
xmlns=”http://mate.asfusion.com/”
xmlns:mx=http://www.adobe.com/2006/mxml>
</EventMap>
I will add the related code later. Now we need tell the main app that initialize the EventMap:
Flex_Mate.mxml:
<?xml version=”1.0″ encoding=”utf-8″?>
<mx:Application
xmlns:map=”com.ny.flex.mate.map.*” xmlns:views=”com.ny.flex.mate.views.*” xmlns:mx=”http://www.adobe.com/2006/mxml” layout=”absolute“>
<mx:Script>
<![CDATA[
[
Bindable
]
public var
viewStackSelectedIndex :int = 0;
]]>
</mx:Script>
<map:BuddyListEventMap/>
<mx:HBox horizontalAlign=”center” verticalAlign=”top” width=”100%” height=”100%” y=”0” x=”0“>
<mx:ViewStack id=”viewStack” resizeToContent=”true” selectedIndex=”{viewStackSelectedIndex}” >
<views:LoginView />
<views:BuddyListView/>
</mx:ViewStack>
</mx:HBox>
</mx:Application>
2. Create the LoginView :
<?xml version=”1.0″ encoding=”utf-8″?>
<mx:Panel
xmlns:mx=”http://www.adobe.com/2006/mxml” layout=”absolute” width=”300” height=”200” horizontalAlign=”center” verticalAlign=”middle” title=”Flex Cirngorm Login“>
<mx:Script>
<![CDATA[
import
com.ny.flex.mate.event.LoginEvent;
import
com.ny.flex.mate.vo.User;
import
mx.validators.Validator;
private function login():void
{
if
(Validator.validateAll(validators).length == 0){
var loginUser:User = new
User();
loginUser.userName=username.text;
loginUser.password=password.text;
var loginEvent:LoginEvent = new
LoginEvent(LoginEvent.LOGIN);
loginEvent.loginUser = loginUser;
dispatchEvent(loginEvent);
}
}
]]>
</mx:Script>
<!– Validators–>
<mx:Array id=”validators“>
<mx:StringValidator id=”userNameValidator” source=”{username}” property=”text” required=”true“/>
<mx:StringValidator id=”passwordValidator” source=”{password}” property=”text” required=”true” />
</mx:Array>
<mx:Form
id=”loginForm” x=”0” y=”0“>
<mx:FormItem label=”Username:” >
<mx:TextInput id=”username” />
</mx:FormItem>
<mx:FormItem label=”Password:” >
<mx:TextInput id=”password” displayAsPassword=”true” />
</mx:FormItem>
<mx:FormItem direction=”horizontal” verticalGap=”15” paddingTop=”5” width=”170“>
<mx:Button id=”loginBtn” label=”Login” click=”login()”/>
</mx:FormItem>
</mx:Form>
</mx:Panel>
See the action method above the LoginEvent is dispatched. Now create the LoginEvent:
package
import
com.ny.flex.mate.vo.User;
import
flash.events.Event;
public class LoginEvent extends
Event
{
public static const LOGIN:String = “login”
;
public var
loginUser:User;
public function LoginEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false
)
{
super
(type, bubbles, cancelable);
}
}
}
com.ny.flex.mate.event
{
The “bubbles” has to be “true”, it makes EventMap can handle it. The magic of mate comes in the EventMap . The LoginEvent can handle by tag :
…..
<EventHandlers type=”{LoginEvent.LOGIN}“>
<RemoteObjectInvoker destination=”flexmvcRO” method=”authenticate”
arguments=”{event.loginUser}“>
<resultHandlers>
<MethodInvoker generator=”{LoginService}”
method=”onResult_Authenticate”
arguments=”{resultObject}“/>
</resultHandlers>
</RemoteObjectInvoker>
</EventHandlers>
……
In the EvevntHandler , You can define the service call function(RemoteObjectInvoker here) . and It also define the result handler specify the service class ,method and arguments.
Lets see the LoginService object:
package
com.ny.flex.mate.service
{
import
com.ny.flex.mate.vo.User;
public class
LoginService
{
[
Bindable
]
public var
authUserName:String;
[
Bindable
]
public var
viewStackSelectedIndex:int ;
public function onResult_Authenticate(user:User):void
{
authUserName = user.userName;
viewStackSelectedIndex = 1;
}
}
}
The service class handle the result and make the return object bindable. now we need update target views. Another shine point of mate is Injecting the bindable object to the target view! you just need add another tag in EventMap class . (This is damn cool):
<Injectors target=”{BuddyListView}“>
<PropertyInjector targetKey=”authUserName” source=”{LoginService}”
sourceKey=”authUserName“/>
</Injectors>
<Injectors target=”{Flex_Mate}“>
<PropertyInjector targetKey=”viewStackSelectedIndex” source=”{LoginService}”
sourceKey=”viewStackSelectedIndex“/>
</Injectors>
You just need specify the target view, taget key , source service object and source key!
Finally lets define the target view :BuddyListView:
<?xml version=”1.0″ encoding=”utf-8″?>
<mx:Panel
xmlns:mx=”http://www.adobe.com/2006/mxml” title=”Buddy List of {authUserName}“ creationComplete=”getBuddyList()” width=”500” height=”320“>
<mx:Script>
<![CDATA[
import
mx.collections.ArrayCollection;
import
com.ny.flex.mate.event.GetBuddyListEvent;
[
Bindable
]
public var
authUserName:String;
[
Bindable
]
public var
buddyCollection:ArrayCollection;
private function getBuddyList():void
{
var getBuddyListEvent:GetBuddyListEvent = new
GetBuddyListEvent(GetBuddyListEvent.GET_BUDDY_LIST);
getBuddyListEvent.authUserName = authUserName;
dispatchEvent(getBuddyListEvent);
}
]]>
</mx:Script>
<mx:DataGrid id=”buddyList” dataProvider=”{buddyCollection}” borderStyle=”none” width=”100%” height=”100%” >
<mx:columns>
<mx:DataGridColumn dataField=”firstName” headerText=”First Name“/>
<mx:DataGridColumn dataField=”lastName” headerText=”Last Name“/>
</mx:columns>
</mx:DataGrid>
</mx:Panel>
Checkout the “{autherUsername}”, it comes from the LoginService.
You probably wonder about “buddyListCollection”. It means another around development:
Action–>Dispatch Event–>Config Handler–>create service–>Inject Bindable Object –>another Action….
All the other code is blow:
GetBuddyListEvent.as:
package
import
flash.events.Event;
public class GetBuddyListEvent extends
Event
{
public static const GET_BUDDY_LIST =“getAllFriends”
;
public var
authUserName:String;
public function GetBuddyListEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false
)
{
super
(type, bubbles, cancelable);
}
}
}
com.ny.flex.mate.event
{
BuddyService.as:
package
import
mx.collections.ArrayCollection;
public class
BuddyService
{
[
Bindable
]
public var
buddyList:ArrayCollection;
public function
onResult_GetBuddyList(buddies:ArrayCollection){
buddyList = buddies;
}
}
}
com.ny.flex.mate.service
{
The completely EventMap file: BuddyListEventMap.mxml
<?xml version=”1.0″ encoding=”utf-8″?>
<EventMap
xmlns=”http://mate.asfusion.com/” xmlns:mx=”http://www.adobe.com/2006/mxml“>
<mx:Script>
<![CDATA[
import
com.ny.flex.mate.event.BDbbdEvent;
import
com.ny.flex.mate.service.BuddyService;
import
com.ny.flex.mate.event.GetBuddyListEvent;
import
com.ny.flex.mate.views.BuddyListView;
import
com.ny.flex.mate.service.LoginService;
import
com.ny.flex.mate.event.LoginEvent;
]]>
</mx:Script>
<EventHandlers type=”{LoginEvent.LOGIN}“>
<RemoteObjectInvoker destination=”flexmvcRO” method=”authenticate” arguments=”{event.loginUser}“>
<resultHandlers>
<MethodInvoker generator=”{LoginService}” method=”onResult_Authenticate” arguments=”{resultObject}“/>
</resultHandlers>
</RemoteObjectInvoker>
</EventHandlers>
<EventHandlers type=”{GetBuddyListEvent.GET_BUDDY_LIST}“>
<RemoteObjectInvoker destination=”flexmvcRO” method=”{GetBuddyListEvent.GET_BUDDY_LIST}” arguments=”{event.authUserName}“>
<resultHandlers>
<MethodInvoker generator=”{BuddyService}” method=”onResult_GetBuddyList” arguments=”{resultObject}“/>
</resultHandlers>
</RemoteObjectInvoker>
</EventHandlers>
<Injectors target=”{BuddyListView}“>
<PropertyInjector targetKey=”authUserName” source=”{LoginService}” sourceKey=”authUserName“/>
<PropertyInjector targetKey=”buddyCollection” source=”{BuddyService}” sourceKey=”buddyList“/>
</Injectors>
<Injectors target=”{Flex_Mate}“>
<PropertyInjector targetKey=”viewStackSelectedIndex” source=”{LoginService}” sourceKey=”viewStackSelectedIndex“/>
</Injectors>
</EventMap>
Summary
I discussed flex development in last 5 blogs. What is the best choice ?
I think the central management will be NO1. choice for the small project. Because you dont need learn any new framework and you still get clear architecture .
For the mate and Cairngorm, Mate seems win the conpetition now. Because
For Cairngorm:
1. Cairngorm is too complicate and study cost is higher.
2. Cairngorm has some Junk code for my opionion, ie.frontcontrollerI.
For Mate,
1 . It is simpler than Cairngorm, and easier to study.
2. You get benefit from EventMap, Because you dont need write ther glue code between event and the service.
3.You get pain from EventMap. Just imagine you have 50 actions and 100 objects to bind. you have to put lots of configration stuff in your EventMap, Then EventMap become the disastor!
So if you have to use cairngorm use my solution Cairngorm without FrontContoller.
For the mate, if they can add the Meta Tag instead of the EventMap. i.e.:
[EventHadler ={name ="myHandler", serviceclass="myservice" result , taget ...}]
MateDispatch(myevent).
So you dont need config anything, I am waiting for this.
Cut It Off-Cairngorm without front controller
As we know when you use Cairngorm, Every action of view need dispatch an event. Every dispatched event need to create command to handle the event. And you have to add the mapping them in the frontcontroller. For example:
Loginvew.xml —> action login() —>dispatch LoginEvent —>Handled by LoginCommand —>mapping LoginEvent and LoginCommand in FrontController.
When you create any new action , you have to create 2 classes and modify front controller, assume the business logic code is same .
So How to simplify Cairngorm? My solution is cut of frontcontroller. The architecture is :
As above diagram show, The view will not dispatch any event. It will service façade directly. The façade will call business delegate to communicate with the server side solution(i.e. remote object). Then the delegate will handle the result and update Model locator. Finally Model locator will update the result view by Binding.
Lets see the code ;
ServiceFacade.as:
package com.ny.flex.cairngorm.no_fc.service
{
import com.ny.flex.cairngorm.no_fc.vo.User;
public class ServiceFacade
{
private static var _serviceFacade:ServiceFacade = null;
public function ServiceFacade(privateClass:PrivateClass)
{
if(ServiceFacade._serviceFacade == null){
ServiceFacade._serviceFacade = this;
}
}
public static function getInstance():ServiceFacade {
if(_serviceFacade == null){
_serviceFacade =
new ServiceFacade(new PrivateClass);
}
return _serviceFacade;
}
public function authenticate(user:User):void{
LoginDelegate.getInstance().authenticate(user);
}
public function getBuddyList():void{
BuddyListDelegate.getInstance().getBuddyList();
}
}
}
class PrivateClass{}
ServiceFacade provides unify interface of all business logic. And the action of view will only call the façade . for example login action in Loginvew.xml :
private function login():void{
if(Validator.validateAll(validators).length == 0){
var loginUser:User = new User();
loginUser.userName=username.text;
loginUser.password=password.text;
serviceFacade.authenticate(loginUser);
}
}
Lets see the function serviceFacade.authenticate(loginUser):
public function authenticate(user:User):void{
LoginDelegate.getInstance().authenticate(user);
}
It calls the function “authenticate(user)” in LoginDelegate:
public function authenticate(user:User):void{
var responder:IResponder =
new Responder(onResult_Authenticate,fault);var call:Object = service.authenticate(user);
call.addResponder(responder);
}
private function onResult_Authenticate(event:ResultEvent)
:void{
var authUser:User = event.result as User;
model.loginUser = authUser;
model.viewStackSelectedIndex = 1;
}
Lets see the completely code from LoginView to LoginDelegate:
BaseView.mxml:
package com.ny.flex.cairngorm.no_fc.service
{
import com.ny.flex.cairngorm.no_fc.vo.User;
public class ServiceFacade
{
private static var _serviceFacade:ServiceFacade = null;
public function ServiceFacade(privateClass:PrivateClass)
{
if(ServiceFacade._serviceFacade == null){
ServiceFacade._serviceFacade = this;
}
}
public static function getInstance():ServiceFacade {
if(_serviceFacade == null){
_serviceFacade =
new ServiceFacade(new PrivateClass);
}
return _serviceFacade;
}
public function authenticate(user:User):void{
LoginDelegate.getInstance().authenticate(user);
}
public function getBuddyList():void{
BuddyListDelegate.getInstance().getBuddyList();
}
}
}
class PrivateClass{}
The façade will locate the concrete Business delegate method :
LoginDelegate.as:
package com.ny.flex.cairngorm.no_fc.service
{
import com.ny.flex.cairngorm.no_fc.*;
import com.ny.flex.cairngorm.no_fc.vo.User;
import mx.rpc.IResponder;
import mx.rpc.Responder;
import mx.rpc.events.ResultEvent;
public class LoginDelegate extends BaseDelegate
{
private static var _loginDelegate:LoginDelegate = null;
public function LoginDelegate(privateClass:PrivateClass){
if(LoginDelegate._loginDelegate == null ){
LoginDelegate._loginDelegate = this;
}
}
public static function getInstance():LoginDelegate{
if(_loginDelegate == null){
_loginDelegate = new LoginDelegate(new PrivateClass);
}
return _loginDelegate;
}
public function authenticate(user:User):void{
var responder:IResponder = new Responder(onResult_Authenticate,fault);
var call:Object = service.authenticate(user);
call.addResponder(responder);
}
private function onResult_Authenticate(event:ResultEvent):void{
var authUser:User = event.result as User;
model.loginUser = authUser;
model.viewStackSelectedIndex = 1;
}
}
}
class PrivateClass{}
This solution simplify Cairngorm framework. And It makes everthing understandable. But the shortage is obviously. It broke some design patten rules Decouple the application as much as possible. It couple the action to the Service Façade.
Lets move the next part:Mate-Tag Base Framework,
Flex MVC-Cairngorm
Do you ever hear a weird word “Cairngorm”? if your answer is no. Please look around if you live in a cageJ
A MVC Framework in Flex area which named by a mountain of Scotland. (Damn it, If I create my own Flex framework, I will call it “TianAnMen”).
There are tons of articles and diagram to discuss about . This is my architecture diagram:
The first step of using Cairngorm should be creating the backbone of frame work. It includes 3 objects:
Model Locater.
Service Locator;
Front Controller.
Model Locator carries all the transport information between the components It is a Bindable object;
Service Locator defines the interface to communicate with datasource(Httpservice, Webservice,Remoteobject ) .
Front Controller builds the mapping between the Dispatched event and Command ;
Lets see the related code of BuddyList app:
BuddyAppModelLocator.as:
package com.ny.flex.cairngorm.model
{
import com.ny.flex.cairngorm.vo.User;
import mx.collections.ArrayCollection;
[Bindable]
public class BuddyAppModelLocator
{
public var buddyList:ArrayCollection=new ArrayCollection();
public var loginUser:User=new User();
public var viewStackSelectedIndex :int = 0;
static private var __instance:BuddyAppModelLocator=null;
static public function getInstance():BuddyAppModelLocator
{
if(__instance == null)
{
__instance=new BuddyAppModelLocator();
}
return __instance;
}
}
}
Somehow almost all the service return information should have an object in the Model locator.
BuddyServiceLocator.mxml:
<?xml version=”1.0″ encoding=”utf-8″?>
<cairngorm:ServiceLocator xmlns:mx=”http://www.adobe.com/2006/mxml” xmlns:cairngorm=”http://www.adobe.com/2006/cairngorm“>
<mx:RemoteObject id=”buddyRo“ destination=”flexmvcRO” >
</mx:RemoteObject>
</cairngorm:ServiceLocator>
It defines remoteobject in this example . it should match the destination in remote_config.xml;
BuddyListController.as:
package com.ny.flex.cairngorm.control
{
import com.adobe.cairngorm.control.FrontController;
import com.ny.flex.cairngorm.command.GetBuddyListCommand;
import com.ny.flex.cairngorm.command.LoginCommand;
import com.ny.flex.cairngorm.event.GetBuddyListEvent;
import com.ny.flex.cairngorm.event.LoginEvent;
public class BuddyListController extends FrontController
{
public function BuddyListController()
{
super();
addCommand(LoginEvent.LOGIN_EVENT,LoginCommand);
addCommand(GetBuddyListEvent.GET_BUDDY_LIST_EVENT,
GetBuddyListCommand);
}
}
}
Obviously it is a mapping house of event and command.
How to glue this junk together? The magic is in Main application view :
BuddList_Main_Cairngorm.mxml:
<?xml version=”1.0″ encoding=”utf-8″?>
<mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml“ xmlns:service=”com.ny.flex.cairngorm.service.*“ xmlns:controller=”com.ny.flex.cairngorm.control.*” xmlns:views=”com.ny.flex.cairngorm.views.*” layout=”absolute“ width=”100%” height=”100%“>
<mx:Script>
<![CDATA[
import com.ny.flex.cairngorm.model.BuddyAppModelLocator;
[Bindable]
public var myModel:BuddyAppModelLocator = BuddyAppModelLocator.getInstance();
]]>
</mx:Script>
<service:BuddyServiceLocator id=”myservice“/>
<controller:BuddyListController id=”myController“/>
<mx:HBox horizontalAlign=”center” verticalAlign=”top“ width=”100%” height=”100%” y=”0” x=”0“>
<mx:ViewStack id=”viewStack“ resizeToContent=”true” selectedIndex=”{myModel.viewStackSelectedIndex}” >
<views:LoginView />
<views:BuddyListView/>
</mx:ViewStack>
</mx:HBox>
</mx:Application>
Now you can create the view component and dispatch the event from it.
LoginView.xml
<![CDATA[
import com.ny.flex.cairngorm.event.LoginEvent;
import com.ny.flex.cairngorm.vo.User;
import mx.validators.Validator;
private function login():void{
if(Validator.validateAll(validators).length == 0){
var loginUser:User = new User();
loginUser.userName=username.text;
loginUser.password=password.text;
var loginEvent:LoginEvent = new LoginEvent();
loginEvent.loginUser = loginUser;
loginEvent.dispatch();
}
}
]]>
</mx:Script>
<!– Validators–>
<mx:Array id=”validators“>
<mx:StringValidator id=”userNameValidator” source=”{username}“ property=”text“ required=”true“/>
<mx:StringValidator id=”passwordValidator” source=”{password}“ property=”text” required=”true” />
</mx:Array>
<mx:Form id=”loginForm” x=”0” y=”0“>
<mx:FormItem label=”Username:” >
<mx:TextInput id=”username” />
</mx:FormItem>
<mx:FormItem label=”Password:” >
<mx:TextInput id=”password” displayAsPassword=”true” />
</mx:FormItem>
<mx:FormItem direction=”horizontal” verticalGap=”15” paddingTop=”5” width=”170“>
<mx:Button id=”loginBtn” label=”Login” click=”login()”/>
</mx:FormItem>
</mx:Form>
</mx:Panel>
Typically every action of view will need create a event:
LoginEvent.as:
package com.ny.flex.cairngorm.event
{
import com.adobe.cairngorm.control.CairngormEvent;
import com.ny.flex.cairngorm.vo.User;
import flash.events.Event;
public class LoginEvent extends CairngormEvent
{
public static var LOGIN_EVENT:String = “loginEvent”
public var loginUser:User ;
public function LoginEvent()
{
super(LOGIN_EVENT);
}
override public function clone() : Event
{
return new LoginEvent();
}
}
}
And Every Event has to be mapped to a Command :
LoginCommand.as:
package com.ny.flex.cairngorm.command
{
import com.adobe.cairngorm.commands.ICommand;
import com.adobe.cairngorm.control.CairngormEvent;
import com.ny.flex.cairngorm.event.LoginEvent;
import com.ny.flex.cairngorm.model.BuddyAppModelLocator;
import com.ny.flex.cairngorm.service.LoginDelegate;
import com.ny.flex.cairngorm.vo.User;
import mx.controls.Alert;
import mx.rpc.IResponder;
public class LoginCommand implements ICommand, IResponder
{
public function LoginCommand()
{
}
public function execute(event:CairngormEvent):void
{
var loginEvent:LoginEvent = LoginEvent(event);
var user:User = loginEvent.loginUser;
var lgoinService :LoginDelegate
= new LoginDelegate(this);
lgoinService.authenticate(user);
}
public function result(event:Object):void
{
var authUser:User = User(event.result);
BuddyAppModelLocator.getInstance().loginUser = authUser;
BuddyAppModelLocator.getInstance().viewStackSelectedIndex=1;
}
public function fault(info:Object):void
{
Alert.show(“Login Fail Error “);
}
}
}
Then Do the mapping thing in the front controller.
addCommand(LoginEvent.LOGIN_EVENT,LoginCommand);
The command has to do the business: you have to add your business logic code in execute method :
var lgoinService :LoginDelegate
= new LoginDelegate(this);
lgoinService.authenticate(user);
The delegate is used to call the data source through the service locator :
LoginDelegate.as
package com.ny.flex.cairngorm.service
{
import com.adobe.cairngorm.business.ServiceLocator;
import com.ny.flex.cairngorm.vo.User;
import mx.rpc.IResponder;
public class LoginDelegate
{
private var responder:IResponder;
private var service:Object;
public function LoginDelegate(responder :IResponder){
this.service =
ServiceLocator.getInstance().getRemoteObject(“buddyRo”);
this.responder = responder;
}
public function authenticate(user:User):void{
var call:Object = service.authenticate(user);
call.addResponder(responder);
}
}
}
The result will be responded in the result method of The command, the model will be updated and binded to the result view:
public function result(event:Object):void
{
var authUser:User = User(event.result);
BuddyAppModelLocator.getInstance().loginUser
= authUser;
BuddyAppModelLocator.getInstance().viewStackSelectedIndex=1;
}
The other view work flow is similar to the above… The FB Project structure like :
Cairngorm makes the application development layable, testable. And make the developer more professional Geek J.
But it is complicate and hard to study and maintain. Is there any way simplify it?
Lets move to : Cut It Off-Cairngorm without front controller.
Flex Central Management
As I discussed in the Flex Chaos-All-in-one. Put everything together is not good solution for big project. That means we need to take the business logic away from the UI.
The Central Management use a remote object manager to control the communication between the Flex and Back-end . The architechture is :
As the above graph shows, Every UI component will call a service first, and the service class will call the Central manager class, The central manager class will call the Server side solution .Then Global Object manager will be used to transfer data between UIs.
Lets see the simple buddy list application on this way.
LoginView.xml
<?xml version=”1.0″ encoding=”utf-8″?>
<mx:Panel xmlns:mx=”http://www.adobe.com/2006/mxml” layout=”absolute” width=”300″ height=”200″ horizontalAlign=”center” verticalAlign=”middle” title=”Flex Central Manager Login”>
<mx:Script>
<![CDATA[
import com.ny.flex.centralManagement.service.LoginService;
import mx.validators.Validator;
import mx.containers.ViewStack;
import mx.rpc.events.ResultEvent;
private function login():void{
if(Validator.validateAll(validators).length == 0){
LoginService.getInstance().login(username.text,password.text);
}
}
]]>
</mx:Script><!– Validators–>
<mx:Array id=”validators”>
<mx:StringValidator id=”userNameValidator” source=”{username}” property=”text” required=”true”/>
<mx:StringValidator id=”passwordValidator” source=”{password}” property=”text” required=”true” />
</mx:Array>
<mx:Form id=”loginForm” x=”0″ y=”0″>
<mx:FormItem label=”Username:” >
<mx:TextInput id=”username” />
</mx:FormItem>
<mx:FormItem label=”Password:” >
<mx:TextInput id=”password” displayAsPassword=”true” />
</mx:FormItem>
<mx:FormItem direction=”horizontal” verticalGap=”15″ paddingTop=”5″ width=”170″>
<mx:Button id=”loginBtn” label=”Login” click=”login()”/>
</mx:FormItem>
</mx:Form>
</mx:Panel>
The core function above is :
LoginService.getInstance().login(username.text,password.text);
It separate the business logic from the view component. The service class is always the singleton, since we don’t use it to carry any status.
Lets see the class LoginService:
import com.ny.flex.centralManagement.event.DataManagerResultEvent;
import com.ny.flex.centralManagement.manager.GlobalObjectManager;
import com.ny.flex.centralManagement.manager.RemoteObjectManager;
public class LoginService
{
public var roManager:RemoteObjectManager = null;
public var gom:GlobalObjectManager = GlobalObjectManager.getInstance();private static var _instance:LoginService =null;
public static function getInstance():LoginService{
if(_instance == null){
_instance = new LoginService(new PrivateClass)
}
return _instance;
}
public function LoginService(privateclass:PrivateClass)
{
if(LoginService._instance == null){
LoginService._instance = this;
}
}
public function login(userName:String,password:String):void{
roManager = RemoteObjectManager.getRemoteObjectManager(“flexmvcRO”);
roManager.addEventListener(“getLoginUser”,loginHandler);
var params:Array = new Array(userName,password);
roManager.makeRemoteCall(“getLoginUserName”,”getLoginUser”,params);
}private function loginHandler(event:DataManagerResultEvent):void {
var userName:String = event.result as String;
if(userName){
gom.loginUserName = userName;
gom.viewStackSelectedIndex=1;
}
}}
There are 2 special objects :
RemoteObjectManager
GlobalObjectManager
RemoteObjectManager is a singleton class to implement central manage all the Remote Object communication. The original code is from Jeff Tapper’s blog :
Creating a Remote Object DataManager in ActionScript 3.0 for Flex 2.0
The “GlobalObjectManager” is used to keep the information between UIs , for example we use viewstack selectedIndex to decide which view will show on the sample application , so the global object viewStackSelectedIndex is used in the application view :
<?xml version=”1.0″ encoding=”utf-8″?>
<mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml“ xmlns:views=”com.ny.flex.centralManagement.views.*” layout=”absolute” width=”100%” height=”100%”>
<mx:Script>
<![CDATA[
import mx.binding.utils.BindingUtils;
import com.ny.flex.centralManagement.manager.GlobalObjectManager;
[Bindable]
public var gom:GlobalObjectManager=GlobalObjectManager.getInstance();
]]>
</mx:Script>
<mx:HBox horizontalAlign=”center” verticalAlign=”top” width=”100%” height=”100%” y=”0″ x=”0″>
<mx:ViewStack id=”viewStack” resizeToContent=”true” selectedIndex=”{gom.viewStackSelectedIndex}” >
<views:LoginView />
<views:BuddyListView/>
</mx:ViewStack>
</mx:HBox></mx:Application>
Just look back the loginHandler method in LoginService , the global object is updated:
private function loginHandler(event:DataManagerResultEvent):void {
var userName:String = event.result as String;
if(userName){
gom.loginUserName = userName;
gom.viewStackSelectedIndex=1;
}
}
The [Bindable] makes the change affect immediately.
The list show is BuddyList.mxml:
<?xml version=”1.0″ encoding=”utf-8″?>
<mx:Panel xmlns:mx=”http://www.adobe.com/2006/mxml” title=”Buddy List of {gom.loginUserName}” creationComplete=”init()” width=”500″ height=”320″>
<mx:Script>
<![CDATA[
import com.ny.flex.centralManagement.service.BuddyService;
import com.ny.flex.centralManagement.manager.GlobalObjectManager;
import mx.collections.ArrayCollection;
import mx.rpc.events.ResultEvent;
[Bindable]
public var gom:GlobalObjectManager = GlobalObjectManager.getInstance();
private function init():void{
BuddyService.getInstance().getBuddyList();
}
]]>
</mx:Script><mx:DataGrid id=”buddyList” dataProvider=”{gom.mybuddyList}” borderStyle=”none” width=”100%” height=”100%” >
<mx:columns>
<mx:DataGridColumn dataField=”firstName” headerText=”First Name”/>
<mx:DataGridColumn dataField=”lastName” headerText=”Last Name”/>
</mx:columns>
</mx:DataGrid>
</mx:Panel>
BuddyService is another service class to communicate the Remote Object.
This is my favorate architecture to develop flex webapps. It is very clean, There is no junk dispatch and listener. and it is very easy to maintain. But I don’t have enough theory supports it.
Next lets move to MVC: Flex MVC-Cairngorm
Flex Chaos:All-in-one
Flex is born with event-driven. That means Flex can do anything by its own tag. So The simplest way to develop Flex application naturally is using Flex Tags. Lets shot the code
In Loginview, the display part as usual:
<mx:Form id=”loginForm” x=”0″ y=”0″>
<mx:FormItem label=”Username:” >
<mx:TextInput id=”username” />
</mx:FormItem>
<mx:FormItem label=”Password:” >
<mx:TextInput id=”password” displayAsPassword=”true” />
</mx:FormItem>
<mx:FormItem direction=”horizontal” verticalGap=”15″ paddingTop=”5″ width=”170″>
<mx:Button id=”loginBtn” label=”Login” click=”login()”/>
</mx:FormItem>
</mx:Form>
And use tag <mx:RemoteObject> to call the service :
<mx:RemoteObject id=”loginReq” destination=”flexmvcRO”>
<mx:method name=”login” result=”loginHandler(event)” fault=”mx.controls.Alert.show(event.fault.faultString)”>
<mx:arguments>
<userName>{username.text}</userName>
<password>{password.text}</password>
</mx:arguments>
</mx:method>
</mx:RemoteObject>
Now, we need send the request in login method:
if(Validator.validateAll(validators).length == 0){
loginReq.login.send();
}
After sending the request A Result handler should be created:
private function loginHandler(event:ResultEvent):void{
var isLogin:Boolean = event.result as Boolean;
if(isLogin){
this.parentApplication.viewStack.selectedIndex=1;
dispatchEvent(new LoginUserEvent(username.text));
}
}
Finally A connection between the pages should be built. I use dispatch event :
dispatchEvent(new LoginUserEvent(username.text));
you want to dispatch event you have to add Meta tag in you page :
<mx:Metadata>
[Event(name="loginUser", type="flash.events.Event")]
</mx:Metadata>
It is time to create the custom event LoginUserEvent.as:
import flash.events.Event;
public class LoginUserEvent extends Event
{
public static const LOGINUSEREVENT:String =”loginUser”;
public var loginUserName:String = “”;
public function LoginUserEvent(userName:String)
{
super(LOGINUSEREVENT, true, true);
this.loginUserName = userName;
}
override public function clone():Event {
return new LoginUserEvent(loginUserName);
}
The Buddy list page(BuddyListVew.mxml) will show up after successful login:
First of first, the list page should listen the LoginUserEvent, so a preinitialize mehtod will be called:
<mx:Panel xmlns:mx=”http://www.adobe.com/2006/mxml” title=”Buddy List of {loginUserName}” preinitialize=”init()” width=”500″ height=”320″>
private function init():void{
this.parentApplication.addEventListener(LoginUserEvent.LOGINUSEREVENT, getUserName);
}
It is time to handle the event listener:
private function getUserName(event:LoginUserEvent):void{
loginUserName = event.loginUserName;
loginReq.getAllFriends.send();
}
then send request to get all Buddy and handle it:
private function getAllFriendsHandler(event:ResultEvent):void{
mybuddyList = event.result as ArrayCollection;
}
Don’t forget the remote object:
<mx:RemoteObject id=”loginReq” destination=”flexmvcRO”>
<mx:method name=”getAllFriends” result=”getAllFriendsHandler(event)” fault=”mx.controls.Alert.show(event.fault.faultString)”>
<mx:arguments>
<userName>{loginUserName}</userName>
</mx:arguments>
</mx:method>
</mx:RemoteObject>
That is all-in-one style: You only need us flex tag. nothing else. It is simple. and It is powerful if your system is simple and the business logic is not complicate. you can do it by this way. But there is no real project as simple as this example. Typically there are lots of remote object calls in the real project.
So It is time to move on : Flex Central Management.
Flex Development Evolvement
Flex is the No.1 choice to develop RIA application. And Java is No.1 choice for Enterprise application. So the migration solution should be that use Flex to develop the front-end, and Java for back-end. The Architecture is :
There are many different ways to implement above architecture. This series blogs will show my experience and evolvement of Flex Application development:
4. Cut It Off-Cairngorm without front controller
5. Tag Based-Mate
Sample application
I use a very simple application to show the dvelop process. I call it buddyList. There are two views :
Login View:
input username and password click login the buddyList view will show:
This application use Remote Object to implement server side data communication by BlazeDS, I create some simple java code to get some dummy data from server side The realated classes and files are :
1. add the java remote destination in Remote-config.xml :
<?xml version=”1.0″ encoding=”UTF-8″?>
<service id=”remoting-service”
class=”flex.messaging.services.RemotingService”><adapters>
<adapter-definition id=”java-object” class=”flex.messaging.services.remoting.adapters.JavaAdapter” default=”true”/>
</adapters><default-channels>
<channel ref=”my-amf”/>
</default-channels>
<destination id=”flexmvcRO”>
<properties>
<source>com.ny.blog.flex.mvc.accessor.DummyAccessor</source>
<scope>session</scope>
</properties>
<adapter ref=”java-object” />
</destination></service>
2. DummyAccessor.java:
package com.ny.blog.flex.mvc.accessor;
import java.util.ArrayList;
import java.util.List;import com.ny.blog.flex.mvc.pojo.Friend;
public class DummyAccessor {
public DummyAccessor() {
}
public boolean login(String userName,String password){
return true;
}
public List<Friend> getAllFriends(String userName){
List<Friend> myBuddy = new ArrayList<Friend>();
Friend dummy1 = new Friend();
dummy1.setFirstName(“John”);
dummy1.setLastName(“Smith”);
myBuddy.add(dummy1);
Friend dummy2 = new Friend();
dummy2.setFirstName(“Andy”);
dummy2.setLastName(“Jones”);
myBuddy.add(dummy2);
Friend dummy3 = new Friend();
dummy3.setFirstName(“Michael”);
dummy3.setLastName(“Niu”);
myBuddy.add(dummy3);
return myBuddy;
}
3. The pojo Friend.java
public class Friend {
private String firstName;
private String lastName;
private String nickName;public Friend() {
}//getter and setters
…
}
-
Archives
- July 2008 (6)
-
Categories
-
RSS
Entries RSS
Comments RSS






