In the previous chapter, we created a chat room. In this chapter, we are going to improve on that chat room by giving our users a name, having multiple chat rooms, and integrating the express and socket.io sessions.
Without a name for our users, chatting becomes difficult. It is impossible to identify who sent the message. So let us provide our users with a method by which they can set a nickname for themselves, so that a message from them can be identified with their name.
We have already worked with the message
event in socket.io to send and receive messages. We also saw the socket.io module's predefined events. In this section, we will learn more about those events and also see how we can work with our own events. We will also see how we can save some information for the session.
Let us start by creating the user interface required for accepting a name from the user when they come to our chat room. To do this, we will modify the index.jade
file by adding the following code to it:
//EXISTING LAYOUT section#nameform.modal div.backdrop div.popup div.pophead Please enter a nickname div.popbody input#nickname(type='text') input#setname(type='button', value='Set Name')
What we are doing here is adding a new section for the modal
overlay. This section has a backdrop div
tag and then a div
tag for the actual form. The look and feel of this will again be defined in the style.css
file, so let's update that too. Refer to the following code block while modifying the stylesheet:
//EXISTING CSS .modal{ -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; -moz-box-sizing: border-box; height: -moz-calc(100% - 102px); height: -webkit-calc(100% - 102px); height: calc(100% - 102px); left: 0; position: absolute; top: 62px; width: 100%; z-index: 1000; } .backdrop{ width: 100%; height:100%; background-color: #777777; } .popup { position: absolute; height: 100px; width: 300px; left: -moz-calc(50% - 150px); left: -webkit-calc(50% - 150px); left: calc(50% - 150px); top: -moz-calc(50% - 50px); top: -webkit-calc(50% - 50px); top: calc(50% - 50px); background: #FFFFFF; } .pophead { background-color: #4192C1; color: #FFFFFF; font-weight: bold; padding: 8px 3px; vertical-align: middle; } .popbody { padding: 10px 5px; }
Now when we refresh the UI, it will look like this:
Next, what we will want to do is when the user enters a name and clicks on the Set Name button, send the name to the server, store it there, and prefix it to every message sent by that user. First, we will change the document ready handler to attach an event handler to the Set Name button. For this, edit public/javascripts/chat.js
:
$(function(){
$('#setname').click(function(){
socket.emit("set_name", {name: $('#nickname').val()});
});
});
In the previous code, we see a new socket.io API and concept, namely socket.emit
. This is used to trigger custom events. The call for emit
is as follows:
socket.emit(<event_name>, <event_data>);
We trigger a set_name
event and pass on the value entered in the username box by the user. We also remove the send message event handler from the socket.emit
declaration. We will come back to this later.
The events emitted on a socket on one side (server) will be handled on the other side of the socket (client). In our case, that is, in the previous code snippet, we trigger the set_name
event on the client, so we will handle it on the server. To do this, we will edit routes/sockets.js
as follows:
var io = require('socket.io'), exports.initialize = function(server) { io = io.listen(server); io.sockets.on("connection", function(socket){ socket.on('message', function(message){ message= JSON.parse(message); if(message.type == "userMessage"){ socket.get('nickname', function(err, nickname){ message.username=nickname; socket.broadcast.send(JSON.stringify(message)); message.type = "myMessage"; socket.send(JSON.stringify(message)); }); } }); socket.on("set_name", function(data){ socket.set('nickname', data.name, function(){ socket.emit('name_set', data); socket.send(JSON.stringify({type:'serverMessage', message: 'Welcome to the most interesting chat room on earth!'})); }); }); }); }
In the practice of keeping it simple, socket.io uses the same socket.on
API, which we used earlier to handle the connection
or message
events, to handle custom events. The data passed to the handler function will contain the data we had sent when we triggered the event.
This brings us to a new feature of socket.io, that is, attaching additional information to the socket for the session. This is achieved using the socket.set
function. The call for this function is as follows:
socket.set(<name>, <value>, <optional_callback>);
In the preceding line of code, <name>
is the name of the key we want to set and <value>
is the value we want to set. The call to set
is asynchronous; it won't be blocked till the value has been set. To perform an action where you want to ensure that the value has been set, we can pass a callback to the set
method. In the previous code, we are passing the callback
function that will emit another name_set
custom event, and will also send the welcome message. Like the set_name
event, the name_set
event will be handled on the other side of the socket, which in this case is the client.
This is great. Now that the name is set, let us put it to some real use by showing it with every message so that people in our chat room know who sent the message.
To get a value set on the socket, socket.io provides a get
method. We will use this get
method to get the username from the socket and append it to the previous message.
Let us rework public/javscripts/chat.js
to handle the name_set
event and then start the actual communication:
var socket = io.connect('/'), socket.on('name_set', function(data){ $('#nameform').hide(); $('#messages').append('<div class="systemMessage">' + 'Hello '+data.name+'</div>'), $('#send').click(function(){ var data = { message: $('#message').val(), type:'userMessage' }; socket.send(JSON.stringify(data)); $('#message').val(''), }); socket.on('message', function (data) { data = JSON.parse(data); if(data.username){ $('#messages').append('<div class="'+data.type+ '"><span class="name">' + data.username + ":</span> " + data.message + '</div>'), }else{ $('#messages').append('<div class="'+data.type+'">' + data.message + '</div>'), } }); }); $(function(){ $('#setname').click(function(){ socket.emit("set_name", {name: $('#nickname').val()}); }); });
In the previous code snippet, we add two new lines of code to hide the overlay and to append the greeting to the messages
area. Apart from this, we have also moved the code to handle the sending and receiving of messages to this handler, so that it is set up only after the user has set the name and avoids people from just hiding the overlay using Firebug or other similar tools. There is one last change in the message received handler; we need to check for the presence of a username in the incoming data and prefix it to the displayed message if it is.
To see the code in action, let's restart our node server and refresh the browser. Once you enter the name, it will bring up the chat room and show the greeting with the name you just entered along with the welcome message:
Open our chat room in another browser window and sign in as Friend this time. Enter a message in the new message box and click Send. The message appears in the message area in both the browsers. Try it from the first chat room you have opened: