| By James Benson, Jay Fienberg | Article Rating: |
|
| May 16, 2007 11:00 AM EDT | Reads: |
5,423 |
This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs for the special pre-order price, click here for more information. Aimed at everyone from enterprise developers to self-taught scripters, Real-World AJAX: Secrets of the Masters is the perfect book for anyone who wants to start developing AJAX applications.
Input Focus and Blur in Chat Windows
We've also included a couple pure usability features in our ChatWindow class. The focus() and blur() methods simply change the background color of the chat window's text input to help the user know which window is active and where they are typing.
Listing 12.30 Input Focus and Blur
inputFocus: function() {
this.chatin.style.background = "#ee0";
},
inputBlur: function() {
this.chatin.style.background = "#fff";
},
Hide Chat Windows
Each chat window, in the HTML, has an [X] button for closing the window. When a user clicks this, the hide() function on the ChatWindow object is called, and this simply sets its style property to "none".
Listing 12.31 Hide
hide: function() {
log('Closed window to ' + this.to, 'debug');
this.chatwindow.style.display = "none";
this.open = false;
return false;
},
Destroy Chat Windows
As with our Buddies object, we want to destroy our ChatWindow objects, which just means hiding them and cleaning up the Event listeners we've set up earlier.
Listing 12.32 Destroy
destroy: function() {
this.hide();
Event.stopObserving(this.chatin, 'focus', this.inputFocusObserver, true);
Event.stopObserving(this.chatin, 'blur', this.inputBlurObserver, true);
Event.stopObserving(this.chatin, 'keypress', this.chatSendObserver, true);
Event.stopObserving(this.chatsend, 'click', this.chatSendObserver, true);
Event.stopObserving(this.closer, 'click', this.hideObserver, true);
}
};
Refresh
The refresh() function, which we've referenced a number of times, is essentially a loop. The refresh() function ends by setting a Timeout timer that re-calls refresh() after the timeout period (which is set in our refreshInterval global variable).
The refresh() function calls load() on the Buddies object, and calls load() on each ChatWindow object that has been created.
Listing 12.33 Refresh
function refresh() {
buddyMgr.load();
log('Loaded the buddy list', 'info');
var ms = false;
for (i in windowMgr) {
if (i.indexOf('buddy')==0) {
windowMgr[i].load();
log('Loaded Messages for '+ i, 'debug');
}
ms = true;
}
if (ms) log('Loaded Messages', 'info');
refreshTimer = setTimeout(refresh,refreshInterval);
log('Set a new refresh timer', 'debug');
}
The Initialization, Login and Logout Related Functions, Round 2
Now that we've seen how we can call the destory() method on the Buddies and ChatWindows objects, let's look at our final logout() method. Besides calling destroy() on the Buddies object, and on each ChatWindow object that has been created, logout() also clears the Timeout timer we use in the refresh() function.
Listing 12.34 Logout
function logout() {
log('Logging out ' + activeUser, 'debug');
$('logout').style.display = 'none';
$('login').style.display = 'block';
buddyMgr.destroy();
for (i in windowMgr) {
if (i.indexOf('buddy')==0) windowMgr[i].destroy();
}
log(activeUser + ' Successfully logged out', 'info');
clearTimeout(refreshTimer);
log('Cleared the refresh timer', 'debug');
buddyMgr = null;
windowMgr = null;
}
On Unload
Finally, we'll create an onunload function on the window object that will be executed whenever the user closes her browser or refreshes or leaves our im.php URL.
This function simply calls our logout() function, and therein ensures that a user who leaves our IM application is fully logged out. We could place other clean-up code here, if needed.
Listing 12.35 OnUnload
window.onunload = function () {
if (buddyMgr != null) logout();
//any other clean-up code can go here
}
Complete JavaScript Code
The complete im.js is listed here in Listing 12.36.
Listing 12.36 Complete JavaScript Code
/* global varaibles that persist across logins */
var refreshInterval = 2 * 1000; //first number = seconds
var logDisplayLevel = 1;
var logWindow = 'logmsgs';
var logLevels = new Array('debug', 'info', 'error');
var logmsgcnt = 0;
var baseURL = 'im.php';
var responseFormat = 'html';
var ChatWindow = Class.create();
var Buddies = Class.create();
/* global varaibles that get re-initialized with each login */
var activeUser, windowsUsed, windowMgr, buddyMgr, refreshtimer;
/* Chat Window class - an instance is created for each chat window */
ChatWindow.prototype = {
initialize: function (handle, to) {
toPrefix = 'buddy_';
this.chatwindow = $('chat' + handle);
this.chatmsg = $('chatmsg' + handle);
this.chatin = $('chatin' + handle);
this.chatsend = $('chatsend' + handle);
this.chatinfo = $('chatinfo' + handle);
this.closer = $('close' + handle);
this.to = to.substring(toPrefix.length);
log('Initializing window #' + handle + ' for chat with ' + this.to, 'debug');
Element.update(this.chatinfo, 'Chat with '+ this.to);
this.open = false;
this.content = '';
this.chatSendObserver = this.chatSend.bindAsEventListener(this);
this.inputFocusObserver = this.inputFocus.bindAsEventListener(this);
this.inputBlurObserver = this.inputBlur.bindAsEventListener(this);
this.hideObserver = this.hide.bindAsEventListener(this);
Event.observe(this.chatin, 'focus', this.inputFocusObserver, true);
Event.observe(this.chatin, 'blur', this.inputBlurObserver, true);
Event.observe(this.chatin, 'keypress', this.chatSendObserver, true);
Event.observe(this.chatsend, 'click', this.chatSendObserver, true);
Event.observe(this.closer, 'click', this.hideObserver, true);
},
openOrFocus: function () {
if (!this.open) {
log('Opened window to ' + this.to, 'debug');
this.chatwindow.style.display = "block";
this.chatin.focus();
this.open = true;
} else {
log('Focused window to ' + this.to, 'debug');
this.chatin.focus();
}
},
hide: function() {
log('Closed window to ' + this.to, 'debug');
this.chatwindow.style.display = "none";
this.open = false;
return false;
},
display: function(xhr) {
log('Displaying Messages for '+this.to, 'debug');
this.content = xhr.responseText;
Element.update(this.chatmsg, this.content);
this.chatmsg.scrollTop = this.chatmsg.scrollHeight;
},
inputFocus: function() {
this.chatin.style.background = "#ee0";
},
inputBlur: function() {
this.chatin.style.background = "#fff";
},
load: function() {
msgsURL = baseURL + '/' + responseFormat+ '/' + activeUser+'/msgs/'+ this.to;
log('Loading Messages to '+ this.to +' via AJAX', 'debug');
new Ajax.Request(
msgsURL,
{
method: 'get',
parameters: '',
onComplete: this.display.bindAsEventListener(this)
});
},
chatSend: function(ev) {
if (ev!=null && (
(ev.type == 'click') ||
(ev.type=='keypress' && ev.keyCode == Event.KEY_RETURN)
)) {
e = Event.element(ev);
log(activeUser + ' Sending message from ' + this.chatwindow.id +' to: ' + this.
to, 'debug');
this.sendMessage();
this.chatin.value = '';
this.chatin.focus();
}
},
sendMessage: function() {
sendmsgURL = baseURL + '/sendmsg';
new Ajax.Request(
sendmsgURL,
{
method: 'post',
postBody: 'from='+activeUser+'&to='+this.to+'&msg='+encodeURI(this.chatin.
value)
});
log('Message sent to: ' + this.to, 'info');
},
destroy: function() {
this.hide();
Event.stopObserving(this.chatin, 'focus', this.inputFocusObserver, true);
Event.stopObserving(this.chatin, 'blur', this.inputBlurObserver, true);
Event.stopObserving(this.chatin, 'keypress', this.chatSendObserver, true);
Event.stopObserving(this.chatsend, 'click', this.chatSendObserver, true);
Event.stopObserving(this.closer, 'click', this.hideObserver, true);
}
};
/* Buddy List class - only instance is created */
Buddies.prototype = {
initialize: function () {
this.listWindow = $('buddy');
this.listWindow.style.display = 'block';
this.statusControl = $('status');
this.statusControl.options.selectedIndex = 0;
this.postStatusUpdate('Available', '');
this.changeStatusObserver = this.changeStatus.bindAsEventListener(this);
Event.observe('status', 'change', this.changeStatusObserver, true);
},
budlist: '',
changeStatus: function () {
idx = this.statusControl.options.selectedIndex;
status = this.statusControl.options[idx].value;
status_msg = '';
log('Updating Status to: ' + status, 'debug');
this.postStatusUpdate(status, status_msg);
if (status=='Offline') {
logout();
}
},
postStatusUpdate: function (status, status_msg) {
updatestatusURL = baseURL + '/updatestatus';
var myAjax = new Ajax.Request(
updatestatusURL,
{
method: 'post',
postBody: 'from='+activeUser+'&status='+status+'&status_
msg='+encodeURI(status_msg)
});
log('Status Updated to: ' + status, 'info');
},
load: function () {
buddylistURL = baseURL + '/' + responseFormat+ '/' + activeUser+'/buddies';
log('Loading Buddies', 'debug');
new Ajax.Request(
buddylistURL,
{
method: 'get',
parameters: '',
onComplete: this.display.bindAsEventListener(this)
});
},
display: function (xhr) {
Element.update('buddylist', xhr.responseText);
this.budlist = document.getElementsByClassName('budlisting', 'buddylist');
this.budlist.each(function(bud) {
Event.observe(bud, 'click', goChat, true);
});
},
destroy: function() {
this.listWindow.style.display = 'none';
this.postStatusUpdate('Offline', 'Logged Out');
this.budlist.each(function(bud) {
Event.stopObserving(bud, 'click', goChat, true);
});
Event.stopObserving('status', 'change', this.changeStatusObserver, true);
}
};
/* Buddy list - window, and refresh cycle functions */
function goChat() {
log('Clicked ' + this.id + ' to chat', 'debug');
if (windowMgr[this.id] == undefined) {
windowsUsed++;
handle = windowsUsed;
windowMgr[this.id] = new ChatWindow(handle, this.id);
log('Created a new handle for window #' + handle, 'debug');
}
windowMgr[this.id].load();
windowMgr[this.id].openOrFocus();
}
function refresh() {
buddyMgr.load();
log('Loaded the buddy list', 'info');
var ms = false;
for (i in windowMgr) {
if (i.indexOf('buddy')==0) {
windowMgr[i].load();
log('Loaded Messages for '+ i, 'debug');
}
ms = true;
}
if (ms) log('Loaded Messages', 'info');
refreshTimer = setTimeout(refresh,refreshInterval);
log('Set a new refresh timer', 'debug');
}
/* logging functions */
function log(msg, level) {
if (level != null & logLevels.indexOf(level) >= logDisplayLevel) {
logmsgcnt++
msg = '<p class="'+level+'text">• ' + level.toUpperCase() +' ('+logmsgcnt+'):
'+ msg+'</p>';
new Insertion.Bottom(logWindow, msg);
$(logWindow).scrollTop = $(logWindow).scrollHeight;
}
}
/*function setLogLevel(level) {
logDisplayLevel = level;
}
*/
/* initialization, login and logout related functions */
function initialize() {
activeUser = '';
windowsUsed = 0;
windowMgr = new Array();
}
function loadUsers() {
userlistURL = baseURL + '/userlist';
log('Loading Users into drop down list', 'debug');
new Ajax.Updater(
'user',
userlistURL,
{
method: 'get',
parameters: ''
});
}
function login() {
idx = this.options.selectedIndex;
user = this.options[idx].value
initialize();
log('Logging in ' + user, 'debug');
activeUser = user;
Element.update('loginuser', activeUser);
$('login').style.display = 'none';
$('logout').style.display = 'block';
this.options.selectedIndex = 0;
log('Successfully logged in as ' + activeUser, 'info');
buddyMgr = new Buddies();
refresh();
}
function logout() {
log('Logging out ' + activeUser, 'debug');
$('logout').style.display = 'none';
$('login').style.display = 'block';
buddyMgr.destroy();
for (i in windowMgr) {
if (i.indexOf('buddy')==0) windowMgr[i].destroy();
}
log(activeUser + ' Successfully logged out', 'info');
clearTimeout(refreshTimer);
log('Cleared the refresh timer', 'debug');
buddyMgr = null;
windowMgr = null;
}
window.onunload = function () {
if (buddyMgr != null) logout();
//any other clean-up code can go here
}
window.onload = function() {
log('Window load complete', 'info');
Event.observe('user', 'change', login, true);
Event.observe('logoutlink', 'click', logout, true);
Event.observe('log0', 'click', function() {logDisplayLevel=0; log('Log Level Changed
to Debug','debug');}, true);
Event.observe('log1', 'click', function() {logDisplayLevel=1; log('Log Level Changed
to Info','info');}, true);
Event.observe('log2', 'click', function() {log('Log Level Changing to Error','info');l
ogDisplayLevel=2}, true);
loadUsers();
}
Exercises for the Reader
- Dynamic windows that are draggable/resizable
- Use XML or JSON methods on the server to allow more sophisticated interactions on the client
- Create a message bus on the client to consolidate refreshes
- Load then display
- Sendmessage and updateStatus
- Single method on the server for updates
- Single response on the server for all changes
- Use timestamps to minimize data sent
- Update local chat windows instantly and then only display subsequent messages
- Add pushlets/comet to the server, client dealing with constant stream. Mention Jetty 6 continuations and server scale issues
var imdata = eval (res);
imdata.buddies.count;
imdata.msgs['Paul'].list[33].msg;
This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs, click here to order.
Published May 16, 2007 Reads 5,423
Copyright © 2007 SYS-CON Media, Inc. — All Rights Reserved.
Syndicated stories and blog feeds, all rights reserved by the author.
More Stories By James Benson
Jim Benson, AICP, is the COO of Gray Hill Solutions in Seattle. Gray Hill creates tools for government and industry to harness and utilize real-time data. Jim has always driven applications for his clients to store and provide information in easily extensible ways. Web 2.0 has therefore been a natural environment for him. He is also involved with the Cooperation Commons and the Institute for the Future's Future Commons to study human cooperation and envision the future of cooperation. Jim's tags: Gray Hill Solutions (www.grayhillsolutions.com), Jim's Blog (http://ourfounder.typepad.com), Cooperation Commons (www.cooperationcommons.org), Institute for the Future (www.iftf.org).
More Stories By Jay Fienberg
Jay Fienberg is co-founder of Juxtaprose where he designs information architecture and user experience for websites and information systems.
He specializes in design for content and information-rich websites and web-based social and collaboration systems. His current preferred CMS is ExpressionEngine, though he also works with Wordpress and Joomla, and still gets called upon to make SharePoint do interesting CMS-like things for enterprise intranets.
Since the early 1990s, Jay also has designed and developed hypertext, database, and content management systems and worked in a wide range of programming languages including XML, SQL, SGML, Python, PHP, Javascript, Java, HTML, CSS and APL.
For anything more official, please visit jayfienberg.com.
- Cloud Computing on Gartner's Top 10 List and SYS-CON Events' 2010 Calendar
- Confessions of a Ulitzer Addict
- IBM Hardware Chief, Intel VC Exec Arrested in Insider Trading Scam
- My Thoughts on Ulitzer
- Tactical Cloud Computing Panel at 1st Annual GovIT Expo
- Ulitzer.com Named Exclusive "New Media" Sponsor of Cloud Computing Conference & Expo
- Moving Your RIA Apps into the Cloud: Seven Challenges
- Adobe’s Aiming ColdFusion at Multiple Clouds
- Windows 7 – Microsoft’s First Step to the Cloud
- Ulitzer Provides a Powerful Social Journalism Platform
- Jill Tummler Singer, Deputy CIO of CIA, Keynotes at GovIT Expo
- Open Source Mobile Cloud Sync and Push Email
- Practical Approaches for Optimizing Website Performance
- The Difference Between Web Hosting and Cloud Computing
- Cloud Computing on Gartner's Top 10 List and SYS-CON Events' 2010 Calendar
- Ajax in RichFaces 3.3, JSF 2 and RichFaces 4
- Confessions of a Ulitzer Addict
- IBM Hardware Chief, Intel VC Exec Arrested in Insider Trading Scam
- My Thoughts on Ulitzer
- Tactical Cloud Computing Panel at 1st Annual GovIT Expo
- US Post Office Hops a Ride on NetSuite’s Cloud
- Ulitzer.com Named Exclusive "New Media" Sponsor of Cloud Computing Conference & Expo
- WPF Controls by DevExpress
- Moving Your RIA Apps into the Cloud: Seven Challenges
- Building a Drag-and-Drop Shopping Cart with AJAX
- What Is AJAX?
- Google Maps! AJAX-Style Web Development Using ASP.NET
- Flashback to January 2006: Exclusive SYS-CON.TV Interviews on "OpenAjax Alliance" Announcement
- AJAXWorld Conference & Expo to Take Place October 2-4, 2006, at the Santa Clara Convention Center, California
- AJAX Sponsor Webcasts Are Now Available at AJAXWorld Website
- How and Why AJAX, Not Java, Became the Favored Technology for Rich Internet Applications
- "Real-World AJAX" One-Day Seminar Arrives in Silicon Valley
- AJAXWorld University Announces AJAX Developer Bootcamp
- AJAX Support In JadeLiquid WebRenderer v3.1
- Where Are RIA Technologies Headed in 2008?
- Struts Validations Framework Using AJAX





























