Creating a chat system with Flex and Coldfusion

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
Coldfusion MX 6+
Flex or Eclipse

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

<cfcomponent>

<cfscript>
this.name = "FlexChat";
this.applicationTimeout = createTimeSpan(0,0,1,0);
this.clientmanagement= "yes";
this.loginstorage = "session" ;
this.sessionmanagement = "yes";
this.sessiontimeout = createTimeSpan(0,0,1,0);
this.setClientCookies = "no";
this.setDomainCookies = "no";
this.scriptProtect = "all";
</cfscript>

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.

<cffunction name="onApplicationStart" output="false">
<cfscript>
application.chat = arrayNew(1);
application.chatUsers = 0;
</cfscript>
<cfreturn true>
</cffunction>

Here we are only setting two global application variables.

Application.chat - type is array will hold our messages for our chat room.
Application.chatUsers- this will let us know how many users are in our chat room.

These values are the only globally accessable variables we need.

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.

<cffunction name="onApplicationEnd" output="false">
<cfargument name="applicationScope" required="true">
<cflock type=
"exclusive" timeout="5" name="userFile" >
<cffile action=
"write" file="#ExpandPath('txt/chatUsers.txt')#" addnewline="no" fixnewline="no" output="" nameconflict="overwrite">
<cfscript>

ArrayClear(application.chat);
StructClear(application);
</cfscript>
</cflock>
</cffunction>

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

<cffunction name="loginUser" access="remote" returntype="struct" hint="used to login users into chatroom">
<cfargument name=
"username" type="string" required="yes">
<cfargument name=
"password" type="string" required="yes">
<cfscript>

var results = structNew();
var startMessage = structNew();
</cfscript>
<cfquery datasource=
"FlexChat" name="qUser">
Select UID,Icon
From Users_tbl
Where UserID = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.username#"> AND
PassID = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.password#">
</cfquery>

[continued below]

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.

<cfif qUser.recordcount GT 0>
<cfoutput>
<cfsavecontent variable=
"userList">;#arguments.username#,#qUser.Icon#,#qUser.UID#</cfsavecontent>
</cfoutput>

<!---Lock out the user file so that we can add a new user.--->
<cflock type="exclusive" timeout="5" name="userFile" >
<cffile action=
"append" file="#ExpandPath('../txt/chatUsers.txt')#" output="#userList#" />
</cflock>
<cfscript>

//set our results struct with the login info
results["login"] = true;
results["ID"] = qUser.UID;
//add one to our application.chatUsers variable
application.chatUsers = application.chatUsers + 1;
//add a message to the struct startMessage then add the struct to the Application.chat array
startMessage["msg"] = "<B>User: </b>" & #arguments.username# & " has joined the room.<br />";
startMessage["id"] = 1;
</cfscript>
<cflock scope="application" type="exclusive" timeout="2">
<cfscript>

ArrayAppend(application.chat,startMessage);
</cfscript>
</cflock>
<cfreturn results>

<cfelse>

[continued below]

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.

<!---User is not found so return a bad login error--->
<cfscript>
results["login"] = false;
return results;
</cfscript>
</cfif>
</cffunction>

That's it for the login function. Now lets hop into the other end of the application flow and log our user out.

<cffunction name="logoutUser" access="remote" returntype="void" hint="used to logout users and remove them from the list">
<cfargument name="userID" required="yes" type="numeric">
<cflock type=
"exclusive" timeout="5" name="userFile">
<cffile action=
"read" file="#ExpandPath('../txt/chatUsers.txt')#" variable="userList">
<cfscript>

results = listToArray(userList,";");
temp = ArrayNew(1);
for(a = 1; a LTE ArrayLen(results); a = a + 1)
{
temp[a] = ListToArray(results[a],",");
}
for(i = 1; i LTE ArrayLen(temp); i = i + 1)
{
if(temp[i][3] EQ arguments.userID)
{
ArrayDeleteAt(results, i);
break;
}
}
newString = ArrayToList(results,";");
application.chatUsers = application.chatUsers - 1;
</cfscript>
<cffile action=
"write" file="#ExpandPath('../txt/chatUsers.txt')#" output="#newString#" fixnewline="no" addnewline="no">
</cflock>
</cffunction>

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:

Array [position] name, icon, id

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:

Array [position] [1] name
[2] icon
[3] id

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

<cffunction name="sendMessage" access="remote" returntype="void" hint="gets the message from the user and add it for all to see.">
<cfargument name="username" required="yes" type="string">
<cfargument name=
"msg" required="yes" type="string">
<cflock scope=
"application" type="exclusive" timeout="#CreateTimeSpan(0,0,0,1)#">
<cfscript>

variables.temp = structNew();
variables.temp["msg"] = "<b>" & arguments.username & ":</b> " & arguments.msg;
variables.temp["id"] = RandRange(Len(arguments.username), 123456, "SHA1PRNG");
ArrayAppend(application.chat,variables.temp);

if(ArrayLen(application.chat) GTE 5)
{
variables.deleteTotal = ArrayLen(application.chat) - 5;
for(a = 1; a LTE variables.deleteTotal; a = a + 1)
{
ArrayDeleteAt(application.chat, 1);
}
}
</cfscript>
</cflock>
</cffunction>

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.

<cffunction name="getMessages" access="remote" returntype=
All ColdFusion Tutorials By Author: Craig
  • Actionscript Basics in CFFORM
    This tutorial teaches some basic ways to achieve effects using actionscript with your flash forms.
    Author: Craig
    Views: 8,982
    Posted Date: Tuesday, February 27, 2007
  • CFCs in Fusebox
    This final part in the tutorials about fusebox 4.1 will explore the use of CFCs.
    Author: Craig
    Views: 6,605
    Posted Date: Tuesday, April 25, 2006
  • Creating a chat system with Flex and Coldfusion
    This tutorial will cover both the Flex and Coldfusion areas of a chat application that uses the users computer to store the entire chat log and coldfusion only stores the 5 newest messages.
    Author: Craig
    Views: 2,611
    Posted Date: Wednesday, February 6, 2008
  • Fusebox 4.1 For Beginners Part 1
    This four part series will introduce cf beginners to the fusebox frame work and help them get a grasp of this powerful technology.
    Author: Craig
    Views: 15,013
    Posted Date: Monday, July 4, 2005
  • Fusebox 4.1 For Beginners Part 2
    This four part series will introduce cf beginners to the fusebox frame work and help them get a grasp of this powerful technology.
    Author: Craig
    Views: 16,505
    Posted Date: Monday, July 4, 2005
  • Fusebox 4.1 For Beginners Part 3
    This four part series will introduce cf beginners to the fusebox frame work and help them get a grasp of this powerful technology.
    Author: Craig
    Views: 10,459
    Posted Date: Monday, July 4, 2005
  • Fusebox 4.1 For Beginners Part 4
    This four part series will introduce cf beginners to the fusebox frame work and help them get a grasp of this powerful technology.
    Author: Craig
    Views: 10,798
    Posted Date: Monday, July 4, 2005
  • Turning up the heat in Fusebox 4.1
    This tutorial teaches you some methodology and new xml tags you can use to create complex, but easy to use application in the fusebox framework.
    Author: Craig
    Views: 6,301
    Posted Date: Thursday, October 27, 2005
  • Using Flash Remoting to take your CFForms Farther
    This tutorial shows you how to make a remote connection to a cfc using actionscript for your cfforms.
    Author: Craig
    Views: 7,729
    Posted Date: Saturday, July 22, 2006