Section 13.6. Text Chat

13.6. Text Chat

A text chat component is an essential part of many applications. Even in the most advanced video conference applications, text chats are almost always available as a reliable low-bandwidth communication tool to fall back on. A very simple text chat application can be built from two text fields, a button, and a shared object. When the user clicks the Send button, the text in the composition field is sent to the server and on to anyone else connected to the same shared object. When new text messages arrive , they are displayed in a larger scrolling message field.

Users who join a text chat midsession also expect to see the conversation that has gone on before they arrived. So text chats need to have some mechanism for storing chat history. Some chats must make available the full text of previous sessions for review. Other common features include sending private messages to just a few individuals, creating smaller breakout private chats, text formatting including selecting fonts and font colors, adding HTML links, and embedding small graphics (or emoticons) within the text. In fact, for something that initially seems so simple, there are endless variations in text chat designs.

The TextChat component described in this section, while relatively basic, is designed to show the essential mechanics of building full-featured text chat components . A shared object is used to send messages to everyone in the chat. Each chat session is recorded as a sequence of remote method calls in data-only streams stored on the server. A record of each chat session, including how many posts each user made, is saved in a persistent shared object. The chat automatically converts text that begins with "http://" or "www." to active HTML links using regular expressions and Server-Side ActionScript, while removing all other HTML markup. The TextChat component can be used either as a global component that every user accesses or in a pop-up window to facilitate breakout or one-to-one chat sessions. This chapter describes designing and building the component. Other chapters, especially Chapter 18 on security, develop the component further to provide different functions to users depending on each user's role.

13.6.1. Text Chat Resources and Messaging

Previous sections of this chapter have described the basic design of each component in terms of the resources it must manage, the user interface it provides, and the services it makes available to other components. For the TextChat component, we'll do the same thing but spend more time on message control and management and the shared object and stream resources the component manages .

13.6.1.1 TextChat message passing

Each text message is created when someone using a Flash movie types something into a text input field. The text message can be sent to FlashCom via NetConnection call( ) or SharedObject.send( ) . If call( ) is used, the message arrives at a remote method on the server and must be redistributed to each Flash movie that needs to receive it. If send( ) is used, the message is automatically delivered to every Flash movie connected to the same shared object. Using send( ) , therefore, appears to be the easiest way to distribute messages. However, in some scenarios, using send( ) can be less secure than using call( ) , because the application must trust the client enough to provide write access to the shared object. See Chapter 18 to understand why trusting the client is often not a good idea. The TextChat component described here uses the call( ) method to send each text message to the server, and a shared object to redistribute the message to each client connected to the shared object. Figure 13-6 is a simple interaction diagram that shows how text messages that originate on one client are distributed to every client.

Figure 13-6. How a text message is sent from one client and distributed via a shared object to many clients

Figure 13-6 shows how call( ) is used to pass the message to the server-side TextChat component, which processes it and then sends it back to all the clients by calling send( ) on the messages shared object. It shows the call( ) method being used to call a method named sendMessage( ) on the server-side Client object. The actual client method name will be a little more complicatedfor example pfcs/TextChat/main/sendMessage( ) because of the component naming scheme used for all the components in this chapter. The client passes a reference to itself and the text message on to the component by calling sendMessage(client, msg) on a server-side TextChat component instance. After the TextChat component finishes processing the message, the message is passed as a parameter into a send( ) call on the messages shared object. Finally, the showMessage( ) remote method is called on each client's copy of the shared object and passed on to each client-side TextChat component.

13.6.1.2 TextChat history

Text messages can be saved in shared objects, in streams, and in a database via Flash Remoting calls. The Macromedia Chat communication component uses a shared object to store and retrieve its chat history. If you want to see how it works, have a look in the Library of a movie that uses the Chat component. You'll find the client-side code on the first frame of the component and the server-side code in the .../Flash Communication Server MX/scriptlib/components/chat.asc file.

One disadvantage of using a shared object is that, over time, the number of messages can get quite large. When a client is connected to a shared object, a complete copy of the shared object resides in memory. Memory consumption can be minimized using separate shared objects for each chat session that are connected, read, and disconnected as needed. Flash Remoting provides an appealing alternative because individual messages, as well as author, timestamp, and session information, can all be stored in a searchable database. To reduce the number of remoting calls, messages and other information can be stored in a stream or shared object until the end of a chat session. When the session ends, a single database call can save the entire chat history. The TextChat component described here creates a stream for each chat session and maintains a listing of each session in a shared object. When a chat starts, the last session is read from the stream and displayed in the chat. Controlled access for retrieving, displaying, and managing older session histories is described in Chapter 18.

13.6.1.3 TextChat message parsing

As a convenience to users, text entered into the chat input text field is often parsed for special instructions or text and represented differently in the actual chat window. A classic example is automatically replacing a smiley, such as :-), in the text field with an emoticon. In Flash, showing graphics within lines of text is an overly complex undertaking and is not shown here. Another convenience is converting text that begins with "http://" or "www." into a full HTML linka feature that is implemented here. We also want to protect users from one another by limiting what HTML tags they can use in the chat. The TextChat component uses regular expressions on the server to parse and manipulate messages before sending them to each client.

In summary, the TextChat component manages a shared object for distributing messages, streams that contain all the messages of each chat session, and a shared object that contains summary information about each session and the location of each stream history.

13.6.2. Text Chat User Interface

The user interface consists of three v2 components: a TextArea, InputText, and Button.

The TextChat.createChildren( ) method from the TextChat.as file in the PeopleList.zip archive is listed in Example 13-11. It shows how the three subcomponents are attached to, initialized , and set up to deliver events to the TextChat component.

Example 13-11. Attaching and initializing TextChat subcomponents
 function createChildren (  ) {   var depth = 1;   // Create and set up the messages area.   createObject("TextArea", "messagesArea", depth++, {_x:0, _y:0});   messagesArea.html = true;   messagesArea.editable = false;   messagesArea.wordWrap = true;   createObject("TextInput", "messageInput", depth++);   messageInput.addEventListener("enter", this);   createObject("Button", "sendButton", depth++, {label:"Send", _width: 70});   sendButton.addEventListener("click", this); } 

When a TextChat component instance is placed on the Stage, it must resize itself and its subcomponents so that they maintain a relative position to one another. The layout of the TextChat component is illustrated in Figure 13-7.

Figure 13-7. TextChat component layout

When resized, the messagesArea TextArea is always positioned in the upper left at (0,0) and has the same width as the entire TextChat component. However, its height must be adjusted to allow for the height of the sendButton and messageInput field as well as a 10-pixel gap. The height of the messagesArea is calculated as:

 height - sendButton._height - 10 

The messageInput TextInput component instance must be moved any time the TextChat is resized. Its _x location is always 0 but its _y coordinate is always 10 pixels more than the height of the TextArea above it. Similarly, its width must be calculated to allow room for the sendButton on its right and a 10-pixel gap. The TextInput component height is the same as the sendButton instance. Example 13-12 shows the size ( ) method that is responsible for repositioning and resizing the three subcomponents that make up the client-side TextChat user interface.

Example 13-12. The size( ) method repositions and resizes subcomponents
 function size (  ) {   super.size( );   messagesArea.setSize(width, height - sendButton._height - 10);   messageInput.move(0, height - sendButton._height);   messageInput.setSize(width - sendButton._width - 10, sendButton._height);   sendButton.move(messageInput.width + 10, messagesArea.height + 10); } 

Even though the three v2 UI components are attached to the TextChat component in the createChildren( ) method, it is often a good idea to place the components in a test movie in order to work with the layout visuallythen code the size( ) method.

13.6.3. Connecting the User Interface and Shared Object

The createChildren( ) method ensures the TextChat component receives enter events from the TextInput component and click events from the Button. Example 13-13 shows the code from the TextChat.as file that handles these two events.

Example 13-13. The enter( ) and click( ) event handlers
 function click (ev) {   enter(ev); } function enter (ev) {   // The static   Text.trim( )   method trims leading and trailing whitespace. It is   // defined in the   Text.as   file, which is provided on the book's web site.    var msg = Text.trim(messageInput.text);   if (msg == "") return;   messageInput.text = "";   __nc.call(path + "/sendMessage", null, msg); } 

Aside from trimming the message text and clearing the messageInput field, the enter( ) method sends a remote method request to a sendMessage( ) method attached to a server-side Client object. If the TextChat instance's path is "pfcs/TextChat/main" then the method call is delivered to client.pfcs.TextChat.main.sendMessage( ) . On the server, the remote method request is passed to a TextChat object and its sendMessage( ) method in much the same way the Status object receives setStatus( ) method calls, as shown in Example 13-6. Example 13-14 shows the server-side TextChat class's addClient( ) method from the TextChat.asc file.

Example 13-14. The server-side TextChat addClient( ) method
 TextChat.prototype.addClient = function (client) {   if (!this.activeSession) {     var startString = this.session.getStartString( )     this.sessions_so.setProperty("currentSession", startString);     this.activeSession = true;   }   this.addObjectPath(client);   var thisComponent = this;   client.pfcs[this.className][this.name].sendMessage = function (msg) {     // Pass the client object and message to the TextChat instance.     thisComponent.sendMessage(client, msg);   }; }; 

When client.pfcs.TextChat.main.sendMessage( ) is called, it just calls the TextChat.sendMessage( ) method and passes it both a reference to the calling client and the text message. Example 13-15, excerpted from the server-side TextChat.asc file, shows the TextChat.sendMessage( ) method as well as the fixURL( ) method it calls to wrap HTML anchor tags around URLs. The server-side sendMessage( ) method replaces HTML tag brackets with encoded characters in order to remove any tags created by the user and creates HTML links for any URLs it finds using regular expressions. Then, it adds the username of the person who sent the message to the beginning of the message text and sends it to every client participating in the chat using the send( ) method of the messages shared object.

Example 13-15. The fixURL( ) and sendMessage( ) methods of the server-side TextChat class
 // Prepend "http://" if one is not found and then create an HTML anchor. TextChat.prototype.fixURL = function (str) {   if (str.indexOf("http://") == -1) str = "http://" + str;   str = '<font color="#0000FF"><u><a href="' + str +          '" target="_blank">' + str + '</a></u></font>';   return str; }; TextChat.prototype.sendMessage = function (client, msg) {   if (typeof msg != "string") return;   if (msg == "") return;   // Replace < with &lt; and > with &gt;   msg = msg.replace(/</, "&lt;");   msg = msg.replace(/>/, "&gt;");   // Turn URLs into links if they start with http:// or www.   // and also have at least one more dot in them.   msg = msg.replace(/(http:\/\/www\.)\S*\.\S*\b/ig, this.fixURL);   msg = "<b>" + client.pfcs.user.userName + "</b>: " + msg;   this.messages_so.send("showMessage", msg);   this.messages_s.send("showMessage", msg, client.pfcs.user.userName, new Date( ));   this.session.addContributor(client.pfcs.user.userName); }; 

Passing messages from the client-side part of a component to its server-side part is a good practice. The server-side part of the component can securely manage or manipulate the data before distributing it to other clients.


In this case, the server makes sure the message's sender name is correctly identified. The userName is retrieved from the client.user object and added to the message. (See the main.asc file in the PeopleList.zip archive.)

The message is also stored in a messages stream for later retrieval, and a session object that counts the number of chat messages generated by each user is updated.

When the remote method call named showMessage( ) arrives at each client, the messagesArea text field is updated by appending the text message as shown in Example 13-16.

Example 13-16. showMessage( ) is called by the showMessage( ) method attached to the messages_soshared object
 function showMessage (ev) {   messagesArea.text += ev.args[0] + "\n";   messagesArea.vPosition = messagesArea.maxVPosition;   messagesArea.redraw( ); } 

There are two things to note about the simple client-side showMessage( ) method in Example 13-16. The method is called by a shared object that has been set up to broadcast remote method calls to registered listeners by using SharedObjectFactory . (See Example 13-2 for more details on SharedObjectFactory , and consult Example 13-17 to see how the TextChat class sets up the remote method.) An event is passed to showMessage( ) that includes an args property that contains the arguments passed to the remote method. Also, the redraw( ) method is called to force the v2 TextArea component to scroll properly to the end. (A bug in the two initial releases of the v2 TextArea component causes the scrollbar to bounce back after scrolling to the bottom unless redraw( ) is called.)

13.6.4. Chat History

Let's look at how the user who joins a chat can see the chat history from the previous session and any messages that were sent during the current session before she connected. Example 13-17 shows the client-side set nc( ) setter that is called when a NetConnection object is passed to a client-side TextChat component. The method saves a reference to the NetConnection , configures shared objects and the stream object it needs, and then connects the shared objects.

Example 13-17. set nc( ) method is passed a NetConnection object and configures and connects to the server-side TextChat's shared objects and streams
 public function set nc (nc:NetConnection) {   __nc = nc;   // Set up the shared object that will send all new messages.   messages_so = SharedObjectFactory.getRemote(path + "messages", nc.uri);   messages_so.addRemoteMethod("showMessage");   messages_so.addEventListener("showMessage", this);   messages_so.connect(nc);   // Set up the NetStream so we can play back the last session's history.   history_ns = new NetStream(nc);   history_ns.owner = this;   history_ns.setBufferTime(1);   history_ns.onStatus = function (info) {     if (info.code == "NetStream.Play.Stop") {       this.hasStopped = true;     }     else if (this.hasStopped && info.code == "NetStream.Buffer.Empty") {       this.owner.showHistory( );     }   };   history_ns.showMessage = function (msg, userName, dateTime) {     this.owner.accumulateHistory(msg, userName, dateTime);   };   // Can't play it until we get the path to the right stream.   sessions_so = SharedObjectFactory.getRemote(path + "sessions", nc.uri, true);   sessions_so.addEventListener("onFirstSync", this);   sessions_so.connect(nc); } 

The setter method uses SharedObject.addRemoteMethod( ) to add a showMessage( ) remote method to the messages shared object and then adds itself as a listener for showMessage( ) remote method calls before connecting:

 messages_so.addRemoteMethod("showMessage"); messages_so.addEventListener("showMessage", this); 

The addRemoteMethod( ) and addEventListener( ) methods are not native methods of the SharedObject class. They were added by the SharedObjectFactory.getRemote( ) method (refer to Example 13-2 and Example 13-3). Calling both methods ensures that the component's showMessage( ) method is called whenever a new message is received as part of a showMessage( ) remote method call. The component's showMessage( ) method is listed in Example 13-16.

The setter method in Example 13-17 also creates a history_ns NetStream object and configures it to receive data-only messages by attaching showMessage( ) and onStatus( ) methods. The idea is that when the stream is played , each chat message in the history will have been stored as a showMessage( ) call. When the stream is played, the showMessage( ) method will be called for every message in the stream. When the server finishes sending the stream to the server, the onStatus( ) method will be passed an information object with a code value of "NetStream.Play.Stop". So the onStatus( ) method remembers that the stream has stopped by setting its hasStopped property to true . Even though the server is not sending messages, there may still be some data left in the stream's buffer; so the onStatus( ) method waits until hasStopped is true and it receives an information object with a code value of "NetStream.Buffer.Empty" before it calls the TextChat component's showHistory( ) method to show all the messages it has received.

Information about each chat session, such as what stream its history is stored in, is available in a sessions shared object. Since it has to be read only once to find the location of the chat history streams, the TextChat component merely sets itself up to listen for an onFirstSync event before connecting the sessions shared object. When the sessions shared object connects, the TextChat component must read it to find the location of two streams. The first location is that of the stream with the last chat session's messages in it. The second stream contains any messages that have been collected during the current chat session. The current session's messages have to be retrieved for clients connecting while a session is in progress.

The sessions shared object contains the stream names for the last session and current session streams in the lastSession and currentSession properties, as shown in Example 13-18, excerpted from the TextChat.as file.

Example 13-18. The onFirstSync( ) method
 function onFirstSync (ev) {   var soPath = sessions_so.data.lastSession;   if (soPath) history_ns.play(path + "/" + soPath, 0, -1, 3);   var soPath = sessions_so.data.currentSession;   if (soPath) history_ns.play(resourcePath + "/" + soPath, 0, -1, 2); } 

The onFirstSync( ) method gets the stream names and plays them using the same NetStream object, one after the other, as part of a playlist. The 0 and -1 parameter values in the calls to NetStream.play( ) tell each stream to play from the beginning until the end. Passing a 2 or 3 as a last parameter tells the stream to play all the data messages immediately rather than delivering them synchronized with stream time. Passing in 3 as a last parameter closes any stream that is playing, and passing 2 adds the stream to the playlist without closing any previous stream. See Chapter 5 for more details on playlists.

As each stream plays, the showMessage( ) method is invoked on the history_ns object, which in turn calls the TextChat.accumulateHistory( ) method, shown in Example 13-19, excerpted from the TextChat.as file. Rather than adding each message as it arrives to the messagesArea TextArea component, the messages are appended to the historyBuffer property of the TextChat. When all the messages have arrived, the showHistory( ) method is called, which inserts the messages before any live messages that have arrived since the client connected.

Example 13-19. Receiving and displaying the chat history
 function accumulateHistory (msg, userName, dateTime) {   var date = dateTime.getFullYear( ) + ":" +              dateTime.getMonth( ) + ":" +              dateTime.getDate( );   if (date != historyDate) {     historyDate = date;     historyBuffer += '<p align="center"><b><i>----' +                       date + '----</i></b></p>\n';   }   historyBuffer += msg + "<br>"; } function showHistory ( ) {   messagesArea.text = historyBuffer + messagesArea.text;   historyBuffer = "";   history_ns.close( ); } 

That's it for how the client collects and shows the chat messages of the last and current chat sessions. It's time to look at how those messages were saved into streams in the first place. Everything is done on the server. If you look back at the sendMessage( ) method in Example 13-15, you'll see these three lines:

 this.messages_so.send("showMessage", msg); this.messages_s.send("showMessage", msg, client.user.userName, new Date(  )); this.session.addContributor(client.user.userName); 

After cleaning up the text in the msg variable, the server-side script sends the message back to every client connected to the messages shared object by calling send( ) . It also stores a remote method call in the messages_s stream by calling its send( ) method. So the message, the user who sent it, and the time it was received are all saved as part of the chat history.

Finally, a session object is updated so that it knows that the contributor has just added a message. The session object is an instance of the TextChatSession class. Each TextChatSession instance contains a path property with the name of its stream file, its startTime and endTime , and a list of contributors to the chat. The session object is used to play back the chat history in the onFirstSync( ) method (see Example 13-18). Note that the contributor information is not used in the code shown here. It could be used by an administrative application collecting TextChat statistics.

Example 13-20 shows the listing of the server-side TextChatSession class from the TextChat.asc file. It is a simple record class with methods that provide formatted stream names that include date information.

Example 13-20. The TextChatSession class
 function TextChatSession (path) {   this.path = path;   this.startTime = new Date( );   this.endTime = null;   this.contributors = {}; } TextChatSession.prototype.getStartString = function ( ) {   var t = this.startTime;   return t.getFullYear( ) + "_" + t.getMonth( ) + "_" + t.getDate( ) + "_" +          t.getHours( ) + "_" + t.getMinutes( ) + "_" + t.getSeconds( ) + "_" +          t.getMilliseconds( ); }; TextChatSession.prototype.getName = function ( ) {   return this.path + "/" + this.getStartString( ); }; TextChatSession.prototype.addContributor = function (userName) {   if (!this.contributors[userName]) this.contributors[userName] = 1;   else this.contributors[userName]++; }; TextChatSession.prototype.endSession = function ( ) {   this.endTime = new Date( ); }; 

Each session object is updated as messages arrive during a chat and is saved into a slot of the session shared object. Each session slot is named after the time when the session started. The session generates a string in the format YYYY_MM_DD_HH_MM_SS_MM in its getStartString( ) method.

Whenever a session ends, the shared object is updated with the new session information, as shown in Example 13-21.

Example 13-21. Closing the chat
 TextChat.prototype.close = function (  ) {   this.messages_so.close( );   var startString = this.session.getStartString( )   this.sessions_so.setProperty(startString, this.session);   this.sessions_so.setProperty("lastSession", startString); }; 

Finally, the currentSession property is set up when the first client is added to the TextChat, as shown in Example 13-14.

There is a fair bit to follow in the TextChat component's server- and client-side code. To help it all fit into a picture of how the component works, it might be helpful to recall the roles of the three server-side resources the server-side code manages:

  • The sessions_so shared object contains every session object. Each session object is stored in a slot named after the session start time. There are two other sessions_so properties: the lastSession property contains the startTime string of the last session, and currentSession contains the startTime string of the current one.

  • The messages_so shared object is used only to copy messages from the server to each client.

  • The messages_s Stream object is used to save each message and is named after the startTime of the session.

We've seen one way to implement a TextChat component with a basic features set. For the sake of brevity, the complete code of the TextChat component is not reproduced here, but it is available on the book's web site in the file PeopleList.zip . Also, the TextChat component's security is improved in Chapter 18.



Programming Flash Communication Server
Programming Flash Communication Server
ISBN: 0596005040
EAN: 2147483647
Year: 2003
Pages: 203

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net