Skip to content

Instantly share code, notes, and snippets.

@justinbmeyer
Forked from fara82/weekly_widget_chat_old.md
Last active December 14, 2015 04:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save justinbmeyer/5027334 to your computer and use it in GitHub Desktop.
Save justinbmeyer/5027334 to your computer and use it in GitHub Desktop.
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 shows how to integrate Socket.io to can.Model's events system.

The Widget

The same demo page is in each window below. Messages created in one page are pushed to and displayed in the other window.

<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

This widget works by integrating socket.io into can.Model's event system and using can.Model's event system to listen for messages being created and display them on the page. To understand how it works, I'll cover:

  • can.Model's event system
  • Connecting can.Model's event system to socket.io
  • Using can.Model to create the chat client

can.Model's event system

By default, can.Model connects to a RESTful interface. When a can.Model is created like:

Message = can.Model({
  findAll, updated, 
},{})

You can create a new message on the server like:

message = new Message({body: "Hi, anyone there?"}).save()

When connecting to a standard RESTful interface, .save() posts the message to the server, the server creates the message and returns the id to can.Model. Upon receiving the response, can.Model triggers a "created" event on the instance and the Model constructor function. This allows you to listen to when a message is created like:

message.bind("created", function( ev, message ){
  // do something when the message is created
})

Or when any message is created like:

Message.bind("created", function( ev, createdMessage ) {
  // do something with the created message
}) 

can.Model also produces "updated" and "destroyed" events on instances and the constructor function:

Message.bind("updated", function( ev, updatedMessage ) {

}).bind("destroyed", function(ev, destroyedMessage) {

})

A real-time web application uses server-side events (SSEs) to send updates to the client. For example, when a new Message is created by anyone, the client is notified immediately. But, it's beneficial to maintain can.Model's event interface for flexibility and loose coupling. Using can.Model's interface, a chat widget could insert all new messages into the page like:

Message.bind("created", function( ev, createdMessage ) {
  $("#messages").append("<p>"+createdMessage.body+"</p>")
}) 

In the next section, I'll show how to connect can.Model to socket.io so anytime ANY message is created on the server, a Message model "created" event is published.

Connecting can.Model's event system to socket.io

First, we will look at the server's code, then the client.

Server

Using a node [Express] server and socket.io, I listen for POST /messages request with the following code:

var messages = [],
  id=0; 

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);
});

The preceding code:

  1. Creates a message object with an id.
  2. Adds the message to an array of messages (which will be used later for retrieving old messages)
  3. Uses socket.io to publish a "message-created" event with the message object.

The following provides a service to retrieve existing messages:

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

Client

Ignoring the server-side events for the moment, I'd create a Message model like:

Message = can.Model({
  findAll: "GET /messages",
  create: "POST /messages"
},{})

The following uses Socket.io to listen to 'message-created' server side events and publish a Message model "created" event:

var socket = io.connect(myServerUrl)
socket.on('message-created', function(message){
new Message(message).created()
})

.created() is a method on can.Model that internally publishes a "created" event on a model instance and model constructor function.

But wait! There's a problem. When the user creates a message like:

new Message({body: "Hi world"}).save()

Two Message model "created" events will fire:

  1. from the response
  2. from the server side event

To prevent this we .....

Message.model({id: attrs.id}).updated(attrs)

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.

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