Home
Demo
Download
Documentation
Community
Credit

 

ICEspresso Chat Framework

 

Developer Guide

Demo Codes

Support Resources

Latest Blog Posts

powered by Push4Free.com

Docs > Tutorial

ICEspresso Chat Tutorial

This tutorial is an extension to the Getting Started section. It will guide you through the steps to creating a real-time based chat room. Each step will be explained in greater details to reveal the underlying mechanism.

This tutorial is not exhaustive in terms of the number of core functions covered; it is designed to let developer quickly start developing real-time chat applications. We provide ICEspresso API Reference which is relatively complete in that regard.

Advanced topics such as session support between pages and friend social systems will not be discussed in this tutorial. Developers are encouraged to read the FAQ sections when they are comfortable with the Framework basics.

Throughout the tutorial, code snippets will accompany each function topic in discussion. The term "we" refers to the application builder whereas application user refers to those who using the final application.

You are encouraged to try it out to get a feeling on ICEspresso-powered chat room.

 

>>>  Download tutorial source code tut.html only (right-click then save as)

>>>  Download ICEspresso Chat Framework Package (in case you have no *.js library)

>>>  Play the live demo version

 

Table of Contents

Before We Start

Acknowledgement

The ICEspresso Chat Framework is based on JavaScript programming language and is designed with object oriented programming concept in mind. You will find it easier to understand the example when possessing the aforementioned skills. Having experiences in asynchronous programming will be a plus too as ICEspresso Chat Framework's event model relies on callback handlers.

The ICEspresso Chat Framework package is available for download from SourceForge.net. You will need icespressochat.js, swfobject.js, json2.js, and pjax.swf from the package.

The actual network communication is handled by pjax.swf, an iPush Flash agent (detail information). As such, a web server is needed to host application even in development stage because of Flash's security issue (file:// protocol is under-privileged to execute Flash networking functions).

Connectivity to Internet is required in order for application to exchange data with ICEspresso Service.

Application Skeleton

This tutorial will build an ICEspresso-powered chat room with simple UI design. You can mix other skills to achieve a higher level of professionalism after learning ICEspresso application's life cycle.

1. Page Layout

We will have several DIVs as content holder. They will be laid out using CSS properties. For the purpose of this tutorial and for what a chat room requires, message content, user list, and chat input control are provided.

<div id="content">
    <!-- chat message list -->
    <div id="chatList"></div>
    <div id="inputFieldContainer">
        <!-- input control -->
        <input type="text" id="inputField" name="inputField" size=20>
    </div>
    <div id="submitButtonContainer">
        <!-- submit button control -->
        <button id="submitButton">Submit</button>
    </div>
    <div id="onlineListContainer">
        <!-- Online user list -->
        <h3>Online users</h3>
        <div id="onlineList"></div>
    </div>
</div>

2. Callback Handlers

ICEspresso Chat Framework is event-triggered, thus relying on the callback handlers as event sink. There are three callback handlers, respectively icespresso.onSystemEvent, icespresso.onChatEvent, and icespresso.onFriendEvent, that are pre-declared and are implemented in this tutorial. Although the three callback handlers bear the same interface, they are logically broken into three distinctive functions to be flexible and sensible.

<script language="javascript">
icespresso.onSystemEvent = function(chat, eventId, arg) {
    // event identifier to be added later
}

icespresso.onChatEvent = function(chat, eventId, arg) {
    // event identifier to be added later
}

icespresso.onFriendEvent = function(chat, eventId, arg) {
    // event identifier to be added later
}
</script>

The three arguments, chat, eventId, arg, are passed by the Framework when handler is invoked. chat is the object that manipulates the Framework. eventId denotes the event identifier. arg is a data variable wrapper that varies from event to event.

icespresso.onFriendEvent is related to specific social network software (like facebook or MySpace) or on-line community (like forum or portal) and it not within the scope of this tutorial.

Debug Support

This tutorial is meant be to walk developers through the developing stage. We thus turn on the Framework debug mode with method setDebug() so each behind-the-scene operation generates a message to the log pane for debugging.

var instance=icespresso.initialize();
instance.setDebug(true);

The Framework also provides a static method icespresso.log() which when called the first time, creates a dynamic floating window that displays the messages written to it. Developer should find this logging utility handy when developing and debugging.


(Figure 1. ICEspresso log pane)

Implementing

With the application skeleton in place, we can start building the chat room by adding required operation to communicate with ICEspresso Service.

1. Initialize

In order to manipulate the Framework, we need an instance to do the job. For all ICEspresso-powered application, calling icespresso.initiaize() will always be the first step. Since icespresso.initiaize() is an asynchronous call, the actual initialization completion status is signaled via icespresso.onSystemEvent callback handler.

icespresso.onSystemEvent = function(chat, eventId, arg) {
    switch (eventId) {
        case chat.SYS_INITIALIZED:
        // ICEspresso initialization is complete
        break;
    }
}

var instance=icespresso.initialize();
instance.setDebug(true);

icespresso.initiaize() returns a reference to the instance with which we can use to perform operations on the Framework. This instance is actually kept inside the Framework and is passed to the application whenever a callback handler is invoked. Consider icespresso.onSystemEvent = function(chat, eventId, arg) and var instance = icespresso.initialize(), the chat object in the callback handler and var instance essentially point to the same icespresso instance.

2. Connect

After initialization, we inevitably want to connect to iPush Server. The connect() call is to be placed in the icespresso.onSystemEvent() handler because we cannot make connection before the Framework is initialized.

icespresso.onSystemEvent = function(chat, eventId, arg) {
    switch (eventId) {
        case chat.SYS_INITIALIZED:
        // ICEspresso initialization is complete
        var prop = {
            host: "www.push4free.com",
            port: 443,
            group: "icespresso",
            product: "chat",
            user: "foo",
            password: "bar1"
        };
        chat.connect(prop);
        break;
    }
}

The var prop part is the connection property setting specific to iPush Server. For this tutorial, we will just use this test account (foo/bar1) to make the connection. The connect() call does the labor work behind the scene. Once the connection is made, the SYS_CONNECTED event will be triggered.

 

    >>>  Get your FREE ICEspresso Chat Connection Pool account

 

icespresso.onSystemEvent = function(chat, eventId, arg) {
    switch (eventId) {
    case chat.SYS_CONNECTED:
    // Connected to iPush Server
    break;
}

The SYS_CONNECTED event is by design informational only. The SYS_HANDSHAKED event that ensues is of more significance.

3. Handshake

Immediately after ICEspresso Chat Framework has connected to iPush Server, the Framework will try to "handshake" with ICEspresso Service to exchange session data. This is done behind the scene automatically and is transparent to the application. Once the handshake process is complete, SYS_HANDSHAKED event will be fired. When application is notified of this event, it represents that the application has registered with the ICEspresso Service and can commence interaction with the system and other users.

icespresso.onSystemEvent = function(chat, eventId, arg) {
    switch (eventId) {
        // SYS_HANDSHAKE event is triggered when application has successfully
        // joined the ICEspresso Service.
        case chat.SYS_HANDSHAKED:
        // arg.connId contains a server assigned connection id
        // with which others can identify us.

        break;    
    }
}

Let it suffice to say that so far we have completed several steps at system-level. We initialized the ICEspresso Chat Framework. Upon initialization we made connection and handshaked with the ICEspresso Service. Most of the work was performed by the Framework itself. At application level, we simply followed a standardized approach to perform the tasks.

SYS_HANDSHAKED event could also serve as "opening event" for the application. Suppose that we want to hide UI details until the application is in-sync with the ICEspresso Service. We could wait until SYS_HANDSHAKED is fired before revealing the UI layout. If something should go wrong, then we display the error page or take other corrective measures.

4. Setting Handle (Nickname)

In a multi-user environment such as chat service, it is natural that each user bears an identifiable screen handle (a.k.a. nickname). The setting handle part should be done as early as possible. SYS_HANDSHAKED appears to be an ideal choice for placing the handle setting code.

icespresso.onSystemEvent = function(chat, eventId, arg) {
    switch (eventId) {
        case chat.SYS_HANDSHAKED:
        /** Code omitted for brevity **/
        // Set handle to "baz_" plus the server-assigned connection id
        chat.iAm("baz_" + chat.getMe().getId()); // chat.getMe().getId() == arg.connId
        break;
    }
}

chat.iAm(handle) updates handle (nickname) locally first then sends handle-update notification to ICEspresso Service. If we are in any chat room(s) when the handle update operation is executed, then the update will be sent to those who are in the room as well so that they are informed of this update operation. Since we have not joined any chat room now, the update will be received ICEspresso Service only.

5. Join Room

When all the preparing works have been completed, we are ready to join chat room by calling joinRoom(roomName).

icespresso.onSystemEvent = function(chat, eventId, arg) {
    switch (eventId) {
        case chat.SYS_HANDSHAKED:
        /** code omitted for brevity **/
        // join room named testRoom
        chat.joinRoom("testRoom");
        break;
    }
}

What joinRoom(roomName) does is that the Framework internally creates a Room object which will hold data pertaining to the room. The joinRoom initiative is also sent to ICEspresso Service so that the back-end Service can set-up a mapping data structure.

6. Room Ready

CHAT_ROOMREADY is a special event that is dispatched whenever we join a room. This event will send us a collection of Chatter and Message objects wrapped in arg argument so that we have a chance to go through the exchanged messages and the participating chatters just prior to joining. Normally we would want the application to print out the messages and list the chatters so that application user has an idea of those historic activities in the room.

// Return a localized representation of time
function getTimeString(d) {
    if (!(d instanceof Date))
        d = new Date();
    return d.toLocaleTimeString();
}

// Format raw chat message and add it to the output
function addChatMsg(m) {
    var d = document.createElement("DIV");
    d.innerHTML = "(" + getTimeString(m.when) + ")&nbsp;";
    d.innerHTML += (m.by + "&nbsp;&gt;&nbsp;");
    d.innerHTML += m.msg;
    add2Chatlist(d);
}

// Format raw join message and add it to the output
function addJoinMsg(m) {
    var d = document.createElement("DIV");
    d.innerHTML = "(" + getTimeString(m.when) + ")&nbsp;";
    d.innerHTML += ("<i>" + m.by + " joined.</i>");
    add2Chatlist(d);
}

// Format raw quit message and add it to the output
function addQuitMsg(m) {
    var d = document.createElement("DIV");
    d.innerHTML = "(" + getTimeString(m.when) + ")&nbsp;";
    d.innerHTML += ("<i>" + m.by + " left.</i>");
    add2Chatlist(d);
}

// Add chatter to the userlist container on the right
function addChatter(chatter) {
    var onlineListDiv = document.createElement("DIV");
    onlineListDiv.id = "idChatter_" + chatter.getId();
    onlineListDiv.innerHTML = chatter.getHandle() + " (" + chatter.getId() + ")";
    document.getElementById("onlineList").appendChild(onlineListDiv);
}

icespresso.onChatEvent = function(chat, eventId, arg) {
    switch (eventId) {
        case chat.CHAT_ROOMREADY:
        // In this event, we add historic messages and
        // chatters to the room display so that application
        // user learns what has been said and who is in
        // the room
        var i = 0;
        var messages = arg.messages;
        var chatters = arg.chatters;

        // messages is an index-based array
        for (i=0; i<messages.length; ++i) {
            var m = arg.messages[i];
            var t = m.getType();
            if (t == m.ROOM_MSG)
                addChatMsg(m);
            else if (t == m.JOIN_MSG)
                addJoinMsg(m);
            else if (t == m.QUIT_MSG)
                addQuitMsg(m);
        }
        // message is an associative array
        for (i in chatters) {
            // The check during loop is important as for..in loop
            // will loop through object prototype which is not a
            // desirable effect for our purpose.
            if (!chatters.hasOwnProperty(i))
                continue;
            addChatter(arg.chatters[i]);
        }
        break;
    }
}

The addXXXMsg() functions deal with UI presentation and should be fairly straightforward to understand. Every Message carries a timestamp field which can be formatted by Javascript Date class to application's choice.

The addChatter(chatter) is also another UI function that displays chatter's information to the user list DIV.

Note that in order to retrieve the chatter information at later time through the use of DOM method getElementById(), we use the connection id associated with each chatter as an unique identifiable key for the DIV id attribute. There are other techniques that can achieve the desired result but the choice of using connection id as unique identifier serves us well in this tutorial.

7. Implement Other Core Events

Up to this point we have added event handlers that result from our own actions. What about the events caused by others? Say if someone joins/leaves the room or changes nickname, how do we learn of those events and process them accordingly?

ICEspresso declares several events that are result of collective actions by all system users. We will implement three events that are logically required for a functional chat room.

// Update chatter's info, In this case, the chatter's handle
function updateChatter(chatter) {
    var id = chatter.getId();
    // obtain the DIV that holds this particular chatter's handle
    var onlineListDiv = document.getElementById("idChatter_" + id);
    if (onlineListDiv != null)
        onlineListDiv.innerHTML = chatter.getHandle();
}

// Format raw quit message and add it to the output
function addQuitMsg(m) {
    var d = document.createElement("DIV");
    d.innerHTML = "(" + getTimeString(m.when) + ")&nbsp;";
    d.innerHTML += ("<i>" + m.by + " left.</i>");
    add2Chatlist(d);
}

icespresso.onChatEvent(chat, eventId, arg) {    
    case chat.CHAT_ROOMJOIN:
    // CHAT_ROOMJOIN event is triggered when others join the room
    // We add an informative joining message as well as update
    // the online user list.
    var msg = arg.msg;
    var chatter = arg.chatter;
    addJoinMsg(msg);
    addChatter(chatter);
    break;
    
    case chat.CHAT_HANDLEUPDATE:
    // In the event of handle change, we add an informative message for
    // the application user.
    // After which, the user list is updated to reflect such change.
    var chatter = arg.chatter;
    var lastHandle = chatter.getLastHandle();
    var newHandle = chatter.getHandle();
    icespresso.log(lastHandle + "'s handle has been changed to " + newHandle);
    updateChatter(chatter);
    break;
    
    case chat.CHAT_ROOMQUIT:
    var chatter = arg.chatter;
    var msg = arg.msg;
    // CHAT_ROOMQUIT is triggered when someone leaves the room.
    // If the 'someone' in question is not me then
    // we add an informative leaving message as well as remove
    // the chatter from the room user list.
    if (!chatter.isMe()) {
        // add quit message to output pane
        addQuitMsg(msg);
        // remove this chatter from userlist
        removeChatter(chatter);
    }
    else {
        // In this tutorial, we simply add quit message
        // and remove our name from the room user list
        addQuitMsg(msg);
        removeChatter(chatter);
        // When it's me who is leaving the room,
        // put application specific code below.
    }
    break;
}

Event CHAT_ROOMJOIN, CHAT_HANDLEUPDATE and CHAT_ROOMQUIT give us the chance to update UI components so application user is informed when these events take place.

addJoinMsg() and addChatter() are already implemented in the introduction of CHAT_ROOMREADY event section. updateChatter() works in such a way that we locate an existing chatter item in user list, then update the user's handle when found.

We may ask ourselves this question: What will happen if we do not provide these event handlers? At application level, nothing will happen because nothing was provided to make something happen. However, from framework's perspective, the internal data structures will always be in-sync with ICEspresso Service.

Say if someone joins the room, the ICEspresso Service will notify the Framework of this event. The Framework updates its internal data structures accordingly and in turn provides application the up-to-date data so the application can make timely adjustment for its presentation layer.

8. Send Message

After we have joined a room and received data and state pertaining to the room, we can start chatting by sending messages to the room. We obtain the user input from the input control and pass the message text to the Framework to be sent. In real world application, we may perform more operations on the text before sending. If business rule dilates that only privileged users are allowed to include HTML tags in the message text, then we would want to run some sanity checks against the text. The requirement varies but the idea is there.

function sendChat() {
    var field = document.getElementById("inputField")
    var msg = field.value;
    if (msg.length > 0) {
        field.value = "";
        instance.send2Room(msg, "testRoom");
        field.focus();
    }
}

The first parameter in send2Room() is the message itself. As long as this parameter is of string type, we can however format the string to suit application's needs. Please refer to the FAQ on "how do I add font color and size to plain text before calling send2room()?"

// inspect onkeypress event so that when the code is 13 (enter key)
// sendChat() is invoked.
function checkSend(e) {
    var code = -1;

    code = window.event ? window.event.keyCode
                        : "which" in e ? e.which
                        : code;
    // hit enter key
    if (code==13) {
        sendChat();
    }
}

In consideration of the application user, it is cumbersome to click on the Submit button each time something has been typed into the input field and is ready to be sent. We fix this by processing browser's onkeypress event so that when enter key is pressed, sendChat() is invoked to process the message sending mechanism.

9. Receive Message

When either we or other in-room chatters send message, we will receive it. We will want to put the newly arrived message to the message pane so that application user sees the message exchanged.

icespresso.onChatEvent = function(chat, eventId, arg) {
    switch (eventId) {
        case chat.CHAT_ROOMMSG:
        addChatMsg(arg.msg);
        break;
    }
}

The arg argument encapsulates three objects, being room, chatter, and message respectively. Only message object is of significance in this tutorial. The message object is passed to addChatMsg() which in turn adds one line of text to the output pane.

10. Leave Room

At the end of the chatting session and after saying goodbye to fellow chatters, we leave the room by calling quitRoom(roomName) so that others have a chance to be informed of our leaving. At application level, leaving a chat room generally involves more work such as redirect to another site page or close the room page if the room page was a pop-up window. All in all, this is an application-specific decision.

CHAT_ROOMQUIT event was presented in the Implement Other Core Events section. Basically, the UI components are updated to mirror the chatter leaving event. When it's the application user who is leaving, application-specific code should be put in place to keep in line with the surfing logic.

icespresso.onChatEvent(chat, eventId, arg) {
    case chat.CHAT_ROOMQUIT:
    var chatter = arg.chatter;
    var msg = arg.msg;
    // CHAT_ROOMQUIT is triggered when someone leaves the room.
    // If the 'someone' in question is not me then
    // we add an informative leaving message as well as remove
    // the chatter from the room user list.
    if (!chatter.isMe()) {
        // add quit message to output pane
        addQuitMsg(msg);
        // remove this chatter from userlist
        removeChatter(chatter);
    }
    else {
        // In this tutorial, we simply add quit message
        // and remove our name from the room user list
        addQuitMsg(msg);
        removeChatter(chatter);
        // When it's me who is leaving the room,
        // put application specific code below.
    }
    break;
}

function quitRoom() {
    instance.quitRoom(room);
}

Conclusion

In this tutorial, we had the chance to experience how ICEspresso Chat Framework provides real-time chat service. Because of the real-time data push nature and the way JavaScript works, most of the logic is realized inside callback handlers. As such, it is advised that the developers take the following approaches when developing ICEspresso-powered application.

1. Include the script files
2. Declare the three callback handlers.
3. Use the standardized method demonstrated in this tutorial to initialize the Framework and make connection.
4. Join chat room when other application logics have been satisfied.
5. Provide UI elements and script functions to add/remove messages and chatters.
6. Handle events that the application is interested in or is required to.

To summarize the events that are generally required to run a chat service, we could refer to this table:

icespresso.onSystemEvent SYS_INITIALIZED - indicates local Framework components are ready for operation.
SYS_HANDSHAKED - indicates the Framework has registered with ICEspresso Service.
icespresso.onChatEvent CHAT_ROOMREADY - indicates joining room completes successfully.
CHAT_ROOMJOIN - indicates someone else has joined the room.
CHAT_ROOMMSG - indicates message is sent to the room.
CHAT_ROOMQUIT - indicates someone(self included) has left the room.

 

The CHAT_HANDLEUPDATE event was not listed. It is due to the fact that handle change is an application-centric feature. Application may be bound to business rule so that handle update occurs at system level rather than at application level. If such rule applies, then the application would not provide handle update feature and thus requires no event handler.

This tutorial does not cover all of the features presented by ICEspresso Chat Framework. It, however, touches the fundamental blocks on developing an ICEspresso-powered chat service. The ICEspresso Chat FAQ will discuss more advanced techniques.