| By James Benson, Jay Fienberg | Article Rating: |
|
| May 16, 2007 11:00 AM EDT | Reads: |
5,212 |
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,212
Copyright © 2007 SYS-CON Media, Inc. — All Rights Reserved.
Syndicated stories and blog feeds, all rights reserved by the author.
About 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).
About Jay Fienberg
Jay Fienberg is co-founder of Juxtaprose (www.juxtaprose.com) where he designs information architecture and user experience for Websites and information systems. He specializes in design for enterprise-scale, Web-based social and collaboration systems. Since the early 1990s, Jay has also 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. Jay has a number of blogs, websites, and online projects available via jayfienberg.com.
- AJAX World RIA Conference & Expo Kicks Off in New York City
- What is Web 3.0?
- AJAXWorld RIA Conference & Expo 2009 West: Call for Papers
- AJAX and RIA 2009: More Choices, Tough Decisions
- Ulitzer’s Amazing First 30 Days in Public Beta
- SYS-CON Announces Government IT Conference & Expo
- RIAs for Web 3.0 Using the Microsoft Platform
- REA Is Where RIA Becomes the Norm
- Why an Application Grid?
- 2nd International Cloud Computing Expo New York Photo Album
- AJAX World RIA Conference & Expo Kicks Off in New York City
- What is Web 3.0?
- Developing Rich Client Applications Using Swing - II
- AJAXWorld RIA Conference & Expo 2009 West: Call for Papers
- AJAX and RIA 2009: More Choices, Tough Decisions
- AJAX World RIA Conference Awards Announced
- WebORB Launched for Flex, Flash, AJAX and Silverlight
- Appcelerator Revolutionizes UI Prototyping
- Adobe Takes LiveCycle into the Cloud
- Ulitzer’s Amazing First 30 Days in Public Beta
- 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





































