YOUR FEEDBACK
Immo Huneke wrote: A well written article, an ingenious solution to a real problem often encountere...
Cloud Computing Conference
March 30 - April 1, New York
Register Today and SAVE !..

SYS-CON.TV

2008 East
DIAMOND SPONSOR:
Data Direct
Frontiers in Data Access: The Coming Wave in Data Services
PLATINUM SPONSORS:
Red Hat
The Opening of Virtualization
Intel
Virtualization – Path to Predictive Enterprise
Green Hills
IT Security in a Hostile World
JBoss / freedom oss
Practical SOA Approach
GOLD SPONSORS:
Software AG
The Art & Science of SOA: How Governance Enables Adoption
PlateSpin
Effective Planning for Virtual Infrastructure Growth
Fujitsu
Automated Business Process Discovery & Virtualization Service
Ceedo
Workspace Virtualization
Click For 2007 West
Event Webcasts

2008 East
PLATINUM SPONSORS:
Appcelerator
Think Fast: Accelerate AJAX Development with Appcelerator
GOLD SPONSORS:
DreamFace Interactive
The Ultimate Framework for Creating Personalized Web 2.0 Mashups
ICEsoft
AJAX and Social Computing for the Enterprise
Kaazing
Enterprise Comet: Real–Time, Real–Time, or Real–Time Web 2.0?
Nexaweb
Now Playing: Desktop Apps in the Browser!
Sun
jMaki as an AJAX Mashup Framework
POWER PANELS:
The Business Value
of RIAs
What Lies Beyond AJAX?
KEYNOTES:
Douglas Crockford
Can We Fix the Web?
Anthony Franco
2008: The Year of the RIA
Click For 2007 Event Webcasts
TOP THREE LINKS YOU MUST CLICK ON


AJAX Load Buddies
When the AJAX request is complete, it calls the display() function

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.

Load Buddies
The load buddies function uses the Prototype library's Ajax.Request() to get the user's buddy list, which includes the current status of each buddy. Again, we use Prototype's bindAsEventListener to ensure that, when the AJAX request is complete, it calls the display() function on the instance of the Buddies object that started the AJAX request.

This load() function gets called regularly by our refresh() loop. And, it's not until refresh() is called for the first time that the buddy list is loaded. This is why refresh() is called at the end of the login() function, see Listing12.13b.

Listing 12.18 Load

load: function () {
buddylistURL = baseURL + '/' + responseFormat+ '/' + activeUser+'/buddies';
log('Loading Buddies', 'debug');

new Ajax.Request(
   buddylistURL,
   {
     method: 'get',
     parameters: '',
     onComplete: this.display.bindAsEventListener(this)
   });
},

As you can see in the example, there are no parameters to pass in the request; all of the values the API requires get encoded in the request URL.

This function makes requests that look like /im.php/html/John/buddies.

Display Buddies
The display() function takes the response returned by the AJAX call initiated in load(), and then actually writes it into the HTML. We use the Prototype library's Element.update() method to display these results.

We also attach a new Event listener to each buddy name that appears in the buddy list. These listen for the "click" event, and trigger our goChat() function (see Listing 12.23).

You will notice that we also use two nice features of the Prototype library: document.getElements-ByClassName() method and the Ruby-style iterator that runs through the buddy list.

Listing 12.19 Display

budlist: '',
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);
     });
},

Change Status
The changeStatus() function is what's called on the change event to the <select id="status"> control. This function captures selected options and calls the Buddies object postStatusUpdate() method, which we'll explore next.

If the user sets the status to "Offline", this function also calls the global logout() function (which we'll cover in more detail in Listing 12.34), which more fully "takes the user offline" from the point of view of both the IM Client and the IM Server.

Listing 12.20 Change Status

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();
   }
},

Notice how the "this" keyword refers to the instance of our Buddies object, and not the HTML element the Event listener is bound to. Since we do need to access that HTML element, we do so through the object's this.statusControl property, in which we stored that HTML element exactly for this kind of access to it.

Also note: we have a status_msg variable set to the empty string. We had planned to add the means for the user to set a comment when they updated their status. This feature is built-out in the IM Server code, and there are stubs for it in the Buddies object. But, it was not built into the interface, and so there is currently no way for the user to set a comment.

Post Status Update
Our postStatusUpdate() function uses the Prototype library's Ajax.Request to update the server with the user's status. This function is called both after login (which we discussed earlier, see Listing 12.17), and each time the user changes their status as described in the previous section.

In this case, we are using the POST method on the AJAX requests, and so we use the postBody option to set the request parameters. Again note that in our current implementation, status_msg passed to this function will always be the empty string. But, you can see in this code that this value is encoded and sent to the server.

Listing 12.21 Post Status Update

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');
},

Destroy Buddies
We also have added a destroy() function to the Buddies class to handle clean-up on log out. Naturally, this method will be called by our logout() function that we'll discuss in Listings 12.34.

Besides setting the buddy list display style to "none" in order to hide the buddy window, this function has the important job of stopping all of the Event listeners we've added.

To do this, we use the Prototype library's Event.stopObserving() function, which has the reverse effect of Event.observe().

This is a context where, again, there can be issues around the binding of the "this" keyword. Our this.changeStatusObserver property stores the properly referenced object that needs to be passed into Event.stopObserving(), and we recommend you compare how we use Event.observe() in Listing 12.17 with how we use Event.stopObserving() in Listing 12.22.

Listing 12.22 Destroy

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);
}

Go Chat
Now we get to the exciting part, our goChat() function that connects the buddy list with the chat windows. This function is bound to the click event on each buddy's name in the buddy list via Event.observe(), as seen in Listing 12.17.

Because goChat() does some management of our chat windows, we decided not to encapsulate it within the Buddies object. Conceivably, one could encapsulate this in Buddies, and each chat window would then be an object dependent on the buddy list.

This dependency is somewhat the case, but we felt there might be other cases when chat windows are managed outside of the buddy list. So, we made goChat() an independent function.

Becuase goChat() is bound to the HTML elements that represent the buddy names, the "this" keyword refers to the element where the event was triggered.

Listing 12.23 Go Chat

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();
}

In Listing 12.10c, we showed the HTML returned from the server, and how each buddy name is embedded in the DIV id, like: <div class="budlisting" id="buddy_Ringo">.

In goChat() we use these IDs to uniquely identify the chat windows. We have an array windowMgr that is defined globally in im.js, and we check to see if windowMgr[this.id] exists, where this.ID is an ID like buddy_Ringo.

If it does not already exist, we append a new item on the windowMgr array identified by the ID, and we set this new item to be a ChatWindow() object (which we'll cover below, Listing 12.24-32).

We also have a windowsUsed counter variable that is global in im.js. We use this to give each chat window a number. These numbers, each of which we call a "handle," connect the ChatWindow object with the "index" of the HTML elements that we described at the beginning of the chapter, in Listing 12. 5.

Finally, we call the load() and openOrFocus() methods on the chat window.

ChatWindow Objects
Like our Buddies object, the ChatWindow objects utilize the Prototype library's extended class features, notably the initialize() method that executes at the time of object creation.

Also like the Buddies class, the ChatWindow class' initialize() method sets a number of properties that we'll use in other methods of the ChatWindow, and also sets a number of Event listeners.

The Event listeners in the ChatWindow listen for the following:

  • Click on the Send button
  • Hit the enter key in the input field
  • Focus on the input field
  • Blur on the input field
  • Click the close button on the chat window
Listing 12.24 Initialize

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);
     }
   }

One trick we do in intialize() is parse out the name of the buddy from the ID that is passed to the ChatWindow at the time of object creation. Our IDs look like buddy_Ringo, and we just parse out what comes after the buddy_ prefix and take that to be the buddy name. This name is used in requests to the server as the "to" parameter.

Open or Focus Chat Windows
By default, as set in our im.css, our chat windows are not displayed. When the user clicks on a buddy name, we then display (open) the chat window. If a user clicks on a buddy name where there is already a chat window visible for that buddy, we shift focus to the already open window.

Listing 12.25 Open or Focus

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();
   }
},

Load Chat Messages
The load() function uses the Prototype library's Ajax.Request() to get the messages between the user and the buddy associated with the chat window. Again, we use Prototype's bindAsEventListener to ensure that, when the AJAX request is complete, that it calls the display() function on the instance of the ChatWindow object that started the AJAX request.

This load() function gets called regularly by our refresh() loop.

Listing 12.26 Load

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)
   });
},

Note that the requests to the server will be in the form /im.php/html/John/msgs/Clarence.

Display Messages
The display() function takes the response returned by the AJAX call initiated in load(), and then actually writes it into the HTML. We use the Prototype library's Element.update() method to display these results.

We also scroll the chat window to the bottom.

Listing 12.27 Display

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;
},

Chat Send Message
There are two ways the user can initiate the sending of a message that they have typed in the chat window: they may hit the "send" button, or hit the enter key while their cursor is in the text input.

Our chatSent() function captures these events and then calls the object's sendMessage() method.

Notice how the Event is passed as a parameter to this function. This is a feature of Prototype's bindAsEventListener() method that we're using. It makes the "this" keyword refer to an object other than the HTML Element to which the Event is bound, but it also passes that Event as a parameter so it still can be accessed.

Listing 12.28 Chat Send

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();
   }
},

Another small feature of this function is that it resets the value of the text input and resets the focus on the input after the message is sent. This is an expected behavior that must be explicitly created - especially for the case where the user is clicking the send button, which otherwise has no connection to this input.

Send Message
The sendMessage() function uses the Prototype library's Ajax.Request to update the server with the new message.

In this case, we are again using the POST method on the AJAX requests, and so we use the postBody option to set the request parameters.

Listing 12.29 Send Message

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');
},

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.

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.

LATEST AJAXWORLD RIA STORIES
Curl announced the release of Curl Data Kit Data Services (CDK-DS) for enterprise developers building new applications using Adobe Flex or Flash, as well as developers upgrading existing Curl applications. This addition to the Curl Rich Internet Application (RIA) Platform is an i...
rPath and WANdisco today announced that WANdisco has selected the rPath rBuilder and rPath Lifecycle Management Platform to build and maintain its Subversion MultiSite solution as a manageable set of application images for delivery in virtualized and cloud-based environments. rPa...
MuleSource has announced a partnership with FastConnect that will provide Mule architecture and implementation services throughout the French market. FastConnect spans the domains of data and service integration, through to the user interface, using technologies such as SOA, dist...
Adobe and Intel plan to collaborate on porting Adobe’s Flash widgetry to Intel’s Media Processor CE 3100, a way to put Flash-enhanced web content and rich Flash applications on television. The chip is bound for cable set-top boxes, Blu-ray Disc players, digital TVs and retail...
Here, SYS-CON's Web 2.0 Journal has asked a selection of the industry's brightest minds what their own advice would be in these troubled times, and assembled it into a ten-point guide for software vendors, entrepreneurs, and startups to riding out a recession.
SUBSCRIBE TO THE WORLD'S MOST POWERFUL NEWSLETTERS
SUBSCRIBE TO OUR RSS FEEDS & GET YOUR SYS-CON NEWS LIVE!
Click to Add our RSS Feeds to the Service of Your Choice:
Google Reader or Homepage Add to My Yahoo! Subscribe with Bloglines Subscribe in NewsGator Online
myFeedster Add to My AOL Subscribe in Rojo Add 'Hugg' to Newsburst from CNET News.com Kinja Digest View Additional SYS-CON Feeds
Publish Your Article! Please send it to editorial(at)sys-con.com!

Advertise on this site! Contact advertising(at)sys-con.com! 201 802-3021

Click Here

SYS-CON FEATURED WHITEPAPERS

ADS BY GOOGLE