Last night I decided to try an experiment. I had read a good article over at Dynamic AJAX: JSON AJAX Web Chat Tutorial. I decided to see if I could build something like that using Spry. Turned out it wasn't that hard. Let me show you what I did...
First off - I want to thank the original author over at Dynamic AJAX for the idea. I borrowed his CSS and techniques and just changed the back end from PHP to ColdFusion. I also, obviously, decided to use Spry to see if it would help me out any.
I'm not going to go over each line of code, but will focus on the interesting parts. The application works by requesting the chat history from the back end. It passes in a "lastID" value which lets the back end return only the chats that are newer than the last one it received. On the back end, I use the application scope to store the chats. Now this isn't a great idea. The original article used a database and I would recommend that as well, but I wanted to write something folks could download and play with on their own servers.
So let's look at the code a bit. The function I run to get the chat data is this:
function loadChat() {
Spry.Utils.loadURL("GET", "chatdata.cfm?start=" + encodeURIComponent(lastToken), true, chatResp);
}
The loadURL function has been covered in this blog before. Essentially it is a simply Spry utility to hit a URL. I tell Spry to run chatResp when finished. That function looks like this:
function chatResp(request) {
var result = request.xhRequest.responseText;
var xmldom = Spry.Utils.stringToXMLDoc(result);
var chats=xmldom.getElementsByTagName("chat");
var lastId = "";
var cdiv = document.getElementById('div_chat');
for(var i=0;i<chats.length;i++) {
var chatNode = chats.item(i);
var message = "";
var user = "";
lastId = chatNode.getAttribute("id");
message = chatNode.getElementsByTagName("message")[0].childNodes[0].nodeValue;
user = chatNode.getElementsByTagName("user")[0].childNodes[0].nodeValue;
time = chatNode.getElementsByTagName("time")[0].childNodes[0].nodeValue;
if(user != "" && message != "" && time != "") {
cdiv.innerHTML += "<span class='chat_time'>[" + time + "]</span> ";
cdiv.innerHTML += "<span class='chat_talk'>" + user + " said: " + message + "</span><br>";
cdiv.scrollTop = cdiv.scrollHeight;
}
}
if(lastId != "") lastToken = parseInt(lastId)+1;
setTimeout('loadChat()', pingDur*1000);
}
Now this function was a pain in the rear. Why? I've never dealt with XML before in JavaScript. While it looks simple enough, I had a heck of a time trying to find decent documentation. It took me twice as long to just find examples as it took for me to understand and use it. I really wish Spry would have a function to make that easier. But anyway - as you can see I parse the result XML and then simply add it to my DIV. Note the cool use of automatically scrolling the DIV to the end. Again - that comes from the guys at Dynamic AJAX.
The back end is pretty trivial as well. Both getting and adding chats uses the same CFM. I'd probably split that up normally but this was a quick demo. Here is the contents of that file:
<cfparam name="url.start" default="1">
<cfif structKeyExists(url, "add") and structKeyExists(url, "user")>
<cfset chat = structNew()>
<cfset chat.user = htmlEditFormat(url.user) & " (" & cgi.remote_addr & ")">
<cfset chat.message = htmlEditFormat(url.add)>
<cfset chat.time = now()>
<cflock scope="application" type="exclusive" timeout="30">
<cfset arrayAppend(application.chatdata, chat)>
</cflock>
<cfelse>
<cfsavecontent variable="chatdata">
<chats>
<cflock scope="application" type="readonly" timeout="60">
<cfloop index="x" from="#max(url.start, arrayLen(application.chatdata)-100)#" to="#arrayLen(application.chatdata)#">
<cfoutput>
<chat id="#x#">
<user>#application.chatdata[x].user#</user>
<message>#application.chatdata[x].message#</message>
<time>#timeFormat(application.chatdata[x].time)#</time>
</chat>
</cfoutput>
</cfloop>
</cflock>
</chats>
</cfsavecontent>
<cfcontent type="text/xml"><cfoutput>#chatdata#</cfoutput>
</cfif>
Nothing too sexy here, although note the use of locking around the code that works with the chat data. Only one thing is kind of neat - note that I return, at most, 100 rows of chats. I do this using the MAX function in the loop. This ensures that if you come into the chat late you won't be bombed with a huge amount of text.
So thats it. Feel free to use this but remember to give credit to Dynamic AJAX. I do have a demo online - but for some reason the CFM is very slow on my box. There is no reason why it should be (as you can see above the code is very simple), but just keep it in mind when you test it out.
Update: I've had a few folks jump in and note how slow the chat is. Yes, I know. Switching to a DB would make it run quicker - but as I said, I wanted something folks could play with it. I'm also dealing with a box that is getting a bit slow with age and will be moving in a week or so. I did a few small JavaScript updates so I've updated the zip.
Archived Comments
Speed issues aside, your demo works well. One thing I'd suggest is to use JSON instead of XML since you didn't seem to enjoy the XML/javascript experience so much. I personally am not a fan of javascript and ever since JSON came around I don't use it anymore. Get CFJSON and say bye bye to XML.
I recently had a problem where I needed to communicate with my girlfriend but they blocked all the IM services at her work (even the web messengers). SO I just ended up writing my own IM app and ran it on my web server, worked beautifully. It was a CF solution storing messages in application scope. Similar concept to this, although here you need to keep messages longer and the application scope will fill-up much faster, whereas my app just cleared the the message once it was received. I actually had a javascript API and everything so anybody could customize it. I kind of shelved it, maybe I should revive it.
* Caveat * Not done this myself yet...
Try XPath filtering:
http://labs.adobe.com/techn...
and use the Spry repeat region code.
I'd probably use the Data Set Explorer to help with figuring out the XPath:
http://labs.adobe.com/techn...
Danilo - I specifically stayed away from DataSets as I wasn't going to be displaying them - and I _believe_ a DS won't load unless you use it on the page. I need to look into that.
It seems that you're taking the response from the server, your custom data set, and then looping over it to add values to some display element, in your example, two spans with a trailing line break are repeated for each item in your data set. So you could apply to the wrapper div_chat opening tag:
spry:region="dsYourDataSet"
and add an "item" wrapper div around your spans and apply:
spry:repeat="dsYourDataSet"
One thing you'd need to consider is caching, and I think that that is handled with:
dsYourDataSet.useCache = false;
Anyway, this is whole lot of talk about nothing on my part, as I've not done this yet. With the amount of time I've spent typing this up I probably could have gotten to work on a sample to test out my claims.
Good luck in getting this to work well for you!
I've also run into issue with getting XML dom navigation working well for me. As you've found out, it's time consuming to work your way down through chatNode.getElementsByTagName("message")[0].childNodes[0].nodeValue. I think that's where the XPath could be helpful, but not sure.
I can't use a dataset and a spry region because I'm not returning the full chat set. I'm returning just the chats since your last ping.
(Note - I can use a dataset and NO region. I'll be blogging on that later.)
I see now. I was thinking that the new data would be dropped into the region, but if you did something like that then it would wipe out the previous messages. So for what I was thinking, it might be a two step process, have a hidden Spry repeat region, and when its data is loaded, copy the HTML over into the display DIV.
Not quite a simple operation, given that you already have a working example.
the Spry guys sent me an example of how it could be done. I'm planning two followup posts. One showing how xml handling is a hell of a lot easier in spry 1.4, and one how it could be done with datasets.
Just wanted to let you know that it doesn't work in IE 7, is this a known issue? Does someone has faced the issue before and has a work around..I'm not a fan of IE 7 but we need to have something working in all the browsers.
It works now. I just added a random date to the URL. I didn't update the zip, but just view source and you will see the change.
You're the MAN!!
I wanted to look at your CFML for a chat engine, but it does not appear to be working. The 10th comment from April 2007 talks to adding a random date to the url and I am trying to figure that out as there is no source to view, only a 404 error.
Thanks much.
I no longer support the demo online (do note that this blog entry is close to 4 years old). You can still download the code though.
Granted that this post is old, but I have found a use for it on a company intranet. One thing I was wondering.. how hard do you think it would be to add sound, as in when a new message is sent by a user. Or perhaps to make the window take focus?
I think you can just use an embed to play a sound. You can use the audio tag if you don't mind multiple formats and html5 browsers only.
Ray, thanks!
That got me thinking. I added an embed because I'm also dealing with IE6/7 browsers and it works. The embed is in the
div_chat see below, just add brackets.
div id="div_chat" class="chat_main"
embed src='chime.wav' autostart='true' hidden='true' loop='false'
/div
-Ron