Skip to content

Instantly share code, notes, and snippets.

@bennadel
Created March 9, 2012 23:17
ColdFusion 10 - Creating A ColdFusion WebSocket AMD Module For Use With RequireJS
<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>
<!---
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>
// 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" )
;
}
);
}
);
socket.on( "welcome", function( event ){ ... } );
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