Skip to content

Instantly share code, notes, and snippets.

@fara82
Last active December 14, 2015 01:08
Show Gist options
  • Save fara82/5003495 to your computer and use it in GitHub Desktop.
Save fara82/5003495 to your computer and use it in GitHub Desktop.
Weekly Widget: Real-time Chat application Using Socket.io and can.Model
title tags author lead layout
Weekly Widget 4 - Chat
open-source can.Model Socket.io
fara82
Sending and receiving messages using Socket.io and can.Model.
post

This week's widget is a real-time chat application that demonstrates sending and receiving messages using Socket.io and can.Model. This article will show how to integrate Socket.io to can.Model's events system.

The Widget

The same chat widget is showing in each window below. Messages created in one fiddle will be pushed and displayed to the other.

<iframe style="width: 100%; height: 300px" src="http://jsfiddle.net/HnYVb/13/embedded/" allowfullscreen="allowfullscreen" frameborder="0"></iframe> <iframe style="width: 100%; height: 300px" src="http://jsfiddle.net/HnYVb/13/embedded/" allowfullscreen="allowfullscreen" frameborder="0"></iframe>

How it works

At the end of the section, I will explain how you can connect Socket.io with can.Model on the server-side. I will touch on how the server is set up and how it accepts requests from can.Model. Now, I'd like explain the general use of the can.Model events system. By default, can.Model provides a RESTful interface. When an instance of a model is created and saved, a 'created' event is fired after the AJAX request completes.

I will touch on how the server is set up and how it accepts requests from can.Model. For the client side, I will then explain how a new message is created. I will the discuss more in depth how you can use can.Model to list all messages and add new messages to a list when they are created. And finally, I will cover how I used can.Model to connect to Socket.io (a library I used to publish events between server and client).

Server setup

I set up a node server and installed Socket.io. Socket.io is a library written in javascript that can be used for real-time applications. Here is the server side code.

var express = require('express'),
  app = express.createServer(),
  socket = require('socket.io'),
  io = socket.listen(app);

// set up static dir
app.use(express.static(__dirname + '/'));
app.use(express.bodyParser());

// io is the Socket.IO server object
io.configure(function () { 
  io.set("transports", ["xhr-polling"]); 
  io.set("polling duration", 10); 
});

app.listen(process.env.PORT || 5000);

// persistent messages array that holds the last 100 messages
var messages = [],
  id=0; 

app.all('/messages', function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "X-Requested-With");
  next();
});

// find all messages
app.get('/messages', function (req, res) {
  res.send(messages);
});

app.post('/messages', function (req, res) {
  var message = {
    id: ++id,
    body: req.body.body
  }

  messages.push(message);

  if(messages.length === 100) {
    messages.shift();
  }

  io.sockets.emit('message-created', message); 
  res.send(message);
});

I have created a messages array to store all chat messages with a threshold of 100. I have used Socket.io to set up services that accept GET AND POST requests. As shown below, the GET request is set up to return what is stored in the messages array.

app.get('/messages', function (req, res) {
  res.send(messages);
});

The POST service is used to create new messages. After a message is created on the server, Socket.io will fire a 'message-created' event that the client will listen for. The client will respond to this event by creating a message model instance and firing a created event. This will append a message to the DOM (more on this later).

app.post('/messages', function (req, res) {
  var message = {
    id: ++id,
    body: req.body.body
  }

  messages.push(message);

  if(messages.length === 100) 
    messages.shift();
  }

  io.sockets.emit('message-created', message); 
  res.send(message);
});

When a new message is created

A user can create a message by entering a message in the form and submitting that form.

 $("#create-message").bind("submit",function(ev){      
     ev.preventDefault()
     new Message({body: $("#body").val()}).save()
 })

In the submit event, a new Message model instance is created and saved. When this message is saved, the Message model will then post that message to the server. Usually when save is called, the model will make an ajax request. When it is done it will fire a 'created' event and return a deferred object. In our Model's create function, we return a deferred object which will never resolve, thereby preventing the 'created' event on the model. We want to do this so that we are not showing duplicate messages to the user that created the message. This approach only displays messages that are created and posted to the server first.

Here is what this looks like:

   create : function(attrs) {
       $.post(myServerUrl + '/messages', attrs)
       return $.Deferred()
   } 

After the message is saved to the 'messages' array, Socket.io will then publish a 'message-created' event that the client will respond to. I will go more into depth about the way the client will respond to this Socket.io event in the next section.

Role of can.Model in creating messages

The client will listen to the 'message-created' event and create yet another Message model instance. It is important to note that this time it will trigger the 'created' event so that the message can be attached to the DOM. The client will listen to this event and create a message using this syntax:

new Message(message).created()

Here I am artificially triggering the 'created' event, because I want to prevent the save function from being called (which would make an unnecessary ajax request). So, when this instance is created and the 'created' event is triggered, I will append this new message to the DOM as seen here.

Message.bind("created", function(ev, message){
    var messagesCont = $("#messages"); 
    messagesCont.append( can.view('messageEJS', [message]) )
})

Leveraging can.Model to get and create messages

When the app loads, I make a request to get all messages that are stored on the server. can.Model is set up to easily make a request to find or create messages.

var Message = can.Model({
    findAll : 'GET ' + myServerUrl + '/messages',
    // create a message, but prevent the 'created' event from firing
    create : function(attrs) {
        $.post(myServerUrl + '/messages', attrs)
        return $.Deferred()
    }
},{});

When a message is created, as I mentioned in the first section, can.Model will make a request to create a message on the server. When the message is created, Socket.io will emit a 'message-created' event that the client will respond to. The important thing to note is that I am using the model to handle my message data. By creating a message instance twice, I am able to use the 'save' function to create the message on the server, while I can use the other message instance's 'created' event after to actually append it to the DOM.

What's cool about it

  1. Single entry point
  • No matter how we create messages, (either after when you create a message or someone else creates a message), we can always respond to the same way. This supports the idea that the client side will be more maintainable using this technique. If you have a single entry point, you can create a message in various other ways and still have the same result.
  1. Using can.Model to connect to Socket.io
  • As described above, we can easily use and setup can.Model to post messages to the server using Socket.io.

So, now you know how to create a real-time chat application using Socket.io and can.Model. For next week's weekly widget, I am going to demonstrate a different technique for sending and receiving messages in another real-time chat app. Stay tuned.

@justinbmeyer
Copy link

it would be nice if you talked a little bit about the server setup.

The "When the app loads" section is confusing to read (ordered list with one unordered list item as child)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment