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.

3 comments

  1. I was getting a run-time error when sending out the first message in a chat session. I think it was generated by the fact that for the first message in a chat, no chat window was opened on the receiving end. I added this line in the RECEIVE_MESSAGE option of the switch/case block of the ClassMediator.as class, and it solved the issue:
    showChatWindow(message.from as JID);
    Thanks for the tutorial, very useful!

Leave a Reply

Your email address will not be published. Required fields are marked *