Monday, December 17, 2012

Overriding The Console Functions

The console functions are the most common functions that developers can use when developing and debugging a web application using JavaScript. Actually, the `console.log` function is the one to use when trying to find an annoying bug. Yet, the console object includes other functions that can help us to trace and debug operations in our applications.

The `console.warn` function can be used when we want to show a message in the console, just like the `console.log` function but with a warning sign next to it.

The `console.error` can be used to console an object with a message (in a red color), along with the message we get the stack trace, showing us where the error came from.

Other cool functions are the `console.time` and `console.timeEnd`. These functions allow us to measure a time frame between operations in order to find and make our algorithm more efficient.

You can explore the `console` object in order to find out about more cool functions inside.

In this post I will talk about how to override the console functions and preserved the original functionality. You asking why? Well, I'm glad you asked... :)

I'm currently working on a system that combines web and mobile clients, I will not go into details, but think about having a web application on a mobile device and you want to trace all the console.log's that comes from it, we all know that we can enable the console messages (known as 'developer mode') on the mobile side, however, it will be a bit annoying to read those messages on the mobile side. So, my goal is to catch every `console.log` call, send it to my Node.js/Socket.io server and transmit it to my desktop web application.
The simple way to achieve this task is to simply override the `console.log` function like so:

console.log = function() {
    //send data to server...
}

When using this process, I can send the data to my server every time `console.log` is called on the mobile web application. But what if I want to preserve the original action of the `console.log` function? I don't want to destroy the real function, just to add some functionality into it.

This is the correct way to override and preserve the `console.log` functionality:

(function(){
    //saving the original console.log function
    var preservedConsoleLog = console.log;

    //overriding console.log function
    console.log = function() {

        //we can't just call to `preservedConsoleLog` function,
        //that will throw an error (TypeError: Illegal invocation)
        //because we need the function to be inside the
        //scope of the `console` object so we going to use the
        //`apply` function
        preservedConsoleLog.apply(console, arguments);

        //and lastly, my addition to the `console.log` function
        if(application.socket){
            application.socket.emit('console.log', arguments);
        }
    }
})();

Now you can take any function of the `console` object and override it in this way, add your functionality and achieve what you want.

Sunday, November 18, 2012

Extending jQuery functions

When developing with jQuery or any other JavaScript framework, sometimes we want to do more with the framework core functions. For example, many times, when I'm adding or removing a class to/from a jQuery element I want to call a function. If I'll call a function every time I'm adding or removing classes right after the addClass or removeClass it will be hard to maintain the code.

In this post I'll show how to expand jQuery core functions, as an example I will demonstrate how to add addClass and removeClass events to each jQuery element.

Let's start by creating a closure to avoid variable conflicts:
(function(){
    // code goes here...
})();

Now, before the core functions override, we need to save them in order to call them later inside the expanded function
(function(){
    // saving the core functions
    var coreAddClass = jQuery.fn.addClass;
    var coreRemoveClass = jQuery.fn.removeClass;
})();

Core functions saved, now we can override them
(function(){
    // saving the core functions
    var coreAddClass = jQuery.fn.addClass;
    var coreRemoveClass = jQuery.fn.removeClass;

    // overriding the addClass function
    jQuery.fn.addClass = function(){

    }

    // overriding the removeClass function
    jQuery.fn.removeClass = function(){

    }
})();

If you will run the code we wrote so far and try to $().addClass() or $().removeClass() nothing will happen. The next step is to add our new logic to the functions and then to call the core functions so we will keep the original behaviors
(function(){
    // saving the core functions
    var coreAddClass = jQuery.fn.addClass;
    var coreRemoveClass = jQuery.fn.removeClass;

    // overriding the addClass function
    jQuery.fn.addClass = function(){
        // every time the addClass function is called
        // for this $element, trigger the addClass event
        $(this).trigger('addClass', arguments);

        // calling the core function
        coreAddClass.apply(this, arguments);
    }

    // overriding the removeClass function
    jQuery.fn.removeClass = function(){
        // every time the removeClass function is called
        // for this $element, trigger the removeClass event
        $(this).trigger('removeClass', arguments);

        // calling the core function
        coreRemoveClass.apply(this, arguments);
    }
})();

By applying the core function at the end of the function we are able to keep the original behavior of the core.

Simple client to test the code:
<!DOCTYPE html>
<html>
    <head>
        <title></title>
  
        <script type="text/javascript" src="jquery.min.js"></script>
        <script type="text/javascript" src="jquery.events.js"></script>
        <script type="text/javascript">
            $(function(){

                // add event listener to the element addClass event
                $('.cube').on('addClass', function(e, className){
                    console.log(e);
                    console.log(className);
                });

                // add event listener to the element removeClass event
                $('.cube').on('removeClass', function(e, className){
                    console.log(e);
                    console.log(className);
                });
    
                $('button').on('click', function(){
                    // toggle a class on the element, the events are fired
                    $('.cube').toggleClass('color');
                });
            });
        </script>
  
        <style type="text/css">
            .cube{width:100px;height:100px;background:yellow;}
            .color{background:red !important;}
        </style>
    </head>
    <body>
        <div class="cube"></div>
        <button>toggle red</button>
    </body>
</html>


Friday, November 16, 2012

Chat Evolution — Node.js and Socket.IO

Not so long ago, if we wanted to build a simple JavaScript chat we had to work with Ajax, Comet or Flash component in the browser.

Ajax can be a little bad for performance, (in the case of chatting) because it’s based on a request and response. We also needed to hold an interval in the client which asked the server for new messages and this process was repeated again and again.

Comet (also known as reverse Ajax and long polling) is pretty much the same. While using Ajax, we asked the server every time for messages, with Comet we needed to open one connection and wait. This connection remained open in the server for a while until the server received messages for us, then the server responded with the messages and closed the opened connection. The client, when receiving these messages opened a new connection and waited again. One advantage of Comet is the reduction of the request amounts, but still, we needed to open and close many of them for each session (in case the user is active).

And lastly, a Flash component - in this case we placed a small flash component in our page to exchange messages with the JavaScript client, the flash could open a socket to the server and this gave us push communication.
However, we are here to talk about Node.js and Socket.IO, so, basically, the flash component is a good solution but in those days not every device or browser came with flash support.

Node.js and Socket.IO
With node.js and socket.io we can enjoy better performance, a bidirectional push communication between a server and a JavaScript client and even the support for all the browsers. Socket.io can work with several transports in order to support even old browsers like IE6 (RiP(.

In this tutorial, I'll show you how to create a multi chat room server with node.js and socket.io and a basic jquery client.

Installing Socket.IO and Express
If you don’t have socket.io and/or expressjs node modules installed on your machine, this will be a great time to do it, you can install them with npm:
$ npm install express
and...
$ npm install socket.io

Now we are ready to go, let's start with the server...

server.js
// creating global parameters and start
// listening to 'port', we are creating an express
// server and then we are binding it with socket.io
var express  = require('express'),
    app      = express(),
    server   = require('http').createServer(app),
    io       = require('socket.io').listen(server),
    port     = 8080,

    // hash object to save clients data,
    // { socketid: { clientid, nickname }, socketid: { ... } }
    chatClients = new Object();

// listening to port...
server.listen(port);

// configure express, since this server is
// also a web server, we need to define the
// paths to the static files
app.use("/styles", express.static(__dirname + '/public/styles'));
app.use("/scripts", express.static(__dirname + '/public/scripts'));
app.use("/images", express.static(__dirname + '/public/images'));

// serving the main applicaion file (index.html)
// when a client makes a request to the app root
// (http://localhost:8080/)
app.get('/', function (req, res) {
 res.sendfile(__dirname + '/public/index.html');
});

// sets the log level of socket.io, with
// log level 2 we wont see all the heartbits
// of each socket but only the handshakes and
// disconnections
io.set('log level', 2);

// setting the transports by order, if some client
// is not supporting 'websockets' then the server will
// revert to 'xhr-polling' (like Comet/Long polling).
// for more configurations go to:
// https://github.com/LearnBoost/Socket.IO/wiki/Configuring-Socket.IO
io.set('transports', [ 'websocket', 'xhr-polling' ]);

// socket.io events, each connection goes through here
// and each event is emited in the client.
// I created a function to handle each event
io.sockets.on('connection', function(socket){
 
 // after connection, the client sends us the 
 // nickname through the connect event
 socket.on('connect', function(data){
  connect(socket, data);
 });

 // when a client sends a messgae, he emits
 // this event, then the server forwards the
 // message to other clients in the same room
 socket.on('chatmessage', function(data){
  chatmessage(socket, data);
 });
 
 // client subscribtion to a room
 socket.on('subscribe', function(data){
  subscribe(socket, data);
 });

 // client unsubscribtion from a room
 socket.on('unsubscribe', function(data){
  unsubscribe(socket, data);
 });
 
 // when a client calls the 'socket.close()'
 // function or closes the browser, this event
 // is built in socket.io so we actually dont
 // need to fire it manually
 socket.on('disconnect', function(){
  disconnect(socket);
 });
});

// create a client for the socket
function connect(socket, data){
 //generate clientId
 data.clientId = generateId();

 // save the client to the hash object for
 // quick access, we can save this data on
 // the socket with 'socket.set(key, value)'
 // but the only way to pull it back will be
 // async
 chatClients[socket.id] = data;

 // now the client objtec is ready, update
 // the client
 socket.emit('ready', { clientId: data.clientId });
 
 // auto subscribe the client to the 'lobby'
 subscribe(socket, { room: 'lobby' });

 // sends a list of all active rooms in the
 // server
 socket.emit('roomslist', { rooms: getRooms() });
}

// when a client disconnect, unsubscribe him from
// the rooms he subscribed to
function disconnect(socket){
 // get a list of rooms for the client
 var rooms = io.sockets.manager.roomClients[socket.id];
 
 // unsubscribe from the rooms
 for(var room in rooms){
  if(room && rooms[room]){
   unsubscribe(socket, { room: room.replace('/','') });
  }
 }

 // client was unsubscribed from the rooms,
 // now we can delete him from the hash object
 delete chatClients[socket.id];
}

// receive chat message from a client and
// send it to the relevant room
function chatmessage(socket, data){
 // by using 'socket.broadcast' we can send/emit
 // a message/event to all other clients except
 // the sender himself
 socket.broadcast.to(data.room).emit('chatmessage', { client: 
           chatClients[socket.id], message: data.message, room: data.room });
}

// subscribe a client to a room
function subscribe(socket, data){
 // get a list of all active rooms
 var rooms = getRooms();

 // check if this room is exist, if not, update all 
 // other clients about this new room
 if(rooms.indexOf('/' + data.room) < 0){
  socket.broadcast.emit('addroom', { room: data.room });
 }

 // subscribe the client to the room
 socket.join(data.room);

 // update all other clients about the online
 // presence
 updatePresence(data.room, socket, 'online');

 // send to the client a list of all subscribed clients
 // in this room
 socket.emit('roomclients', { room: data.room, clients: 
                            getClientsInRoom(socket.id, data.room) });
}

// unsubscribe a client from a room, this can be
// occured when a client disconnected from the server
// or he subscribed to another room
function unsubscribe(socket, data){
 // update all other clients about the offline
 // presence
 updatePresence(data.room, socket, 'offline');
 
 // remove the client from socket.io room
 socket.leave(data.room);

 // if this client was the only one in that room
 // we are updating all clients about that the
 // room is destroyed
 if(!countClientsInRoom(data.room)){

  // with 'io.sockets' we can contact all the
  // clients that connected to the server
  io.sockets.emit('removeroom', { room: data.room });
 }
}

// 'io.sockets.manager.rooms' is an object that holds
// the active room names as a key, returning array of
// room names
function getRooms(){
 return Object.keys(io.sockets.manager.rooms);
}

// get array of clients in a room
function getClientsInRoom(socketId, room){
 // get array of socket ids in this room
 var socketIds = io.sockets.manager.rooms['/' + room];
 var clients = [];
 
 if(socketIds && socketIds.length > 0){
  socketsCount = socketIds.lenght;
  
  // push every client to the result array
  for(var i = 0, len = socketIds.length; i < len; i++){
   
   // check if the socket is not the requesting
   // socket
   if(socketIds[i] != socketId){
    clients.push(chatClients[socketIds[i]]);
   }
  }
 }
 
 return clients;
}

// get the amount of clients in aroom
function countClientsInRoom(room){
 // 'io.sockets.manager.rooms' is an object that holds
 // the active room names as a key and an array of
 // all subscribed client socket ids
 if(io.sockets.manager.rooms['/' + room]){
  return io.sockets.manager.rooms['/' + room].length;
 }
 return 0;
}

// updating all other clients when a client goes
// online or offline. 
function updatePresence(room, socket, state){
 // socket.io may add a trailing '/' to the
 // room name so we are clearing it
 room = room.replace('/','');

 // by using 'socket.broadcast' we can send/emit
 // a message/event to all other clients except
 // the sender himself
 socket.broadcast.to(room).emit('presence', { client:
                  chatClients[socket.id], state: state, room: room });
}

// unique id generator
function generateId(){
 var S4 = function () {
  return (((1 + Math.random()) * 0x10000) | 
                                     0).toString(16).substring(1);
 };
 return (S4() + S4() + "-" + S4() + "-" + S4() + "-" +
                S4() + "-" + S4() + S4() + S4());
}

// show a message in console
console.log('Chat server is running and listening to port %d...', port);

Don't be afraid of this long code, actually it's not so long if you removes the comment lines :), However, I won't explain it because it's well documented so if you missed something feel free to comment to this post and I will explain...

Now let's see the client side, I will show here only the JavaScript part but if you want to download the full application, see the references at the bottom for a github link and a live demo url.

chat.io.js

(function($){

 // create global app parameters...
 var NICK_MAX_LENGTH = 15,
     ROOM_MAX_LENGTH = 10,
     lockShakeAnimation = false,
     socket = null,
     clientId = null,
     nickname = null,

          // holds the current room we are in
     currentRoom = null,

     // server information
     serverAddress = 'localhost',
     serverDisplayName = 'Server',
     serverDisplayColor = '#1c5380',

     // some templates we going to use in the chat,
     // like message row, client and room, this
     // templates will be rendered with jQuery.tmpl
     tmplt = {
      room: [
   '<li data-roomId="${room}">',
    '<span class="icon"></span> ${room}',
   '</li>'
  ].join(""),
  client: [
   '<li data-clientId="${clientId}" class="cf">',
    '<div class="fl clientName">',
                                '<span class="icon"></span>',
                                     '${nickname}',
                                '</div>',
    '<div class="fr composing"></div>',
   '</li>'
  ].join(""),
  message: [
   '<li class="cf">',
    '<div class="fl sender">${sender}: </div>',
                                '<div class="fl text">${text}</div>', 
                                '<div class="fr time">${time}</div>',
   '</li>'
  ].join("")
 };

 // bind DOM elements like button clicks and keydown
 function bindDOMEvents(){
  
  $('.chat-input input').on('keydown', function(e){
   var key = e.which || e.keyCode;
   if(key == 13) { handleMessage(); }
  });

  $('.chat-submit button').on('click', function(){
   handleMessage();
  });

  $('#nickname-popup .input input').on('keydown', function(e){
   var key = e.which || e.keyCode;
   if(key == 13) { handleNickname(); }
  });

  $('#nickname-popup .begin').on('click', function(){
   handleNickname();
  });
  
  $('#addroom-popup .input input').on('keydown', function(e){
   var key = e.which || e.keyCode;
   if(key == 13) { createRoom(); }
  });

  $('#addroom-popup .create').on('click', function(){
   createRoom();
  });

  $('.big-button-green.start').on('click', function(){
   $('#nickname-popup .input input').val('');
   Avgrund.show('#nickname-popup');
   window.setTimeout(function(){
          $('#nickname-popup .input input').focus();
         },100);
  });

  $('.chat-rooms .title-button').on('click', function(){
   $('#addroom-popup .input input').val('');
   Avgrund.show('#addroom-popup');
   window.setTimeout(function(){
          $('#addroom-popup .input input').focus();
         },100);
  });

  $('.chat-rooms ul').on('scroll', function(){
   $('.chat-rooms ul li.selected').css('top', 
                            $(this).scrollTop());
  });

  $('.chat-messages').on('scroll', function(){
   var self = this;
   window.setTimeout(function(){
    if($(self).scrollTop() + $(self).height() <
                                           $(self).find('ul').height()){
     $(self).addClass('scroll');
    } else {
     $(self).removeClass('scroll');
    }
   }, 50);
  });

  $('.chat-rooms ul li').live('click', function(){
   var room = $(this).attr('data-roomId');
   if(room != currentRoom){
    socket.emit('unsubscribe', 
                                              { room: currentRoom });
    socket.emit('subscribe', { room: room });
   }
  });
 }

 // bind socket.io event handlers
 // this events fired in the server
 function bindSocketEvents(){

  // when the connection is made, the server emiting
  // the 'connect' event
  socket.on('connect', function(){
   // firing back the connect event to the server
   // and sending the nickname for the connected client
   socket.emit('connect', { nickname: nickname });
  });
  
  // after the server created a client for us, the ready event
  // is fired in the server with our clientId, now we can start 
  socket.on('ready', function(data){
   // hiding the 'connecting...' message
   $('.chat-shadow').animate({ 'opacity': 0 }, 200,
                            function(){
    $(this).hide();
    $('.chat input').focus();
       });
   
   // saving the clientId localy
   clientId = data.clientId;
  });

  // after the initialize, the server sends a list of
  // all the active rooms
  socket.on('roomslist', function(data){
      for(var i = 0, len = data.rooms.length; i < len; i++){
   // in socket.io, their is always one default room
   // without a name (empty string), every socket is 
   // automaticaly joined to this room, however, we
   // don't want this room to be displayed in the
   // rooms list
   if(data.rooms[i] != ''){
    addRoom(data.rooms[i], false);
   }
      }
  });

  // when someone sends a message, the sever push it to
  // our client through this event with a relevant data
  socket.on('chatmessage', function(data){
   var nickname = data.client.nickname;
   var message = data.message;
   
   //display the message in the chat window
   insertMessage(nickname, message, true, false, false);
  });
  
  // when we subscribes to a room, the server sends a list
  // with the clients in this room
  socket.on('roomclients', function(data){
   
      // add the room name to the rooms list
      addRoom(data.room, false);

      // set the current room
      setCurrentRoom(data.room);
      
      // announce a welcome message
      insertMessage(serverDisplayName,
                        'Welcome to the room: `' + data.room +
                        '`... enjoy!', true, false, true);
      $('.chat-clients ul').empty();
   
      // add the clients to the clients list
      addClient({ nickname: nickname, clientId: clientId },
                                   false, true);
      for(var i = 0, len = data.clients.length; i < len; i++){
       if(data.clients[i]){
        addClient(data.clients[i], false);
       }
      }

      // hide connecting to room message message
      $('.chat-shadow').animate({ 'opacity': 0 }, 200,
                        function(){
           $(this).hide();
           $('.chat input').focus();
      });
  });
  
  // if someone creates a room the server updates us
  // about it
  socket.on('addroom', function(data){
   addRoom(data.room, true);
  });
  
  // if one of the room is empty from clients, the server,
  // destroys it and updates us
  socket.on('removeroom', function(data){
   removeRoom(data.room, true);
  });
  
  // with this event the server tells us when a client
  // is connected or disconnected to the current room
  socket.on('presence', function(data){
   if(data.state == 'online'){
    addClient(data.client, true);
   } else if(data.state == 'offline'){
    removeClient(data.client, true);
   }
  });
 }

 // add a room to the rooms list, socket.io may add
 // a trailing '/' to the name so we are clearing it
 function addRoom(name, announce){
     // clear the trailing '/'
     name = name.replace('/','');

     // check if the room is not already in the list
     if($('.chat-rooms ul li[data-roomId="' + name + '"]').length == 0){
  $.tmpl(tmplt.room, { room: name })
                                      .appendTo('.chat-rooms ul');
  // if announce is true, show a message about this room
  if(announce){
          insertMessage(serverDisplayName, 'The room `' + name + '`
                                   created...', true, false, true);
  }
     }
 }

 // remove a room from the rooms list
 function removeRoom(name, announce){
  $('.chat-rooms ul li[data-roomId="' + name + '"]').remove();
  // if announce is true, show a message about this room
  if(announce){
      insertMessage(serverDisplayName, 'The room `' + name +
                                        '` destroyed...', true, false, true);
  }
 }

 // add a client to the clients list
 function addClient(client, announce, isMe){
  var $html = $.tmpl(tmplt.client, client);
  
  // if this is our client, mark him with color
  if(isMe){
   $html.addClass('me');
  }

  // if announce is true, show a message about this client
  if(announce){
   insertMessage(serverDisplayName, client.nickname +
                              ' has joined the room...', true, false, true);
  }
  $html.appendTo('.chat-clients ul')
 }

 // remove a client from the clients list
 function removeClient(client, announce){
  $('.chat-clients ul li[data-clientId="' +
                                       client.clientId + '"]').remove();
  
  // if announce is true, show a message about this room
  if(announce){
   insertMessage(serverDisplayName, client.nickname +
                                ' has left the room...', true, false, true);
  }
 }

 // every client can create a new room, when creating one, the client
 // is unsubscribed from the current room and then subscribed to the
 // room he just created, if he trying to create a room with the same
 // name like another room, then the server will subscribe the user
 // to the existing room
 function createRoom(){
     var room = $('#addroom-popup .input input').val().trim();
     if(room && room.length <= ROOM_MAX_LENGTH &&
                              room != currentRoom){
   
  // show room creating message
  $('.chat-shadow').show().find('.content')
                        .html('Creating room: ' + room + '...');
  $('.chat-shadow').animate({ 'opacity': 1 }, 200);
   
  // unsubscribe from the current room
  socket.emit('unsubscribe', { room: currentRoom });

  // create and subscribe to the new room
  socket.emit('subscribe', { room: room });
  Avgrund.hide();
     } else {
  shake('#addroom-popup', '#addroom-popup .input input',
                          'shake', 'yellow');
  $('#addroom-popup .input input').val('');
     }
 }

 // sets the current room when the client
 // makes a subscription
 function setCurrentRoom(room){
  currentRoom = room;
  $('.chat-rooms ul li.selected').removeClass('selected');
  $('.chat-rooms ul li[data-roomId="' + room + '"]')
                                            .addClass('selected');
 }

 // save the client nickname and start the chat by
 // calling the 'connect()' function
 function handleNickname(){
  var nick = $('#nickname-popup .input input').val().trim();
  if(nick && nick.length <= NICK_MAX_LENGTH){
      nickname = nick;
      Avgrund.hide();
      connect();
  } else {
      shake('#nickname-popup',
                        '#nickname-popup .input input', 'shake', 'yellow');
      $('#nickname-popup .input input').val('');
  }
 }

 // handle the client messages
 function handleMessage(){
  var message = $('.chat-input input').val().trim();
  if(message){

      // send the message to the server with the room name
      socket.emit('chatmessage', { message: message,
                                                   room: currentRoom });
   
      // display the message in the chat window
      insertMessage(nickname, message, true, true);
      $('.chat-input input').val('');
  } else {
      shake('.chat', '.chat input', 'wobble', 'yellow');
  }
 }

 // insert a message to the chat window, this function can be
 // called with some flags
 function insertMessage(sender, message, showTime, isMe, isServer){
  var $html = $.tmpl(tmplt.message, {
   sender: sender,
   text: message,
   time: showTime ? getTime() : ''
  });

  // if isMe is true, mark this message so we can
  // know that this is our message in the chat window
  if(isMe){
   $html.addClass('marker');
  }

  // if isServer is true, mark this message as a server
  // message
  if(isServer){
   $html.find('.sender')
                             .css('color', serverDisplayColor);
  }
  $html.appendTo('.chat-messages ul');
  $('.chat-messages').animate({
                       scrollTop: $('.chat-messages ul').height() }, 100);
 }

 // return a short time format for the messages
 function getTime(){
     var date = new Date();
     return (date.getHours() < 10 ? '0' + date.getHours().toString() :
                    date.getHours()) + ':' + (date.getMinutes() < 10 ? '0' +
                    date.getMinutes().toString() : date.getMinutes());
 }

 // just for animation
 function shake(container, input, effect, bgColor){
  if(!lockShakeAnimation){
   lockShakeAnimation = true;
   $(container).addClass(effect);
   $(input).addClass(bgColor);
   window.setTimeout(function(){
    $(container).removeClass(effect);
    $(input).removeClass(bgColor);
    $(input).focus();
    lockShakeAnimation = false;
   }, 1500);
  }
 }
 
 // after selecting a nickname we call this function
 // in order to init the connection with the server
 function connect(){
  // show connecting message
  $('.chat-shadow .content').html('Connecting...');
  
  // creating the connection and saving the socket
  socket = io.connect(serverAddress);
  
  // now that we have the socket we can bind events to it
  bindSocketEvents();
 }

 // on document ready, bind the DOM elements to events
 $(function(){
  bindDOMEvents();
 });

})(jQuery);

Summary
There are some ways to send a message from the server to the client with Socket.io, Let's explore some of them:

  • socket.emit - Fire event to the client side which associated to this socket.
    socket.emit('eventName', { data: 'tosend' });
    
  • socket.broadcast.emit - Like the socket.emit, firing an event to the client but instead of firing the event to the associated socket, the event will be fired in all other connected socket except from the broadcasting one.
    socket.broadcast.emit('eventName', { data: 'tosend'});
    
  • socket.broadcast.to('roomName').emit - Sometimes we want to broadcast from one socket to all another socket in the same room rather than all the sockets in the server, in this way we can broadcast to all other sockets who joined a specific room
    socket.broadcast.to('roomName').emit('eventName', {data: 'tosend'});
    
  • io.sockets.emit - In this way we can fire an event to all the clients that connected to the server.
    io.sockets.emit('eventName', { data: 'tosend'});
    
  • io.of('/namespaceName').emit - Like rooms, in socket.io we can create namespaces, if you want to emit an event to all the clients that connected to a namespace, this is the way.
    io.of('/namespaceName').emit('eventName', { data: 'tosend'});
    

References

  • Demo of this chat application
  • Download the source code from github

Wednesday, November 7, 2012

Facebook API with Node.js

Facebook API is very powerful, it reveals a lot of data. Lets say that we want to work with the Facebook API from node.js to get some data from Facebook.

In this post I will explain how to achieve this task, I assume that you already have a valid access token of a Facebook user, If you didn't reach to this point, this link will show you how to start.

The module
The first thing is to build a module for Node, I want this module to be able to get a Facebook API open graph path (such as: /me/friends) and retrieve the data from Facebook. In this module I choose to work with the https module of node.js, with this module I can make secured requests to Facebook via https.

facebook.js
var https = require('https');

exports.get = function(accessToken, apiPath, callback) {
    // creating options object for the https request
    var options = {
        // the facebook open graph domain
        host: 'graph.facebook.com',

        // secured port, for https
        port: 443,

        // apiPath is the open graph api path
        path: apiPath + '?access_token=' + accessToken,

        // well.. you know...
        method: 'GET'
    };

    // create a buffer to hold the data received
    // from facebook
    var buffer = '';

    // initialize the get request
    var request = https.get(options, function(result){
        result.setEncoding('utf8');

        // each data event of the request receiving
        // chunk, this is where i`m collecting the chunks
        // and put them together into one buffer...
        result.on('data', function(chunk){
            buffer += chunk;
        });

        // all the data received, calling the callback
        // function with the data as a parameter
        result.on('end', function(){
            callback(buffer);
        });
    });
    
    // just in case of an error, prompting a message
    request.on('error', function(e){
        console.log('error from facebook.get(): '
                     + e.message);
    });

    request.end();
}

Now, In your Node.js server, include this facebook module and call the get() function with the open graph api path that you want.

var facebook = require('./facebook');
facebook.get('FB_ACCESS_TOKEN', '/me/friends', function(data){
    console.log(data);
});
The get() function is a generic function, you can call it with your api request path, pass the access token of the current user and a callback function to be called when the data is ready.


Monday, November 5, 2012

Hello World!

After writing thousands lines of code, I thought that it will be great to share the knowledge I've collected through all my years of coding. So I decided to start with.. you know.. a blog, and after playing a bit with the very cool admin panel of Blogspot, finally, I got one.


This blog is all about my passion, the Web. From front-end development with JavaScript, HTML5 and CSS3, to Node.js server side programming, Real-Time communication technologies right from the browser (The web is not stateless anymore :), MongoDB and even UI/UX and Web design.

I will try to keep things updated as much as I can, feel free to comment, ask questions and give suggestions.

Check out the About page to find out more about me.

Cheers.