Cairngorm For Beginners (part 2)
In yesterday's post I explained how to get a simple Cairngorm application compiling. Today I'd like to give an overview of that same example.
There are plenty of documents on-line which discuss the workings of Cairngorm, so I'm going to try and make this post a little different. I'm not going to cover every little detail in detail. Instead we'll assume that this is the first time we've set eyes on the example, and get a sense of where to find the information we need, quickly. You will soon find that you know where to look for certain types of class. The ability to structure projects is, after all one of the big selling points of Cairngorm.
We are going to be looking at the files in the com.adobe.cairngorm.samples.login package of our example. The package structure below this is one that you'll become familiar with, as it is the basic structure for all Cairngorm based applications. Here's a screen shot of that structure; Say hello to your li'l friends...

Below I have listed the classes in the order I would explore them when going through this particular application. Class names are in bold, and the most important things to look for in each class are noted below that. You'll probably get the most out of this post if you follow along, looking at each class as I discuss it. This isn't a big application and I'm purposely not going to tell you what package each class is in - finding them for yourself will be quick and a good exercise in helping you to familiarise yourself with the general structure.
1. CairngormLogin
This is the Application's main file and is therefore the obvious place to start.
Given that we're not going to cover every detail here, all you need to be aware of is that our View is called LoginPanel, which is unsurprisingly the main login panel that you see when you compile the application. It is specified like so:
-
<view:LoginPanel id="login" login="{ model.login }"/>
Our application is currently just sitting there, not doing much. In fact, nothing will happen until we interact with the login panel, therefore the LoginPanel view class is the next obvious place to look...
2. LoginPanel
The app isn't going to do anything specific until it receives a user-gesture, which in this case will be the press of a button, so the login button's click event is the next place to look.
When the button is clicked loginUser() is called. loginUser() creates a simple storage object (also known as a Value Object or Data Transfer Object - V.O. and D.T.O. respectively), in which we store the username and password entered by the user. This object is passed to an Event Object, which is then dispatched. The event will eventually get picked up by something called the FrontController (or more specifically, a subclass of FrontController), whose job it is to intercept such events and execute a corresponding Command. I will talk about controllers and commands in a moment. First, let's take a look at the next logical step - the event which gets dispatched:
3. LoginEvent
You should have found this class in the control package. As I mentioned previously, events are picked up by the Controller, and so this is a pretty logical place to put it.
As you can see, Cairngorm's event dispatcher is a subclass of flash.events.Event. This is done to differentiate Cairngorm events from events raised by the underlying Flex framework, and is mandatory for Cairngorm event dispatching.
How do we know what event is being dispatched? Looking inside the LoginEvent class we can see that a constant, called LoginControl.EVENT_LOGIN is being passed to the Superclass (CairngormEvent) as the String representing the event-type name. Here is that code:
-
public function LoginEvent( loginVO : LoginVO ){
-
super( LoginControl.EVENT_LOGIN ); //the static constant passed to the superclass
-
this.loginVO = loginVO;
-
}
Note: This constant's value is simply the string "login". The reason a constant is used is to
facilitate type checking - if you accidentally mistype "login" the compiler will alert you by way of an error.
Also note that the constant is declared on a class called LoginControl. This is the most logical place, as our Controller is the class which is going to be catching the event and executing the specified Command. We'll now jump straight to LoginControl, because this is the class which extends FrontContoller and which will 'catch/react' to our event.
A quick terminology recap:
I've mentioned "controllers" and "commands" before, and hopefully you've read the documentation and understand these terms already, but in case you haven't now is the time to offer a quick overview because I'll be using these terms frequently in the next few paragraphs:
Both "Front Controller" and "Command" are Design Patterns. They work together in Cairngorm to provide what is collectively known as a "Service to Worker" microarchitecture. Commands are classes which the application need not know anything about, other than how to invoke them (they all have a single point of entry, which is a method called execute()). The Front Controller registers to listen to all events broadcast by the application, and upon hearing one, it looks up the appropriate command for handling that event. It then dispatches control to the command by calling its execute() method.
4. LoginControl
LoginControl is our Controller class, and will be listening for the event from our button and doing something in response to it. Here you can see the static constant EVENT_LOGIN being declared, as discussed earlier. You can also see that EVENT_LOGIN, along with the name of a Command class, is being passed to a method called addCommand().
-
public function LoginControl(){
-
addCommand( LoginControl.EVENT_LOGIN, LoginCommand );
-
}
This line of code ensures that behind the scenes when the event is received, Cairngorm will call LoginCommand (remember, LoginCommand is a Command class and so has a single point of entry - execute()), so the Controller will essentially be calling loginCommandInstance.execute(). The Command attempts to encapsulate what happens as a result of this particular user gesture (i.e. the "Login" button press). If we had to deal with another gesture, be it user generated or application generated, we can simply add another command in the same way...
addCommand( LoginControl.ANOTHER_EVENT, AnotherCommand );
Although we've discussed a fair few classes so far, essentially the button has dispatched an event, and in response, the execute() method of LoginCommand is called ( ...that sounds so much simpler doesn't it). So, LoginCommand is where it all kicks off. This is where you put stuff that you want to happen in response to your button press. Let's have a look at that class now...
5. LoginCommand
As you can see, this class implements the Command interface*- the Command interface is what specifies that the implementing class must specify some method called execute(). LoginCommand also implements Responder* which specifies that onResult and onFault methods must be declared.
*Note: Command and Responder have been deprecated. They should be replaced with com.adobe.cairngorm.commands.ICommand (which still specifies an "execute" method) and mx.rpc.IResponder (which specifies methods called "result" and "fault"). I will ignore any further deprecated methods for the duration of this post, and talk about them in the next post when I discuss updating the Login example.
In the case of an example demonstrating a login, LoginCommand is where we'd want to call the server and pass along the username and password given by the user, to see if they are valid. However, there is actually one more layer of abstraction before we do that, and it is the Delegate class.
Before we get on to the Delegate class, the only other thing to happen in the execute() method is that model.login.isPending is set to be true - this is simply a value upon the model that the login button is bound to (the value is set on the model, and the view uses DataBinding to bind certain values to the view - in this case, to the button's "visible" property). This way of updating the model and having the view reflect those changes is another core concept that you should already be aware of. It ensures that while your model knows nothing about the view, the view automatically reflects the current state of the model.
-
//look back in the LoginPanel class to see this...
-
<mx:button label="Login" enabled="{ !login.isPending }" click="loginUser()">
-
</mx:button>
Now we instantiate the Delegate class (passing in a reference to *this* class - i.e. LoginCommand). After that a method is called on the Delegate (to which we pass along our Event object ... ensuring that it's already been cast to the correct type):
6. LoginDelegate
LoginDelegate lives in the business package with Services.mxml.
You'll notice that when we instantiated the Delegate class (i.e. from within LoginCommand class) we passed in a reference to 'this' (i.e. we passed in a reference to LoginCommand). The Delegate acts as a kind of go-between for the Command class and the Services. Those of you coming from Flash 8 should note that Cairngorm's Delegate class has nothing to do with the AS2 Delegate class.
Why do we need this layer of abstraction? In my (fairly limited) experience of Cairngorm I use it for 'heavy-lifting'. Imagine that we make our asynchronous call to the server and send over the information that the user entered and we get a response from the server. The response may not come back in an instantly usable format. For example if it is XML we may need to deserialise it first (e.g. if our application is expecting to deal with a certain type of object, and not xml directly). We can keep this kind of work out of the Command class by doing it in the Delegate.
In the Delegate class, AsyncToken is used to set up result and fault handlers, and then the call to the service is made. In this example, a simple timer is used to mimic the call. There is no manipulation of the data needing to be done (because there is no real call being made and therefore no real data), and so we simply use our reference to the LoginCommand class ( held in a variable called responder) to proxy the call back to the Command class's onResult and onFault handlers.
7. LoginCommand (revisited)
Control has now been returned to the Command class, and it is here, assuming that no fault has occurred, that we set some more properties on the model. As previously discussed, parts of the view will be bound to the model, and so the view should update itself when the model changes. For example, we can see that the model.login.isPending is set back to false which should affect the state of the login button that we talked about before. We won't actually see that change though, because the following line actually changes the entire view, removing the login panel altogether...
-
model.workflowState = ModelLocator.VIEWING_LOGGED_IN_SCREEN;
This Caringorm example application shows a complete round-trip. To see why the view changes we need to look back at the class which instantiated the view in the first place...
8. CairngormLogin (revisited)
Here we are, right back at the beginning. Why does the above line of code change our view? If you look back at CaringormLogin you will see that the view was declared within a ViewStack component, and the ViewStack's selectedChild property is bound to a method which returns a Container based upon that property, so when we changed the property on the model, it initiated a change on the ViewStack:
-
<mx:viewstack>
-
id="appView"
-
selectedChild="{ getView( model.workflowState ) }"/>
I think that pretty much wraps it up for this post. I hope it's provided a useful overview of Cairngorm and got you to a point where you're happy to start using it for your own projects. I welcome any feedback that can help improve the information in this post.
20 Comments so far
Leave a reply
How about presenting this stuff for us a LFPUG sometime?
Hi Tink. Hmm I may be up for that. I’m a bit of a nervous live speaker but it would probably do me good to try and address that.
I’d love to see it at LFPUG.
I enjoyed this article, first i’ve seen at the right sort of level for me,
good clear overview and the right level of detail (only) when needed.
cheers,
Jim.
Thanks Jim.
It looks like this is pencilled in for August.
I found this very helpful! Thanks! I agree with Jim. This article is very clearly written. Thanks again!
This is really useful…now am very clearly understand the cairngorm flow….Thanks a lot…
Manimaran.T
Hi that was a great article, I am struggline with the caringorm framework (its my first framework) I just have one question, in the LoginControl class how does the constructor get called? I cant find any code that actually calls the constructor. I thought it might be called via caringormevent class but I cant where it would be.
I must be missing something but any help would be appreciated.
Hi Colin. An instance of LoginControl is instantiated in the default application file (using mxml). The line you are looking for is this one:
<control:LoginControl id=”controller”/>
Hope this clears things up.
hey guys
even though i have implemented the login according to the above notations.
when i logout .i cannot login with out refreshing the page .
a helping hand would be greate.
this is what my main look like.
i have a view stack.
2 views login and mainapp
//workflow container function
public function getView( workflowState : Number ) :Container
{
if( model.workflowState == TrainingModelLocator.VIEW_ADMIN_SECTION )
{
return mainApplicationView;
}
else
{
return loginView;
}
}
private function logout():void
{
appViews.selectedChild =loginView;
}
//ma viewstack
as i can see the flow stops at the logout function
thankx in advance
well i think i have reached the limit of the comments so
ma view stack is not visible
but hear is ma viewstack
well i think i have reached the limit of the comments so
ma view stack is not visible (
but hear is ma viewstack
mx:ViewStack id=”appViews” width=”200%” height=”200%” selectedChild=”{ getView( model.workflowState ) }” >
mx:Panel id=”loginView” layout=”absolute” >
mx:Canvas width=”100%” height=”100%” >
view:LoginPanel id=”login” login=”{ model.login }” >
/view:LoginPanel>
/mx:Canvas>
/mx:Panel>
mx:Panel title=”Main Application View” width=”100%” height=”100%” id=”mainApplicationView” >
mx:Canvas width=”100%” height=”100%” >
view:AdminView x=”10″ y=”10″ width=”90%”/>
mx:Button label=”Logout” top=”10″ click=”logout()” width=”150″ horizontalCenter=”377″/>
/mx:Canvas>
/mx:Panel>
/mx:ViewStack>
Awesome tutorials! I was doing well up to the #3 where you update to ICommand and IResponder. Looked simple but after I did it:
package com.adobe.cairngorm.samples.login.commands
{
import mx.rpc.events.ResultEvent;
import mx.rpc.IResponder;
import com.adobe.cairngorm.commands.ICommand;
import com.adobe.cairngorm.control.CairngormEvent;
import com.adobe.cairngorm.samples.login.business.LoginDelegate;
import com.adobe.cairngorm.samples.login.control.LoginEvent;
import com.adobe.cairngorm.samples.login.model.ModelLocator;
import com.adobe.cairngorm.samples.login.vo.LoginVO;
public class LoginCommand implements ICommand,IResponder
{ …
line: public class LoginCommand implements ICommand,IResponder —- gives error:
1044:Interface method fault in namespace mx.rpc:IResponder not implemented by class com.adobe.cairngorm.samples.login.commands:LoginCommand
Not sure why. Thanks in advance,
Mic.
Hi Mic, thanks. Have you declared both the ‘result’ and ‘fault’ methods for LoginCommand so it conforms to the interface ( see http://nwebb.co.uk/blog/?p=63 )?
hi buddy,
i dont know how much this article helped others. but trust me it came to be a boon for me as I learned cairngorm in just 2 hurs and implemented a full fledged working example just going through your article.
Thanks, thanks a ton
cheers,
Anand
[edit: That's great to hear - thanks. It's harder teaching it in a blog post than in a conference session ... although the latter is more daunting :] ]
[...] Webb :: Cairngorm For Beginners :: Covers version 2.2 :: Part 1 :: Part 2 :: Part [...]
[...] Webb :: Cairngorm For Beginners :: Covers version 2.2 :: Part 1 :: Part 2 :: Part [...]
[...] Webb :: Cairngorm For Beginners :: Covers version 2.2 :: Part 1 :: Part 2 :: Part [...]
[...] Webb :: Cairngorm For Beginners :: Covers version 2.2 :: Part 1 :: Part 2 :: Part [...]
[...] Webb :: Cairngorm For Beginners :: Part 1 :: Part 2 :: Part [...]
[...] Nice Tutorials: Documentation, Getting started with cairngorm, Cairngorm For Beginners, Cairngorm [...]