Building an engaging online chat application will definitely boost interaction between the involved parties, be it an online store with chat app for customer support in order to improve sales and ensure customers satisfaction, or a social platform with numerous users. In addition to the realtime engagement, wouldn’t it be nice to be able to determine the attitude of users based on a particular response during a chat?
In this tutorial, we will build a chat application with sentiments. Sentiment analysis will help us to computationally identify and categorize opinions expressed in a message during a chat. Using sentiment analysis, we will then proceed to suggest emojis based on the context of the received message.
Here’s what the completed demo will look like:
This chat application will be built using Ember.js and Pusher Channels. We will approach the project by setting up the client side first and then proceed to configure the server side of the application by using Express.
It is important that you have Node.js and npm installed on your machine in order to properly run the basic setups, learn how to install Node.js and npm here. A quick run-down of the core technologies we will be using include:
req
, hence req.body
stores this payload for each request.Installing Ember.js is quite easy. The most convenient and recommended way of getting an Ember app up and running with a single command is by using the Ember CLI tool. You can install it with npm, open your terminal and type this command:
npm install -g ember-cli
The Ember CLI will give us access to the ember
command that will be used to set up our project easily.
We can now proceed to create our application by using the ember new
command. Open up the terminal on your machine and run a command that will create an application named chat-app-ember
in your project directory or any location specified:
ember new chat-app-ember
Next, change directory into the newly created project and start the development server:
1// change directory 2 cd chat-app-ember 3 4 // start the development server 5 ember serve
This will start the development server on http://localhost:4200. Open your favorite browser and navigate to that URL, you should see this:
That is the default welcome page by Ember.js, we will restructure this in a bit.
Run the following command to install the dependencies required for this project using:
1npm install --save axios pusher pusher-js he strftime 2 npm install --save body-parser cors dotenv express sentiment 3 ember install ember-browserify
To get started with Pusher Channels, sign up](https://dashboard.pusher.com/accounts/sign_up) for a free Pusher account.
Create a new app by selecting Channels apps on the sidebar and clicking Create Channels app button on the bottom of the sidebar:
Configure an app by providing basic information requested in the form presented. You can also choose the environment you intend to integrate Pusher Channels with to be provided with some boilerplate code:
You can retrieve your keys from the App Keys tab:
Create a .env
file in the root directory of your application and add your application credentials as obtained from your Pusher dashboard as follows:
1PUSHER_APP_ID=YOUR_APP_ID 2 PUSHER_APP_KEY=YOUR_APP_KEY 3 PUSHER_APP_SECRET=YOUR_APP_SECRET_KEY 4 PUSHER_APP_CLUSTER=CLUSTER
We will make use of the variables specified in the above snippet later in our project. And do ensure that you replace YOUR_APP_ID
, YOUR_APP_KEY
, YOUR_APP_SECRET_KEY
and CLUSTER
placeholders with the appropriate credentials.
Our application will need and work better with a simple server that will receive and process a POST
request with a new message during chat. It’ll trigger an event on the Pusher channel that the client side can subscribe to. We’ll set it up by using Node and Express and run it on http://localhost:3000.
Since the Ember application that we configured earlier is running on a different domain, we’ll enable CORS in order to ensure communication between the Express server and Ember.
Create an app.js
file in the root directory of your application and add the following code snippet to it to set up the server:
1// app.js 2 3 const express = require('express'); 4 const bodyParser = require('body-parser'); 5 const cors = require('cors'); 6 const Pusher = require('pusher'); 7 const Sentiment = require('sentiment'); 8 require('dotenv').config(); 9 10 const app = express(); 11 12 app.use(cors()); 13 app.use(bodyParser.urlencoded({extended: false})); 14 app.use(bodyParser.json()); 15 16 // Ensure that your pusher credential are properly set in the .env file 17 const pusher = new Pusher({ 18 appId: process.env.PUSHER_APP_ID, 19 key: process.env.PUSHER_APP_KEY, 20 secret: process.env.PUSHER_APP_SECRET, 21 cluster: process.env.PUSHER_APP_CLUSTER, 22 encrypted: true 23 }); 24 25 app.set('port', process.env.PORT || 3000); 26 27 app.post('/messages', (req, res) => { 28 const sentiment = new Sentiment(); 29 const sentimentScore = sentiment.analyze(req.body.text).score; 30 const payload = { 31 text: req.body.text, 32 username: req.body.username, 33 time: req.body.time, 34 sentiment: sentimentScore 35 } 36 pusher.trigger('chat', 'message', payload); 37 res.send(payload) 38 }) 39 app.listen(app.get('port'), () => { 40 console.log("Listening on port " + app.get('port')); 41 })
Here we loaded all the necessary middlewares for the Express server and then configured Pusher using the credentials we added to our environment variables earlier.
Furthermore, we also created /messages
endpoint that will receive and process messages sent in from our Ember app. Finally, we triggered an event named message
to Pusher Channels on a channel called chat
.
Open another terminal and run the command below to start the server on http://localhost:3000
node app.js
This will log a message to the console as shown by the image below:
Now that we are done setting up the server, it’s time to switch our focus to the client. Next, we’ll create components and services required for our application.
Our application will require a username so that we can identify the chatting individuals. In order to keep things simple, we won’t setup a registration and login process but rather use Ember service to store data that components within our application needs to access.
We are going to create a service that will be responsible for storing the name of the active user. Once created, the service can be accessed by injecting it into the UI components as we’ll see later.
Generate a new Ember service by running the command below from your terminal:
1// generate a service 2 ember g service active-user
The command above will create a new file app/services/active-user.js
and a corresponding test file that is not required for now. active-user
service will be responsible for storing the user’s name before they can start chatting. Open the file and update it with:
1// ./app/services/active-user.js 2 3 import Service from '@ember/service'; 4 export default Service.extend({ 5 user: null, 6 setUser(username) { 7 this.set('user', username); 8 }, 9 hasUser() { 10 return this.get('user') != undefined; 11 } 12 });
We created a property user
and provided a method for setting the user. The second method hasUser
will return true if we have an active user so that we can decide if a user is logged in or not.
Ember supports the usage and sharing of UI elements on multiple pages. We will leverage this and generate components for separate parts of our application. The components required for our application are:
chat-app
: this will encompass the entire chat applicationchat-username
: this component will be used to show the user a form to enter their usernamechat-message
: component to display each chat messagelist``-messages
: will list all the existing messagesThe Ember component generally consist of two parts: a JavaScript component file that defines behavior and a Handlebars template that defines the markup for the component’s UI.
We’ll start by generating a component for the chat-app
, by using the command below:
ember g component chat-app
Locate the component file ./app/components/chat-app.js
and update it with:
1// ./app/components/chat-app.js 2 3 import Component from '@ember/component'; 4 import { inject as service } from '@ember/service'; 5 import { computed } from '@ember/object'; 6 export default Component.extend({ 7 activeUserService: service('active-user'), 8 nameIsSet: computed('activeUserService.user',function() { 9 return this.get('activeUserService').hasUser(); 10 }) 11 });
Here we injected the active-user
service into the chat-app
component by using the inject
function from the @ember/service
module.
We then created a computed property which will be set to true or false if a username for a user is set.
The template for the chat-app
component will conditionally render two other components list-messages
and chat-username
if the username of a user is set. Open the template file ./app/templates/components/chat-app.hbs
and update it as shown below:
1{{!-- ./app/templates/components/chat-app.hbs --}} 2 3 <div> 4 {{#if nameIsSet}} 5 <div class="container"> 6 <div class="row chat-window col-xs-12 col-md-5 col-md-offset-3" id="chat_window_1"> 7 <div class="col-xs-12 col-md-12"> 8 <div class="panel panel-default"> 9 <div class="panel-heading top-bar"> 10 <div class="col-md-8 col-xs-8"> 11 <h3 class="panel-title"><span class="glyphicon glyphicon-comment"></span> Welcome <b>{{ userName }}</b></h3> 12 </div> 13 <div class="col-md-4 col-xs-4" style="text-align: right;"> 14 <a href="#"><span class="glyphicon glyphicon-remove icon_close" data-id="chat_window_1"></span></a> 15 </div> 16 </div> 17 <div class="panel-body msg_container_base"> 18 {{#list-messages}}{{/list-messages}} 19 </div> 20 </div> 21 </div> 22 </div> 23 </div> 24 {{else}} 25 <div class="text-center"> 26 {{#chat-username}}{{/chat-username}} 27 </div> 28 {{/if}} 29 </div>
The username component will show the user a form to enter their name. Run this command to generate the component:
ember g component chat-username
Once a user inputs a username and submits the form, we will use the userSubmittedName
action to get the name and send it to the active-user
service. Update the username component file as shown here:
1// ./app/components/chat-username.js 2 3 import Component from '@ember/component'; 4 import { inject as service } from '@ember/service'; 5 export default Component.extend({ 6 activeUserService: service('active-user'), 7 actions: { 8 userSubmittedName() { 9 const user = this.get('userName'); 10 this.get('activeUserService').setUser(user); 11 } 12 } 13 });
The username template just shows the input field and a button that calls the userSubmittedName
action that we defined earlier. Add the content below to ./app/templates/components/chat-username.hbs
file:
1{{!-- ./app/templates/components/chat-username.hbs --}} 2 3 <div class="intro"> 4 <p>Enter your username name and start chatting! </p> 5 <div class="start-chat"> 6 {{input placeholder="jack" value=userName }} 7 <button {{action "userSubmittedName"}}> 8 Start chat 9 </button> 10 </div> 11 </div>
Next, we’ll generate the chat-message
component with:
ember g component chat-message
This component will display each individual’s message:
1// ./app/components/chat-message.js 2 3 import Component from '@ember/component'; 4 import { computed } from '@ember/object'; 5 import strftime from 'npm:strftime'; 6 import he from 'npm:he'; 7 export default Component.extend({ 8 timestamp: computed('message.time', function() { 9 return strftime('%H:%M:%S %P', new Date(this.get('message').time)); 10 }), 11 text: computed('message.text', function() { 12 return he.decode(this.get('message').text); 13 }), 14 });
In the code snippet above, we defined two computed properties; timestamp
and text
. The timestamp
property uses the strftime library to format time while we used the he library to decode our HTML from the server.
This template will display the username along with two properties that we defined earlier in our component. Open ./app/templates/components/chat-message.hbs
and update it like this:
1{{!-- ./app/templates/components/chat-message.hbs --}} 2 3 <div class="col-md-2 col-xs-2 avatar"> 4 <img src="http://www.bitrebels.com/wp-content/uploads/2011/02/Original-Facebook-Geek-Profile-Avatar-1.jpg" class=" img-responsive "> 5 </div> 6 <div class="col-md-10 col-xs-10"> 7 <div class="messages msg_receive"> 8 <p>{{ text }}</p> 9 <time datetime="2009-11-13T20:00">{{ message.username }} • {{ timestamp }}</time> 10 <p>{{ message.mood }}</p> 11 </div> 12 </div>
All the existing messages posted by an individual user will be listed by this component. To generate it, open your terminal and run the command below:
ember g component list-messages
Open the ./app/components/list``-messages``.js
file and add the following content:
1// ./app/components/list-messages.js 2 3 import Component from '@ember/component'; 4 import { inject as service } from '@ember/service'; 5 import axios from 'npm:axios'; 6 import Pusher from 'npm:pusher-js'; 7 8 const SAD_EMOJI = [55357, 56864]; 9 const HAPPY_EMOJI = [55357, 56832]; 10 const NEUTRAL_EMOJI = [55357, 56848]; 11 12 export default Component.extend({ 13 activeUserService: service('active-user'), 14 messages: ['Welcome to your chat app!'].map((message) => { 15 return { 16 username: 'Admin', 17 time: new Date(), 18 text: message, 19 }; 20 }), 21 init() { 22 this._super(...arguments); 23 let pusher = new Pusher('YOUR_APP_KEY', { // update your APP_KEY 24 cluster: 'CLUSTER', 25 encrypted: true 26 }); 27 const channel = pusher.subscribe('chat'); 28 channel.bind('message', data => { 29 const analysis = data.sentiment > 0 ? HAPPY_EMOJI : (data.sentiment === 0 ? NEUTRAL_EMOJI : SAD_EMOJI); 30 const response = { 31 text: data.text, 32 username: data.username, 33 time: data.time, 34 mood: String.fromCodePoint(...analysis) 35 } 36 this.get('messages').pushObject(response); 37 }); 38 }, 39 actions: { 40 newMessage() { 41 const text = this.get('newMessage'); 42 const username = this.get('activeUserService').get('user'); 43 const time = new Date(); 44 45 axios.post('http://localhost:3000/messages', { text, username, time }); 46 this.set('newMessage', ''); 47 } 48 } 49 });
First, we imported the required module for this component and we added some constants of code points for a particular sentiment emoji. This will help us rendered an emoji corresponding to the mood of the user during the chat as analyzed by the sentiment module.
In addition, we injected the active-user
service and set a default chat message for every user to see before starting a chat.
Next, we initialize Pusher with the APP_KEY
and CLUSTER
as obtained from our Pusher account dashboard and then proceeded to use the subscribe()
method from Pusher to subscribe to the created chat
channel.
Finally, the newMessage()
action basically receives the message posted by a user and POST
it to the server alongside the username
and timestamp. This action will be triggered once the Send button in the template for this component is clicked.
Don’t forget to replace the YOUR_APP_KEY
and CLUSTER
placeholder with the appropriate details from your Pusher account dashboard.
The responsibility of this template is to loop over each message and render a chat-message
component.
1{{!-- ./app/templates/components/list-messages.hbs --}} 2 3 {{#each messages as |message|}} 4 <div class="row msg_container base_receive"> 5 {{#chat-message message=message}}{{/chat-message}} 6 </div> 7 {{/each}} 8 <div class="panel-footer"> 9 <div class="input-group"> 10 {{ input value=newMessage id="btn-input" type="text" class="form-control input-sm chat_input" placeholder="Write your message here..."}} 11 <span class="input-group-btn"> 12 <button class="btn btn-primary btn-sm" id="btn-chat" {{action "newMessage"}}>Send</button> 13 </span> 14 </div> 15 </div>
Update the application template with:
1{{!-- ./app/templates/application.hbs --}} 2 3 {{#chat-app}}{{/chat-app}} 4 {{outlet}}
To add some minimal styling to the page, open ./app/styles/app.css
and paste the code in it:
1// ./app/styles/app.css 2 3 .col-md-2, .col-md-10{ 4 padding:0; 5 } 6 .chat-window{ 7 margin-top: 50px; 8 } 9 .chat-window > div > .panel{ 10 border-radius: 5px 5px 0 0; 11 } 12 .icon_minim{ 13 padding:2px 10px; 14 } 15 .msg_container_base{ 16 background: #e5e5e5; 17 margin: 0; 18 padding: 0 10px 10px; 19 min-height:150px; 20 overflow-x:hidden; 21 } 22 .top-bar { 23 background: #666; 24 color: white; 25 padding: 10px; 26 position: relative; 27 overflow: hidden; 28 } 29 .msg_sent{ 30 padding-bottom:20px !important; 31 margin-right:0; 32 } 33 .messages { 34 background: white; 35 padding: 10px; 36 border-radius: 2px; 37 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); 38 max-width:100%; 39 } 40 .messages > p { 41 font-size: 16px; 42 margin: 0 0 0.2rem 0; 43 } 44 .messages > time { 45 font-size: 13px; 46 color: #ccc; 47 } 48 .msg_container { 49 padding: 10px; 50 overflow: hidden; 51 display: flex; 52 } 53 img { 54 display: block; 55 width: 100%; 56 } 57 .avatar { 58 position: relative; 59 } 60 .base_receive > .avatar:after { 61 content: ""; 62 position: absolute; 63 top: 0; 64 right: 0; 65 width: 0; 66 height: 0; 67 border: 5px solid #FFF; 68 border-left-color: rgba(0, 0, 0, 0); 69 border-bottom-color: rgba(0, 0, 0, 0); 70 } 71 .msg_container_base::-webkit-scrollbar-track 72 { 73 -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 74 background-color: #F5F5F5; 75 } 76 .msg_container_base::-webkit-scrollbar 77 { 78 width: 12px; 79 background-color: #F5F5F5; 80 } 81 .msg_container_base::-webkit-scrollbar-thumb 82 { 83 -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); 84 background-color: #555; 85 } 86 .btn-group.dropup{ 87 position:fixed; 88 left:0px; 89 bottom:0; 90 } 91 .red-gradient-background { 92 background: #300D4F; 93 background-image: -webkit-linear-gradient(top, #300D4F, #d14e37); 94 background-image: linear-gradient(to bottom, #300D4F, #d14e37); 95 } 96 .red-gradient-background h2 { 97 color: #ffffff; 98 text-transform: capitalize; 99 } 100 .intro { 101 margin-top: 50px; 102 font-size: 16px; 103 } 104 .intro p { 105 margin-bottom: 20px; 106 font-size: 16px; 107 }
Open the ./app/index.html
file and include the CDN file for Bootstrap to enhance the styling and layout of our application, also include a navigation bar code snippet just after the opening of the <body>
tag. Open up the file and add update as shown below:
1<!-- ./app/index.html --> 2 <!DOCTYPE html> 3 <html> 4 <head> 5 <meta charset="utf-8"> 6 <meta http-equiv="X-UA-Compatible" content="IE=edge"> 7 <title>ChatAppEmber</title> 8 <meta name="description" content=""> 9 <meta name="viewport" content="width=device-width, initial-scale=1"> 10 {{content-for "head"}} 11 <link integrity="" rel="stylesheet" href="{{rootURL}}assets/vendor.css"> 12 <link integrity="" rel="stylesheet" href="{{rootURL}}assets/chat-app-ember.css"> 13 <link href="http://netdna.bootstrapcdn.com/bootstrap/3.1.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css"> 14 15 {{content-for "head-footer"}} 16 </head> 17 <body> 18 <nav class="navbar navbar-expand-lg navbar-light bg-light red-gradient-background"> 19 <div class="collapse navbar-collapse justify-content-md-center" id="navbarsExample08"> 20 <h2>Realtime chat app</h2> 21 </div> 22 </nav> 23 {{content-for "body"}} 24 <script src="{{rootURL}}assets/vendor.js"></script> 25 <script src="{{rootURL}}assets/chat-app-ember.js"></script> 26 {{content-for "body-footer"}} 27 </body> 28 </html>
Stop the Ember development server if it is currently running by typing Ctrl + C
in the terminal and restart it with:
1// start the server 2 ember serve
Don’t forget to run node app.js
to start the Express server incase you haven’t done that. Once you are done, navigate to http://localhost:4200 to test the application:
In this tutorial, we have been able to build a basic chat application with sentiment. Sentiment analysis was used here to detect the mood of users during a chat. This tutorial can be improved on by adding extra features to make it go beyond just a basic chat application.
I hope you found this helpful. The source code can be found here on GitHub.