Hey everyone! Sorry about the long pause since the last blog post, life has been quite hectic for the past little while. Today we'll be looking at developping collaborative web applications, that is applications which more than one person can interact with the data at the same time. However, we'll be spicing it up a little bit by making it real time, so no need to refresh the page every time you want to see the new data! In order to do this, you'll be needing Node.js and the Socket.IO module (easily installable via the npm tool. Although I don't like having too many requirements for a post, I'll also be using jQuery 1.7.2 in order to facilitate DOM manipulation. Finally, you'll also need some kind of web server (try and implement it in Node for fun!) as we'll encounter some cross-domain constraints if we don't.

So what will we be building today? We'll be building an application which shows a simple task tracking application, which will contain a master list of tasks to be done. We'll also be keeping it simple, so people will be able to add tasks and mark tasks as done. It will update real time, so therefore we will have to make a system which allows us to notify everyone watching the list when a task is added or completed. The list will also have to be sent whenever there is a new visitor for the page in order to make sure they also see all the current tasks. For simplicity's sake, we will be storing the tasks in memory on the server side, however I encourage you to use it as an experiment for connecting Node to a number of different persistence services.

In case you've never worked with Socket.IO before, it is a library which allows us to have two-way real-time communication on a web page. It makes use of a variety of techniques in order to increase the browser compatibility, making use of WebSockets if available and falling back to techniques such as AJAX polling and a Flash socket. If you've ever done socket programming for desktop applications, working with Socket.IO will feel very familiar. If you haven't, have no fear! Messages (packets) are sent back and forth with an identifier. Callbacks for a given identifier are implemented on the receiving end which works with the data. The nice thing about this is that we are using Javascript for both the sending and receiving data. Think of sending a packet like leaving a voicemail: you have a phone number and a message, and if the receiver chooses to receive messages for that phone number, he can work with the message.

So let's begin! We'll start with the Node.js server! An important thing to mention is that we will be building a Socket.IO server and not a web server, so our server will only take care of the socket interaction and not the HTTP interaction. It's not very difficult to turn it into a web server, but let's not worry about anything else for now. First thing is to install the Socket.IO module in your application's directory, so right now you should have a directory which only contains a node_modules folder and the socket.io folder inside it. The tasks will be stored on the server in an array, where each task will have a 'unique' ID as well as a boolean stating whether the task is complete or not. For this first project, we are not aiming for efficiency but simplicity. Let's start with our basic Node server:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var io = require('socket.io').listen(4000),
    tasks = [];

io.sockets.on('connection', function(socket) {
    // When a client connects, send them all the tasks.
    socket.emit('init', tasks);
    // When a task is added, make sure we have the correct data
    // and then add it to the list. We then broadcast the task
    // to all other clients.
    socket.on('addTask', function(data) {
        if (data.content && data.id) {
            var task = {id: data.id, content: data.content, complete: false};
            tasks.push(task);
            socket.broadcast.emit('addTask', task);
        }
    });
    // When a completeTask packet is received, search for a task
    // with that ID, mark it as completed and broadcast it.
    socket.on('completeTask', function(id) {
        for (var i = 0, l = tasks.length; i < l; i++) {
            if (tasks[i].id == id) {
                tasks[i].complete = true;
                socket.broadcast.emit('completeTask', id);
                break;
            }
        }
    });
});

Let's go through this from the top! First we are creating a Socket.IO server and listening on port 4000. We also create an empty array of tasks. We then come to our first socket code: the io.sockets.on method. This method is provided by the library and is called when a new socket connection is made, invoking our callback and passing it the socket. It is in this callback that we can set up handlers for certain types of packets for a given socket. I guess now would be a good time to mention the Socket.IO documentation. The first thing we do in our connection callback is send our tasks, which is done via the emit method. The emit method lets us send a packet to a given socket, with the first argument being the packet identifier and the second being the packet data. We then use the on method of the socket itself to create handlers for a given identifier. A final interesting thing is the lines which call socket.broadcast.emit, rather then socket.emit. The socket.broadcast.emit function sends a packet to all other sockets apart the actual socket object, which is great because we only want to notify the other clients that something has changed.

We are now ready to start our client side! Let's start with our basic HTML structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8">
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script src="http://localhost:4000/socket.io/socket.io.js"></script>
    <script src="client.js"></script>
    <title>Online Collaboration With Node.js and Socket.IO</title>
    <style type="text/css">
        .complete { text-decoration: line-through; }
    </style>
</head>
<body>
    <h1>Tasks:</h1>
    <ul id="task_list"></ul>
    <input type="text" id="new_task"/><a href="#" id="new_task_submit">Add</a>
</body>

Our HTML page simply shows a list to which we will append tasks, and also a text field and button to allow us to add a task. An interesting thing to note is our inclusion of the socket.io.js file, which is served by our socket server. Finally, let's make this baby dynamic by creating our client.js file!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
$(document).ready(function() {
    var taskList = $('#task_list'),
        newTaskTextbox = $('#new_task'),
        addTask,
        completeTask,
        socket;
    // This function will add a task to the list and potentially 
    // send it to the server.
    addTask = function(task, sendToServer) {
        var newItem = $('<li></li>');
        newItem.attr('id', 'task_' + task.id);
        // Using the text function is necessary for escaping HTML
        newItem.text(task.content);
        // Attach the click handler to the list item
        newItem.click(function() {
            completeTask(task.id);
        });
        // If the task was marked as completed, mark the LI as well
        if (task.complete) {
            newItem.addClass('complete');
        }
        taskList.append(newItem);
        // If we specified sending to server, send it now.
        if (sendToServer) {
            socket.emit('addTask', task);
        }
    };
    // This function will add the completed class to a task by ID
    completeTask = function(id) {
        var element = $('#task_list #task_' + id);
        // If we found an element with that ID, and it isn't already
        // completed, mark it as complete and send the status update
        // to the server.
        if (element && !element.hasClass('complete')) {
            element.addClass('complete');
            socket.emit('completeTask', id);
        }
    };
    // Connect our socket and set up all our packet handlers
    socket = io.connect('http://localhost:4000');
    // When we receive the init, add all the default tasks
    socket.on('init', function(tasks) {
        for (var i = 0, l = tasks.length; i < l; i++) {
            addTask(tasks[i], false);
        }
    });
    // Set up the handlers for completing and adding task,
    // which conveniently can use our methods
    socket.on('completeTask', completeTask);
    socket.on('addTask', addTask);
    // Set up the click handler for our 'button', which will
    // generate a 'unique' (not very safe) id for our task
    // and add it if it's not empty.
    $('#new_task_submit').click(function() {
        if ($.trim(newTaskTextbox.val()) != '') {
            var task = {
                content: $.trim(newTaskTextbox.val()),
                id: new Date().getTime() 
            };
            // Add our task and send to server.
            addTask(task, true);
            newTaskTextbox.val('');
        }
        return false;
    });
});

First, we define two functions, addTask and completeTask. The code for both of these is fairly straightforward, although you will notice there are some socket.emit calls! As you may have guessed, it works in the exact same manner as the socket.emit on the server side, and allows us to send a packet to the server! The one other interesting code snippet is where we actually connect to the server, which is done via the io.connect method (accepting a URL to our socket server). We can then use the socket.on method in the same fashion as the server in order to set up handlers for the packets being received! In order to make our application seem more responsive, whenever we add or mark a task completed on the client side we process it right away, and then send it to the server in order to be sent to everyone else. That's the beauty of the server side socket.broadcast.emit sending to all other clients - we don't receive what we just sent!

I hope this has been a helpful introduction to using Node.js and Socket.IO in order to make real-time web applications! If you have any questions, improvements, or comments, feel free to post them in the comments section!

Thanks for reading, Dominic.