In this recipe, we'll combine what you learned with initializers and dependency injection to create a chat room. The chat room will use WebSockets to communicate with the host.
$ ember g service sockjs $ ember g component chat-room $ ember g initializer application $ bower install sockjs --save
These will generate the files needed for the project. The chat-room
component will have all the logic for the chat room that we'll create.
// ember-cli-build.js … app.import('bower_components/sockjs/sockjs.min.js'); …
This will import the library so that we can use the global variable, sockjs
, anywhere in our application.
SockJS
:// app/services/sockjs.js /* global SockJS */ import Ember from 'ember'; const {run} = Ember; export default Ember.Service.extend(Ember.Evented,{ socket: null, init() { this._super(); let socket = new SockJS('http://localhost:7000'); socket.addEventListener('message', run.bind(this, (event)=> { this.trigger('messageReceived', event.data); console.log(event.data); })); this.set('socket',socket); }, sendInfo(message) { this.get('socket').send(message); } });
Let's take a look at this in more detail:
/* global SockJS */
This line is needed so that JSHint won't complain about the SockJS global variable. JSHint is the built-in library for Ember CLI that detects errors in your program:
export default Ember.Service.extend(Ember.Evented,{
This adds the Ember.Evented
mixin to the service. This mixin allows Ember objects to subscribe and emit events. This is perfect for what we need to do in this example:
init() { this._super(…arguments); },
The init
method is where the SockJS socket will be set up and the event listener will be created. This method will fire after the service is initialized. The this._super
method guarantees that the init
method is set up properly:
let socket = new SockJS('http://localhost:7000');
The preceding line creates a new socket server at the localhost port 7000
:
socket.addEventListener('message', run.bind(this, (event)=> { this.trigger('messageReceived', event.data); console.log(event.data); })); this.set('socket',socket);
This creates an event listener that is fired when a message is received. The run.bind
method is a part of the Ember run
loop that we'll describe later in this chapter. This ensures that all the requests are taken care of properly in the run
loop.
The this.trigger
is a part of the Event.Evented
class. The trigger
method creates a new event called messageReceived
. We can subscribe to this event so that other methods in Ember can be triggered when a message is received. Finally, we log
the information in event.data
to the console and set
the socket
property:
sendInfo(message) { this.get('socket').send(message); }
This method accepts message
and sends it the socket
server we defined earlier. The socket
property is accessed here.
// app/initializers/application.js export function initialize( application ) { application.inject('component', 'sockjs', 'service:sockjs'); } export default { name: 'websockets', initialize };
The initializer takes the service called sockjs
and injects it into all the components. This will be run whenever the program first boots. We use this so that we don't have to specifically inject the sockjs
service into each component.
// app/components/chat-room.js import Ember from 'ember'; const {$} = Ember; export default Ember.Component.extend({ message: '', init() { this._super(…arguments); this.sockjs.on('messageReceived',this, 'messageReceived'); }, messageReceived(message){ $('#chat-content').val((i, text)=> `${text}${message} `; ); this.set('message',message); }, actions: { enter(info,username) { this.sockjs.sendInfo(`${username}: ${info}`); } } });
Let's break this down into smaller parts:
init() { this._super(…arguments); this.sockjs.on('messageReceived',this, 'messageReceived'); },
This init
method fires on initialization and sets up the component. We can then subscribe to the event that we created earlier in the service using on. The first parameter is the name of the event. The second is the binding. The last is the name of the callback function. Therefore, in this example, whenever a message is received in the service, the messageReceived
callback in this component will be fired:
messageReceived(message){ $('#chat-content').val((i, text)=> `${text}${message} ` ); this.set('message',message);
This is the messageReceived
callback. It uses a little bit of jQuery to find the chat-content
ID and concatenate the existing message to it using ES6 string interpolation. In addition, the message
property is set:
actions: { enter(info,username) { this.sockjs.sendInfo(`${username}: ${info}`); } }
This action sends info
and username
to the socket. This way, any other clients connected will be notified.
chat-room.hbs
template file for the component:// app/templates/components/chat-room.hbs <textarea id="chat-content" style="width:500px;height:300px" ></textarea><br/> {{input type='text' placeholder='User Name' value=uname}} {{input type='text' placeholder='Chat Message' value=mess}} <button {{action 'enter' mess uname}}>Send</button><br> Message received:{{message}}
This code displays the messages from the server. The input
helpers capture the username and message. Each value is passed to the enter
action when the Send
button is clicked.
application.hbs
file:// app/templates/application.hbs <h2 id="title">Welcome to Ember</h2> {{outlet}} {{chat-room}}
This adds the component to the application.
As each client connects they'll be able to send messages to the server. Each client will receive these messages and display them in the chat box.
A chat room consists of multiple clients talking to a server. The server's job is to notify all the other clients connected when a messages is received. This is done in this example using SockJS with WebSockets. The SockJS library has message events that we can set up in Ember. When a message is received, it is then sent to a component that updates its template with the message.
To use the preceding example, you'll need to set up a WebSocket server. Here are the steps to create a simple Node.js SockJS server. To learn more about SockJS, check out their GitHub page at https://github.com/sockjs/sockjs-node.
npm init
command:$ npm init $ npm install sockjs –save
This will generate the package.json
file and install the SockJS server in it.
app.js
file for the WebSocket server:// app.js var http = require('http'); var sockjs = require('sockjs'); var clients = {}; function broadcast(message){ for (var client in clients){ clients[client].write(message); } } var socketServer = sockjs.createServer({ sockjs_url: 'http://cdn.jsdelivr.net/sockjs/1.0.1/sockjs.min.js' }); socketServer.on('connection', (conn)=> { clients[conn.id] = conn; conn.on('data', (message)=> { console.log('received ' + message); broadcast(message); }); conn.write("hello from the server thanks for connecting!"); conn.on('close', ()=> { delete clients[conn.id]; }); console.log("connected"); }); var server = http.createServer(); socketServer.installHandlers(server); server.listen(7000, '0.0.0.0');
This server uses the SockJS library to create a new socket server. When a new client connects, it's added to an array. When it receives data, it broadcasts this data to all the other servers connected using this function:
function broadcast(message){ for (var client in clients){ clients[client].write(message); } }
This function sends a broadcast
message
to every other client connected with the message
it just received. When Ember receives this information, it's written to the chat box.