API Version: Development

Chat Client

Downloads

Explained

This is an example of a very simple, but fully functional chat application. It demonstrates use of the chat subsystem including opening, closing and listening to rooms and posting messages in them.
/* API is ready, loop through the list of contacts */
IPCortex.PBX.contacts.forEach(
    function(contact) {
        /* Listen for updates in case the user changes state */
        contact.addListener('update', function() {
            processContact(contact);
        });
        processContact(contact);
    }
);

/*
 * Enable chat subsystem. This call provides a shortcut for providing a new-room callback.
 * Enabling chat automatically makes the current user 'online'
 */
console.log(TAG, 'Chat enable');
IPCortex.PBX.enableChat(processRoom);
This first piece of code runs once the API is running and the live data feed has started. First it gathers a list of contacts currently on the system, and registers an 'update' event to call processContact() which manages the contact's presence in the DOM based on their availability.
Once that is complete, IPCortex.PBX.enableChat() is called with a callback parameter of processRoom. This both enables the chat subsystem and sets a new-room listener event to call processRoom. An alternate mechanism for detecting new chat rooms is used in the Video Client sample.
Let's look at processContact():
function processContact(contact) {
    /* Return early if this is "myself" */
    if ( contact.cID == IPCortex.PBX.Auth.id ) {
        return;
    }
    /* Return early if contact exists */
    var element = document.getElementById(contact.cID);
    if ( element ) {
        /* Remove offline contacts */
        if ( ! contact.canChat && element.parentNode )
            element.parentNode.removeChild(element);
        return;
    }
    /* Create online contact */
    if ( contact.canChat ) {
        var element = document.createElement('div');
        document.getElementById('contacts').appendChild(element);
        element.innerHTML = contact.name;
        element.className = 'contact';
        element.id = contact.cID;
        var offer = document.createElement('i');
        element.appendChild(offer);
        offer.className = 'material-icons contact-offer';
        offer.innerHTML = 'chat_bubble';
        offer.addEventListener('click', function() {
            contact.chat();
        });
    }
}
This simple function first checks whether the supplied contact is the logged-in user. If so, it is ignored as you cannot usefully chat to yourself.
It then handles updates to existing contacts. If an element already exists then we know that the contact has previously been created, but if it is not longer possible to chat to that user (checked with contact.canChat) then the contact should be removed from the webpage.
Finally it adds a new contact to the page if they are currently 'chattable'. An event listener for 'click' is added to the contact's chat icon, which directly calls contact.chat() - Because of Javascript closure and scope rules (read this if you need to brush up), each 'click' event will reference the correct contact object, and therefore call the correct chat() method with no further processing.
processRoom() is called whenever a new room is created, and we can break it down into smaller chunks as follows:
function processRoom(newRoom) {
    /*
     * 1 - Add a listener for all future room updates, such as new messages.
     * 2 - Handle the creation of the new room in the DOM
     * 3 - Save some useful information about the room
     * 4 - Enable room specific 'click' events for the room buttons.
     */
}

1 - Add a listener for all future room updates, such as new messages.

Here we see addListener being used on the new room to register for 'update' events. In this case, the events we handle are the 'death' of the room, in which case it is removed from the DOM; the setting or changing of the room name, which is injected into the DOM; and the arrival of messages which are placed into the <textarea>.
When a room is closed, the DOM elements are removed by the application, and the room itself will be destroyed by the API code internally. This will result in all remaining references being cleaned up so that the Javascript garbage collector can free up all memory related to the chatroom.
NOTE: This callback function could be further simplified - All of the details about the room could be stored in-scope on the thisRoom variable, making the use of the global room object un-necessary. The technique of storing the data externally is used, because a larger scale application would quickly become unwieldy if all such functions were declared inline as below.
newRoom.addListener('update', function(room) {
    /* If a room is dead, delete from DOM */
    if ( rooms[room.roomID] && room.state == 'dead' ) {
        rooms[room.roomID].elem.parentNode.removeChild(rooms[room.roomID].elem);
        delete rooms[room.roomID];
        return;
    }
    /* If room name has changed, update on display */
    if ( rooms[room.roomID].label !== room.label ) {
        rooms[room.roomID].label = room.label;
        rooms[room.roomID].title.innerHTML = room.label + ' (' + newRoom.roomID + ')';
    }
    /* Add new messages into textarea */
    room.messages.forEach(function (message) {
        rooms[room.roomID].text.value += '\n' + message.cN + ' :\n  ' + message.msg;
        rooms[room.roomID].text.scrollTop = rooms[room.roomID].text.scrollHeight;
    });
});

2 - Handle the creation of the new room in the DOM

Rather than construct a new set of DOM elements for the room, a hidden <div> is cloned and then updated to have a unique ID.
/* For each new room we need to store the provided Class object
 * and create display elements
 */
var elem = document.getElementById('clone').cloneNode(true);
elem.style.display = 'block';
elem.id = newRoom.roomID;
document.body.appendChild(elem);

3 - Save some useful information about the room

Finding elements in the DOM can be time consuming. While this is not a big issue, it feels cleaner to keep a record of where our 'title' and 'text area' are in the DOM as they may be updated regularly. These are values that will be used in item (1) above.
/* Store important room info needed as events occur */
var thisRoom = rooms[newRoom.roomID] = {
    room: newRoom,
    elem: elem,
    title: elem.getElementsByTagName('div')[0],
    text: elem.getElementsByTagName('textarea')[0]
};

4 - Enable room specific 'click' events for the room buttons.

The variable thisRoom declared in part 3 above will remain in scope for the life of the room, allowing it to be referenced within the 'click' events for the 'close' and 'send' buttons.
Notice that the DOM is not updated by these methods. To close a room, we simply request to room.leave() which will subsequently cause a 'dead' update event to arrive and clean up the room. Similarly the sent message is not put into the textarea, but instead it is sent with room.post(), and will arrive through the update event to be handled accordingly.
/* Quick way to get hold of input box and submit buttons */
var inputs = elem.getElementsByTagName('input');
inputs[0].addEventListener('click', function() {
    console.log(TAG, 'Close room', thisRoom.room.roomID);
    thisRoom.room.leave();
});
inputs[2].addEventListener('click', function() {
    console.log(TAG, 'Send message on room', thisRoom.room.roomID);
    /* No text entered, nothing to send */
    if ( ! inputs[1].value )
        return;
    /* Send the message and clear the input */
    thisRoom.room.post(inputs[1].value);
    inputs[1].value = '';
});