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: 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…