Instant Messaging for your Apps by Christian Schneider
CFDJ 2-11 p8

Listing 1: index.cfm
<head>
 <title>Instant Messaging</title>
 <frameset rows="*,170">
  <frame name="frameMain" src="welcome.cfm">
  <frame name="frameBottom" src="Login.cfm">
 </frameset>
</head>

Listing 2: welcome.cfm
<!--- /// welcome.cfm /// --->

<cfset tmpNickname = "">
<cfif IsDefined("SESSION.nickname")>
 <cflock name="#SESSION.sessionID#" type="ReadOnly" timeout="20" throwontimeout="Yes">
  <cfset tmpNickname = SESSION.nickname>
 </cflock>
</cfif>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<html>
<head>
 <title>Welcome</title>
</head>

<body><basefont face="Arial">

<cfoutput>
 Welcome <b>#tmpNickname#</b>,<p>
 Just browse our site here...
 <p>
 <a href="something_a.cfm">Some category</a>
 <p>
 <a href="something_b.cfm">Some other category</a>
</cfoutput>

</body>
</html>

Listing 3: Login.cfm
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
 <title>Login</title>
</head>
<body><basefont face="Arial">

<form name="formLogin" action="makeLogin.cfm" method="post">
 Nickname: <input type="Text" name="nickname">
 <input type="Submit" value="Go">
</form>

</body>
</html>

Listing 4: makeLogin.cfm
<cflock name="#APPLICATION.applicationName#" type="Exclusive" timeout="20" throwontimeout="Yes">
 <!--- Set up Nickname-Structure for mapping IPs to Nicknames --->
 <cfparam name="APPLICATION.NickNames" default=#StructNew()#>
 <!--- Insert submitted Nickname into Nickname-Structure --->
 <cfset dummy = StructInsert(APPLICATION.NickNames, CGI.REMOTE_ADDR, FORM.nickname, true)>
</cflock>

<cflock name="#SESSION.sessionID#" type="Exclusive" timeout="20" throwontimeout="Yes">
 <!--- Insert submitted Nickname into Session-Scope (just for remembering the user's nickname) --->
 <cfset SESSION.nickname = FORM.nickname>
</cflock>

<cflocation url="listUsers.cfm">

Listing 5: listUsers.cfm
<cflock name="#SESSION.sessionID#" type="ReadOnly" timeout="20" throwontimeout="Yes">
 <cfset tmpNickname = SESSION.nickname>
</cflock>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<html>
<head>
 <title>Currently Active Users</title>
 <style type="text/css">
  body {
   font-family: Arial, Helvetica, Verdana;
  }
  td {
   font-size: 8pt;
  }
  input {
   font-size: 9pt;
  }
 </style>
</head>

<body onLoad="window.setTimeout('location.reload()', 30000);">

<cfoutput>

Currently #StructCount(APPLICATION.SessionTracker)# active Users online

<cflock name="#APPLICATION.applicationName#" type="Exclusive" timeout="20" throwontimeout="Yes">

 <form name="formUsers" action="sendMessage.cfm" method="post">
  <input type="Hidden" name="from" value="#tmpNickname#">
  <input type="Hidden" name="sender" value="#CGI.REMOTE_ADDR#">

  <table border="1" cellspacing="0" cellpadding="1" width="40%">
  <tr bgcolor="##eeeeee">
   <td width="20">&nbsp;</td>
   <td width="*"><b>User</b></td>
   <td width="*"><b>Online-Status</b></td>
  </tr>
 
  <!--- Loop through Session-Tracker and list current online users --->
  <CFLOOP collection=#APPLICATION.SessionTracker# item="aUser">
   <cfset onlineSince = StructFind(APPLICATION.SessionTracker, aUser)>
 
   <!--- Check for time-outs --->
   <CFIF DateCompare(onlineSince+theTimeout, Now()) EQ 1>
    <!--- User's last activity lies within session-timeout, so his session is active --->
    <cfset inactiveSince = DateDiff("n", onlineSince, Now())>
    <!--- The threshold for coloring the report may be set individually --->
    <cfif inactiveSince LTE 2>
     <cfset theColor = "Red">
    <cfelseif inactiveSince LTE 5>
     <cfset theColor = "Yellow">
    <cfelse>
     <cfset theColor = "Cyan">
    </cfif>
    <!--- Output of current user in list --->
    <tr>
     <td><input type="Checkbox" name="Users" value="#aUser#"></td>
     <td><cfif StructKeyExists(APPLICATION.NickNames,aUser)>#StructFind(AP-PLICATION.NickNames, aUser)#<cfelse>#aUser#</cfif></td>
     <td bgcolor="#theColor#">inactive since <b>#inactiveSince#</b> mins</td>
    </tr>
   <CFELSE>
    <!--- User's session has timed-out, so we can delete his IP from the Session-Tracker and Nickname-Structure, as well as the Message-Queue --->
    <cfscript>
     StructDelete(APPLICATION.SessionTracker, aUser);
     StructDelete(APPLICATION.NickNames, aUser);
     StructDelete(APPLICATION.MessageQueue, aUser);
    </cfscript>
   </CFIF>
  </CFLOOP>
 
 </table>
  <input type="Text" name="message" size="70" maxlength="200">
  <input type="Submit" value="Send Instant Message">

 </form>

</cflock>

<p>
<a href="welcome.cfm">Go back</a>

</cfoutput>
</body>
</html>

Listing 6: Application.cfm
<!--- Set this Variable in local scope: can be accessed from the report-page directly --->
<cfset theTimeout = CreateTimespan(0,0,15,0)>

<!--- Set Application name and options --->
<CFAPPLICATION  NAME="My_Test_App"
    CLIENTMANAGEMENT="YES"
    SESSIONMANAGEMENT="YES"
    SESSIONTIMEOUT=#theTimeout#>

Listing 7: OnRequestEnd.cfm
<!--- Session-Tracker Code --->

<cflock name="#APPLICATION.applicationName#" type="Exclusive" timeout="20" throwontimeout="Yes">

  <!--- If Session-Tracker does not exist, generate it... --->
 <cfparam name="APPLICATION.SessionTracker" default=#StructNew()#>

 <!--- Instead of using the clients IP-address we can also log the client's user-name
    or similar session-scoped login info here --->
 <!--- It's important to specify TRUE here to allow the overwriting of existing entries (= updating) --->
 <CFSET dummy = StructInsert(APPLICATION.SessionTracker, CGI.REMOTE_ADDR, Now(), true)>
 
 <!--- ***** Message-Queue Code ***** --->

 <cfif CompareNoCase(GetFileFromPath(CGI.SCRIPT_NAME),"sendMessage.
cfm") NEQ 0>
 
  <!--- If Message-Queue does not exist, generate it... --->
  <cfparam name="APPLICATION.MessageQueue" default=#StructNew()#>
 
  <!--- Check Message-Queue for messages for current user --->
  <cfif StructKeyExists(APPLICATION.MessageQueue, CGI.REMOTE_ADDR)>
   <cfoutput>
    <!--- Fetch current user's queue --->
    <cfset tmpQueue = StructFind(APPLICATION.MessageQueue, CGI.REMOTE_ADDR)>
    <!--- Loop through messages in that queue and display each message --->
    <cfloop index="i" from=1 to=#ArrayLen(tmpQueue)#>
     <cfset tmpPackage = tmpQueue[i]>
     <SCRIPT LANGUAGE="JavaScript" TYPE="text/javascript">
              <!--
      // Present message
               doReply = confirm("Message from #tmpPackage["from"]#:\n\n#tmpPackage["text"]#\n\nReply?");
      if (doReply) {
       // Prompt for reply
       message = prompt("Your reply to #tmpPackage["from"]#:","");
       if ((message != "") && (message != null)) {
        // Send reply message
        parent.frames.frameBottom.location.href="sendMessage.cfm?message="+escape(message)+"&users=#URLEncodedFormat(tmpPackage["sender"])#";
       }
      }
              //-->
              </SCRIPT>
    </cfloop>
    <!--- Delete message from the queue --->
    <cfset dummy = ArrayClear(APPLICATION.MessageQueue[CGI.REMOTE_ADDR])>
   </cfoutput>
  </cfif>

 </cfif>

</cflock>

Listing 8: sendMessage.cfm
<cfset tmpNickname = "">
<cfif IsDefined("SESSION.nickname")>
 <cflock name="#SESSION.sessionID#" type="ReadOnly" timeout="20" throwontimeout="Yes">
  <!--- Remember user's nickname --->
  <cfset tmpNickname = SESSION.nickname>
 </cflock>
</cfif>

<!--- Use either URL or FORM parameter to get the message to send --->

<cfparam name="URL.from" default=#tmpNickname#>
<cfparam name="URL.sender" default=#CGI.REMOTE_ADDR#>
<cfparam name="URL.message" default="">
<cfparam name="URL.users" default="">

<cfparam name="FORM.from" default=#URL.from#>
<cfparam name="FORM.sender" default=#URL.sender#>
<cfparam name="FORM.message" default=#URL.message#>
<cfparam name="FORM.users" default=#URL.users#>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<html>
<head>
 <title>Instant Messaging</title>
 <SCRIPT LANGUAGE="JavaScript" TYPE="text/javascript">
    <!--
  // for going back to the user-listing template
     function gotoListUsers() {
   location.href="listUsers.cfm";
  }
    //-->
    </SCRIPT>
</head>

<body onLoad="window.setTimeout('gotoListUsers()', 15000);">
 

<cfif IsDefined("FORM.users")>

 <cflock name="#APPLICATION.applicationName#" type="Exclusive" timeout="20" throwontimeout="Yes">
  <cfoutput>
   Sent the message &quot;<b>#FORM.message#</b>&quot; to<p>

   <!--- Loop through all receivers of the message --->
   <cfloop index="aUser" list=#FORM.users#>
    <!---  Append message at the receiver's queue --->
    <cfscript>
     // Generate Message-Queue for receiver if it does not already exist
     if (NOT StructKeyExists(APPLICATION.MessageQueue, aUser)) {
      StructInsert(APPLICATION.MessageQueue, aUser, ArrayNew(1), true);
     }
 
     // Setting up the message package to send
     tmpInsert = StructNew();
     tmpInsert["from"] = FORM.from;
     tmpInsert["sender"] = FORM.sender;
     tmpInsert["text"] = FORM.message;
 
     // Send the package (= append it at receiver's Message-Queue)
     ArrayAppend(APPLICATION.MessageQueue[aUser], tmpInsert);
    </cfscript>
 
    <!--- Print current receiver as sent --->
    <cfif StructKeyExists(APPLICATION.NickNames,aUser)>
     #StructFind(APPLICATION.NickNames, aUser)#
    <cfelse>
     #aUser#
    </cfif><br>
   </cfloop>

  </cfoutput>
 
 </cflock>

<cfelse>
 No users specified
</cfif>

<p>
<a href="listUsers.cfm">Go back</a>
 

</body>
</html>