Created
March 9, 2012 23:17
ColdFusion 10 - Creating A ColdFusion WebSocket AMD Module For Use With RequireJS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<cfscript> | |
// NOTE: The CFScript tag is added purely for Gist color-coding. | |
component | |
output="true" | |
hint="I define the application settings and event handlers." | |
{ | |
// Define the application settings. | |
this.name = hash( getCurrentTemplatePath() ); | |
this.applicationTimeout = createTimeSpan( 0, 0, 20, 0 ); | |
this.sessionManagement = false; | |
// Set up the WebSocket channels. For this demo, I'm only | |
// going to use one channel listener for all my channels. The | |
// WSApplication component simply acts as a proxy and points | |
// ass WebSocket requests back to the Application.cfc, as if | |
// they were standard requests. Using four new methods: | |
// | |
// - onWSRequestStart() :: Boolean | |
// - onWSRequest() :: Any (message) | |
// - onWSResponseStart() :: Boolean | |
// - onWSResponse() :: Any (message) | |
// | |
// NOTE: These are *NOT* core WebSocket methods. This is simply | |
// the approach I've used to learn about ColdFusion 10 WebSockets. | |
this.wsChannels = [ | |
{ | |
name: "chat", | |
cfcListener: "WSApplication" | |
} | |
]; | |
// I authenticate the given WebSocket user.... | |
// NOT SURE WHAT THIS DOES JUST YET. | |
function onWSAuthenticate( username, password, connection ){ | |
// Authenticate all users. | |
connection.authenticated = true; | |
connection.role = "anyUser"; | |
return( true ); | |
} | |
// I initialize the incoming WebSocket request. The possible | |
// types are [ subscribe | unsubscribe | publish ]. If I return | |
// False, the request will not processed and the given request | |
// (subscribe | publish) will be refused. | |
function onWSRequestStart( type, channel, user ){ | |
// Check to see if the current request is for a new | |
// subscription to the Chat. If so, we'll want to announce | |
// the new user to the rest of the chat room. | |
if ( | |
(type == "subscribe") && | |
(channel == "chat.message") | |
){ | |
// Publish a new subscription notice to all users. | |
wsPublish( "chat.userlist.subscribe", user ); | |
} else if ( | |
(type == "unsubscribe") && | |
(channel == "chat.message") | |
){ | |
// Publish a new unsubscription notice to all users. | |
wsPublish( "chat.userlist.unsubscribe", user ); | |
} | |
// Return true so the request will be processed. | |
return( true ); | |
} | |
// I execute the incmoing WebSocket request (to publish). A | |
// message must be returned (which will initialize the response) | |
// that gets published to all relevant subscribers. | |
function onWSRequest( channel, user, message ){ | |
// Check to see if the message ends in "!". If so, we'll | |
// upper-case the entire value. | |
if ( | |
(channel == "chat.message") && | |
reFind( "!$", message.message ) | |
){ | |
// Upper-case the EXCITED message! | |
message.message = ucase( message.message ); | |
} | |
// Return the message to publish to all users. | |
return( message ); | |
} | |
// I initialize the outgoing WebSocket response from the given | |
// publisher to the given subscriber. This is called for every | |
// subscriber on the given channel. Return True to allow the | |
// message to be published to the given client. Return False to | |
// prevent the message from being publisehd to the given client. | |
function onWSResponseStart( channel, subscriber, publisher, message ){ | |
// We don't want to post BACK to the same user. So, only let | |
// response (publication) through if the publisher and the | |
// subscriber are NOT the same person. | |
if ( | |
(channel == "chat.message") && | |
(publisher.clientID == subscriber.clientID) | |
){ | |
// Prevent message echo. | |
return( false ); | |
} | |
// Return true so the message will be published. | |
return( true ); | |
} | |
// I execute the outgoing WebSocket response. A message must be | |
// returned (which is what will be sent to the given user). This | |
// provides a chance to format a message for an individual user. | |
function onWSResponse( channel, user, message ){ | |
// Return the message to publish to THIS user. | |
return( message ); | |
} | |
// ------------------------------------------------------ // | |
// ------------------------------------------------------ // | |
// I log the arguments to the text file for debugging. | |
function logData( data ){ | |
// Create a log file path for debugging. | |
var logFilePath = ( | |
getDirectoryFromPath( getCurrentTemplatePath() ) & | |
"log.txt" | |
); | |
// Dump to TXT file. | |
writeDump( var=data, output=logFilePath ); | |
} | |
} | |
// NOTE: The CFScript tag is added purely for Gist color-coding. | |
</cfscript> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
We need to pass the Application name to the ColdFusion WebSocket | |
so that it knows which memory space to use. To use this, we'll | |
pass it through with the HTML element. | |
---> | |
<cfset appName = getApplicationMetaData().name /> | |
<!doctype html> | |
<html data-app-name="<cfset writeOutput( appName ) />"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Using ColdFusion 10 WebSockets With RequireJS</title> | |
<!-- Load the demo styles. --> | |
<style type="text/css"> | |
div.chatWindow { | |
border: 1px solid #666666 ; | |
height: 200px ; | |
overflow: hidden ; | |
position: relative ; | |
width: 450px ; | |
} | |
ol.chatHistory { | |
bottom: 0px ; | |
left: 0px ; | |
margin: 0px 0px 0px 0px ; | |
padding: 0px 0px 0px 0px ; | |
position: absolute ; | |
right: 0px ; | |
} | |
ol.chatHistory li { | |
border-top: 1px solid #CCCCCC ; | |
margin: 0px 0px 0px 0px ; | |
padding: 5px 5px 5px 5px ; | |
} | |
ol.chatHistory li.event { | |
color: #999999 ; | |
font-style: italic ; | |
} | |
ol.chatHistory span.handle { | |
font-weight: bold ; | |
margin-right: 10px ; | |
} | |
ol.chatHistory span.message {} | |
form { | |
margin: 10px 0px 0px 0px ; | |
} | |
input.handle { | |
font-size: 16px ; | |
width: 100px ; | |
} | |
input.message { | |
font-size: 16px ; | |
width: 275px ; | |
} | |
input.submit { | |
font-size: 16px ; | |
} | |
p.chatSize { | |
color: #999999 ; | |
font-size: 13px ; | |
font-style: italic ; | |
} | |
</style> | |
<!-- | |
Load the script loader and boot-strapping code. In this | |
demo, the "main" JavaScript file acts as a Controller for | |
the following Chat interface. | |
--> | |
<script | |
type="text/javascript" | |
src="./js/lib/require/require.js" | |
data-main="./js/main"> | |
</script> | |
</head> | |
<body> | |
<h1> | |
Using ColdFusion 10 WebSockets With RequireJS | |
</h1> | |
<div class="chatWindow"> | |
<ol class="chatHistory"> | |
<!-- Chat history will be populated dynamically. --> | |
</ol> | |
</div> | |
<form class="chatMessage"> | |
<input type="text" name="handle" class="handle" /> | |
<input type="text" name="message" class="message" /> | |
<input type="submit" value="Send" class="submit" /> | |
</form> | |
<p class="chatSize"> | |
People in chat room: <span class="count">0</span> | |
</p> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Define the paths to be used in the script mappings. Also, define | |
// the named module for certain libraries that are AMD compliant. | |
require.config({ | |
baseUrl: "js/", | |
paths: { | |
"domReady": "lib/require/domReady", | |
"jquery": "lib/jquery/jquery-1.7.1", | |
"order": "lib/require/order", | |
"text": "lib/require/text", | |
} | |
}); | |
// Load the application. In order for the Chat controller to run, | |
// we need to wait for jQuery and the CFWebSocket module be available. | |
require( | |
[ | |
"jquery", | |
"cfwebsocket", | |
"domReady" | |
], | |
function( $, ColdFusionWebSocket ){ | |
// I activate all the form fields. | |
function activateForm(){ | |
dom.handle.removeAttr( "disabled" ); | |
dom.message.removeAttr( "disabled" ); | |
dom.submit.removeAttr( "disabled" ); | |
// Focus the handle input. | |
dom.handle.focus().select(); | |
} | |
// I add the given message to the chat history. | |
function addMessage( handleData, messageData ){ | |
var handle = $( "<span />" ) | |
.text( handleData + ":" ) | |
.addClass( "handle" ) | |
; | |
var message = $( "<span />" ) | |
.text( messageData ) | |
.addClass( "message" ) | |
; | |
var item = $( "<li />" ) | |
.addClass( "message" ) | |
.append( handle ) | |
.append( message ) | |
; | |
// Add the new history item. | |
dom.chatHistory.append( item ); | |
} | |
// I deactivate all the form fields. | |
function deactivateForm(){ | |
dom.handle.attr( "disabled", "disabled" ); | |
dom.message.attr( "disabled", "disabled" ); | |
dom.submit.attr( "disabled", "disabled" ); | |
} | |
// I log the given event to the chat history. | |
function logEvent( description ){ | |
var item = $( "<li />" ) | |
.text( description ) | |
.addClass( "event" ) | |
; | |
// Add the new history item. | |
dom.chatHistory.append( item ); | |
} | |
// I select a random name and return it. | |
function getRandomHandle(){ | |
var names = [ | |
"Sarah", "Joanna", "Tricia", "Ben", "Dave", "Arnold", | |
"Kim", "Anna", "Kit", "Sly", "Vin", "Dwayne" | |
]; | |
// Return a random name. | |
return( | |
names[ Math.floor( Math.random() * names.length ) ] | |
); | |
} | |
// I update the room count. | |
function updateRoomSize(){ | |
// Get all the users who are subscribed to the chat | |
// room. Since we can't subscribe to the main "chat" | |
// channel AND a sub-channel at the same time, just get | |
// all the users that are subscribed to the message sub- | |
// channel. That should be good enough. | |
var countPromise = socket.getSubscriberCount( "chat.message" ); | |
// When the result comes back, update the room count. | |
countPromise.done( | |
function( count ){ | |
dom.roomSize.text( count ); | |
} | |
); | |
} | |
// -------------------------------------------------- // | |
// -------------------------------------------------- // | |
// Cache the DOM elements that we'll need in this demo. | |
var dom = {}; | |
dom.chatHistory = $( "ol.chatHistory" ); | |
dom.form = $( "form" ); | |
dom.handle = $( "input.handle" ); | |
dom.message = $( "input.message" ); | |
dom.submit = $( "input.submit" ); | |
dom.roomSize = $( "p.chatSize span.count" ); | |
// Create an instance of our ColdFusion WebSocket module | |
// and subscribe to several "chat" sub-channels. We're using | |
// sub-channels so we can have more fine-tuned control over | |
// how we respond to messages. | |
var socket = new ColdFusionWebSocket( | |
"chat.message", | |
"chat.userlist.subscribe", | |
"chat.userlist.unsubscribe" | |
); | |
// Set a random handle. | |
dom.handle.val( getRandomHandle() ); | |
// Let the user know that we are connecting. | |
logEvent( "Connecting to ColdFusion server." ); | |
// Disable the form elements until the socket has been | |
// connected. We don't want people trying to push messages | |
// until the subscription is open - that would cause an error. | |
deactivateForm(); | |
// When the socket has connected, activate the form. | |
socket.on( | |
"open", | |
function(){ | |
// Let the user know we have connected. | |
logEvent( "Connected." ); | |
// Activate the form. | |
activateForm(); | |
// Update the room-size. | |
updateRoomSize(); | |
} | |
); | |
// When a message comes down in the "message" sub-channel, we | |
// want to display it in the chat history. | |
socket.on( | |
"message", | |
"chat.message", | |
function( event, responseData ){ | |
// Deserialize the response. | |
var response = socket.parse( responseData ); | |
// Add the message to the chat. | |
addMessage( response.handle, response.message ); | |
} | |
); | |
// When the a new user has entered or left the chat room, we | |
// want to announce the event and update the subsriber count. | |
socket.on( | |
"message", | |
"chat.userlist", | |
function( event, responseData ){ | |
// Check to see which sub-channel we are using. | |
if (event.channel === "chat.userlist.subscribe"){ | |
// Deserialize the data for our new user. | |
var user = socket.parse( responseData ); | |
// Log subscription event. | |
logEvent( | |
"A new user has entered the chat [ " + | |
user.clientid + " ]." | |
); | |
} else { | |
// Log the unsubscription event. | |
logEvent( "A user has left the chat." ); | |
} | |
// Update the room size. | |
updateRoomSize(); | |
} | |
); | |
// Bind to the form submission so we can pipe the request | |
// through our ColdFusion WebSocket connection. | |
dom.form.submit( | |
function( event ){ | |
// Prevent the form submission. | |
event.preventDefault(); | |
// Get the cleaned form values. For this demo, we're | |
// not going to do any real error handling. No need | |
// to further complicate an ALREADY complex system. | |
var handle = (dom.handle.val() || "User"); | |
var message = (dom.message.val() || ""); | |
// Publish the message, including the handle that | |
// the user has chosen. | |
socket.publish( | |
"chat.message", | |
{ | |
handle: handle, | |
message: message | |
} | |
); | |
// Post the local copy directly to the chat history. | |
addMessage( handle, message ); | |
// Clear the message form and re-focus it. | |
dom.message | |
.val( "" ) | |
.focus() | |
; | |
} | |
); | |
// When the window closes (unloads), unsubscribe the user | |
// from the various channels. This way, any other user in | |
// the chat room can see what is happening. | |
$( window ).bind( | |
"beforeunload", | |
function( event ){ | |
// Unsubscribe from all the channels. | |
socket | |
.unsubscribe( "chat.message" ) | |
.unsubscribe( "chat.userlist.subscribe" ) | |
.unsubscribe( "chat.userlist.unsubscribe" ) | |
; | |
} | |
); | |
} | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
socket.on( "welcome", function( event ){ ... } ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
socket.on( "message", "chat", function( event, data ){ ... } ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment