Windows Live Messenger Web Controls – Part 2

Hopefully you will have read and followed the code from the first part of this article. The first part covered most of base functionality including how to sign in, get the users details and get their contact list. What is left is how to actually carry out a conversation with a contact.

If you remember from the first article, in the signInCompleted event handler we defined three delegates to handle various events. The first part of this article talked about the first two of these delegates, and that leaves the last one to talk about :-

_user.get_conversations().add_propertyChanged(Delegate.create(null, conversation_collectionChanged));

This code adds an event handler that will call the conversation_collectionChanged() event handler whenever any of the conversations that you currently have ongoing changes.

function conversation_collectionChanged(sender,e) {
   displayConversations();
}

 

Our event handler simply calls another method in which we will display the conversation. Again we are using a separate function as this function is used in multiple places so it makes sense to separate it out rather than code directly in the event handler itself.

function displayConversations() {
   convArray = new Array();
   var sb = new StringBuilder('<p><b>Active Conversations: (click a conversation to resume):</b></p>');
   var item = 0;
   var enum1 = _user.get_conversations().getEnumerator();
   while (enum1.moveNext()) {
    var c = enum1.get_current();
    convArray.push(c + ":" + item);
    if (c.get_closed())
       continue;
    sb.append(convLink(c, item));
    sb.append("<hr />");
    item++;
   }
   document.getElementById('divConversations').innerHTML = sb.toString();
}

 

The displayConversations() method does exactly what it’s name suggests, it displays a list of all the conversations that you currently have ongoing. What it does not do however is display the actual conversation itself, just the list of whom you are having conversations with.
Like so many things in the Windows Live Writer API conversations are a collection. So in this code we simply loop through the conversation collection. For each individual conversation item we make a call to convLink() to extract some information from the conversation, and make a list of current conversations taking place which we then display in the conversations zone we defined the main body of the html page.

function convLink(c, item)
{
   var roster = c.get_roster();
   var enum1 = roster.getEnumerator();
   var names = new Array();
 
while (enum1.moveNext())
{
   var dispName = enum1.get_current().get_presence().get_displayName();
   var dispEmail = enum1.get_current().get_address();
   if (dispName !== '') {
    names.push(dispName);
   } else {
    names.push(dispEmail);
   } 
}
 
   var sb = new StringBuilder();
   sb.append('<a href="javascript:switchConv(' + item + ')">');
   if (c == _conv)
    sb.append('<b>');
    sb.append(names.join(', '));
    if (c == _conv)
       sb.append('</b>');
       sb.append('</a>');
       sb.append('&nbsp;&nbsp;');
       sb.append('<a href="javascript:closeConv(' + item + ')">');
    sb.append('close</a>');
    return sb.toString();
}

 

 

Because you can have conversations with multiple participants, what we do here is get the roster of participants in the conversation from the conversation object that is passed in, loop through that list of participants and extract either display name if they have one or their email address if they don’t. We store that information in an array then create a hyperlink that the user can click on to display that actual conversation. The hyperlink is simply a reference to the switchConv() function which displays the conversation. We also add a close conversation hyperlink in case the uses wishes to exit that conversation. This is passed back to our displayConversations() function which actually displays the information.

function switchConv(id)
{
   var c = _user.get_conversations().get_item(id);
   if (c)
   {
   if (_conv) {
    _conv.remove_messageReceived(_convSink);
   }
   _convSink = Delegate.create(null, recvMsg);
   _conv = c;
   _conv.add_messageReceived(_convSink);
   removeChildrenFromNode('txtConv');
   /* Display all messages from the conversation history. */
   var hist = c.get_history();
   var histEnum = hist.getEnumerator();
   while (histEnum.moveNext()) {
    displayMsg(histEnum.get_current());
   }
   document.getElementById('btnSend').disabled = false;
}
   displayConversations();
   document.getElementById('txtMessage').focus();
}

 

active%20conversations_thumb Developer

Each conversation object in the conversations collection has a unique id to reference it. In the first line we get the actual conversation object from the ID passed into the function. We remove the existing delegate for messages received and setup a new one for this particular conversation. Then we clear out the zone we defined on the page to display the actual conversation.
Next we get a collection of all the conversation items have already taken place in this conversation and simply loop through the conversation history collection, extract out individual conversation item and call a helper function to format the conversation text ready for display on the page. Once we have all the history of that conversation taken care of, we make sure that the button on the page to send a message is enabled, we update the list of the conversations that the user has going and finally we set the focus on the textbox we have defined on the page for the user to enter their message.

function displayMsg(message) {
   var elMsg = message.createTextElement();
   var txtConv = document.getElementById('txtConv');
   var userName = "";
   if (message.get_sender().get_presence().get_displayName() != null)
    userName = message.get_sender().get_presence().get_displayName();
else
    userName = message.get_sender().get_address();
 
txtConv.appendChild(document.createTextNode(userName + ' says: '));
txtConv.appendChild(elMsg);
txtConv.appendChild(document.createElement('br'));
}

 

The helper method to display a message takes in a conversation item, from that we extract the username of the person who posted that particular message or the email address if that person does not have a username defined and format our output similar to what the Windows Live Messenger client displays (if you wish to make it exactly the same you can as the message object also has a property to get the timestamp of the message. See here for details). Once we have formatted the text by displaying the username/email address followed by the actual message we insert the conversation message into the zone we have defined in the actual html of the page.

message%20window_thumb Developer

I’m sure at this point you’ll be glad to hear that we’re nearly finished. Up to now you’ve added code so that the user can login to Windows Live ID, displayed certain information about the user, received and displayed all their contacts within the appropriate groups, displayed a list of conversations that are ongoing and hooked up events various to help with all the above. All that really remains is sending a conversation message, receiving a conversation message and closing a conversation.

If you remember back in our convLink() function, inserted two Javascript function calls into the display. One was to switch conversation which we’ve discussed above, the other was a hyperlink to close a conversation.

sb.append('<a href="javascript:closeConv(' + item + ')">');

 

Here is the code to close a conversation :-

function closeConv(id) {
   var c = _user.get_conversations().get_item(id);
   convArray.splice(id, 1);
   c.close();
   if (c == _conv)
   {
    removeChildrenFromNode('txtConv');
   }
   displayConversations();
   if(_user.get_conversations().get_count() == 0)
    document.getElementById('btnSend').disabled = true;
}

To close a conversation we need the ID of the conversation we are about to close (remember each conversation object within the conversation collection has it’s own unique ID). So first we retrieve the actual conversation object from the ID passed into the function then we call the close() method on the conversation object. Next we do some cleanup work. If the conversation we just closed was the current conversation (i.e. the one the user was currently interacting with) then we clear out all the messages that were displayed in our conversation zone. Next we update the list of conversations that the user has ongoing and finally we disable the send message button if there are no more conversations taking place.

function recvMsg(sender, e) {
   var message = e.get_message();
   displayMsg(message);
   document.getElementById('msgLastRecv').innerText = 'Last message at: ' + _conv.get_history().get_lastReceived().toString();
}

 

In the receive message event handler, we get the message that was received (passed in as the event argument), display the message using the function we discussed above and also notify the user about this message in a zone we’ve defined in our html markup specifically for this purpose. We do this because we might receive a message from a conversation different to that that the user currently has displayed, so we need to inform them that a message from another conversation has been received.

function sendMsg() {
   var txtMessage = document.getElementById('txtMessage');
   var messageText = txtMessage.value;
   var message = new Microsoft.Live.Messenger.TextMessage(messageText, null);
   if (_user) {
    _conv.sendMessage(message, null);
   }
   displayMsg(message);
   txtMessage.value = '';
   txtMessage.focus();
}

 

The sendMsg() function is activated by the send message button defined in our html markup. Here we get the text the user has entered into the message textbox in our main markup and create a new Message object from it. Next we call the sendMessage() method of the conversation object and pass in the new message object we’ve just created. Essentially this adds the message to the conversation history and your message gets passed through the cloud and will display in the contacts messenger.
We then do some housekeeping by adding the new message to our conversation zone, blank off the message textbox where the user enters a message and make sure that that textbox has the focus.

There you have it, a working Windows Live Messenger that you can insert into your own web page :-

working%20messenger_thumb Developer

There are plenty of things you can add to this including adding the ability to add contacts and add nudges etc. In fact the Windows Live Messenger API has all the functions, event handlers etc. in it that you could create essentially an exact copy of the Windows Live Messenger client within your web page.

Here is the completed code that we’ve discussed over the last two articles :-

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>WLMessenger Site</title>
   <script src="http://settings.messenger.live.com/api/1.0/messenger.js" type="text/javascript" language="javascript"></script>
   <script type="text/javascript" language="javascript">
function scriptMain() {
   var privUrl = 'http://[url]/Privacy.htm';
   var chanUrl = 'http://[url]/Channel.htm';
   _signin = new Microsoft.Live.Messenger.UI.SignInControl('signinframe', privUrl, chanUrl, 'en-US');
   _signin.add_authenticationCompleted(Delegate.create(null, authenticationCompleted));
}   
 
function authenticationCompleted(sender, e) {
   _user = new Microsoft.Live.Messenger.User(e.get_identity());
   _user.add_signInCompleted(Delegate.create(null, signInCompleted));
   _user.signIn(null);
}
 
function signOutCompleted(sender, e)
{
 
}
function signInCompleted(sender, e) {
   if (e.get_resultCode() === Microsoft.Live.Messenger.SignInResultCode.success) {
    _user.get_presence().add_propertyChanged(Delegate.create(null, user_Presence_PropertyChanged));
    displayUserInfo();
    _addressList = new Array(_user.get_contacts().get_count());
    var enum1 = _user.get_contacts().getEnumerator();
    while (enum1.moveNext()) {
      var c = enum1.get_current();
      var address = c.get_currentAddress();
      address.get_presence().add_propertyChanged(Delegate.create(null, presence_PropertyChanged));
    }
    displayContacts();
    var selectStatus = document.getElementById('selectStatus');
    selectStatus.selectedIndex = 6;
    _contactCollection = _user.get_contacts(); 
    _user.get_conversations().add_propertyChanged(Delegate.create(null, conversation_collectionChanged));
   }
}
 
    /* Called by the Messenger Library on remote sign-out (i.e. user logged into Messenger on another computer). */
    function onSignedOutRemotely(sender, e) {
       alert('You have been signed out remotely.');
    }
 
function conversation_collectionChanged(sender,e) {
   displayConversations();
}
 
/* Called by the Messenger Library when presence status changes occur (i.e. contacts sign in or out). */
function presence_PropertyChanged(sender, e) {
   displayContacts();
}
 
/* Called by the Messenger Library when status changes of the user occur. */
function user_Presence_PropertyChanged(sender, e) {
   displayUserInfo();
   if (_user.get_presence().get_status() === Microsoft.Live.Messenger.PresenceStatus.offline) {
            document.getElementById("divContacts").innerHTML = "";
            document.getElementById("txtConv").innerHTML = "";
      document.getElementById('btnSend').disabled = true;
   }
}
 
/* Populate the user information string. */
function displayUserInfo() {
   var userInfo = document.getElementById('userInfo');
   var userAddress = _user.get_address().get_address();
   var userDispName = _user.get_presence().get_displayName();
   var userPersonalMessage = _user.get_presence().get_personalMessage();
   var userStatus = Enum.toString(Microsoft.Live.Messenger.PresenceStatus, _user.get_presence().get_status());
   var statusLine = document.createElement('p');
   removeChildrenFromNode('userInfo');
   if (userDispName !== '') {
      statusLine.appendChild(document.createTextNode(userDispName + ' (' + userAddress + '): ' + userStatus));
   }
   else {
      statusLine.appendChild(document.createTextNode(userAddress + ': ' + userStatus));
   }
   userInfo.appendChild(statusLine);
   document.getElementById('personalMessage').value = userPersonalMessage;
}
 
    /* Called when the user changes the status drop-down list.
    Messenger changes the status property for the user. */
    function selectStatusChanged() {
       var selectStatus = document.getElementById('selectStatus');
       var presenceStatus = [ Microsoft.Live.Messenger.PresenceStatus.appearOffline, 
       Microsoft.Live.Messenger.PresenceStatus.away, 
       Microsoft.Live.Messenger.PresenceStatus.beRightBack, 
       Microsoft.Live.Messenger.PresenceStatus.busy, 
       Microsoft.Live.Messenger.PresenceStatus.idle, 
       Microsoft.Live.Messenger.PresenceStatus.inACall, 
       Microsoft.Live.Messenger.PresenceStatus.online, 
       Microsoft.Live.Messenger.PresenceStatus.outToLunch ];
  &nb
sp;    if (_user.get_presence().get_status() !== Microsoft.Live.Messenger.PresenceStatus.offline) {
        _user.get_presence().set_status(presenceStatus[selectStatus.selectedIndex]);
       }
    }
 
function displayContacts() {
   var sb = new StringBuilder();
   _addressList = new Array(_user.get_contacts().get_count());
    var groupList = new Array(_user.get_groups().get_count());
   var enum2 = _user.get_groups().getEnumerator();
   var enum1 = _user.get_contacts().getEnumerator();
   var groupindex = 0;
   while (enum2.moveNext())
   {
        var c = enum2.get_current();
        var name = c.get_name();
        sb.append("<div>");
        sb.append("<a href="javascript:ToggleContactGroup('");
        sb.append(name);
        sb.append("');">");
        sb.append("<img class="ContactImage" alt="ToggleContacts" src="minus_icon.gif" id="ContactsExpand");
        sb.append(name);
        sb.append(""/>");
        sb.append("</a>");
        sb.append(name);
        sb.append('<hr />');
        sb.append("<div id='");
        sb.append(name); 
        sb.append("'>");
        var _contacts = new Array(c.get_contacts().get_count());
        var enum3 = c.get_contacts().getEnumerator();
        index = 0;
   while (enum3.moveNext()) {
    var contact = enum3.get_current();
    var address = contact.get_currentAddress();
    var dispName = address.get_presence().get_displayName();
    var currAddress = address.get_address();
    var status = Enum.toString(Microsoft.Live.Messenger.PresenceStatus, address.get_presence().get_status());
        //Change status to an image
        var statusimg = null;
        switch(status)
        {
            case "online":
                statusimg = "<img src='online.gif' class='ContactImage' alt='online' />";
                break;
            case "offline":
                statusimg = "<img src='offline.gif' class='ContactImage' alt='offline' />";
                break;
            case "appearOffline":
                statusimg = "<img src='offline.gif' class='ContactImage' alt='offline' />";
                break;
            case "away":
                statusimg = "<img src='away.gif' class='ContactImage' alt='away' />";
                break;
            case "beRightBack":
                statusimg = "<img src='away.gif' class='ContactImage' alt='beRightBack' />";
                break;
            case "busy":
                statusimg = "<img src='busy.gif' class='ContactImage' alt='busy' />";
                break;
            case "idle":
                statusimg = "<img src='away.gif' class='ContactImage' alt='idle' />";
                break;
            case "inACall":
                statusimg = "<img src='busy.gif' class='ContactImage' alt='InACall' />";
                break;
            case "outToLunch":
                statusimg = "<img src='away.gif' class='ContactImage' alt='OutToLunch' />";
                break;
            default:
                statusimg = "<img src='offline.gif' class='ContactImage' alt='offline' />";
                break;
            }
    var statusLine = '';
    var strDelete = '';
    _a
ddressList[index] = address;
    if (dispName !== '') {
   statusLine = '<a href='javascript:createConv(' + index + ')'>' + statusimg + dispName + '</a>';
}
else {
   statusLine = '<a href='javascript:createConv(' + index + ')'>' + statusimg + currAddress + '</a>';
}
    sb.append(statusLine);
    sb.append('<hr />');
 
    index++;
   }
        sb.append("</div></div>");
        groupindex++;
   }
   document.getElementById('divContacts').innerHTML = sb.toString();
}
 
      /* Update the user's personal status message. */
      function setPersonalMessage() {
      var personalMessage = document.getElementById('personalMessage');
      var messageText = personalMessage.value;
        messageText = messageText.replace(/</g, "").replace(/>/g, "");
        if (_user) {
_user.get_presence().set_personalMessage(messageText);
        }
      }
 
      function createConv(index) {
   var newConv = _user.get_conversations().create(_addressList[index]);
   var convId = _user.get_conversations().get_count();
   switchConv(newConv.convId); 
}
 
/* Send a message in the current conversation. */
function sendMsg() {
   var txtMessage = document.getElementById('txtMessage');
   var messageText = txtMessage.value;
   var message = new Microsoft.Live.Messenger.TextMessage(messageText, null);
   if (_user) {
    _conv.sendMessage(message, null);
   }
   displayMsg(message);
   txtMessage.value = '';
   txtMessage.focus();
}
 
/* Called by the Messenger Library when a new message is received. */
function recvMsg(sender, e) {
   var message = e.get_message();
   displayMsg(message);
   document.getElementById('msgLastRecv').innerText = 'Last message at: ' + _conv.get_history().get_lastReceived().toString();
}
 
/* Show text for a received message in the conversation window. */
function displayMsg(message) {
   var elMsg = message.createTextElement();
   var txtConv = document.getElementById('txtConv');
   var userName = "";
   if (message.get_sender().get_presence().get_displayName() != null)
    userName = message.get_sender().get_presence().get_displayName();
else
    userName = message.get_sender().get_address();
 
txtConv.appendChild(document.createTextNode(userName + ' says: '));
txtConv.appendChild(elMsg);
txtConv.appendChild(document.createElement('br'));
}
 
function displayConversations() {
   convArray = new Array();
   var sb = new StringBuilder('<p><b>Active Conversations: (click a conversation to resume):</b></p>');
   var item = 0;
   var enum1 = _user.get_conversations().getEnumerator();
   while (enum1.moveNext()) {
    var c = enum1.get_current();
    convArray.push(c + ":" + item);
    if (c.get_closed())
       continue;
    sb.append(convLink(c, item));
    sb.append("<hr />");
    item++;
   }
   document.getElementById('divConversations').innerHTML = sb.toString();
}
 
function convLink(c, item)
{
   var roster = c.get_roster();
   var enum1 = roster.getEnumerator();
   var names = new Array();
 
while (enum1.moveNext())
{
   var dispName = enum1.get_current().get_presence().get_displayName();
   var dispEmail = enum1.get_current().get_address();
   if (dispName !== '') {
    names.push(dispName);
   } else {
    names.push(dispEmail);
   } 
}
 
   var sb = new StringBuilder();
   sb.append('<a href="javascript:switchConv(' + item + ')">');
   if (c == _conv)
    sb.append('<b>');
    sb.append(names.join(', '));
    if (c == _conv)
       sb.append('</b>');
       sb.append('</a>');
       sb.append('&nbsp;&nbsp;');
       sb.append('<a href="javascript:closeConv(' + item + ')">');
    sb.append('close</a>');
    return sb.toString();
}
 
function switchConv(id)
{
   var c = _user.get_conversations().get_item(id);
   if (c)
   {
   if (_conv) {
    _conv.remove_messageReceived(_convSink);
   }
   _convSink = Delegate.create(null, recvMsg);
   _conv = c;
   _conv.add_messageReceived(_convSink);
   removeChildrenFromNode('txtConv');
   /* Display all messages from the conversation history. */
   var hist = c.get_history();
   var histEnum = hist.getEnumerator();
   w
hile (histEnum.moveNext()) {
    displayMsg(histEnum.get_current());
   }
   document.getElementById('btnSend').disabled = false;
}
   displayConversations();
   document.getElementById('txtMessage').focus();
}
 
/* Close the current conversation. */
function closeConv(id) {
   var c = _user.get_conversations().get_item(id);
   convArray.splice(id, 1);
   c.close();
   if (c == _conv)
   {
    removeChildrenFromNode('txtConv');
   }
   displayConversations();
   if(_user.get_conversations().get_count() == 0)
    document.getElementById('btnSend').disabled = true;
}
 
/* Clear all children from the specified node 
(i.e. clear messages from the conversation window). */
function removeChildrenFromNode(id){
   var node = document.getElementById(id);
   if(node == undefined || node == null)
    {
       return;
    }
   var len = node.childNodes.length;
   while (node.hasChildNodes())
   {
    node.removeChild(node.firstChild);
   }
}
 
function ToggleContacts()
{
    var ContactImg = document.getElementById("ContactsExpand");
    var Contacts = document.getElementById("divContacts");
    if(ContactsOpen == true)
    {
        ContactImg.src = "plus_icon.gif";
        ContactsOpen = false;
        Contacts.style.display = 'none';
    }
    else
    {
        ContactImg.src = "minus_icon.gif";
        ContactsOpen = true;
        Contacts.style.display = 'block';
    }
}
 
function ToggleContactGroup(elm)
{
    var divtag = document.getElementById(elm);
    var imgtag = "ContactsExpand" + elm;
    var groupimg = document.getElementById(imgtag);
    var lastslash = groupimg.src.lastIndexOf("/");
    var imgcompare = groupimg.src.substr(lastslash + 1, groupimg.src.length - lastslash);
    if(imgcompare == "minus_icon.gif")
    {
        groupimg.src = "plus_icon.gif";
        divtag.style.display = 'none';
    }
    else
    {
        groupimg.src = "minus_icon.gif";
        divtag.style.display = 'block';
    }
}
 
_user = null;
_signin = null;
_addressList = null;
_contactCollection = null;
_conv = null;
_convSink = null;
convArray = null;
ContactsOpen = true;
</script>
</head>
<body onload="scriptMain()">
    <form id="form1" runat="server">
   <div id="msgr">
   <table>
   <tr><td>
   <div id="signinframe"></div>
   </td></tr>
   <tr><td>
   <div id="userInfo"></div>
   </td></tr>
   <tr><td>
   <div id="setUserStatus">
      <span><b>Change Your Status:</b></span>
      <select id="selectStatus" onchange="selectStatusChanged()">
        <option>Appear Offline</option>
        <option>Away</option>
        <option>Be Right Back</option>
        <option>Busy</option>
        <option>Idle</option>
        <option>In a Call</option>
        <option>Online</option>
        <option>Out to Lunch</option>
      </select>
   </div>
   </td></tr> 
   <tr><td>
   <div id="setPersonalMessage">
   <span><b>Personal Message: </b></span> <input id="personalMessage" type="text" />
   <input onclick="setPersonalMessage()" id="btnSetPersonalMessage" type="button" value="Set" />
   </div>
   </td></tr>
   <tr><td>
   <div id="sendMessage">
    <hr />
    <span><b>Send a Message:</b></span>
    <p id="contactLabel"></p>
    <p id="msgLastRecv"></p>
    <div id="txtConv"></div><br />
    <input id="txtMessage" type="text"/><br />
    <input onclick="sendMsg()" id="btnSend" type="button" value="Send Message" disabled="disabled" />
   </div>
   </td></tr>
   <tr><td>
   <div id="divConversations"></div>
   </td></tr>
   <tr><td>
        <div id="Contacts">
            <p><b>Contact List
                <a href="Javascript:ToggleContacts();">
                <img alt="ToggleContacts" src="minus_icon.gif" id="ContactsExpand" class="ContactImage" />
                </a>
            </b></p>
               <div id="divContacts"></div>
        </div>
   </td></tr> 
   </table>
   </div>
    </form>
</body>
</html>

In the meantime, I’ll continue to go through the new Windows Live Messenger API and have another article soon describing how to add additional functionality to your web based Windows Live Messenger.