Introduction
If you followed my previous posts on the Employment Management Console application leveraging Spring ActionScript + Cairngorm, then you’ll be familiar with this small example as I simply ported it over from SAS + CG to Swiz using the Passive View (PV) design pattern.
Tutorial Goal: Create an Employee Management Console using Swiz and the Passive View Design Pattern
Assets
Acronyms
- PV = PassiveView
- VM = ViewMediator
- CG = Cairngorm
- IoC = Inversion of Control
- DTO = Data Transfer Object
Caveats
I won’t go into the details of what Swiz is and how it works as I’d like to let the code speak for itself and you can just hit up the Swiz homepage and view their simple examples; that said, I’ll point out several things I really like about Swiz.
I am using Swiz 0.6.4, as I found issues with the 1.0.0 alpha release (that I’m going to bring up with Ben Clinkenbeard).
As an additional side note, I chose the PV because I was always a fan of code-behind — I like separation of concerns so much that I actually detest looking at MXML with ActionScript in it — what a mess!..that and the fact that MXML is declarative and should just perform basic UI layout and not presentation logic. What I love about the PV is that it relies on composition as opposed to inheritance (ie the biggest issue I have with code-behind).
Finally, there are other examples of the PV + Swiz out there, like Ben Clinkenbeard’s example, but I went ahead and created an AbstractViewMediator that takes care of some race conditions when working with Views in a ViewMediator, like setting event handlers and data bindings. And I did some other things that I didn’t see in others that I’ll point out.
Swiz is Noninvasive
Swiz is a noninvasive framework that allows you to provide well organized structure and architecture to your app without writing a ton of boilerplate code that can be overly verbose and time consuming. Remember CG and how you were forced to do the following for service calls:
- Create an
Eventthat extends theCairngormEvent. - Create a
Commandto act as a responder for your service call. - Create a corresponding
Delegatethat actually makes the service call and transforms your data to client-side types before returning it to theCommand. - Don’t forget to map your Custom CG Event to your Command in the
FrontController. - Wash, rinse, repeat…and repeat…and repeat…and…mehhh
With Swiz, you can (and I emphasize can b/c you can still do it the old school way too) just broadcast a DynamicEvent from any object in the IoC Container (those objects that are defined in the Swiz BeanLoader — in my example, it’s in the class Beans.mxml) and then add some metadata to another object in the Container to “Mediate” that event.
Here’s an example — so in my LoginViewMediator I want to broadcast a “login” type event and pass the username and password along with it and I want my LoginController to handle this event:
private function submitBtnClickHandler(evt:MouseEvent):void
{
logger.debug("submitBtnClickHandler");
var dto:LoginDTO;
// create a login dto to contain the required fields for login
dto = new LoginDTO();
dto.userName = this.view.userNameTextInput.text;
dto.password = this.view.passwordTextInput.text;
var dynEvt:DynamicEvent = new DynamicEvent("login");
dynEvt.dto = dto;
this.eventDispatcher.dispatchEvent(dynEvt);
}
And now we handle this event in the LoginController like so:
[Mediate( event="login", properties="dto" )]
public function login( dto:LoginDTO ):void
{
logger.debug("login");
// TODO - make service call -- see in example src code
}
So let’s dig a bit deeper here and note all the cool stuff that’s going on and why I’m psyched about Swiz + PV:
No Custom Events (If You Don’t Want To)
Notice that I didn’t have to create a custom Event — less code — YES! Now, you can by all means create a custom event and Swiz will type check the event object and the event type to make sure they exist — check it out in the Swiz Docs for Event Handling — or you can save yourself some extra code and do what I did. While I’m usually a stickler for strongly typed objects, sometimes you have to ask yourself it it’s not just overkill for something as simple as this…personally preference I suppose, but less code without sacrificing organization and good practices wins that battle in my head.
Event Mediation
Swiz automatically handles the event and passes the meat of the event (the LoginDTO that I packages in my DynamicEvent) for me…uhm, yeah…that’s frigging cool! And you can pass multiple args as well. Again, see Swiz Docs. NOTE: One thing to remember about event mediation is that the handler method must be marked public — I’m used to making event handlers protected (and sometimes even private), but this won’t allow the Swiz event mediation to work it’s magic so make sure event handler methods are marked as public.
No Logic in MXML
My LoginViewMediator grabs the username and password from it’s corresponding LoginView and creates the event and DTO. The actual LoginView is just MXML…nuff said.
Event Dispatching and Handling Thereof in IoC Container Objects
Notice that the LoginViewMediator broadcasts the event from a class member variable called “eventDispatcher”. This next part is important: in order for Swiz to Mediate events, the events need to go through the central event bus using Swiz.dispatch(<eventType>) or by using the event dispatcher in the BeanLoader — I have chosen the latter. Why?…I have 2 reasons:
- I don’t like having a reference of Swiz in my classes — it’s just unnecessary and makes my application that much more coupled to the framework, something Swiz tries to get developers away from to begin with.
- The Swiz.as class is a singleton and singletons are bad…this is too long a topic to get into, but my main reason has to do with the use of Modules in large Flex apps and while this example doesn’t require modules, I’d still rather just stay the hell away from singletons.
I shove a reference of the BeanLoader’s eventDispatched into my IoC Container managed objects like this:
<controller:LoginController id="loginController" eventDispatcher="{this.dispatcher}" />
<mediator:LoginViewMediator id="loginViewMediator" eventDispatcher="{this.dispatcher}" />
Note that they both don’t need it in order for the controller to hear events from the view mediator — just the object broadcasting the event needs to have a reference to it, but I pushed it into the controller as well as since I’m broadcasting other events from it that I’d like other managed objects to listen to via mediation.
AbstractViewMediator
Since all of my views need a reference to their corresponding view, I decided to create a super class for all the mediators called AbstractViewMediator. This bad boy does several things starting with the injection of the view:
[Autowire( view="true" )]
public function set view( value:* ):void
{
logger.debug("AUTOWIRE :: view = " + value);
this._view = value;
// determine if the view has been initialized...
// NO...listen for it;s creation complete event
if(this._view.initialized == false)
{
this._view.addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
}
// YES...call the init() method to kick off the instation of the view mediator
else
{
this.init();
}
// don't get in GC's way if the view is removed
this._view.addEventListener(Event.REMOVED_FROM_STAGE, cleanup);
}
Again, there’s a couple things to note about this method:
- The argument for the method is untyped, so how does the concrete
ViewMediator(VM) know how to inject the correct view? And then what about code-hinting for the view in the VM? Well, if you look at an actual implementation of a VM, like theLoginViewMediatoryou’ll see the following method:public function get view():LoginView { return this._view as LoginView; }Put this into method into each concrete VM with it’s corresponding view type and you’re good to go! Problem solved.
- We check to see if the view has been initialized, ie, has the creationComplete event fired and can I start to work with children components of this view without getting runtime errors. If it has, then we call the init() method (which can and should be overridden by concrete VMs to initialize themselves); if not, then we listen for the creationComplete event and call the
init()method then and only then. This ensures that when we want to set event handlers and data bindings on the view’s children we know they exist and we don’t hit nulls leading to runtime errors. - Finally, if you look at the
init()method in the AbstractViewMediator, you’ll see that it also calls thesetViewListeners()andsetViewDataBindings()methods — these are placeholder methods where developers can again add concrete event handlers and data bindings in VM subclasses by overriding them.
The last thing the AbstractViewMediator does is provide the event dispatcher class member variable that we discussed earlier: public var eventDispatcher:IEventDispatcher;
Controllers & Delegates
In Swiz, we eliminate the idea of a Command class (from Cairgorm) and kind of replace it with the controller — I say kind of b/c it’s not exact mapping as the Cairngorm Command follows the actual J2EE Command design pattern whereas Swiz uses a controller to handle events from the UI to make service calls, act as a responder for the service calls, and finally to orchestrate what happens in the app after the service call (usually by dispatching an event). Since we already looked at the event mediation of the in the LoginController we won’t discuss that, but we will dig into how the controller leverages a corresponding LoginDelegate to call the service and perform data transformations on the service’s response object. So let’s look at the internals of the actual login event mediation in the LoginController:
[Mediate( event="login", properties="dto" )]
public function login( dto:LoginDTO ):void
{
logger.debug("login");
var call:AsyncToken = this.delegate.login(dto);
// I created 2 ways to handle the login service delegate
// you can either have the result come back to the controller
// or you can catch the result in the delegate and have it perform
// the necessary data transformations (traditional approach) before
// kicking it back to this controller via an event
// APPROACH 1) have the results come back directly to this controller
//this.executeServiceCall(call, onLoginResult, onLoginFault);
// APPROACH 2) have the results come back the delegate for data transformations
// before coming back to this controller
this.executeServiceCall(call, this.delegate.result, onLoginFault);
}
Out of the box, Swiz recommends that the controller handle the actual service response and data transformations before deciding what the app should do next…that’s too much responsibility for one object in my opinion, so as you can see I decided to allow the LoginDelegate to handle the actual service response: this.executeServiceCall(call, this.delegate.result, onLoginFault);. After the delegate finishes with the response, it dispatches an event:
LoginDelegate
public function result(resultEvent:ResultEvent):void
{
logger.debug("result");
var userModel:UserModel;
var xml:XML;
var dynEvt:DynamicEvent;
// get the response typed as desired
userModel = this.getTypedResponse(resultEvent);
// let something know that the login delegate is done
dynEvt = new DynamicEvent("loginDelegateComplete");
dynEvt.userModel = userModel;
this.eventDispatcher.dispatchEvent(dynEvt);
}
And then the event is mediated by the LoginController where it updates the necessary model properties and dispatches an event to signify the end of the login process.
[Mediate( event="loginDelegateComplete", properties="userModel" )]
public function completeLogin(userModel:UserModel):void
{
logger.debug("completeLogin");
var role:String;
// populate the model with data from the response DTO
this.appModel.user = userModel;
// determine of user is an admin
for each ( role in userModel.rolesList )
{
if(role == RolesConstants.ROLE_ADMIN)
{
this.appModel.user.isAdmin = true;
break;
}
}
// set this last as this is what binds the view change
this.appModel.isUserAuthenticated = true;
var evt:DynamicEvent = new DynamicEvent(EVENT_LOGIN_COMPLETE);
this.eventDispatcher.dispatchEvent(evt);
}
I think that about covers it for this edition. Any questions?
More to come on Swiz in the future.
#1 by Don H. - January 9th, 2010 at 21:45
That is a hell of a lot of code for such a small app.
#2 by brianr - January 11th, 2010 at 10:09
@don — For a small app like this, yes, it’s more code than really necessary and you can simply bang this sucker out with MXML + AS mixed in and be done in 10 mins…but that’s not the point of this post…This is simply a small demonstration of the Swiz framework, purposely implemented on a small app so users unfamiliar with Swiz can come to a quick understanding of it and then apply it to an enterprise-level app.
#3 by Evan G - January 14th, 2010 at 16:29
I’ve been using Swiz for about a month now. Love the DynamicEvents example, I was creating my own custom events but this seems much more practical.
thanks for posting this!
evan
#4 by Rupert - February 3rd, 2010 at 13:01
thanks a lot for this example. I has been really ilustrative for me.
#5 by josh - May 19th, 2010 at 18:42
very very helpful. thank you!