Creating a chat system with Flex and Coldfusion Introduction: Creating a chat system in coldfusion is fairly easy. Creating the interface in Flex with Coldfusion on the back end, well that is fairly easy as well as long as you know a few basic tricks. This tutorial will cover both the Flex and Coldfusion areas of our chat application. Unfortunatly the complex nature of the code has left me no choice but to split this tutorial into parts. Please read the full tutorial from begining to end before using this application. Please note that I have not stressed test this with more than 5 users. You should throughly test this before using it with large numbers of users. Prerequites: You should have a fundimental knowledge of Webservices, CFCs, Structures, Arrays, and cfscript. You should also know the basics of components in Flex and layout using Flex builder or Eclipse. What you'll need: Sample files download Getting Started: I've included every file you will need to get the application up and running. You can set up a website for your cfcs and add the db to your server. It already comes with two names in the user table so you can test.The same goes with the actionscript and mxml files. Create a project through your editor of choice and add these files to the project. I am going to let you set this up how you want with the names you want. Building the Coldfusion End Now that you have everything set up let us start with the Application.cfc. I chose the Application.cfc in place of the application.cfm, beacuase I wanted to handle some logic on specific calls such as onApplicationEnd, without running into the problem of having several files to accomplish everything the appication itself must do. That being said lets open the Application.cfc and look at the code. The first part is the cfscript syntax for the same thing you can do in the application tag. Here we are setting out timeout values, our application name, etc. Application.cfc
You can change these values as you see fit, I find it helpful to keep a quick timeout on application and session scopes during constructing and testing so that if I did make a mistake I don't have to wait forever for the application to set back to zero or need to change the application name so I can start a new run. The next step in our application.cfc file is the onApplicationStart function. This is where we can set up the variables that will be used site wide for our application to work with. Realize that this area only happens once per application initiation. For example you just finished this application and you load it to your website for use. If you visit that website with this application it would be considered first run. Yet Mary across the world visits the page as well less then two seconds after you did. She would still use the same application information that you initiated, thus allowing us to send messages across the board through the global application scope.
Here we are only setting two global application variables. Application.chat - type is array will hold our messages for our chat room. Our next line of code involves the closing of our application. If there are no chat users left and the application times out we want it to clean up the application for the next use. Since we are going to be using a text file to store and get our users we need to clean it up and set it for our next chat session. We also need to make sure that the application variables are gone as well. We place a lock on it so that no one tries to start up a new chat without the old one being deleted.
The extra attributes in the cffile tag that refer to new lines are set to no so that we get a correctly form document when we use it again. Note: More than one line in the blank document will throw off code we will use later and will result in an error. That's it for the Application.cfc file. Now lets look at the User.cfc file. I seperated the login and logout from the chat functions, because they really have no influence over how the chat room users interact except to say that they lose one less user from the room. This also helped make it easier to get an idea of where any errors could have occured. If an error happened at login then I knew to go to the User.cfc and so on. I find it helps greatly to seperate your logic when dealling with cfcs. Anyway lets look at the "loginUser" function of the User.cfc. User.cfc
The first part here we are accepting a remote request to the cfc function and we are also requiring two arguments be passed when the function is called, the username and password. Then we are setting up variables to be used in our function both of which are structs. The results struct will return to Flex the login information or a bad login error. The other struct will add a message to our chat room that this user has logged in if that occurs. Finally we have the query that will validate the username and password against a database that contains our user table. This query will also return the user ID according to the primary key in our database and the user's selected icon/avatar which will be added to the user list we will create.
First we test to see if the query we performed to find the user has actually done so. If true we are going to continue by creating a variable for our chatUsers.txt file to add this user. We want the username first and we want the icon second and the id as last. Do not change this order unless you are extremely comfortable with arrays and structures, because you will have to change alot of code in Flex and Coldfusion with loops within loops and what have you to adjust for the change. Next we add this savecontent variable to the file by appending it with cffile. You do not want to write the file because you will delete the other users (if any) from the list that are already logged in. Next we want to set the results structure to return to the user with the information it will need. It needs to know that the login has succeded and the user id. The username is not important, because we can pull it from the Flex login screen where it was typed. Then lastly we want to add a welcome message to the chat room for this new User. We are giving our chat messages id for a good reason instead of just continuosly add them to one another as has been done in the past. When we get to the chat.cfc I will explain further. Lastly in the code above we return the struct that Flex has requested. The last tibit of code mearly sends a fault structure back to let Flex know that the user isn't in the db and the login has failed.
That's it for the login function. Now lets hop into the other end of the application flow and log our user out.
This function does alot of actions, but really isn't to complicated. First it requires that only the user id that we passed Flex in our login as an arguments. Next we put a lock on the script we are about to run, because we do not want to let alterations be made to the text file until we are done. We read the text file using the cffile action attribute "read" and apply the file we read to a variable. If you took notice to our login script that was adding users to the file you saw they we had a delimieter at the begining of a semi-colon and then commas between each item something like this: ; name, icon, id This was so we could use the list function called, "ListToArray". What it does is change a delimited list into an Array. Our text file is a list of users and their attributes. Our first task is we need to seperate the users from one another. So again to do that we use ListToArray. This will only give us each user in an array that was seperated by our semi-colon delimiter. Now we have this:
Well since we don't know at what position from the left or right our id value starts or ends we need to seperate the list even more. To do so we need to loop over this array and change each position of the array with a list value into an array. So again we are going to use our friend ListToArray to pull out the data. We do this by adding the comma delimited values in the results array to the temp array while looping over them. This will make our list within the temp array look like this:
Now we can easily find our id value and remove the user from the text list of users. We do this by looping over the temp array and writing an if statement to compare temp[postion][3] against our arguments.userID when we find it we use an array function called "DeleteArrayAt" and use our results array and position of the loop index to delete our user. Once our selected user has been found and deleted we can stop the loop from continuing by using "break" this help let loose the processing power and time it would take to go over every user. Which would grow as the number of users grew. Finally in our logout function we take the new array and convert it back into a list. We don't need to start with the temp array and move back. Since we chose to delete the user from our orginal array we can just use it. We convert an array back to a list by using the array function "ArrayToList". This function requires an array and a delimiter be passed. Since we already established our delimiter between users would be a semi-colon we will use that again. Once we have the updated list lets update the file and use the cffile action "write" attribute to earse the old file and replace it with the new one.
|
Part Two : In our last section we looked at the application.cfc and user.cfc files. Now we are going to take a look at our workhorse cfc titled "Chat.cfc". This cfc is responsible for the receiving and sending of the messages users input as well as the user list that we played with a little in our user.cfc. If you haven't opened the chat.cfc file do so and lets look at the first function. Chat.cfc
Ok our first function is the "sendMessage" function where we receive the message that was created by the user. It asks for two parameters the message (msg) and the username of the sender. Then it locks out the application variable so we can add the message. Once locked we do something a little different than most chat applications. We create a struct to go into the application array. Instead of concatenating the messages strings. We format the message with the username sending it and add it to the struct, but we also add a identifier by turning the username into a number and calling the RandRange function provided in Coldfusion we also use an algorithm to ensure unique numbers. We then append the application array to include the struct we just made. After all that we also check to see how many messages are in our application.chat array. If we find more than 5 then we want to delete the oldest one. So we find out how many need to be deleted and then loop over that number and delete the message at position one each time. Since we are appending the array the newest messages are the ones with the higher numbered positions. Our next function is "getMessages" this function is called by our Flex interface to display the newest messages that have not already been received.
This function requires one variable be passed and that is the message id of the last time they checked for new messages. This would be the same as our id we gave in the sendMessage function. Our Flex interface will store the last id of the message it receives to compare with the current listing in our application.chat array. Once it loops through the application.chat array to see if there is a matching id it will then run another loop extracting only the first position after the message id till the last position. This number can range from 0 in which case we return the struct with nothing in it , to 4 in which case we would return 4 new messages. We lock out the application scope so we can do a proper count without fear of the value changing. Our next function is pretty much like our function to logout users. Except in this case we are turning our list into a query result that will be cached. Here is the script.
This function is called every 30 seconds to get the latest users. It requires a user id just as a security measure even though it is far from secure. First the function checks to see if the query exists if it does it skips passed all the code and just returns the existing query. If it doesn't it reads the user file to get the list, turns the list into an array and then loops again over the array to extract the list within each position while populating a query. We then use cfquery to get the information and send it back to Flex.
|
Part Three : So far we have been focusing on the coldfusion end of our application now lets look at the flex end of it. I will not be covering the layout of the application this is fairly basic. Instead I will be focusing on the actionscript part where the real work is done. I will however be reviewing the webservice tag in Flex. So lets take a look at that now. Webservice tag within Chat.mxml
Now before I get hate mail about how I should have used the RemoteObject tags and what have you I built this app as proof of concept and as a tool to learn. Had I used RemoteObject I probably would have gotten hate mail about how the application won't work, because of the very specific way you need to invoke it and since I don't have control over everyone's test server I figured this was the best way. That said lets take a look at the code. We have two webservice tags that have several operation tags within. The wsdl attribute is the url to your cfc for the user.cfc and chat.cfc. You will need to change this value to match how you would call up the wsdl file for your server. I set the useProxy value to false this is the default state. You would set it to true if you were using a Flex server. Another attribute of this tag which I have set above is the showBusyCursor. I only used it when we were calling the login information. This is because it would become very annoying to have it pop up every second during chat. The Webservice tag also requires an id if you plan on using it. I named both Webservice tags according to what cfc they were calling with a WS attached to the name. Nested inside the webservice tags you will see that there are operation tags. Operation tags tell the webservice tag the name of the function/request to call from our cfc. The name attribute must be set to a name of a function found on your cfc. If you don't name it correctly it will throw an error. Also included in the attributes of operation tags is a result and fault event attribute. Since we want to handled each response back from the webservice differently I created Event handling functions for each type of result returned. You could use one handler for all results much like I did with the fault event for all the operations, but your code would get really messy after a while and if you had some sort of error you would take forever to find it. I find it best to keep them separated. initApp.as Ok lets dive into the code that will be our application's backbone. We are going to start with the initApp.as file found in our ascript folder in our Flex project. This is the file we are going to use to initiate the majority of the things we are going to do in this application. First lets look at the imports found at the top of the page.
The first thing you will notice is the use of Flash Classes. A class is a series of functions combined to perform one or more tasks. Our application uses several classes from flash and flex. If we did not have these classes available it would cause us to have to hand code everything and make our code extremely large and difficult to deal with. The Flash classes that we are calling are three Event Classes and one Utility Class. An Event class is more or less a listener. Events are any response to an action. When you click on a link or button you mouse is causing an event with a response. The event being the "click" and the response being the page that is navigated to or the form being submitted. The same goes with any event. Our utility class here is a timer. The Timer class allows us to trigger an event behind the scenes without requiring any user input. We can set a timer to continuously loop and cause an event with a response at our chosen interval. Our Flex imports are also classes. Here we are importing the Alert class which is similar to a javascript popup alert. Our application also has a root class that we want to use thus the Application class. And our webservice has the result and fault events that we referenced before.
The tidbit of code above are our global variables. Much like coldfusion has variables that are application wide, session, etc. Actionscript has global variables that are similar to the application variable in cf, but Actionscript global variables are only available to the application itself and not outside. Also in Actionscript it is generally a good idea to strict data type your variables. Much like a cfargument tag does. The chatTimer variable will be our interval trigger to let the program Know to check for new messages. The usersOnline variable will count the number of times messages were called and then when it hits a certain number it to will help call a function from our webservice that will get the newest list of users currently chatting. Our msgID variable will keep track of the value of the last message received. If you remember from the chat.cfc we did this so we wouldn't get the same message twice. The username and userID are self explanatory. Our user's username and id from login.
Our first function of this page takes no arguments and returns nothing. This function is used to set up our application to listen for certain events. First we set up our interval to call the webservice for new chat messages. In Actionscript a second is represented by 1000. Number below a 1000 are less than a second, realize that the faster/lower the number the more processing speed your user's computer will need in order not to freeze. A second is very good number for this application. If we were using it to perform a smooth effect I would go with a lower number. We need to add a event listener to the chatTimer so our application knows to perform an action when the timer "ticks". We also add event listeners to the login button for user mouse click and as well to the application. The application is listening for the browser window to close or be redirected. We don't want to have our user logged in while they are off surfing the web all because they forgot to log out.
This is the action that is performed when the logout button has been clicked. We need to pass it the event parameters/arguments this is done in something like a struct. The MouseEvent creates a struct of variables that were created when the user clicked on the button with the mouse. Within this function we are clearing everything so that we can return to the same state that our app was in when first opened. We stop our timer so no more calls are being made to the webservice. and clear all the fields that had information in them. We also return our application back to the login screen. login.as Our next page of code called login.as is the next step in the logical flow of our application. This page of code will be the first code that the user interacts with and our application will respond to.
After the application has loaded the user will be presented with a login screen. Here the user will enter the username and password they have registered with. When they have entered the information they will press the login button much like any other login screen. When they press the login button the event listener we assigned to it in our initApp.as code will hear the click and call this function to perform its task. The task this function performs is fairly simple it submits the username and password entered to the webservice and its function for login. After the login has been submitted our operation tag that specified the function to call if there is a result listens for the response. If the response is not an error such as would occur if the webservice couldn't find the function or a error was thrown by coldfusion then it would call the next function.
Ok we received our response back from our user.cfc for our loginUser function. If you recall we returned a struct from that function. Actionscript handles struct fairly easily. Since we don't know if our login was successful or not lets switch the results to see if it was. If true then we can clean up the login panel. We also can start our message getting timer. (To this point we only set it up without telling it to start, because the user would not have been able to see the message anyway.) And change the view over to the chat room view/page. We also want to populate our list of users so we are going to call that on start. Had the response been false that our user is not logged in, then we would have just did a pop up alert stating that the login failed. And that is it for the login page. |
Part Four: So far we have built our cfcs, initiated our Flex application, allowed the user to login. Yet we still can't send or receive messages. In this final part we will explore the sending and receiving of chat messages as well as at the end I will give you some ideas on where to take this application from here. First lets look at the final two pages of code that are short and to the point. user_msg.as
Ok this is the only function on this page, but without it the user would not be able to send messages. This function is triggered through the "Send" button within the chat room. The button listens for the mouse to click on it and fires off to this function. Within this function we are using a new class that wasn't listed in our imports. We don't need to include it, because it is built in as common knowledge much like a String or Number. The RegExp class is what you probably already think it is. A regular expression or string to compare against. Since coldfusion does not like single pound signs and double quotes within strings we need to change the users text and turn it into a coldfusion friendly string. If you look at the first RegExp you will see that we are using forward slashes to enclose a pound sign along with a "g" after it. The forward slashes are to tell the expression what characters to look for. The "g" is telling it to search throughout the entire string for any and all single pound signs. If we did not specify this it would only find the first one and stop. Same is true with the double quotes. We then use a function of the string class called "replace". Works much the same a cf replace would work. You provide it the string, the substring to replace, and the replace value. Except in Actionscript the string remains on the outside of replace function. Where cf would have it within. Since we want to let our user use rich text we must run the html and the text code. So always remember to use the htmlText attribute of any text input component to get the rich text format. After we have formatted the code correctly for coldfusion we send it to our webservice for processing. Then we clear out our users message text input area so they don't try sending it again. And that's it. Our message has been sent. Next we will look at receiving our messages. ChatResults.as We've seen how to send our messages now lets look at receiving them. Open up ChatResults.as and lets take a look at the first function.
Our first function is short and to the point. This is our function that is called when our onlineUsers variable reaches 30 and calls the webservice for the most up to date user list available. It then takes those results and applies them to our user list component on our chat screen. It is already formatted for use in Actionscript when we converted our list into a query. If you take a look at the "userList" component in our mxml file you will find that I have created an item render to accept images and names only not the ids that are also passed. I will explain why I also passed the id later.
Ok this looks like a headache, but it really is simple. This function is called when our chat webservice returns a result for messages. The webservice is "pinged" every second for any new messages. Keeping in line with real time chat application. When the webservice function returns a struct that struct is analyzed. If the result equals "good" then the switch statement stops at case good. Inside that there is a "if" statement to check the last msgID value sent from coldfusion against the msgID value that our application has stored. If they are the same the application will do nothing. If you tried adding the messages returned you would have a stuttering chat window. Meaning your window would show the same messages over and over again until they cycled out and the new ones would show up multiple times as well. (Very annoying I liken it to bamboo sticks under the nails.) If the messages are new then lets fix them so they show up correctly in our app. Again we use the RegExp class and also the replace function of the string class. We only need to fix the double pound signs here, because Flex/Actionscript/Flash don't care about single or double quotes when outputting html styled text strings. Now that we have our string properly formatted we need to add it to our chat window. Now we haven't kept the entire chat session in our coldfusion application scope. Nor have we kept it in a database or file. And we have been deleting everything over 5 messages in cf. So where on Earth have we been storing it. I mean what is the sense of a chat app if the chat can only ever be 5 lines long? Well we keep in on the users desktop within their own browser. Why make our server work harder if the user can easily perform the same task. Our chat text window has saved every single message received since our user logged on. The line of code that follows our formatting the string is as follows:
The += expression in Actionscript is equivalent to the following code in coldfusion:
We kept all the prior messages (even the ones already removed from our cf array) and stored them with the user. So any new messages just get added to the old ones and so forth. This allows for more speed and less resources being drained by the coldfusion server. The next two lines in our code above simply set the msgID to a new last message received value and then moves the text area for our chat down to the last line so the user doesn't need to scroll.
This is the function we pointed to with all of our operation tags. This handles any fatal errors returned from calling any function within our webservice. This function is not so much for the user, but for coders so they can test the webservice and calls to it. It pops up a window with the error description so you can see the exact error.
Our last function for this application serves a dual purpose. If you remember we have already told our timer variable to call this function every second. We also set another variable to hold a number. When our chatTimer variable "ticks" every second this function is called. Based on the current number that the usersOnline variable is set to it either calls the webservice for new messages or it calls the webservice for the updated user list. If the usersOnline variable is equal to less than thirty it only calls the "getMessages" function of our webservice and adds one to the usersOnline variable. If the usersOnline variable is greater than or equal to 30 then it calls the "getUsersOnline" function of the webservice. The usersOnline variable reaches 30 every 30 seconds. And that's it you have a functioning chat application that looks great and doesn't require frames or refreshing. Where to go from here: Now that you have a chat room application and have a solid foundation from which to build from I will give you a few suggestions of where you can take this application from here. First and foremost you could build on the existing app to include smiley's, user specific "winks" (where only you and the other user selected from the list can see.) You can also turn this into a private messaging application for use by any site that wants to have live chat. You could easily add sound bits that users can send through the chat for everyone. Something similar to yahoo's messenger. You can also use the space to have chat that has ads on it. These are just of the few possibilities that you can do to add more functionality to the application. I look forward to seeing your creations. |