Oct
17
Simplified Cairngorm, Easy MVC for Adobe Flex


Posted: 17th October 2007
Tags: , , , ,
Posted in Flex
Comments: 28 Comments »

Like many others, I have been struggling to fully get my head fully around the Cairngorm Micro Architecture, and even with the excellent Cairngorm Creator, I find it a little overkill for many Adobe Flex projects I am involved with, in fact, so does Steven Webster (one of the creators of Cairngorm).

So yesterday I sat in on an excellent Adobe eSeminar, presented by Tom Bray of SearcherCoders who presented an easy / simplified Model View Controller architecture based on Cairngorm.

To demonstrate the need for these frameworks, Tom started with an excellent example of an application based on a simple chat client. Inspired by his excellent Chatopica perhaps?:

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <mx:Application xmlns:mx=http://www.adobe.com/2006/mxml layout=vertical>
  3. <mx:Script>
  4. <![CDATA[
  5. import mx.collections.ArrayCollection;
  6. [Bindable]
  7. public var rooms:ArrayCollection = new ArrayCollection( ["Flex", "Flash", "AIR", "ColdFusion" ] );
  8. [Bindable]
  9. public var users:ArrayCollection = new ArrayCollection( ["John", "Paul", "George", "Ringo" ] );
  10. [Bindable]
  11. public var currentRoom:String;
  12. [Bindable]
  13. public var messages:String = “”;
  14. private function joinRoom( room:String ):void
  15. \{
  16. currentRoom = room;
  17. views.selectedChild = chatPanel;
  18. \}
  19. private function sendMessage( message:String ):void
  20. \{
  21. messages += message + “\\n”;
  22. \}
  23. ]]>
  24. </mx:Script>
  25. <mx:ViewStack id=views resizeToContent=true>
  26. <mx:Panel title=Room List width=300 height=400>
  27. <mx:VBox width=100% height=100%>
  28. <mx:List id=roomList width=100% height=100%
  29. borderSides=top dataProvider=\{rooms\}/>
  30. <mx:Button width=100% label=Chat enabled=\{roomList.selectedItem != null\}
  31. click=joinRoom(roomList.selectedItem as String)/>
  32. </mx:VBox>
  33. </mx:Panel>
  34. <mx:Panel title=Topic: \{currentRoom\} id=chatPanel width=500 height=400>
  35. <mx:HBox width=100% height=100% paddingLeft=10 paddingBottom=10
  36. paddingRight=10 paddingTop=10>
  37. <mx:VBox width=150 height=100%>
  38. <mx:Label text=User List/>
  39. <mx:List width=100% height=100% dataProvider=\{users\}/>
  40. </mx:VBox>
  41. <mx:VBox width=100% height=100% >
  42. <mx:Label text=Chat/>
  43. <mx:TextArea id=chatText text=\{messages\}
  44. editable=false width=100% height=100% wordWrap=true/>
  45. <mx:HBox width=100%>
  46. <mx:TextInput id=messageInput width=100% minWidth=0/>
  47. <mx:Button label=Send click=sendMessage( messageInput.text )/>
  48. </mx:HBox>
  49. </mx:VBox>
  50. </mx:HBox>
  51. </mx:Panel>
  52. </mx:ViewStack>
  53. </mx:Application>

The above example application is a classic example of how most of us start out developing in Flex, we end up putting all our properties and event handlers in the same file. This may be fine for a tiny application but if we want to scale it up we are going to run into problems.

For example, Flex offers us the ability to create a custom MXML component, that we can then reuse through our application, so as Tom showed, maybe we want to take the code that displays the Room list (above) and copy this into a separate mxml component . We can easily cut and paste the code and create a new MXML file RoomList.mxml with the following code:

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <mx:Panel title=Room List xmlns:mx=http://www.adobe.com/2006/mxml width=300 height=400>
  3. <mx:VBox width=100% height=100%>
  4. <mx:List id=roomList width=100% height=100%
  5. borderSides=top dataProvider={rooms}/>
  6. <mx:Button width=100% label=Chat enabled={roomList.selectedItem != null}
  7. click=joinRoom(roomList.selectedItem as String)/>
  8. </mx:VBox>
  9. </mx:Panel>

The problem is that we now have introduced two errors as the ArrayCollection ‘rooms’ is no longer in this mxml file then we cannot bind to it as the dataProvider for the roomList, also the event handler ‘joinRoom’ is no longer accessible. We could move these into this file, but then they couldn’t access the view stack and the problem goes on. So what we really need is to centralise our data into a central model and also centralise our even handlers into a controller.

Centralising Data into a Model

To centralise data, Tom took the Cairngorm’s modelLocator pattern, based around a singleton pattern . Where by the singleton ensures there is only ever one instance of itself, we define our Model as follows:

  1. package com.chatopica.chat.model
  2. {
  3. public class ChatModel
  4. {
  5. private static var instance:ChatModel;
  6. public function ChatModel()
  7. {
  8. if( instance != null )
  9. {
  10. throw( new Error( there can be only one instance of ChatModel ) );
  11. }
  12. }
  13. public static function getInstance():ChatModel
  14. {
  15. if( instance == null )
  16. {
  17. instance = new ChatModel();
  18. }
  19. return instance;
  20. }
  21. }
  22. }

This is a typical singleton, implemented in ActionScript 3. As Tom pointed out in the eSeminar, ActionScript does not allow for private constructors, so here he is throwing an error if we try and instantiate the class and it is already instantiated. We can now access this Model anywhere in our application by simply typing

  1. ChatModel.getInstance()

So the next thing to do is move our ArrayCollection rooms into the Model so we end up with the following:

  1. package com.chatopica.chat.model
  2. {
  3. import mx.collections.ArrayCollection;
  4. public class ChatModel
  5. {
  6. private static var instance:ChatModel;
  7. [Bindable]
  8. public var rooms:ArrayCollection = new ArrayCollection( [ new Room("Flex"), new Room("Flash"), new Room("AIR"), new Room("ColdFusion") ] );
  9. public function ChatModel()
  10. {
  11. if( instance != null )
  12. {
  13. throw( new Error( there can be only one instance of ChatModel ) );
  14. }
  15. }
  16. public static function getInstance():ChatModel
  17. {
  18. if( instance == null )
  19. {
  20. instance = new ChatModel();
  21. }
  22. return instance;
  23. }
  24. }
  25. }

Because our ArrayCollection is now in the Model that we can access from anywhere. We can change the mx:List ‘roomList’ in RoomList.mxml to use the rooms ArrayCollection in the Model as the dataProvider as follows:

  1. <mx:List id=roomList width=100% height=100% borderSides=top dataProvider={ChatModel.getInstance().rooms}/>

We have now fixed the first issue but we still have the issue of the click handler for the button being inaccessible. We can solve this by centralising our events into a Controller

Centralising Events into a Controller

As with many event driven languages, ActionScript has a handy feature known as event bubbling. When an event is fired in a container, if the event has been set to bubble (by default events don’t bubble) it will also fire on it’s parent container, and in turn fire on the grandparent container all the way up to the main Application MXML class. By bubbling events, we can register event listeners on the System Manager and listen for any global application events that are set to bubble. So we can dispatch an event anywhere in our application using the following, and we can listen for it on the System Manager:

  1. dispatchEvent(new Event(myEvent, true))

Note: We have set the second parameter (bubbles) on the Event constructor to true, which tells the event dispatcher to bubble this event.

As we are centralising our events we may need to send related information along with the event, for use by the event handler. For example, as the event handler is no longer able to directly query the roomList, to see which item has been selected (i.e. roomList.selectedItem), we need to send this information with the event. We can easily do this by creating a custom event that extends the Event class as follows:

  1. package com.chatopica.chat.events
  2. {
  3. import com.chatopica.chat.model.Room;
  4. import flash.events.Event;
  5. public class JoinRoomEvent extends Event
  6. {
  7. public static const JOIN:String = joinRoom;
  8. public var room:Room;
  9. public function JoinRoomEvent( room:Room )
  10. {
  11. super( JOIN, true );
  12. this.room = room;
  13. }
  14. }
  15. }

Above we have simply extended the event using inheritance and have added a public property called room (of type Room, Tom has created a Room class that holds the name as well as a collection of Users). We have also declared the event name/type as a constant JOIN = “joinRoom”. This allows us to reference this event anywhere. In our constructor, we call the constructor on the parent class (Event) setting the event name/type to “joinRoom” and stating that it should bubble (like this we don’t have to specify that it bubbles when we create a new JoinRoomEvent). We then set the room property from the parameter passed to the constructor.

So now we can fire a JoinRoomEvent anywhere in our application, passing in a room object. The event handler listening for this event will have access to this room object. So we can fix the last error in RoomList.mxml as follows:

  1. <mx:Button width=100% label=Chat enabled={roomList.selectedItem != null}
  2. click=dispatchEvent( new JoinRoomEvent( roomList.selectedItem as Room ) )/>

So far we have created a custom event and we have discussed where to look to find bubbling events but we have not looked at how to do this.

The Controller

As I mentioned before, we can listen to all events that have been set to bubble on the systemManager and as UIComponent (anyone who has created a custom component will know this one) has a reference to systemManager within it, we can extend this class. The other benefit of extending UIComponent for our class that will be our central event handler or Controller is that we can then use it in MXML.

We extend the UIComponent as follows:

  1. package com.chatopica.chat.controller
  2. {
  3. import com.chatopica.chat.events.JoinRoomEvent;
  4. import com.chatopica.chat.events.SendMessageEvent;
  5. import com.chatopica.chat.model.ChatModel;
  6. import flash.events.Event;
  7. import mx.core.UIComponent;
  8. import mx.events.FlexEvent;
  9. public class ChatController extends UIComponent
  10. {
  11. public function ChatController()
  12. {
  13. addEventListener( FlexEvent.CREATION_COMPLETE, setupEventListeners );
  14. }
  15. private function setupEventListeners( event:Event ):void
  16. {
  17. systemManager.addEventListener( JoinRoomEvent.JOIN, handle_joinRoom );
  18. }
  19. private function handle_joinRoom( event:JoinRoomEvent ):void
  20. {
  21. ChatModel.getInstance().currentActivity = ChatModel.VIEWING_CHAT;
  22. }
  23. }
  24. }

Note:: Above we have not directly added an event listener to the systemManager in the ChatController constructor. In fact we can’t to do this, because at the time the class is instantiated the systemManager is not initialised and is Null. We would therefore be adding an event listener to nothing. We get around this by adding an event listener for the global FlexEvent.CREATION_COMPLETE event, and at that point we can then add our event handler(s) to the systemManager.

Controlling the View Stack

The final thing we have left to achieve is the ability to change the View Stack from the controller, we first need to store the currentActivity or state in the Model as follows:

  1. public static const VIEWING_CHAT:String = viewingChat;
  2. public static const VIEWING_ROOMS:String = viewingRooms;
  3. [Bindable]
  4. public var currentActivity:String;

In addition to the currentActivity property above, we have also added a constant value for each state. Unfortunately it is not as simple as binding this property to the mx:ViewStack, but that does not mean it isn’t easy. All we have to use is the ChangeWatcher class that monitors a property and fires and event when it changes. We do this in the file MainView.mxml that holds out viewstack as follows:

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <mx:ViewStack xmlns:mx=http://www.adobe.com/2006/mxml resizeToContent=true xmlns:views=com.chatopica.chat.views.>
  3. <mx:Script>
  4. <![CDATA[
  5. import mx.events.PropertyChangeEvent;
  6. import mx.binding.utils.ChangeWatcher;
  7. import com.chatopica.chat.model.ChatModel;
  8. private var activityWatcher:ChangeWatcher = ChangeWatcher.watch( ChatModel.getInstance(), "currentActivity", handle_activityChange );
  9. private function handle_activityChange( event:PropertyChangeEvent ):void
  10. {
  11. if( event.newValue == ChatModel.VIEWING_CHAT )
  12. {
  13. selectedChild = chatPanel;
  14. }
  15. }
  16. ]]>
  17. </mx:Script>
  18. <views:RoomList/>
  19. </mx:ViewStack>

Now, when the value of currentActivity in the ChatModel changes, Flex will dispatch a PropertyChangedEvent. We can then listen for this event and change the view, accessing what the new value of currentActivity is in the newValue property of the PropertyChangeEvent (shown above as event.newValue).

Refactor the rest of your application…

Having done this for the RoomList component, we can now repeat the process for the code that implements the chat window. Finally we are simply left with the following in our mx:Application tags:

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <mx:Application xmlns:mx=http://www.adobe.com/2006/mxml layout=vertical xmlns:views=com.chatopica.chat.views. xmlns:controller=com.chatopica.chat.controller.*>
  3. <controller:ChatController/>
  4. <views:MainView/>
  5. </mx:Application>

This process now makes our code cleaner, loosely coupled and more DRY (Don’t Repeat Yourself), easy to scale and we can reuse these components without the risk of breaking our system.

Thanks again to Tom for the excellent eSeminar which has really clarified my understanding for the necessities of using an architecture like this.

Source code for this example was kindly supplied by Tom Bray, you can download them directly from his blog.

A transcript of the questions and answers session from Tom’s eSemminar, is also available on his blog

Update 23/01/08: I have added an article on scaling up EasyMVC, you can read it by clicking here.

For more information:

Comments below..

Advertisement

 




Comments

Note: You can leave a response, or trackback from your own site.

 
Avatar

Mike@opco
2/11/2007
12:15 am

Comment:

Great summary of Tom Bray’s EasyMVC presentation. I also attended the e-seminar, but missed some of the details because of my lack of experience with Flex. Your article covered all of my questions. Thanks.

 
 
Comment:

Thanks a lot for this summary! If I remember correctly, the first versions of cairngorm relied of event bubbling too, but they decided to give up this technique because you can get stuck with it in some situations. More on this here: http://weblogs.macromedia.com/auhlmann/archives/2006/07/cairngorm_2_for.cfm

 
 
Comment:

Great post. I put a post linking to this one today on my site. I am working on getting the concept of using amfphp and getting it to work in the model. Hitting some problems but it is mostly my knowledge of action script 3 that is lacking. I do like his MVC. Thanks

 
 
Pingback:

[...] Read the rest of this great post here [...]

 
 
 
 
Comment:

I just posted a sample of using easyMVC (Tom’s Version) with AMFPHP 1.9

http://themadadmin.com/wp/?p=1070

I put a link back to your writeup on easyMVC in the links section of the application.

Thanks

Dave

 
 
Comment:

Thanks Dave,

I have been making a great deal of use of this framework and have found it an excellent approach. I am interested in reading your article of using EasyMVC with AMFPHP.

Jon

 
 
Comment:

Jon,

I like this system also. Keeps the code understandable and easy to update. Let me know any and all questions. I did not get to do a proper write up on the ins and out. Biggest resource is the sample code.

I did the AMFPHP code because someone asked me how to do it and a sample was easier than explaining it. Tom had answered my questions on it already.

 
 
 
Pingback:

[...] EasyMVC is an excellent, easy to use Model View Controller architecture for Adobe Flex designed by Tom Bray from Chatopica. However as your apps grow you may find yourself outgrowing this architecture. For example as all your event handlers are centralised into one class, this class may get to large to maintain, especially as the team maintaining the app also expands.One of the best solutions I have found to handling a growing controller is to borrow the command pattern from Cairngorm which uses the Command design pattern. [...]

 
 
Comment:

You don’t have to have your controller extend UIComponent in order to be able to declare it in MXML. Personally I’d consider not extending UIComponent because your controller really isn’t a view component. I’d pass a reference in to the systemManager and have the controller simply extend EventDispatcher in order to take care of any event dispatching needs.

 
 
Comment:

Tim, Thank you for your alternative solution, just to clarify.

There is a good reason why Tom extends UIComponent, as he is well aware of the fact you raise. He mentioned it in his presentation, but it has slipped my mind. I think it is to do with being able to listen to the creationComplete event, as eventListeners need to be added to the systemManager at this point.

Also to clarify the controller does not dispatch any events it is purely there to listen for them.

I’ll double check with Tom…

 
 
Comment:

I was just throwing in the EventDispatcher point in case it did need to dispatch events. I’d be interested in seeing what the arguments are for UIComponent. I still extending UIComponent is pulling in a lot of inherited code that really isn’t needed. The controller isn’t a view component. You could always pass a reference to the main view, listen for creation complete or what I’ve done in some cases is to create a public init() function which the main view could call once the creationComplete event fires. I also think it would be fine to add event listeners to the systemManager before the creationComplete event. Unless there’s some case that Tom ran into of course where this didn’t work. Is the presentation available online? Maybe I should watch it before commenting more :)

 
 
Pingback:

[...] prototypes like “WS-ThumbsGenerator”. Fore more information about easyMVC check out the excellent tutorial of Jon Baker as [...]

 
 
Pingback:

[...] Click here to read an introduction on EasyMVC Click here to read Scaling up EasyMVC part 1. [...]

 
 
Comment:

Tim, To clarify why the controller uses UIComponent I have had the following back from Tom Bray: “I extend UIComponent so I can listen for the ADDED_TO_STAGE event which lets me know when the systemManager is no longer null…, the amount of code in UIComponent doesn’t concern me because it’s already linked in to the compiled SWF because everything, including mx:Application extends it, Cairngorm’s FrontController does the same thing”

Thanks for the clarification Tom!

Jon

 
 
Comment:

Hey Jon,

These are some great examples for setting up a lightweight framework for applications utilizing the MVC pattern. Thanks for putting them out there!

However, i think it is a mistake to say that the systemManager acts the same as the FrontContoller/CairngormEvent/CairngormEventDispatcher mechanism of the Ciarngorm framework. The FrontController is not added to the display list of an application built on the Ciarngorm framework and commands are executed based on the EventDispatcher instance of the CairngormEventDispatcher (CED) delegate.

As such, since the SystemManager manages an application window, you may run into problems using EasyMVC effectively with modular Flex/AS applications and AIR applications.

That is not to say that EasyMVC does not have its place, and i would certiainly use it in some cases :) Just a little info to think about prior to building modular or windowed apps. However, as you have said and pointed out in this great series is that it is not much work to switch over to the Cairngorm framework if you do find that the application needs it down the line.

 
 
Comment:

Hi Todd,

Thank you for your comments, and clarification of some of the issues with this approach.

Jon

 
 
Comment:

I thought this was a good way to introduce the concept of MVC, however I really disagree with the widespread use of singletons.

How do you plan to test your controller when it gets more complex? How are you going to mock the model for testing purposes?

For more information on the appropriate use of singletons please read this article http://www.softwarereality.com/design/singleton.jsp

I also agree with tim that you shouldn’t need to extend from UIComponent, i’d prefer to see a seperate MVC event dispatcher (in which case a singleton is appropriate) used to dispatch and consume these custom events.

 
 
Comment:

Hi Shanon,

Thank you for your point of view. Firstly, I understand the concerns regarding singletons. And I agree that in my example, from my articles on extending EasyMVC I have used Singletons for my services which possibly could lead to problems of extendability in the future, however I stand by the fact a singleton should be used to access these services, via a singleton servicelocator (which is the widely agreed approach to locator patterns). This locator creates instances and provides access to the relevant services rather thank accessing them directly. As for the models in the example above, (this uses a modellocator pattern), the ChatModel is a modellocator to the Data Transfer Objects / Value Objects which can actually make testing easier as the test data can be easily injected via the modellocator from within the test rig, and I have used this approach using FlexUnit.

Again I want to clarify that this article puts forward a simple framework that can be used where Cairngorm or PureMVC is overkill. This article was originally presented after Tom Brays Adobe eSeminar was not recorded and has proved to be a very popular approach. It is also designed to allow a fairly trivial upgrade to Cairngorm if needed and my articles on extending EasyMVC are a step towards that.

I also want to clarify that these extending articles are designed to help understand how frameworks like Cairngorm work. I have come accross a lot of developers that are new to Flex come across Cairngorm and implement it without fully understanding it. EasyMVC works well (I use it regularly) where Cairngorm is overkill. Tom could have created a custom eventDispatcher as Caingorm does and then registered event listeners against that but for the sake of simplicity I agree with Tom Bray comments regarding the use of UIComponent.

But like I say the use of design patterns are subjective, their use should depends on the software architects professional judgment, based on the project and project team, not opinions on the web. Although there are always arguments for and against there are no hard and fast rules.

 
 
Comment:

Hi Jon,

Yes I agree that the service locator and model locator patterns can be VERY useful.

I would like to clarify that theChatModel presented is in fact NOT a singleton. If I never call the getInstance() method I can create as many instances I like:

var chatOne:ChatModel = new ChatModel();
var chatTwo:ChatModel = new ChatModel();

And even if it were a singleton I think using static variables would haven been a lot simpler.

e.g. ChatModel.currentActivity = ChatModel.VIEWING_CHAT;

Now lets say I want to expand on the example and add the ability to join more than one room at once. You simply can’t do it with the chat model as a singelton. Yes you can tell me that you dont need to do this for the purposes of this example, but I’d tell you that anyone who has a need for MVC isn’t developing a one room chat application. And since when was a singleton recommended for the Model in MVC?

 
 
Comment:

“Tom could have created a custom eventDispatcher as Caingorm does”

All one would have to do here is extend EventDispatcher, extending UIComponent just to get event dispatching is overkill.

 
 
Comment:

Sounds like shanon is well versed in singleton theory. The method Jon used to create his singleton is very commonly used in Actionscript.

Even if a private constructor were allowed in Actionsctipt, you’d be able to instantiate two instances of a “singleton” from inside the class itself. If you didn’t use the getInstance() method (as suggested in your comment), then you wouldn’t be abiding by the singleton’s contract to begin with.

Regarding singletons in Actionscript, there has been more of a trend toward using singleton enforcers to compensate for the lack of a private constructor.

 
 
 
Comment:

As an alternative to the ChangeWatcher to control the view stack, depending on how your application views are structured, you can use binding to easily contorl the view. Bind the view container (which contains the states) currentState attribute to the model’s currentActivity: currentState=”{model.currentActivity}”

 
 
Comment:

Johan:

I’m a big noob here, but what happens if you wanted to execute something like a state change (or an effect)? Wouldn’t it be easier to watch, catch the event, and then do more than just data bind?

 
 
Comment:

Hi, Thank you for this good example. I think you have just forget a line for instantiate the instance in your ChatModel class :
If the instance is null you must instantiate like “instance = this” else it’s doesn’t work.

Sorry for my bad English ^^.

Your website is really usefull.

 
Add a comment: