Transforming ArrayCollection data using a map function to keep your view seperated in an MVC app

Last night I was working on a project which involved me having to display large amounts of data in a Flex list.  However, this data needed to be transformed before being displayed.  Let me explain with a simplified example.

The problem

Suppose we need to display a huge list of Result objects, where a Result object contains a score property (lets say it is a Number).  If we just need to display the number then we provide the ArrayCollection of Results as the dataProvider to the list and set the dataField to ‘score’ and everything is nice and easy.  Now suppose that we need to do some kind of calculation on score – for example, we need to divide it by 2 before displaying it.  Again, this is no problem; we use the labelFunction in the list and add a callback function in the MXML file that divides the score by two before returning it:

   1: private function labelFunction(item:Object):String {

   2:     var result:Result = item as Result;

   3:     return result.score / 2;

   4: }

This is all fine.

Now lets suppose that we need to transform the score in a more complicated way – imagine that we are writing some kind of multiuser application and we need to divide the score by the number of users currently logged on to the application before displaying it in the list.  At this point I should point out that I tend to use PureMVC so I will explain this using PureMVC terminology, but its equally applicable to all other MVC frameworks (Mate, Swiz, Robotlegs, etc).

Lets assume that the number of users logged on is in our Model tier; we then have a couple of options on how to go about this:

Inject the number of users into the view from our mediator and continue to use labelFunction.

       1: public var userCount:Number;

       2:

       3: private function labelFunction(item:Object):String {

       4:     var result:Result = item as Result;

       5:     return results.score / userCount;

       6: }

This is an ok solution – it doesn’t break MVC encapsulation and it works fine.  However, the drawbacks are that we need to monitor when userCount changes and each time re-inject it into the view (and tell the list to update itself).  But the main problem with this is that it gives our view knowledge of something that it shouldn’t know about.

Extend Result to do the calculation itself

We can add a getter to Result that does the calculation which means we can get rid of labelFunction in the view and just set dataField to the getter (e.g. dataField=”transformedScore”).  But this just moves the problem elsewhere; now the Result object will need to know the number of users; we’d either have to inject it in via a static variable, or give the Result object knowledge of the MVC framework.  This solution is in fact much worse than our first effort.

Pre-build the ArrayCollection with transformed scores

In this method we iterate through the Results, doing our calculation on score each time and building up a new dataprovider.  This is fine from an MVC perspective; the view gets its data fully formed in the format it requires, and since the mediator is building the list there is no problem with it querying the model tier.  However, this is a bad solution for other reasons; say that you have 5000 Result objects; the mediator will need to iterate through the entire list before passing the new collection to the view, incurring a nasty performance hit.  Furthermore we will need to rebuild this list entirely if the score or the number of logged in users change.

The solution

Here, at last, is my solution to the problem.  Like all good solutions it is disarmingly simple 🙂

   1: package org.davekeen.collections {

   2:     import mx.collections.IList;

   3:     import mx.collections.ListCollectionView;

   4:

   5:     public class MappedListCollectionView extends ListCollectionView {

   6:

   7:         public var mapFunction:Function;

   8:

   9:         public function MappedListCollectionView(list:IList = null) {

  10:             super(list);

  11:         }

  12:

  13:         override public function getItemAt(index:int, prefetch:int = 0):Object {

  14:             if (mapFunction == null) {

  15:                 return super.getItemAt(index, prefetch);

  16:             } else {

  17:                 return mapFunction(super.getItemAt(index, prefetch));

  18:             }

  19:         }

  20:

  21:     }

  22:

  23: }

MappedListCollectionView extends the normal ListCollectionView (which is the superclass of ArrayCollection) adding a single property – mapFunction – which transforms an item of the array collection into another item.  So now we can do the following in the mediator:

   1: var mappedListCollectionView:MappedListCollectionView = new MappedListCollectionView(resultsArrayCollection);

   2:

   3: mappedListCollectionView.mapFunction = function(item:Object):String {

   4:     var userCount:Number = getUserCountFromModel();

   5:

   6:     var result:Result = item as Result;

   7:     return result.score / userCount;

   8: }

   9:

  10: myView.myList.dataProvider = mappedListCollectionView;

I think this is an excellent solution – it maintains encapsulation within the view, it follows Flex standards (in that its syntax is the same as filterFunction), it will work with Flex binding and COLLECTION_CHANGE events and best of all it is high performance as mapFunction is only called for the rows that are actually on the screen.

One thing to bear in mind when using this solution is that Flex databinding will only be triggered when elements of the array collection itself change, so if userCount changes you will need to manually dispatch a COLLECTION_CHANGE event on the array collection.

Hope you find this as useful as I do!

PureMVC Tutorial – Flex, PureMVC, Jabber and XIFF 3: Conclusion, Demo & Downloads

Introduction
Part 1 – Frameworks
Part 2 – Directory structure
Part 3 – Application and ApplicationFacade
Part 4 – Notifications, Commands & Use Cases
Part 5 – Model & Proxy
Part 6 – The Application View & Mediator
Part 7 – The Login View & Mediator
Part 8 – The Roster View & Mediator
Part 9 – The Chat View & Mediator
Conclusion, Demo & Downloads

Here is a working demo of the application!

If you get a ‘Security Error’ when trying to connect, it means that the particular Jabber server you are connecting to hasn’t implemented its crossdomain.xml file correctly (many servers leave out the content-type which means that the latest versions of Flash Player will refuse to load it).  Anyway, this security policy is ignored when running from your local machine so even if you have trouble using it here it will work fine when you compile it yourself.

You can download the full source code for the project here.

If you spot any mistakes, want to have a lively discussion or just want to say how much you enjoyed this tutorial please feel free to leave comments on the appropriate page!

My thanks go out to Jive Software for XIFF, Cliff Hall for PureMVC and all Flashers everywhere 🙂

Dave

PureMVC Tutorial – Flex, PureMVC, Jabber and XIFF 3: Part 9 – The Chat View & Mediator

Introduction
Part 1 – Frameworks
Part 2 – Directory structure
Part 3 – Application and ApplicationFacade
Part 4 – Notifications, Commands & Use Cases
Part 5 – Model & Proxy
Part 6 – The Application View & Mediator
Part 7 – The Login View & Mediator
Part 8 – The Roster View & Mediator
Part 9 – The Chat View & Mediator
Conclusion, Demo & Downloads

The final mediator, and the final actor in our application. The job of the chat view is to popup a window where you receive and send message to another user. You should be able to open multiple chat windows at once, and each window is bound to a specific and unique JID.

I’m just going to present the code for this with a short explanation of anything odd rather than explicitly go through the steps. If you have trouble understanding anything go back to the previous sections on mediators and read them through again.

ChatView.mxml

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" title="" showCloseButton="true">
   3:     <mx:Script>
   4:         <![CDATA[
   5:         import mx.formatters.DateFormatter;
   6:         import org.davekeen.xiffer.events.ChatEvent;
   7:         import org.jivesoftware.xiff.core.JID;
   8:         import org.jivesoftware.xiff.data.Message;
   9:
  10:         private var jid:JID;
  11:
  12:         /**
  13:          * The send button was clicked so dispatch an event to the mediator
  14:          */
  15:         private function sendClick():void {
  16:             var inputText:String = inputTextArea.text;
  17:
  18:             if (inputText && inputText.length > 0) {
  19:                 dispatchEvent(new ChatEvent(ChatEvent.SEND_MESSAGE, jid, inputText));
  20:                 inputTextArea.text = "";
  21:             }
  22:         }
  23:
  24:         public function setJID(jid:JID):void {
  25:             this.jid = jid;
  26:
  27:             title = jid.toBareJID();
  28:         }
  29:
  30:         public function getJID():JID {
  31:             return jid;
  32:         }
  33:
  34:         /**
  35:          * Format and add a message to the chat pane
  36:          * 
  37:          * @param    message
  38:          */
  39:         public function addMessage(message:Message):void {
  40:             var dateFormatter:DateFormatter = new DateFormatter();
  41:             dateFormatter.formatString = "HH:NN";
  42:             chatTextArea.text += "[" + dateFormatter.format(new Date()) + "] " + message.body + "n";
  43:         }
  44:
  45:         ]]>
  46:     </mx:Script>
  47:     <mx:VBox>
  48:         <mx:TextArea editable="false" width="270" height="200" id="chatTextArea" />
  49:         <mx:HBox>
  50:             <mx:TextArea width="210" height="40" id="inputTextArea" />
  51:             <mx:Button id="sendButton" width="50" height="40" label="Send" click="sendClick()" />
  52:         </mx:HBox>
  53:     </mx:VBox>
  54: </mx:TitleWindow>
  55:

ChatMediator.as

There is something slightly different about this view compared to the Login or Roster views. The chat windows are dynamically created when needed and thrown away when they are closed by the user so we can’t have a permanent view component as the viewComponent of ChatMediator.as. Instead we’ll pass the top level Application.mxml component since this is what’s required in the arguments of PopupManager.addPopup.

   1: /*
   2:  Mediator - PureMVC
   3:  */
   4: package org.davekeen.xiffer.view {
   5:     import flash.display.DisplayObject;
   6:     import flash.display.DisplayObjectContainer;
   7:     import flash.events.Event;
   8:     import mx.managers.PopUpManager;
   9:     import mx.managers.PopUpManagerChildList;
  10:     import org.davekeen.xiffer.ApplicationFacade;
  11:     import org.davekeen.xiffer.events.ChatEvent;
  12:     import org.jivesoftware.xiff.core.JID;
  13:     import org.jivesoftware.xiff.data.Message;
  14:     import org.puremvc.as3.interfaces.IMediator;
  15:     import org.puremvc.as3.interfaces.INotification;
  16:     import org.puremvc.as3.patterns.mediator.Mediator;
  17:     import org.davekeen.xiffer.view.*;
  18:     import org.davekeen.xiffer.view.components.ChatView;
  19:
  20:     /**
  21:      * Chat Mediator - controls and stewards all popup chat windows
  22:      */
  23:     public class ChatMediator extends Mediator implements IMediator {
  24:
  25:         // Cannonical name of the Mediator
  26:         public static const NAME:String = "ChatMediator";
  27:
  28:         /**
  29:          * An associative array of open ChatViews
  30:          */
  31:         private var chatViews:Array;
  32:
  33:         public function ChatMediator(viewComponent:Object) {
  34:             // pass the viewComponent to the superclass where 
  35:             // it will be stored in the inherited viewComponent property
  36:             super(NAME, viewComponent);
  37:
  38:             chatViews = new Array();
  39:         }
  40:
  41:         /**
  42:          * Get the Mediator name.
  43:          * <P>
  44:          * Called by the framework to get the name of this
  45:          * mediator. If there is only one instance, we may
  46:          * define it in a constant and return it here. If
  47:          * there are multiple instances, this method must
  48:          * return the unique name of this instance.</P>
  49:          * 
  50:          * @return String the Mediator name
  51:          */
  52:         override public function getMediatorName():String {
  53:             return ChatMediator.NAME;
  54:         }
  55:
  56:         /**
  57:          * List all notifications this Mediator is interested in.
  58:          * <P>
  59:          * Automatically called by the framework when the mediator
  60:          * is registered with the view.</P>
  61:          * 
  62:          * @return Array the list of Nofitication names
  63:          */
  64:         override public function listNotificationInterests():Array {
  65:             return [
  66:                     ApplicationFacade.OPEN_CHAT_WINDOW,
  67:                     ApplicationFacade.RECEIVE_MESSAGE,
  68:                     ApplicationFacade.VALID_LOGIN,
  69:                     ApplicationFacade.DISCONNECT
  70:                     ];
  71:         }
  72:
  73:         /**
  74:          * Handle all notifications this Mediator is interested in.
  75:          * <P>
  76:          * Called by the framework when a notification is sent that
  77:          * this mediator expressed an interest in when registered
  78:          * (see <code>listNotificationInterests</code>.</P>
  79:          * 
  80:          * @param INotification a notification 
  81:          */
  82:         override public function handleNotification(note:INotification):void {
  83:             switch (note.getName()) {
  84:                 case ApplicationFacade.OPEN_CHAT_WINDOW:
  85:                     var jid:JID = note.getBody() as JID;
  86:                     showChatWindow(jid);
  87:                     break;
  88:                 case ApplicationFacade.RECEIVE_MESSAGE:
  89:                     var message:Message = note.getBody() as Message;
  90:
  91:                     // Add the message to the view
  92:                     chatViews[message.from.toBareJID()].addMessage(message);
  93:                     break;
  94:                 case ApplicationFacade.VALID_LOGIN:
  95:                     // Enable all chat windows
  96:                     for each (var chatView:ChatView in chatViews)
  97:                         chatView.enabled = true;
  98:
  99:                     break;
 100:                 case ApplicationFacade.DISCONNECT:
 101:                     // Disable all chat windows
 102:                     for each (chatView in chatViews)
 103:                         chatView.enabled = false;
 104:
 105:                     break;
 106:                 default:
 107:                     break;
 108:             }
 109:         }
 110:
 111:         /**
 112:          * Open up a chat window for this particular JID
 113:          * 
 114:          * @param    jid
 115:          */
 116:         private function showChatWindow(jid:JID):void {
 117:             // If the window exists already don't do anything
 118:             if (!chatViews[jid.toBareJID()]) {
 119:                 var chatView:ChatView = new ChatView();
 120:
 121:                 PopUpManager.addPopUp(chatView, viewComponent as DisplayObjectContainer, false);
 122:                 PopUpManager.bringToFront(chatView);
 123:                 PopUpManager.centerPopUp(chatView);
 124:
 125:                 chatView.addEventListener(Event.CLOSE, onChatViewClose);
 126:                 chatView.addEventListener(ChatEvent.SEND_MESSAGE, onSendMessage);
 127:                 chatView.setJID(jid);
 128:
 129:                 // Add the chat view to the associative array
 130:                 chatViews[jid.toBareJID()] = chatView;
 131:             }
 132:         }
 133:
 134:         /**
 135:          * The user has typed a message and sent it
 136:          * 
 137:          * @param    chatEvent
 138:          */
 139:         private function onSendMessage(chatEvent:ChatEvent):void {
 140:             var chatView:ChatView = chatEvent.currentTarget as ChatView;
 141:
 142:             // Construct a XIFF message
 143:             var message:Message = new Message(chatEvent.getJID(), null, chatEvent.getMessage(), null, Message.CHAT_TYPE);
 144:
 145:             // Echo it to our own view
 146:             chatViews[chatView.getJID().toBareJID()].addMessage(message);
 147:
 148:             // And send off a notification
 149:             sendNotification(ApplicationFacade.SEND_MESSAGE, message);
 150:         }
 151:
 152:         /**
 153:          * The chat window has been closed
 154:          * 
 155:          * @param    event
 156:          */
 157:         private function onChatViewClose(event:Event):void {
 158:             var chatView:ChatView = event.currentTarget as ChatView;
 159:             chatView.removeEventListener(Event.CLOSE, onChatViewClose);
 160:             PopUpManager.removePopUp(chatView);
 161:
 162:             // Delete the chat view from the associative array
 163:             delete chatViews[chatView.getJID().toBareJID()];
 164:         }
 165:
 166:     }
 167: }

SendMessageCommand.as

   1: /*
   2: Simple Command - PureMVC
   3:  */
   4: package org.davekeen.xiffer.controller {
   5:     import org.davekeen.xiffer.events.ChatEvent;
   6:     import org.davekeen.xiffer.model.XMPPProxy;
   7:     import org.jivesoftware.xiff.data.Message;
   8:     import org.puremvc.as3.interfaces.INotification;
   9:     import org.puremvc.as3.patterns.command.SimpleCommand;
  10:     import org.puremvc.as3.patterns.observer.Notification;
  11:
  12:     /**
  13:      * Send a message to the proxy
  14:      */
  15:     public class SendMessageCommand extends SimpleCommand {
  16:
  17:         override public function execute(note:INotification):void {
  18:             var message:Message = note.getBody() as Message;
  19:             var xmppProxy:XMPPProxy = facade.retrieveProxy(XMPPProxy.NAME) as XMPPProxy;
  20:
  21:             // Send the message
  22:             xmppProxy.sendMessage(message);
  23:         }
  24:
  25:     }
  26: }

And there it is! A working Jabber, Flex and PureMVC application of your very own.

Let’s wrap it all up with a conclusion, a working demo and the full source code.

PureMVC Tutorial – Flex, PureMVC, Jabber and XIFF 3: Part 8 – The Roster View & Mediator

Introduction
Part 1 – Frameworks
Part 2 – Directory structure
Part 3 – Application and ApplicationFacade
Part 4 – Notifications, Commands & Use Cases
Part 5 – Model & Proxy
Part 6 – The Application View & Mediator
Part 7 – The Login View & Mediator
Part 8 – The Roster View & Mediator
Part 9 – The Chat View & Mediator
Conclusion, Demo & Downloads

By now you should be becoming a bit of an old hand at this PureMVC lark, so I’m going to be explaining stuff in a bit less detail.  If you get stuck re-read the previous sections until you have everything clear in your head.

The Roster is very simple.  We want a Flex DataGrid displaying usernames and login status and we need a ‘Start chat…’ button that opens a chat window.

In order to implement this view we’ll need a new event called ChatEvent.as.  The JID class is part of the XIFF framework and is used to identify a Jabber user.  The RosterView will only use START_CHAT, but we’ll need SEND_MESSAGE in the next and final view.

   1: package org.davekeen.xiffer.events {
   2:     import flash.events.Event;
   3:     import org.jivesoftware.xiff.core.JID;
   4:     
   5:     /**
   6:     * This event is related to the ChatViews
   7:     * 
   8:     * @author Dave Keen
   9:     */
  10:     public class ChatEvent extends Event {
  11:         
  12:         public static const START_CHAT:String = "start_chat";
  13:         public static const SEND_MESSAGE:String = "send_message";
  14:         
  15:         private var jid:JID;
  16:         private var message:String;
  17:         
  18:         public function ChatEvent(type:String, jid:JID, message:String = null, bubbles:Boolean=false, cancelable:Boolean=false) { 
  19:             super(type, bubbles, cancelable);
  20:             
  21:             this.jid = jid;
  22:             this.message = message;
  23:         } 
  24:         
  25:         public function getJID():JID {
  26:             return jid;
  27:         }
  28:         
  29:         public function getMessage():String {
  30:             return message;
  31:         }
  32:         
  33:         public override function clone():Event { 
  34:             return new ChatEvent(type, jid, message, bubbles, cancelable);
  35:         } 
  36:         
  37:     }
  38:     
  39: }

We’ll also need to define one new notification in ApplicationFacade.as called OPEN_CHAT_WINDOW:

   1: public static const OPEN_CHAT_WINDOW:String = "open_chat_window";

Next we create our RosterView.mxml file in components:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*" width="100%" height="100%">
   3:     <mx:Script>
   4:         <![CDATA[
   5:         import flash.events.Event;
   6:         import mx.events.ListEvent;
   7:         import org.davekeen.xiffer.events.ChatEvent;
   8:         import org.jivesoftware.xiff.core.JID;
   9:         
  10:         private function onChatClick():void {
  11:             if (rosterGrid.selectedItem) {
  12:                 // Get the selected item and convert it to a JID
  13:                 var jid:JID = new JID(rosterGrid.selectedItem.jid);
  14:                 
  15:                 // Dispatch an event for the mediator
  16:                 dispatchEvent(new ChatEvent(ChatEvent.START_CHAT, jid));
  17:             }
  18:         }
  19:         
  20:         ]]>
  21:     </mx:Script>
  22:     <mx:TitleWindow id="titleWindow" title="Buddy list" enabled="false">
  23:         <mx:DataGrid id="rosterGrid" editable="false" width="160" height="300" showHeaders="true">
  24:             <mx:columns>
  25:                 <mx:DataGridColumn dataField="displayName" headerText="Username" />
  26:                 <mx:DataGridColumn dataField="online" width="55" headerText="Online?" />
  27:             </mx:columns>
  28:         </mx:DataGrid>
  29:         <mx:Button width="160" label="Chat..." click="onChatClick()" />
  30:     </mx:TitleWindow>
  31: </mx:Canvas>

Add this component to the display list of our top-level Application.mxml component so that the <mx:Canvas> component now reads:

   1: <mx:Canvas left="0" top="0" right="0" bottom="0">
   2:     <view:LoginView id="loginView" />
   3:     <view:RosterView y="30" id="rosterView" />
   4: </mx:Canvas>

Now we need to create RosterMediator.as in the view folder, and register it with PureMVC.  Using the rule we applied in the previous section we can see that we need to do this registration in ApplicationMediator.as:

   1: public function ApplicationMediator(viewComponent:Object) {
   2:     // pass the viewComponent to the superclass where 
   3:     // it will be stored in the inherited viewComponent property
   4:     super(NAME, viewComponent);
   5:     
   6:     facade.registerMediator(new LoginMediator(application.loginView));
   7:     facade.registerMediator(new RosterMediator(application.rosterView));
   8: }

All that is left now is to configure our mediator using the same steps as in the previous section.

1. Add listeners

RosterView.mxml dispatches only ChatEvent.START_CHAT so let’s add a listener in the constructor:

   1: public function RosterMediator(viewComponent:Object) {
   2:     // pass the viewComponent to the superclass where 
   3:     // it will be stored in the inherited viewComponent property
   4:     super(NAME, viewComponent);
   5:     
   6:     rosterView.addEventListener(ChatEvent.START_CHAT, onStartChat);
   7: }
   8:  
   9: private function onStartChat(chatEvent:ChatEvent):void { }

2. Add notifications

The Roster window need to enable and disable depending on whether or not the user is connected to a jabber server, and also needs to fill in the DataGrid with the list of buddies.  To achieve these goals the mediator will need to register an interest in ApplicationFacade.VALID_LOGIN and ApplicationFacade.DISCONNECT.

   1: override public function listNotificationInterests():Array {
   2:     return [
   3:             ApplicationFacade.VALID_LOGIN,
   4:             ApplicationFacade.DISCONNECT
   5:             ];
   6: }

And here are the clauses for the switch statement:

   1: override public function handleNotification(note:INotification):void {
   2:     switch (note.getName()) {
   3:         case ApplicationFacade.VALID_LOGIN:
   4:              break;
   5:         case ApplicationFacade.DISCONNECT:
   6:             break;
   7:         default:
   8:             break;        
   9:     }
  10: }

3. Fill in the event listeners and switch clauses

Firstly we’ll fill in the onStartChat event listener.  This will very simply send an ApplicationFacade.OPEN_CHAT_WINDOW with the JID as the argument.  Although we could have passed the ChatEvent itself as a parameter in this case there is no need to use a value object as to open a chat window we only require a single item of information – the JID.

   1: private function onStartChat(chatEvent:ChatEvent):void {
   2:     sendNotification(ApplicationFacade.OPEN_CHAT_WINDOW, chatEvent.getJID());
   3: }

You’ll notice that the ApplicationFacade.OPEN_CHAT_WINDOW notification is not mapped to a command – this is because the only responder to the notification will be the ChatMediator that we are about to create in the next section so there is no need to have a command.  However, one of the beautiful things about PureMVC is that if it turned out later in the development that this notification needed to affect the model in some way we could implement this merely by adding a new command and registering it, and there will be no need to make any changes to the rest of the application.

Compile and run the application and enjoy the working buddy list!  Only one view left to go…

PureMVC Tutorial – Flex, PureMVC, Jabber and XIFF 3: Part 5 – Model & Proxy

Introduction
Part 1 – Frameworks
Part 2 – Directory structure
Part 3 – Application and ApplicationFacade
Part 4 – Notifications, Commands & Use Cases
Part 5 – Model & Proxy
Part 6 – The Application View & Mediator
Part 7 – The Login View & Mediator
Part 8 – The Roster View & Mediator
Part 9 – The Chat View & Mediator
Conclusion, Demo & Downloads

The model is the ‘data’ of our application and the proxy is our interface onto that data.  For our particular application the model itself is basically our Jabber connection object, so the Proxy will encapsulate that and expose an interface with the following methods:

  • connect(username:String, password:String, server:String):void
  • disconnect():void
  • sendMessage(message:Message):void
  • getRosterDataProvider():ArrayCollection

As you can see, the interface is very similar to our use case and commands, and this in no coincidence; each command is going to call the relevant method in its execute method.

Create a Proxy in the model folder called XMPPProxy.as using Add->New Proxy… (if you don’t see this menu item be sure you’ve installed the PureMVC FlashDevelop templates from PureMVC: First thoughts & FlashDevelop templates correctly).

I’m just going to include the Proxy code here without much explanation as it doesn’t really do anything overly complicated (all of the hard stuff has been done for us in the XIFF library 🙂 )  Have a read through the comments of each method and everything should be clear.

   1: /*
   2: Proxy - PureMVC
   3: */
   4: package org.davekeen.xiffer.model {
   5:     import flash.events.Event;
   6:     import flash.system.Security;
   7:     import org.davekeen.xiffer.ApplicationFacade;
   8:     import org.jivesoftware.xiff.core.JID;
   9:     import org.jivesoftware.xiff.core.XMPPSocketConnection;
  10:     import org.jivesoftware.xiff.data.Message;
  11:     import org.jivesoftware.xiff.data.Presence;
  12:     import org.jivesoftware.xiff.events.*
  13:     import org.jivesoftware.xiff.im.Roster;
  14:     import org.puremvc.as3.interfaces.IProxy;
  15:     import org.puremvc.as3.patterns.proxy.Proxy;
  16:     import mx.collections.ArrayCollection;
  17:  
  18:     /**
  19:      * Proxy to XMPP server
  20:      */
  21:     public class XMPPProxy extends Proxy implements IProxy {
  22:         
  23:         public static const NAME:String = "XMPPProxy";
  24:         
  25:         private var xmppSocketConnection:XMPPSocketConnection;
  26:         private var roster:Roster;
  27:  
  28:         public function XMPPProxy(data:Object = null) {
  29:             super(NAME, data);
  30:             
  31:             setupConnection();
  32:             configureListeners();
  33:         }
  34:         
  35:         /**
  36:          * Create the required XMPP objects and do any configuration on them that we might require
  37:          */
  38:         private function setupConnection():void {
  39:             xmppSocketConnection = new XMPPSocketConnection();
  40:             
  41:             roster = new Roster();
  42:             roster.connection = xmppSocketConnection;
  43:         }
  44:         
  45:         private function configureListeners():void {
  46:             // Add event listeners related to the connection
  47:             xmppSocketConnection.addEventListener(LoginEvent.LOGIN, onLogin);
  48:             xmppSocketConnection.addEventListener(XIFFErrorEvent.XIFF_ERROR, onXiffError);
  49:             xmppSocketConnection.addEventListener(DisconnectionEvent.DISCONNECT, onDisconnect);
  50:             
  51:             // Add event listeners related to messages
  52:             xmppSocketConnection.addEventListener(MessageEvent.MESSAGE, onMessage);
  53:             
  54:         }
  55:         
  56:         /**
  57:          * Attempt to connect to a XMPP server
  58:          * 
  59:          * @param    username
  60:          * @param    password
  61:          * @param    server
  62:          */
  63:         public function connect(username:String, password:String, server:String):void {
  64:             // Attempt to load a crossdomain permissions file
  65:             Security.loadPolicyFile(server + "/crossdomain.xml");
  66:             
  67:             // Connect using standard profile
  68:             xmppSocketConnection.username = username;
  69:             xmppSocketConnection.password = password;
  70:             xmppSocketConnection.server = server;
  71:             xmppSocketConnection.connect("standard");
  72:         }
  73:         
  74:         /**
  75:          * Disconnect from a XMPP server.  If not currently connected this will have no effect.
  76:          * 
  77:          */
  78:         public function disconnect():void {
  79:             xmppSocketConnection.disconnect();
  80:         }
  81:         
  82:         /**
  83:          * Return the roster as a data provider
  84:          * 
  85:          * @return
  86:          */
  87:         public function getRosterDataProvider():ArrayCollection {
  88:             return roster;
  89:         }
  90:         
  91:         /**
  92:          * Send a message to the server
  93:          * 
  94:          * @param    message
  95:          */
  96:         public function sendMessage(message:Message):void {
  97:             xmppSocketConnection.send(message);
  98:         }
  99:         
 100:         /**
 101:          * The user has successfully logged on to the XMPP server
 102:          * 
 103:          * @param    connectionSuccessEvent
 104:          */
 105:         private function onLogin(loginEvent:LoginEvent):void {
 106:             roster.setPresence(Presence.SHOW_CHAT, "", 0);
 107:             
 108:             sendNotification(ApplicationFacade.VALID_LOGIN);
 109:         }
 110:         
 111:         /**
 112:          * There has been a Jabber error - most likely an incorrect username/password error
 113:          * 
 114:          * @param    xiffErrorEvent
 115:          */
 116:         private function onXiffError(xiffErrorEvent:XIFFErrorEvent):void {
 117:             if (xiffErrorEvent.errorCode == 400)
 118:                 sendNotification(ApplicationFacade.INVALID_LOGIN);
 119:             
 120:         }
 121:         
 122:         /**
 123:          * The user has disconnected from the XMPP server
 124:          * 
 125:          * @param    disconnectionEvent
 126:          */
 127:         private function onDisconnect(disconnectionEvent:DisconnectionEvent):void {
 128:             sendNotification(ApplicationFacade.DISCONNECT);
 129:         }
 130:         
 131:         /**
 132:          * Received a message from the server
 133:          * 
 134:          * @param    messageEvent
 135:          */
 136:         private function onMessage(messageEvent:MessageEvent):void {
 137:             sendNotification(ApplicationFacade.RECEIVE_MESSAGE, messageEvent.data);
 138:         }
 139:         
 140:     }
 141: }

One thing you will notice is that the proxy dispatches another few notifications that we haven’t included in our ApplicationFacade:

  • VALID_LOGIN
  • INVALID_LOGIN
  • RECEIVE_MESSAGE
  • DISCONNECT

So lets go back to our ApplicationFacade and add them in:

   1: public static const VALID_LOGIN:String = "valid_login";
   2: public static const INVALID_LOGIN:String = "invalid_login";
   3: public static const DISCONNECT:String = "disconnect";
   4: public static const RECEIVE_MESSAGE:String = "receive_message";

The final thing we need to do is register our new Proxy with PureMVC.  We do this in StartupCommand.as using the registerProxy method:

   1: override public function execute(notification:INotification):void {
   2:     facade.registerProxy(new XMPPProxy());
   3: }

Its quite easy to forget to do this and end up with all kinds of strange errors, so be sure to remember to register any proxies you create.

We’ve finally got the bones of our application up and running so now its on to the views and mediators!

PureMVC Tutorial – Flex, PureMVC, Jabber and XIFF 3: Part 4 – Notifications, Commands & Use Cases

Introduction
Part 1 – Frameworks
Part 2 – Directory structure
Part 3 – Application and ApplicationFacade
Part 4 – Notifications, Commands & Use Cases
Part 5 – Model & Proxy
Part 6 – The Application View & Mediator
Part 7 – The Login View & Mediator
Part 8 – The Roster View & Mediator
Part 9 – The Chat View & Mediator
Conclusion, Demo & Downloads

Notifications are the nuts and bolts of a PureMVC application and are used to communicate between the various actors of your application. Notifications can be used for the following paths of communication:

  • Mediator -> Mediator
  • Mediator -> Command
  • Command -> Command
  • Command -> Mediator
  • Proxy -> Mediator
  • Proxy -> Command

In other words, Commands and Mediators listen out for notifications, and everything can send them. The ‘pattern’ reason for Proxies not being able to directly listen for notifications is to try and encourage the developer to use a command to do this. In other words, if a mediator wanted to effect a change on a proxy it might send a DO_SOMETHING notification, which would invoke DoSomethingCommand which would then retrieve the SomethingProxy and call doSomething() on it. This is less complicated than it sounds and there will be examples of this in the code we’ll be writing.

PureMVC also makes it possible for a mediator to directly retrieve a proxy and call methods on it that change its state, although this is often frowned upon by MVC purists. Having said that, there are some situations when it makes more sense to do this in simple cases rather than run through the Mediator->Command->Proxy chain (probably creating an extra command along the way). Always remember that MVC guidelines are just that – guidelines 🙂

Use case diagram

Use cases

Here’s the use case diagram for our user. Its useful to map these out at the start of the project as there is often a 1-1 mapping between use cases and commands. So now lets create these commands and map them to some notifications (we’ll leave them empty for the moment, but fill them in later).

To add an empty command right-click on the controller folder in the project area and select Add->New SimpleCommand… (if you don’t see this menu item be sure you’ve installed the PureMVC FlashDevelop templates from PureMVC: First thoughts & FlashDevelop templates correctly). Add these three commands:

  • LoginCommand.as
  • LogoutCommand.as
  • SendMessageCommand.as

Now we need to setup our notification. Notification names are just strings, so we need to setup static constants for each one. For larger projects with a lot of notifications its usual to create extra classes to hold the constants, but since we are only going to have a few of them we’ll put them in ApplicationFacade.as.

   1: public static const LOGIN:String = "login";
   2: public static const LOGOUT:String = "logout";
   3: public static const SEND_MESSAGE:String = "send_message";

Now we need to map these notifications to their associated commands. This means that whenever one of these notifications is sent PureMVC will automatically invoke its mapped command. To do this we call the registerCommand method in the initializeController method of ApplicationFacade (note that the STARTUP->StartupCommand notification was already setup in the FlashDevelop ApplicationFacade template).

   1: // Register commands with the controller
   2: override protected function initializeController():void {
   3:     super.initializeController();
   4:
   5:     registerCommand(STARTUP, StartupCommand);
   6:
   7:     registerCommand(LOGIN, LoginCommand);
   8:     registerCommand(LOGOUT, LogoutCommand);
   9:
  10:     registerCommand(SEND_MESSAGE, SendMessageCommand);
  11: }

Now let’s create our XMPP proxy…

PureMVC Tutorial – Flex, PureMVC, Jabber and XIFF 3: Part 3 – Application and ApplicationFacade

Introduction
Part 1 – Frameworks
Part 2 – Directory structure
Part 3 – Application and ApplicationFacade
Part 4 – Notifications, Commands & Use Cases
Part 5 – Model & Proxy
Part 6 – The Application View & Mediator
Part 7 – The Login View & Mediator
Part 8 – The Roster View & Mediator
Part 9 – The Chat View & Mediator
Conclusion, Demo & Downloads

Every application needs an entry point. PureMVC projects can in fact be thought of as having two entry points – one is the AVM entry point; the first class that is actually executed (this is the ‘real’ entry point). The other is the ApplicationFacade which kicks off the PureMVC framework and this is the class that should be considered the centre of our application.

FlashDevelop will probably have automatically created an entry point called Main.mxml – go ahead and delete this at this point as we will be replacing it with our own file.

Our ‘Flex’ entry point will be called Application.mxml (my PureMVC project entry points are always named Application.mxml or Application.as – I’m not sure if this is an official PureMVC naming convention but its always a good idea to decide on a naming scheme and them stick to it). Navigate to the org.davekeen.xiffer directory in the project area of FlashDevelop, right click and select Add->New MXML File… and in the prompt box name the new file Application.mxml and click OK.

Here’s the code for our application entry point:

   1: <?xml version="1.0" encoding="utf-8"?>

   2: <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"

   3:     xmlns:view="org.davekeen.xiffer.view.components.*"

   4:     layout="absolute"

   5:     width="760" height="440"

   6:     verticalGap="0"

   7:     horizontalGap="0"

   8:     creationComplete="facade.sendNotification(ApplicationFacade.STARTUP, this);">

   9:     <mx:Script>

  10:         <![CDATA[

  11:         import org.davekeen.xiffer.ApplicationFacade;

  12:

  13:         private var facade:ApplicationFacade = ApplicationFacade.getInstance();

  14:         ]]>

  15:     </mx:Script>

  16:

  17: </mx:Application>

This MXML file has no function at all apart from sending a STARTUP notification, which will kick off our PureMVC application. Later we’ll add the top-level view components to this, but for now we’re just interested in getting the basic framework up and running. In order to let FlashDevelop know that this is the entry point right-click the file in the project area and select Always Compile from the popup menu.

Now we need the ApplicationFacade. Right click on the xiffer folder in the project area and select Add->New ApplicationFacade… and name it ApplicationFacade.as. If you can’t see New ApplicationFacade… in the Add menu be sure you have installed the PureMVC FlashDevelop templates from PureMVC: First thoughts & FlashDevelop templates correctly.

Finally we need a StartupCommand. Right click on the controller folder and select Add->New SimpleCommand… and name it StartupCommand.as. Let go for a classic – in the execute method add:

trace(“Hello world!”);

Your StartupCommand.as should now look like this:

   1: /*

   2: Simple Command - PureMVC

   3:  */

   4: package org.davekeen.xiffer.controller {

   5:     import org.puremvc.as3.interfaces.INotification;

   6:     import org.puremvc.as3.patterns.command.SimpleCommand;

   7:     import org.puremvc.as3.patterns.observer.Notification;

   8:

   9:     /**

  10:      * SimpleCommand

  11:      */

  12:     public class StartupCommand extends SimpleCommand {

  13:

  14:         override public function execute(note:INotification):void {

  15:             trace("Hello world!");

  16:         }

  17:

  18:     }

  19: }

And there we have a working Flex/PureMVC HelloWorld application! Run it and feel proud 🙂

Now lets add some notifications…

PureMVC Tutorial – Flex, PureMVC, Jabber and XIFF 3: Part 2 – Directory structure

Introduction
Part 1 – Frameworks
Part 2 – Directory structure
Part 3 – Application and ApplicationFacade
Part 4 – Notifications, Commands & Use Cases
Part 5 – Model & Proxy
Part 6 – The Application View & Mediator
Part 7 – The Login View & Mediator
Part 8 – The Roster View & Mediator
Part 9 – The Chat View & Mediator
Conclusion, Demo & Downloads

We need to create a PureMVC-friendly folder structure for our application – right click on src and create org, then within that create davekeen, then within that create xiffer.  This will be the base package for our Jabber client.  Now we need folders for the various bits of our application.  Within xiffer create three folders named controller, view, model and events.  Finally within view create a folder called components.  We should have ended up with a folder structure that looks like this:

org
davekeen
—–xiffer
——-controller
——-events
——-model
——-view
———components

Now is a good time to explain what each folder’s contents and functions are.  If you haven’t already done so you should have a read through the PureMVC best practices and framework overview as it will make things a lot clearer.

org.davekeen.xiffer

This is the base package of our application and will contain Application.mxml (the Flex entry point) and ApplicationFacade.as (the PureMVC entry point).  The rest of the application will be contained in the other folders.

org.davekeen.xiffer.controller

The controller package contains commands which implement individual pieces of business logic.  For our Jabber client this will be things like login, logout and send message.

org.davekeen.xiffer.model

The model package contains proxies which are the bits of code that do all the dirty work.  All communication between our application and the Jabber server will happen within the proxy; in fact this is the only bit of the application that will even know that a Jabber server exists.

org.davekeen.xiffer.view.components

The components packages contains our actual MXML files – the things we’ll see on the screen.  Cliff Hall has put a lot of thought into the design of PureMVC and these components know nothing about the framework or internals of our app.  When things happen they dispatch events, and they might expose public methods which the framework can invoke.

org.davekeen.xiffer.view

The view package contains mediators which control our components.  In some MVC frameworks this folder might contain the actual objects that are displayed (i.e. subclassing DisplayObject), but PureMVC adds an extra level of abstraction.  The mediators know about PureMVC, but the components are completely self-contained and rely on their associated mediators to do any application-wide communication.

org.davekeen.xiffer.events

The majority of communication within PureMVC is done using notifications, but the only exception to this is communication from components to their mediators which is done using normal AS3 events.  Its also perfectly acceptable to put this package within the view folder.

Onto part 3…

PureMVC Tutorial – Flex, PureMVC, Jabber and XIFF 3: Introduction

Introduction
Part 1 – Frameworks
Part 2 – Directory structure
Part 3 – Application and ApplicationFacade
Part 4 – Notifications, Commands & Use Cases
Part 5 – Model & Proxy
Part 6 – The Application View & Mediator
Part 7 – The Login View & Mediator
Part 8 – The Roster View & Mediator
Part 9 – The Chat View & Mediator
Conclusion, Demo & Downloads

I like Actionscript 3, I like Flex, I like Flash and I especially like PureMVC. In fact, I like them so much that its about time I wrote a fully fledged tutorial to help the rest of you like them as much as I do. The code we are going to recreate here started life as a technical test for a job I was interested in. Unfortunately it ended up not being for me, but the code itself lends itself excellently to demonstrating how to use PureMVC as well as creating a decent application along the way. As you have probably guessed from the title we’ll be creating a simple but fully functional Jabber chat client. Its not exactly going to be feature rich, but by the end of this tutorial your application will allow you to login, logout, show your buddy list and have one-to-one chats with your friends. Chatrooms, invites and all the other bits of Jabber goodness are left as an exercise for the reader 🙂

Before we start I should also humbly point out that there are many ways to program using PureMVC – what I’ve presented in this tutorial is the way that I like to do it, and the way that makes the most sense to me. However there are many equally valid ways to develop within a framework and you shouldn’t take my word as gospel, but work in whichever way you find suits you best.

Note that I’ve programmed the client using Flex merely because its quicker to create popup windows, form elements, etc in Flex than Flash. However, the bones of the app are valid in both so even if you don’t use Flex often you should still be able to get something out of this.

Before we get going, you’ll need the following:

Now we have the tools, its time to setup our project. We’re going to create an empty FlashDevelop project ready to receive our code. Open FlashDevelop and select Project->New Project. Choose Flex 3 project from the list, name the project ‘XIFFer’, check ‘Create directory for project’ and finally click OK to create the new project.

Finally we need to set the dimensions of our Flex application

  1. Right click on ‘XIFFer’ and select Properties…
  2. Set the dimensions to 760 x 400 px
  3. Click OK

We’ve got everything we need to get going! Continue onto part 1…

PureMVC: First thoughts & FlashDevelop templates

For those who aren’t interested in this blog and just want to get the FlashDevelop templates for PureMVC 2.0.3 you can download them here!

I’ve been meaning to have a good play with PureMVC for a while, and finally found some time a few days ago to check it out. For those who don’t know, PureMVC is one of a few Actionscript Model-View-Controller (MVC) frameworks for Actionscript. Two big established frameworks are ARP and Cairngorm (for Flex), and there are a bunch of other less well known frameworks, lots of which are available from OSFlash.

Personally I’m looking to get a few specific things out of my MVC framework:

  • Encapsulated concerns, high cohesion and loose coupling
  • Minimum configuration & ease of coding
  • Ease of maintenance

I’ll talk about each of these things separately. At this point I should point out that I have only been working with PureMVC for a little more than a day (that’s why it says first thoughts in the title), and some of what I’m saying here might be wrong! Any discussion on these points is gratefully received.

Encapsulated concerns, high cohesion and loose coupling

PureMVC takes a slightly different approach to the MVC design pattern than the official (if there is such a thing) stance, adding a bunch of extra design patterns into the mix. The View, Model and Controller are Singletons with access provided through a central Facade which is effectively the entrypoint and hub of your application. Models are then implemented as Proxies, controllers as Commands and views as Mediators. Communication between object is mostly done using PureMVC’s own Notification system rather than AS3 Events – this seems an odd design decision, but now that PureMVC is being ported to a whole host of different languages which do not support AS3 Events (Ruby, Python, HaXe, etc) this makes sense. I haven’t yet delved into the code to see if AS3 events are being used under the hood but if they are not, and if this performance boost was required, I imagine it would be fairly easy to implement. Note that AS3 events are still used on the visual side of things (i.e. between the Views and their Mediators).

Implementing the model through a proxy makes excellent sense, and makes the implementation of the model transparent to the rest of the application. All the app needs to care about is whether the call is synchronous or asynchronous and it should be possible to swap models around to your heart’s content. In fact, I’m sure it would be possible to ammend the Proxy such that all calls are treated as asynchronous giving complete transparency to the rest of the app.

Implementing controllers through commands is tried and tested, and pretty common. Its always worked fine for me. One nice touch that PureMVC adds is to allow direct mapping of Notifications->Commands with code such as:

   1: registerCommand(STARTUP, StartupCommand);

Nice and easy 🙂

The View/Mediator decision is a little more controversial, and I’ve read more than a few bloggers who find this a serious flaw in PureMVC. I can certainly see the motivation for it and in some ways its a very elegant solution, but I’ll need to try writing a medium-large size app before I can really comment. If the coder keeps it together and organises his components, mediators and composition carefully it could be that this works well. More on this in a later blog.

Minimum configuration & ease of coding

PureMVC involves a lot of typing. Creating a Mediator from scratch is a whole lot of hassle. Luckily for you FlashDevelop users I’ve updated some templates to work with PureMVC 2.0.3 (apologies to whoever wrote these in the first place – can’t find the URL, but will link as soon as I do!). Download them here and unzip to:

C:Documents and Settings<user>Local SettingsApplication DataFlashDevelopTemplatesProjectFilesAS3Project

However, even with templates taking away a lot of the setup work, there is still a lot of typing to do. Need to get a reference to a Proxy in your Command?

   1: var userProxy:UserProxy = facade.retrieveProxy(UserProxy.NAME) as UserProxy;

Ok, maybe I’m being pedantic, but when I’m writing a large RIA application I can imagine this getting very annoying. And my initial thoughts are that this seems to be a general theme throughout PureMVC; there is often a lot of work to do in order to get a simple task done. However, my feeling is that there will be a bunch of common tasks that will be able to be automated either through utility classes or through your IDE. And if the result of this extra typing is an elegantly structured and maintainable app then I guess its all worthwhile in the end 🙂

Ease of maintenance

GIGO. PureMVC goes a long way to forcing the programmer to ‘do the right thing’, but I reckon you can still end up with a mess of spaghetti if you are not careful. At this point the potential stumbling blocks seem to me to include:

  • Badly composed view and mediators.
  • Unnecessary numbers of Proxies, Commands and Mediators.
  • De-centralised logic (i.e. business logic placed in the view, which is actually possible with PureMVC)

I can easily envisage a situation where a bug arises and the developer is hard pushed to work out if it is located in the Mediator, Command or Proxy. However, I could be wrong about this and am very much looking forward to finding out myself.

Conclusion

Well, there isn’t one yet. I’ve a couple of weeks contractual work beginning next week where with any luck the client will be happy to have PureMVC let loose on their project so once I have some more hands-on practical experience with this framework I’ll be blogging something new. More later 🙂