Since I wrapped GameOne, and begun work on GameTwo, I've been thinking a lot about messaging about AIR/ColdFusion applications. I had an idea today for a simple application. Imagine your web site sells widgets. Every time you sell a widget, your boss wants an alert. You could easily use email, but emails tend to get ignored. How about writing a quick AIR application instead?
I began on the server side. I didn't want to build a real ecommerce site for this demo. Instead I decided to simply simulate one person spending X amount of money and a certain time.
Data Services Messaging is fairly simple to setup. I went to my ColdFusion Administrator, Event Gateways, Gateway Instances, and created a new instance. I named it SimpleTest, used the DataServicesMessaging type, and pointed to a CFC in my code folder. This CFC is used when Flex sends data to the server. However, in my application, the client will never send data. A blank file then is fine for the CFC. However, if you try to skip making the file the administrator will get pissy with you.

Important: Note that you have to start the gateway instance. Even with the startup set to auto, which I would assume would mean 'start now too please', don't forget to hit that start icon after you create it.
At this point I can write a quick CFM that will simulate a web site sale.
<cffunction name="firstName" output="false">
<cfset var list = "Bob,Ray,Jacob,Noah,Lynn,Jeanne,Stacy,Mel,Darth,Luke,Anakin,Padme,Kirk,Frank,James,Hal,Ben,Lori,Kerry,Gorf">
<cfset var name = listGetAt(list, randRange(1, listLen(list)))>
<cfreturn name>
</cffunction>
<cffunction name="lastName" output="false">
<cfset var list = "Camden,Smith,Nadel,Stroz,Pinkston,Sharp,Jonas,Vader,Palpatine,Break,Sneider">
<cfset var name = listGetAt(list, randRange(1, listLen(list)))>
<cfreturn name>
</cffunction>
<cftry>
<cfset msg = StructNew()>
<cfset msg.body = {}>
<cfset msg.body["firstName"] = firstName()>
<cfset msg.body["lastName"] = lastName()>
<cfset msg.body["amount"] = "#randRange(1,100)#.#randRange(0,9)##randRange(0,9)#">
<cfset msg.body["timestamp"] = now()>
<cfset msg.destination = "ColdFusionGateway">
<cfdump var="#msg#">
<cfset ret = SendGatewayMessage("simpletest", msg)>
<cfdump var="#ret#">
<cfcatch>
<cfdump var="#cfcatch#">
</cfcatch>
</cftry>
Those first two UDFs were simply me playing around. They return a random first and last name. The important part is the msg structure. I set the name, a random amount, and set a timestamp. (The docs say I don't have to, but frankly, the timeformat of the embedded timestamp wasn't something I knew how to parse.) Why? Those keys are entirely application dependent. If I were building a simple chat application, I may have just had a username and text property. For GameOne, one of my broadcasts includes stock data.
The destination, ColdFusionGateway, is actually specified in the XML files that ship with ColdFusion. I'll be honest and say I only kinda half-grok these files. I had to peek around in there when I played with BlazeDS locally, but all of the code here today runs on a 'stock' ColdFusion8. It has the ColdFusionGateway specified in the Flex XML files so for now, just go with it.
Once the data is set, I pass it to sendGatewayMessage. The first argument is the name of the event gateway I just created. The second is the structure defined in the file.
And that's it. Obviously this would be in a CFC method, you could imagine it being called after the order process is done.
The Flex/AIR side is even simpler. I used all of 2 files. (Ignoring the XML file AIR uses to create the build.) My main file contains one tag - a list. This list uses another file to handle rendering sales updates from the server. Here is the complete application file:
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()">
<mx:Consumer id="consumer" message="handleMessage(event)" channelSet="{myChannelSet}" destination="ColdFusionGateway" />
<mx:Script>
<![CDATA[
import mx.messaging.channels.AMFChannel;
import mx.messaging.Channel;
import mx.messaging.ChannelSet;
import mx.controls.Alert;
import mx.collections.ArrayCollection;
import mx.messaging.events.MessageEvent;
[Bindable]
public var myAC:ArrayCollection = new ArrayCollection();
[Bindable]
public var myChannelSet:ChannelSet
private function handleMessage(e:MessageEvent):void {
var body:Object = e.message.body
var newMsg:Object = new Object()
newMsg.firstName = body.firstName
newMsg.lastName = body.lastName
newMsg.amount = new Number(body.amount)
newMsg.timestamp = body.timestamp
myAC.addItemAt(newMsg,0)
}
private function init():void {
myChannelSet = new ChannelSet()
// var customChannel:Channel = new AMFChannel("my-cfamf","http://localhost/flex2gateway/cfamfpolling")
var customChannel:Channel = new AMFChannel("my-cfamf","http://www.coldfusionjedi.com/flex2gateway/cfamfpolling")
myChannelSet.addChannel(customChannel)
consumer.subscribe();
}
]]>
</mx:Script>
<mx:List dataProvider="{myAC}" itemRenderer="ItemRenderer" height="100%" width="100%"/>
</mx:WindowedApplication>
I won't cover every line, but let's talk a bit about the important bits - specifically the consumer. Since this application doesn't send messages, I don't need a producer, only a consumer. The consumer uses a channel set, myChannelSet, that I define in my init function. You can see where I commented out my local address and replaced it with the 'production' value. (And yes, this can be done better via Ant.) My renderer isn't that complex, and could be sexier, but here ya go:
<?xml version="1.0" encoding="utf-8"?>
<mx:Box xmlns:mx="http://www.adobe.com/2006/mxml"
backgroundColor="#e0e0e0" cornerRadius="4" borderStyle="solid" borderThickness="0"
paddingBottom="5" paddingTop="5" paddingLeft="5" paddingRight="5" height="90" dropShadowColor="#000000" dropShadowEnabled="true">
<mx:CurrencyFormatter id="fmtCurrency" precision="2"/>
<mx:DateFormatter id="fmtDate" formatString="H:NN:SS" />
<mx:Text text="Purchaser: {data.firstName} {data.lastName}" />
<mx:Text text="Purchased: {fmtCurrency.format(data.amount)}" />
<mx:Text text="Time: {fmtDate.format(data.timestamp)} " />
</mx:Box>
You can download the AIR application below. You can force a fake sale by simply visiting: http://www.coldfusionjedi.com/demos/messagetest/test.cfm. What's cool is - if someone else does it, your AIR application will see it as well.
So this is probably a bit of a trivial example, but shoot, look how simple it is. I'm really excited about LCDS/BlazeDS!
Archived Comments
That is a really great idea. To say that emails get ignored is an understatement! Thanks for the great example and use case.
Subscribing to comments
Great! I've been looking for something like that but: could you do the same but with only javascript in the client side?
I whould love to see this example in plain html-js.
Is it possible? I read something about Flex-JS bridge script but i haven t got it to work.
thanks
Technically, the Flex-JS bridge would not be 'just JS'. Still interested in having me a dig a bit more into it?
Oh yes!
if I'm the only one and it s too much work you can just point me some links ;)
I understand that a flash file will always be there to permit the comunication with the push from the server... and the bridge sends the "message" forward to JS...
a jedi example is always nice to have... thanks
@Ray and @daniel
I'm very much interested in this for some pretty cool reasons. For instance, I'm writing a custom manufacturing app for someone using CFAjax and SQL Server and it would be wonderful if I had a small cfdiv area of the interface where I could route alerts to people who were subscribed to those types of events. So say if the production manager were logged in and he's a member of a specific security group (production inquiry) then I could route a message to the production inquiry stating that an order just reached a specific stage in the production process. I would ideally have a small, system tray style area of the user interface where these types of alerts would flash. Perhaps a scrollable cfdiv or cflayoutarea that displayed alerts by type in a grid or a group of links where a user could onClick them to drill down to a cfwindow that provided a bit more detail.
Event Type Event Count
--------------------------
Receiving 10
Production 3
Sales 0
Shipping 17
When I say routing a message that could be as simple as inserting a record in a table. The part that I'm confused on is the auto refresh without bringing the browser to its knees.
Did I miss where you declared 'consumer'?
You did. Line 3 of the first Flex code. Sorry if this wasn't obvious!
Ahh! My fault, didn't even look up there. Probably because I usually do style first, then script, then things like this next.
Nice little example that seems to work well.
Can I ask how much bandwidth or resource this takes up? I left mine running while I was at work today and it seems quite a few people were trying it out.
Any idea how many this could work for or is the resource on the client side?
Ray,
I installed Air app on my desktop; however, when i run the application, it just shows blank window. Air window suppose to show something right?
Did you visit the 'special' URL to fake a sale?
@Andy: Some thoughts on your comment, and PLEASE remember I'm still earning this.
In my GameOne post, I talked about subtopics. That is one way to have multiple messages going through but w/ different guys listening in on them. In GameOne, it was like I had 2 radios in the AIR app, each tuned to a different station. That could possibly work for you.
Another way to do is with selectors. (Warning, not 100% sure that is the right term.) This lets you create a consumer that uses SQL-like instructions to listen in for messages. This allows for more complex, dynamic consumers than simply "listen for topic X". Unfortunately, ColdFusion's Message Gateway does not support this feature.
@peter: My understanding is that it uses AMF for communication. Therefore it shoul dbe nice and slim.
Hei again.
I come back to the ajax thema, I'm really interested to get server push into js.
The only documentation I found about the flex-js-bridge is in this link.
http://livedocs.adobe.com/l...
ray, do you have any experience with that?
The reason why i want to have the server push in js is that I don t have the time to learn Flex, I know I should and Flex has a lot of goodies that I should care about. I have more experience in JS and getting JSON pushed from the server to jQuery could open so many exciting posibilities... and get data pushed from the server into old applications that are not flex based could be an exellent idea.
I hope to get some time so I can post you some details if i do manage.
thanx again ray, really inspired example you posted.
where i wrote ray I ment off course Raymond ;)
I did a bit of research into the bridge yesterday. You are still going to need to learn a bit of Flex in order to use it in your applications. Also, I think for the purposes described in this blog entry, we don't really need the bridge. The bridge provides a way for JS to natively call ActionScript functions within your Flex code. But in our example here, we want Flex to run JS whenever a message is received. In that case, the built in support Flex has via externalInterface (I believe that is the name) allows for all the communication we need.
Going to try to get a demo of this sometime this week.
you rock man, looking forward to see you example.
Some AS3 would be nice to learn, and sure i will... but the main logikk i have it already in jQuery and I can t rebuild everything.
I m sure this is something a lot of people will enjoy.
cheers from Norway!
@Ray
Just to follow up, I did extensive research on this last night and I've basically got this narrowed down to 2 options.
1. I can create a companion (system tray) style AIR app like you've done that will pop-up event information for my users. They'd obviously have to login to it so that the app would know would groups the respective users belonged to and could send messages accordingly.
2. I can try to have a the included menu page for the app do polling with a JavaScript setInterval function and the page will handle the create, hide and destroy of a cfwindow when it catches an applicable event from the db or a log file. I know that this is not a true push-style solution and will require much testing to gauge browser resource hogging impact on the client. Luckily the users have all standardized on one browser (Firefox).
At any rate, I'll let you know which path I end up choosing and how it works out.
Thanks for the ideas.
@Ray - the cfajax polling idea works without any performance degradation!
CFC Code (applisten.cfc)...
<cfcomponent>
<cffunction name="readEventLog" access="remote" returntype="boolean">
<cfquery name="qryAppEventLog" datasource="#REQUEST.ds1#">
SELECT AppEventLogID
FROM AppEventLog
</cfquery>
<cfset eventFired = (qryAppEventLog.recordCount GT 0)>
<cfreturn eventFired>
</cffunction>
</cfcomponent>
CFM Template Code (eventloop.cfm)...
<cfajaximport tags="cfwindow">
<cfajaxproxy cfc="bmems.cfc.applisten" jsclassname="proxyAppListen">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
<script type="text/javascript">
checkAlertsErrorHandler = function(code,msg) {
alert('Error ' + code + ': ' + msg);
}
setAlertInterval = function(){
alertInterval = setInterval('checkAlerts()', 10000);
}
clearAlertInterval = function(){
clearInterval(alertInterval);
}
checkAlerts = function(){
proxyReadEventLog.readEventLog();
console.log('checkAlerts was called...');
}
checkAlertsCallback = function(newEvents){
if (newEvents && eventWinClosed){
day = new Date();
eventWinID = 'event' + day.getTime();
eventWinClosed = false;
ColdFusion.Window.create(eventWinID,'Event Notification','eventnotifier.cfm?formName=' + eventWinID, windowOptions);
ColdFusion.Window.onHide(eventWinID, killEventWin);
}
}
closeEventWin = function(){
ColdFusion.Window.onHide(eventWinID, killEventWin);
ColdFusion.Window.hide(eventWinID);
}
killEventWin = function(){
ColdFusion.Window.destroy(eventWinID, true);
eventWinClosed = true;
}
initHandler = function(){
windowOptions = new Object();
windowOptions.width = 200;
windowOptions.height = 100;
windowOptions.x = 50;
windowOptions.y = 50;
windowOptions.modal = false;
windowOptions.resizeable = false;
windowOptions.initshow = true;
windowOptions.draggable = true;
windowOptions.closable = true;
windowOptions.headerstyle = 'font-family: verdana; background-color: #0066FF;';
eventWinClosed = true;
proxyReadEventLog = new proxyAppListen();
proxyReadEventLog.setErrorHandler(checkAlertsErrorHandler);
proxyReadEventLog.setCallbackHandler(checkAlertsCallback);
proxyReadEventLog.readEventLog();
setAlertInterval();
}
</script>
</head>
<body>
<h1>Event Loop Test 1 - cfwindow notifier...</h1>
<cfset AjaxOnLoad("initHandler")>
</body>
</html>
CFM Template Code for the cfwindow (eventnotifier.cfm)...
New Events have occurred...
<a href="javascript:closeEventWin()">Close</a>
End Code - Begin Rambling...
I loaded this in Firefox, checked the Task Manager and was at 60MB. Went outside, fired up a smoke and had a coffee, still at 60MB. Went to BK, got the talking Star Trek Scotty bobble head, ate and drove back - still at 60MB!
It's definitely not perfect and it's definitely not push but it works. Next I'll try to test this with a cfdiv setup bound to a JavaScript ColdFusion.navigate style function rather than a cfwindow.
If you look closely I'm doing some stuff with globals (so that they're only conjured up once) and I'm testing to make sure that the window is not already opened. There are other things that I want to try in various ways to gauge browser performance impact. As of now it's been running for over an hour without any significant processor or memory usage.
The trick of this is that I'm going to have to expand upon this for the real app as I'll have 30 people hitting this app throughout the course of the day. Basically I'll be tracking everyone's event notifications and the various methods within the app will write event records to a table. Once the cfc/js lets the user know that an important event has occurred I can chain together a couple of callbacks to mark the table record as being "read" for that user's event record. I'm thinking about using some timer magic to auto-close and destroy the cfwindow after a few seconds (if the user doesn't close it first). I also know that I'll have to keep this table lean and mean so I'm considering a ColdFusion Scheduled task that will purge the events that have been "read" so as to keep the filtered select based on the user's session info to check for events pertinent to them down to a very small result set.
Let me know what you think!
Pretty interesting there. I'm going to try to find time later int he week to work on the HTML version (with embedded Flex). I'd be curious to compare memory usage.
@Andy: Nice solution but as you say it s not a push from a server. I think ajax calls each 10 seconds could be a great solution for a 30 people application. What about if you have 10 000 users? I think your server will be answering a lot of unesesary requests... or maybe the push solution is even worst ... i don t know.
@Ray
I definitely want to see what you've done because I'll probably deploy it!
@daniel
I know, but remember from my earlier postings that this is not a public facing application. Also keep in mind that I'm that one idiot that will try something just to see if it can be done! Calling home to check for data is not an optimal solution - but remember that anyone who has a smart phone other than blackberry is doing just that - all the time :0
In case anyone tried to use the cfajax polling code above I need to make a correction to a typo on the cfwindow options...
It should've read
windowOptions.resizable = false;
and NOT
windowOptions.resizeable = false;
Hei guys, i didn t got the time to try the flash js bridge thing we were talking about... but someone pointed this link to me yesterday.
http://cfjavascript.riaforg...
I will take a look this weekend.