nwebb

Flex, Flash, AIR

Archive for June, 2007

FlexBuilder 3 Tip – Imports

In FlexBuilder 2 and 3 you can choose to organise the import statements you have in your .as files

This is done by either choosing source -> organize imports from the main menu, or by using the shortcut Ctrl + Shift + o (Apple +Shift + o for Mac I believe).

Note: These options are not available for mxml files, even those with script blocks in them

I don’t know about you, but this wasn’t something I really used in FB2, however in FB3 it does a little more than just organise. The difference for FlexBuilder 3 is that when you organise imports it will also delete any import statements for imported classes which are no longer referenced, so if you import a DataGrid and then choose not to actually instantiate an instance, the import will be removed. Nice :]

Also, if you go to window -> preferences -> Flex -> Editors -> ActionScript Code you can choose to automatically keep your imports organised:

Organised Imports

With this option checked, your imports will organise themselves; however the import statement for a deleted item does not automatically get removed until the next time you import another class using auto-complete (I guess that is the trigger needed for Flexbuilder to re-examine the list of imports – fair enough!)

For example, in the picture below, hitting the enter key (to import the Button class) will overwrite the ScrollBar import, as there is no ScrollBar being used in our class:

Imports Organised in Flex 3 - auto-complete

5 comments

Cairngorm For Beginners (part 3)

In parts 1 and 2 of this article I explained how to get up and running with Cairngorm and I walked you through an example application which had been built using a prior release of the framework. In this part I want to go over some of the changes made in versions 2.1 and 2.2, and update the sample application to reflect those. Mercifully (for both you and my type-weary fingers) this should be a shorter post than the last two.

I have already made reference to some of the things that have changed - mainly deprecated interfaces. There is also a new helper class for events.

Okay where to start ...
You should be pretty familiar with those classes from the example by now, so let's get a feel for replacing some of the deprecated stuff. This is going to be pretty obvious to most of you, but I'll cover it for completeness. As an example we'll look at changes to LoginDelegate and LoginCommand:

Changing the LoginDelegate class
In part 2 I mentioned that Responder had been deprecated.
The Flex class mx.rpc.IResponder is now used in favour of the old Responder interface. This new interface specifies that the implementing class must have both a result and fault method. The changes are simple:

  • Change the import statement to: import mx.rpc.IResponder;
  • Change all references for Responder to IResponder. For example: private var responder:IResponder;
  • In the loginResult() method, change the methods called from onResult & onFault, to result and fault
  • Both result and fault expect a parameter, so for fault, send in null. For example: responder.fault(null);

This should leave you with one listed error still in the FlexBuilder 'Problems' panel, and it is an error in the LoginCommand class:

Changing the LoginCommand class
The error is a type coercion error, and the problem is with this line:

var delegate : LoginDelegate = new LoginDelegate( this );

The reason for the error is that we changed our Delegate class to expect a parameter of type IResponder to be sent in to the constructor, yet LoginCommand is still of type Responder. The changes required are as follows:

  • Change the import statement to: import mx.rpc.IResponder;
  • Change all references for Responder to IResponder
  • Change the method signatures for result and fault so that they are in line with the IResponder interface. For example: result( event : Object ) and fault( event : Object )

We can also substitute ICommand for the deprecated interface Command now. This is really easy and simply involves replacing Command with ICommand throughout the class - including in the import statement.

The other interface changes that I'm aware of are to ModelLocator (now IModelLocator) and ValueObject (now IValueObject), so feel free to go ahead and make those substitutions. However, note that in the example, Alex has specified his own class called ModelLocator, which resides in the package com.adobe.cairngorm.samples.login.model. This extends the ModelLocator which resides in the package com.adobe.cairngorm.model.ModelLocator . Take note of which one is being referred to before making any changes!

Event Dispatching & Using Clone()
In part 2 I mentioned that Cairngorm uses CairngormEvent and not flash.events.Event, and you may have noticed that Alex's example does not have a clone() method. Well CairngormEvent actually extends flash.events.Event so you may wonder why clone() wasn't used. After all, the Flash help files make it clear that you should override clone(). A good explanation of the need for overriding the method has been posted by Daniel Rinehart here. Here is a summarised quote (words in square brackets are my own):

...most of the time not implementing a clone() method won't hurt you but you really should create one ... it only becomes an issue if you relay [redispatch] an [custom] event

Also note that another small but nice addition to the CaringormEvent class is the data property, which can be used to hold information passed with the event in cases where the developer does not want to extend CairngormEvent.

Self-Dispatching Events
New to Cairngorm 2.2 is a helper class which makes event dispatching a little less verbose. Alistair McLeod posted about this here.

Actionscript:
  1. //Instead of:
  2. CairngormEventDispatcher.getInstance().dispatchEvent( loginEvent );
  3. //You can now do:
  4. loginEvent.dispatch();

I believe that's pretty much it with regards to updating the example file. I may edit this post in the near future if anything else is brought to my attention. Please feel free to leave feedback if you have any questions or simply found these posts helpful. Cheers.

19 comments

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...

Cairngorm - familiar package structure

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:

Actionscript:
  1. <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:

Actionscript:
  1. public function LoginEvent( loginVO : LoginVO ){
  2.    super( LoginControl.EVENT_LOGIN ); //the static constant passed to the superclass
  3.    this.loginVO = loginVO;
  4. }

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().

Actionscript:
  1. public function LoginControl(){
  2.    addCommand( LoginControl.EVENT_LOGIN, LoginCommand );
  3. }

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.

Actionscript:
  1. //look back in the LoginPanel class to see this...
  2. <mx:button label="Login" enabled="{ !login.isPending }" click="loginUser()">
  3. </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...

Actionscript:
  1. 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:

Actionscript:
  1. <mx:viewstack>
  2.     id="appView"
  3.     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.

22 comments

Next Page »

Bad Behavior has blocked 399 access attempts in the last 7 days.