Sunday, 19 April, 2015 UTC


Summary

I guess you could call this the sequel to my Previous Post about building real time applications in Ember and Node using SocketIO. Like all sequels the plan is to just do the same thing again, with a twist!
This time I'll be swapping Express out with Sails. Which incedentally means we have to write less code to achieve the same thing, because Sails is awesome.
If you don't know what Sails is, or would like an introduction to how to use Sails + Ember together, check out my previous post on this topic.
NOTE: Yes, I have heard of Sane Stack.
The Helpdesk Ticketer
We'll be building a new version of the helpdesk ticket application that was created in the previous post. Like the old version the tickets will be displayed in real-time.
Improvements made over the old version will be a new interface using bootstrap, and more importantly tickets will be saved to a database this time around. Since Sails makes all of this completely trivial, because Sails is awesome.
Create a Sails App
We need a directory to hold our stuff! We also need to create the Sails application:
> mkdir helpdesk-ticketer
> cd helpdesk-ticketer
> sails new server
The commands above give us a directory to keep all our files in (both the client and server, I like to keep them separate) and also commands sails to create a new project for us called server, because that seems like the most logical thing to call it.
Create the ticket API
> sails generate api ticket
Look at all the work I'm not doing!
Fleshing out the Ticket Model
Open the api/models/Ticket.js and add the following code:
module.exports = {
  attributes: {
    requestedBy: {
      type: 'string',
      required: true
    },

    description: {
      type: 'string',
      required: true
    }
   }
  };
This will tell Waterline what we expect the database schema to look like when creating a Ticket.
That's all we need to do on the server, so let's move on to the client application.
Add the Ember App
> ember new client
Look at all the work I'm still not doing! (Make sure you're in the root directory when you run this command)
Again there won't be much explanation of the actual ember code, I will explain the socket.io code present in the helpdesk tickets component however.
Adding some important dependencies
We'll use bootstrap (or in this case a themed bootstrap) for styling, we'll install it using bower and import it in our Brocfile.js
> bower install bootswatch --save

// brocfile.js
app.import('bower_components/bootswatch/paper/bootstrap.css');
We also need to provide a javascript file generated by Sails that provides access to the socket.io client. You can find this file in the server/assets/js/dependencies folder. Copy the file named sails.io.js to the vendor directory in our ember app. Then we will import that also in our Brocfile.
app.import('vendor/sails.io.js');
Setting up the Router
We only need one route for this application, we'll name it tickets and set it to be loaded on the root path. This essentially replaces our index route, you can just use the index route if you want, I just like to be more verbose.
this.route('tickets', { path: '/' });
Creating the Ticket Service
Here's the ticket service:
// client/services/ticket.js

import Ember from 'ember';

export default Ember.Service.extend({
  apiUrl: 'http://localhost:1337/api/v1/ticket',

  save(ticket) {
    return Ember.$.post(this.apiUrl, ticket);
  },

  find() {
    return Ember.$.getJSON(this.apiUrl);
  }
});
I'm just using ajax to find and save tickets, I'd recommend using something like Ember data in production, but I just want to keep it simple.
Creating the Ticket Route
Here's the route:
// client/tickets/route.js
import Ember from 'ember';

export default Ember.Route.extend({
  helpdesk: Ember.inject.service(),

  model() {
    return this.get('helpdesk').find();
  },

  actions: {
    addTicket(ticket) {
      this.get('helpdesk').save(ticket);
    }
  }

});
Creating the Ticket Template
The template:
{{helpdesk-tickets tickets=model}}
{{new-ticket-form action="addTicket"}}
The template is pretty minimal, it's highly reccommended that your separate your application out into components. As I have done here.
The Components
and finally the components that tie everything together:
New Ticket Form Component
Template
<footer class="new-ticket">
  <h5 class="text-muted">New Ticket</h5>
  <div class="row">
    <form {{action "createTicket" on="submit"}}>
      <div class="col-xs-3">
        {{input value=name placeholder="Your Name" class="form-control"}}
      </div>

      <div class="col-xs-7">
        {{input value=description placeholder="What's wrong?" class="form-control"}}
      </div>

      <div class="col-xs-2 text-right">
        <button type="submit" class="btn btn-success">Create Ticket</button>
      </div>
    </form>
  </div>
</footer>
Component
import Ember from 'ember';

export default Ember.Component.extend({
  actions: {
    createTicket() {
      var ticket = {
        requestedBy: this.get('name'),
        description: this.get('description'),
        createdAt: new Date()
      };

      this.sendAction('action', ticket);
      this.setProperties({
        name: '',
        description: ''
      });
    }
  }
});

Helpdesk Tickets Component
Template
{{#each tickets as |ticket|}}
  {{helpdesk-ticket ticket=ticket}}
{{/each}}
Component
import Ember from 'ember';

export default Ember.Component.extend({
  setup: function () {
    io.socket.get('/api/v1/ticket');
    io.socket.on('connect', () => {
      Ember.Logger.debug('helpdesk-tickets component is listening for socket.io events');
    });

    io.socket.on('ticket', (message) => {
      if(message.verb === "created") {
        this.get('tickets').pushObject(message.data);
      }
    });

  }.on('init')
});
This is where the real-time data binding is happening. The sails.io.js library provided to us will automatically connect to the sails server, it provides us with a global io object what we can leverage to perform REST-like operations against the server.
For example io.socket.get('/todo') would send a get request to the /todo url, which can then be handled on the server. The same is true for .put .post and .delete operations. Pretty cool!
In this case I am using io.socket.get('/api/v1/ticket'); to subscribe to changes to the ticket model. By default Sails will emit socket.io events whenever anything has changed about the model, passing us the ID of the entity affected, the verb (like created, deleted, updated) and the new data.
We listen for these events using io.socket.on(). Again, by default, Sails will emit an event named the same as the model for changes to that model. So if ticket is updated, the event emitted will be named 'ticket'.
We are listening for this event, and checking if the messages verb is created, if it is, we are pushing the messages data to our tickets array, thus updating the view automatically (because Ember). Since since is all achieved using socket.io this will happen in real time.
Helpdesk Ticket Component
Template
<div class="panel panel-default">
  <div class="panel-heading">
    <h5>{{ticket.requestedBy}}</h5>
  </div>
  <div class="panel-body">
    {{ticket.description}}
  </div>
  <div class="panel-footer">
    Created on: {{ticket.createdAt}}
  </div>
</div>
Component
import Ember from 'ember';

export default Ember.Component.extend({
  classNames: ['panel', 'panel-default']
});

Running the Application
 sails lift
 # in a new terminal
 ember server --proxy http://localhost:1337
We'll start up our sails server, and also our Ember server proxies to the sails servers local address.
Navigate to http://localhost:4200 in the browser, or a couple of browsers. To see our application in action.
Done!
You can view this project on GitHub, but please be aware this isnt even close to being a production ready application. I cut a lot of corners, and missed a lot out that you might want to do in the real world. This is just a coding exercise to show how this stuff works.