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.
IPCortex.PBX.contacts.forEach(
function(contact) {
contact.addListener('update', function() {
processContact(contact);
});
processContact(contact);
}
);
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) {
if ( contact.cID == IPCortex.PBX.Auth.id ) {
return;
}
var element = document.getElementById(contact.cID);
if ( element ) {
if ( ! contact.canChat && element.parentNode )
element.parentNode.removeChild(element);
return;
}
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.
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 ( rooms[room.roomID] && room.state == 'dead' ) {
rooms[room.roomID].elem.parentNode.removeChild(rooms[room.roomID].elem);
delete rooms[room.roomID];
return;
}
if ( rooms[room.roomID].label !== room.label ) {
rooms[room.roomID].label = room.label;
rooms[room.roomID].title.innerHTML = room.label + ' (' + newRoom.roomID + ')';
}
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.
var elem = document.getElementById('clone').cloneNode(true);
elem.style.display = 'block';
elem.id = newRoom.roomID;
document.body.appendChild(elem);
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.
var thisRoom = rooms[newRoom.roomID] = {
room: newRoom,
elem: elem,
title: elem.getElementsByTagName('div')[0],
text: elem.getElementsByTagName('textarea')[0]
};
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.
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);
if ( ! inputs[1].value )
return;
thisRoom.room.post(inputs[1].value);
inputs[1].value = '';
});