Adding rooms

We will use our current app as the basis since most of it can be reused. Copy namespaces.js and namespace.html and create rooms.js and rooms.html. Open up rooms.js and get rid of the namespace connection listener, as we are only using rooms here. Then, we will modify the normal connection and add our room-specific elements to it. Your rooms.js should look like the following code:

var io = require('socket.io').listen(4000);
io.sockets.on('connection', function(socket){
  socket.on('join', function(data){
    socket.username = data.username;
    socket.join(data.room);
    socket.broadcast.to(data.room).emit('join', {username: data.username, socket: socket.id, room: data.room});
  });
  socket.on('ping', function(data){
    socket.broadcast.to(data.room).emit('ping', {username: socket.username, room: data.room});
  });
  socket.on('privatePing', function(data){
    io.sockets.connected[data.socket].emit('ping', {username: socket.username, priv: true, room: data.room});
  });
});

So, let's talk about what's new and different. The initial thing to note is that every listener now is expecting data to be sent with it. This is because every event will need to send what room it came from.

On our connection event, we use socket.join(data.room). This is how we join a room. All it takes is a string of the room name. Rooms do not require an additional connection, but they do require us to join. The only exception to this is the default room of '' (an empty string). Every client on connection is in that room.

This brings us to our next new function: socket.broadcast.to(data.room).emit(). When we add to(room), it will only send the emit event to the connections that have joined that room. Because it is not a separate connection such as a namespace, the client does not inherently know what room this event is coming from. That is why we are sending the room back out in the message.

Our ping event changes very much like the connection. We need to pass in the event with the room and then send it back out with the room as an attribute. Other than this, it is the same.

Lastly, our privatePing is in the same boat. We need the room, so we can determine where it came from and where it is going. If you compare our emit function to namespace.js, you will see the only thing that changed is the addition of the room attribute.

Our server is ready, so let's update the client. Open rooms.html to edit it. We do not have to change any of the head or body, as it can be reused as is.

Note

HTML should always just be structure. JavaScipt should be behavior. Do not mix them! Do not add onclick attributes to your HTML. Our examples here show this. The look of the page stays, so we do not touch HTML. The behavior does change, so we have to modify JavaScript. This same rule applies to CSS. Do not use inline styles. Use CSS that can target and style elements on a page. HTML is structure, JavaScript is behavior, and CSS is style.

Inside the script tag, we will modify our JavaScript code. First, remove the reference to the vip namespace connection. The only Socket.IO connection we should have is our default connection:

var socket = io.connect('http://localhost:4000');

We also can leave the element references and the createButton utility function alone, as we will need them:

var defaultArea = $('.default'),
  vipArea = $('.vip'),
  $username = $('#username');
//some code
function createButton(user){
  return '<li>' + user.username + '<button class="private_ping" data-socket="' + user.socket + '">Ping Me</button></li>';
};

This brings us to the key part we need to change: the wireEvents function. The following code is what it should eventually look like:

function wireEvents(area, room){
  var users = area.find('.users'),
    events = area.find('.events');

  area.on('click', function(e){
    if (e.target.className === 'join') {
      socket.emit('join', {username: $username.val(), room: room});
    }else if (e.target.className === 'ping') {
      socket.emit('ping', {room: room});
    }else if (e.target.className === 'private_ping') {
      socket.emit('privatePing', {socket: e.target.getAttribute('data-socket'), room: room});
    }
  });
  socket.on('join', function(user){
    if (user.room === room)
      users.append(createButton(user));
  });
  socket.on('ping', function(user){
    if (user.room === room){

if (user.priv === undefined){
        events.append('<li>Ping from ' + user.username + '</li>');
      }else{
        events.append('<li>Ping from ' + user.username + ' sent directly to you!</li>');
      }    }
  });
};

It is very similar to our wireEvents namespace. In fact, it has all the same listeners as the namespace function.

The parameters coming in are different. In the room version, we pass in the element as the area and a string as the room. We changed the server side to expect a room on any incoming event and send a room on every outgoing event. Really, all we are doing is changing this to match that.

On our click handler, we only have added the room attribute to every event going to the server. We also have changed the socket object to use just the one default connection.

Finally, we have added a room check before doing anything with events sent from the server. This is because we only have one socket connection. A ping from different rooms will look exactly the same except for the room in the data object passed with the event. What we end up doing is adding two event handlers to the ping event and then just checking to see if it is sent to the room we are listening for. If so, then do something with it. If not, do nothing.

The last thing we have to do is run wireEvents for our two rooms.

wireEvents(defaultArea, '');
wireEvents(vipArea, 'vip');

We can launch this and run the same exact type of test we did with namespaces. Launch Node and our Python server and go to http://localhost:8000 on a couple of tabs and click around.

One thing you may have noticed is that you will not get events in the vip room without first joining. This is different than our namespace app because we immediately connect to the vip namespace. This will send us all the events in that namespace whether or not we have clicked on Join. The room version does not put us in that room until we click on the Join button. We will get the default events as everyone is in the '' room.