Build a typing indicator with JavaScript

Introduction

A popular feature of chat applications is a typing indicator, which displays the name or username of who is currently typing. In this tutorial, we will walk through how to build a typing indicator in a chat application using Pusher with JavaScript.

Here's how the final application will look:

We will focus on the implementation of the typing indicator feature of the application.

Now, let's dive into how we built the typing indicator in the chat application.

Setup a Pusher account

We will be using Pusher for the realtime features of this chat application, so first off you'll need to create a Pusher account. When you first log in, a pop-up dialogue appears as shown below:

If you already have an account, log in to the dashboard and click on the Create new app button in the Your apps to the left. Select VanillaJS for the front-end tech and Node.js for the backend tech. (Don't worry, the tech stack you select doesn't matter as you can always change it later. Its purpose is to generate starter code that you can use to start communicating with Pusher)

After creating the application, go to the App Keys tab and copy your App ID, Key, and Secret credentials. We will use them later in the tutorial.

Setup the Node.js server

Now that you have your Pusher Keys, let's get to building the realtime chat application.

First, generate a Node.js application using this command:

npm init -y

Next, install Express, Pusher and some other dependencies the server will be needing:

npm install --save body-parser ejs express pusher

When done, the dependency section of your package.json file should look like this:

1"dependencies": {
2    "body-parser": "^1.16.0",
3    "ejs": "^2.5.5",
4    "express": "^4.14.1",
5    "pusher": "^1.5.1"
6  }

To serve our application we need to do three things:

  • Set up Express and Pusher.
  • Create routes to serve our application pages and listen for web requests.
  • Start the Express server.

Setup Express and Pusher

Inside the server.js file, we initialize Express and Pusher like this:

1const express = require('express');
2const bodyParser = require('body-parser');
3const ejs = require('ejs');
4const path = require('path');
5const Pusher = require('pusher');
6
7const app = express();
8
9//Initialize Pusher
10const pusherConfig = {
11  appId: 'YOUR_PUSHER_APP_ID',
12  key: 'YOUR_PUSHER_KEY',
13  secret: 'YOUR_PUSHER_SECRET',
14  encrypted: true
15};
16const pusher = new Pusher(pusherConfig);

Remember to replace the parameters in the pusherConfig object with the Pusher credentials you copied earlier from the Pusher dashboard.

Create routes to serve our application

Setup Express to serve our static files from the public folder and to load our HTML views from the views :

1app.use(express.static(path.join(__dirname, 'public')));
2
3app.set('views', path.join(__dirname, 'views'));
4app.set('view engine', 'ejs');
5
6app.get('/', function(req, res) {
7  res.render('index', {
8    //pass pusher key to index.ejs for pusher client
9    pusherKey: pusherConfig.key
10  });
11});

Next, we create a route that uses Pusher to broadcast a user_typing event.

1const chatChannel = 'anonymous_chat';
2const userIsTypingEvent = 'user_typing';
3
4app.post('/userTyping', function(req, res) {
5  const username = req.body.username;
6  pusher.trigger(chatChannel, userIsTypingEvent, {username: username});
7  res.status(200).send();
8});

This route broadcasts the request's username to everyone subscribed to the channel.

Start the Express Server

Then start the Express server to listen on the app port 3000.

1app.listen(3000, function () {
2  console.log('Node server running on port 3000');
3});

Now we have the application server set up. Next, we develop the chat application's user interface and functionalities.

Set up the Chat Application Web Page

The HTML for the chat application is in the views/index.ejs file. First, we load jQuery, bootstrap and our custom style and JavaScript:

1<head>
2  <meta charset="utf-8">
3  <meta name="viewport" content="width=device-width, initial-scale=1">
4  <title>Who is typing functionality with Pusher</title>
5
6  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
7  <link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,300,400,600,700,900,200italic,300italic,400italic,600italic,700italic,900italic' rel='stylesheet' type='text/css'>
8  <link rel="stylesheet" href="/css/style.css">
9
10  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.js"></script>
11  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
12  <script src="https://cdnjs.cloudflare.com/ajax/libs/cuid/1.3.8/browser-cuid.min.js"></script>
13  <script src="https://js.pusher.com/4.0/pusher.min.js"></script>
14  <script type="text/javascript" src="/js/app.js"></script>
15
16</head>

There are three sections in the application's user interface:

  • Top Menu: This contains the application title and a section to show who is currently typing.
  • Messages: This contains a list of all messages sent and received.
  • Input: This contains the text field to input messages and also contains the send button.

And here is the HTML body for it:

1<div class="chat_window">
2
3  <div class="top_menu">
4    <div class="is_typing">
5      <em id="user-is-typing"></em>
6    </div>
7    <div class="title">Anonymous Chat App</div>
8  </div>
9
10  <ul class="messages">
11  </ul>
12
13  <div class="input bottom_wrapper clearfix">
14    <div>
15      Your username: <em><span id="username">loading...</span></em>
16    </div>
17    <br/>
18    <div class="message_input_wrapper">
19      <input id="message-text-field" class="message_input" placeholder="Type your message here..." />
20    </div>
21    <button id="send-button" class="send_message">
22      <div class="text">Send</div>
23    </button>
24  </div>
25</div>

Now that the user interface is set up, let us add the typing indicator to the application.

How the typing indicator will work

Before we proceed to the implementation, let me explain how the typing indicator functionality will work.

When the user starts typing in the message text field, the page sends a request to the server. The server then broadcasts this to everyone as a Pusher event. The page would also subscribe to the user's typing event from Pusher and show a 'user is typing' message when a broadcast is received.

Therefore we can split this process into two steps:

  • Publishing user_typing Event.
  • Subscribing to user_typing Event.

Now, open the public/js/app.js file for their implementations.

Publishing

First, we need to initialize Pusher and some other variables.

1var pusher = new Pusher(PUSHER_KEY, {encrypted: true,});
2var chatChannelName = 'anonymous_chat';
3var userIsTypingEvent = 'user_typing';
4var currentUsername = getCurrentUsername();

Next, we need to listen to the current user's input events and publish their username.

1var messageTextField = $('#message-text-field');
2messageTextField.on('keyup', function(event) {
3    $.post('/userTyping', {username: username});
4});

This would post a 'user is typing' event for each input in the message text field. While this is good, it is not optimal because every input will make a call to the server.

A better way to do this is to throttle the rate at which we post events to the server. Throttling is a technique used to ensure a particular code is executed only once within a specified time interval.

So let's update our code to ensure we only post typing events to the server once every 0.2 seconds.

1var messageTextField = $('#message-text-field');
2var canPublish = true;
3var throttleTime = 200; //0.2 seconds
4
5messageTextField.on('keyup', function(event) {
6  if(canPublish) {
7    $.post('/userTyping', {username: username});
8
9    canPublish = false;
10    setTimeout(function() {
11      canPublish = true;
12    }, throttleTime);
13  }
14});

To explain the code, the canPublish boolean is set to false immediately after we post a 'user is typing' request to the server, preventing us from posting more requests. We then set a timeout function that sets the canPublish boolean to true after 0.2 seconds, thus allowing us to post another request to the server. This way, we throttle the requests made to the server.

Next, let's see how we will subscribe to the user_typing event.

Subscribing to

We need to subscribe to our chat channel on Pusher and bind to the user_typing event like so:

1var channel = pusher.subscribe(chatChannelName);
2channel.bind(userIsTypingEvent, function(data) {
3  if(data.username !== currentUsername) {
4    $('#user-is-typing').html(data.username + 'is typing...');
5  }
6});

So, if the username of the event is not the current users' username, we show a 'user is typing' text on the web page.

The application now updates the web page with the username that is typing. But the 'user is typing' message needs to be cleared when the user stops typing or else the message stays forever (and we definitely don't want that). An easy solution is to set a timer that clears the 'user is typing' message after some seconds of not receiving an event. From experience, a clear timer of 0.9 seconds has given the best results.

We can set the clear timer by doing the following:

1var clearInterval = 900; //0.9 seconds
2var clearTimerId;
3channel.bind(userIsTypingEvent, function(data) {
4  if(data.username !== currentUsername) {
5    $('#user-is-typing').html(data.username + 'is typing...');
6
7    //restart timeout timer
8    clearTimeout(clearTimerId);
9    clearTimerId = setTimeout(function () {
10      //clear user is typing message
11      $('#user-is-typing').html('');
12    }, clearInterval);
13  }
14});

To explain the code, the userIsTypingEvent will clear the 'user is typing' message after 0.9 seconds of not receiving a broadcast.

Putting all the codes together we have:

1var channel = pusher.subscribe(chatChannelName);
2var clearInterval = 900; //0.9 seconds
3var clearTimerId;
4channel.bind(userIsTypingEvent, function(data) {
5  if(data.username !== currentUsername) {
6    $('#user-is-typing').html(data.username + 'is typing...');
7
8    //restart timeout timer
9    clearTimeout(clearTimerId);
10    clearTimerId = setTimeout(function () {
11      //clear user is typing message
12      $('#user-is-typing').html('');
13    }, clearInterval);
14  }
15});

And there you have it. The chat application now has the functionality to display who's currently typing.

Testing

First, ensure you have updated your Pusher credentials in the server.js file. To run the application, execute the server.js file using the following command:

node server.js

The application should be running now. Visit the chat web page at http://localhost:3000. To try out the typing indicator feature, open two of the chat web pages beside each other. When you start typing in one, you should notice the other window shows that you are currently typing.

Conclusion

In this tutorial, we saw how to build a typing indicator using Pusher with JavaScript. As you can see, it is trivial and easy to build such realtime features using Pusher.