This blog post was written under the Pusher Guest Writer program.

An electronic poll simplifies the way polls are carried out and aggregates data in realtime. (These days, nobody needs to take a bus to town just to cast a vote for their favorite soccer team!) As voters cast their votes, every connected client that is authorised to see the poll data should see the votes as they come in.

This article explains how to seamlessly add realtime features to your polling app using Pusher while visualising the data on a chart using CanvasJS, in just 5 steps.

Some of the tools we will be using to build our app are:

  • Node: JavaScript on a server. Node will handle all our server related needs.
  • Express: Node utility for handling HTTP requests via routes
  • Body Parser: Attaches the request payload on Express’s req, hence req.body stores this payload for each request.
  • Pusher: Pub/Sub pattern for building realtime solutions.
  • CanvasJS: A UI library to facilitate data visualization with JavaScript on the DOM.

Together, we will build a minimalist app where users can select their favourite JavaScript framework. Our app will also include an admin page where the survey owner can see the polls come in. You can follow along with the source code here.

Let’s walk through the steps one by one:

1. Polling screen

First things first. The survey participants or voters (call them whatever fits your context) need to be served with a polling screen. This screen contains clickable items from which they are asked to pick an option.

Try not to get personal with the options, we’re just making a realtime demo. The following is the HTML behind the scenes:

<!-- ./index.html -->
<div class="main">
      <div class="container">
        <h1>Pick your favorite</h1>
        <div class="col-md-8 col-md-offset-2">
          <div class="row">
            <div class="col-md-6">
              <div class="poll-logo angular">
                <img src="images/angular.svg" alt="">
              </div>
            </div>
            <div class="col-md-6">
              <div class="poll-logo ember">
                <img src="images/ember.svg" alt="">
              </div>
            </div>
          </div>
          <div class="row">
            <div class="col-md-6">
              <div class="poll-logo react">
                <img src="images/react.svg" alt="">
              </div>
            </div>
            <div class="col-md-6">
              <div class="poll-logo vue">
                <img src="images/vue.svg" alt="">
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="js-logo">
      <img src="images/js.png" alt="">
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.16.2/axios.js"></script>
    <script src="app.js"></script>

The HTML renders the polling cards and imports axios and our custom app.js file. axios will be used to make HTTP calls to a server we will create. This server is responsible for triggering/emitting realtime events using Pusher.

2. Send vote requests

When a user clicks on their chosen option, we want to react with a response. The response would be to trigger a HTTP request. This request is expected to create a Pusher event, but we are yet to implement that:

// ./app.js
window.addEventListener('load', () => {
  var app = {
    pollLogo: document.querySelectorAll('.poll-logo'),
    frameworks: ['Angular', 'Ember', 'React', 'Vue']
  }

  // Sends a POST request to the
  // server using axios
  app.handlePollEvent = function(event, index) {
    const framework = this.frameworks[index];
    axios.post('http://localhost:3000/vote', {framework: framework})
    .then((data) => {
      alert (`Voted ${framework}`);
    })
  }

  // Sets up click events for
  // all the cards on the DOM
  app.setup = function() {
    this.pollLogo.forEach((pollBox, index) => {
      pollBox.addEventListener('click', (event) => {
        // Calls the event handler
        this.handlePollEvent(event, index)
      }, true)
    })
  }

  app.setup();

})

When each of the cards are clicked, handlePollEvent is called with the right values as argument depending on the index. The method, in turn, sends the framework name to the server as payload via the /vote (yet to be implemented) endpoint.

3. Set up a Pusher account and app

Before we jump right into setting up a server where Pusher will trigger events based on the request sent from the client, you’ll need to create a Pusher account and app, if you don’t already have one:

  1. Sign up for a free Pusher account.

  2. Create a new app by selecting Apps on the sidebar and clicking Create New button on the bottom of the sidebar.

  3. Configure your app by providing basic information requested in the form presented. You can also choose the environment you intend to integrate Pusher into for a better setup experience.

  4. You can retrieve your app credentials from the App Keys tab

4. Realtime server

The easiest way to set up a Node server is by using the Express project generator. You need to install this generator globally on your machine using npm:

npm install express-generator -g

The generator is a scaffold tool, therefore it’s useless after installation unless we use its command to create a new Express app. We can do that by running the following command:

express poll-server

This generates a few helpful files including the important entry point (app.js) and routes (found in the routes folder).

We just need one route to get things moving: a /vote route which is where the client is sending a post request.

Create a new vote.js file in the routes folder with the following logic:

// ./routes/votes.js
var express = require('express');
var Pusher = require('pusher');

var router = express.Router();
var pusher = new Pusher({
  appId: '<APP_ID>',
  key: '<APP_KEY>',
  secret: '<APP_SECRET>',
  cluster: '<APP_CLUSTER>',
  encrypted: true
});
// /* Vote
router.post('/', function(req, res, next) {
  pusher.trigger('poll', 'vote', {
    points: 10,
    framework: req.body.framework
  });
  res.send('Voted');
});
module.exports = router;

For the above snippet to run successfully, we need to install the Pusher SDK using npm. The module is already used but it’s not installed yet:

npm install --save pusher
  • At the top of the file, we import Express and Pusher, then configure a route with Express and a Pusher instance with the credentials we retrieved from the Pusher dashboard.
  • The configured router is used to create a POST /vote route which, when hit, triggers a Pusher event. The trigger is achieved using the trigger method which takes the trigger identifier(poll), an event name (vote), and a payload.
  • The payload can be any value, but in this case we have a JS object. This object contains the points for each vote and the name of the option (in this case, a framework) being voted. The name of the framework is sent from the client and received by the server using req.body.framework .
  • We still go ahead to respond with “Voted” string so we don’t leave the server hanging in the middle of an incomplete request.

In the app.js file, we need to import the route we have just created and add it as part of our Express middleware. We also need to configure CORS because our client lives in a different domain, therefore the requests will NOT be made from the same domain:

// ./app.js
// Other Imports
var vote = require('./routes/vote');

// CORS
app.all('/*', function(req, res, next) {
  // CORS headers
  res.header("Access-Control-Allow-Origin", "*");
  // Only allow POST requests
  res.header('Access-Control-Allow-Methods', 'POST');
  // Set custom headers for CORS
  res.header('Access-Control-Allow-Headers', 'Content-type,Accept,X-Access-Token,X-Key');
});

// Ensure that the CORS configuration
// above comes before the route middleware
// below
app.use('/vote', vote);

module.exports = app;

5. Connect a dashboard

The last step is the most interesting aspect of the example. We will create another page in the browser which displays a chart of the votes for each framework. We intend to access this dashboard via the client domain but on the /admin.html route.

Here is the markup for the chart:

<!-- ./admin.html -->
<div class="main">
  <div class="container">
    <h1>Chart</h1>
    <div id="chartContainer" style="height: 300px; width: 100%;"></div>
  </div>
</div>
<script src="https://js.pusher.com/4.0/pusher.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/canvasjs/1.7.0/canvasjs.js"></script>
<script src="app.js"></script>
  • The div with the id charContainer is where we will mount the chart.
  • We have imported Pusher and Canvas JS (for the chart) via CDN as well as the same app.js that our home page uses.

We need to initialize the chart with a default dataset. Because this is a simple example, we won’t bother with persisted data, rather we can just start at empty (zeros):

// ./app.js
window.addEventListener('load', () => {
  // Event handlers for
  // vote cards was here.
  // Just truncated for brevity

  let dataPoints = [
      { label: "Angular", y: 0 },
      { label: "Ember", y: 0 },
      { label: "React", y: 0 },
      { label: "Vue", y: 0 },
    ]
    const chartContainer = document.querySelector('#chartContainer');

    if(chartContainer) {
      var chart = new CanvasJS.Chart("chartContainer",
        {
          animationEnabled: true,
          theme: "theme2",
          data: [
          {
            type: "column",
            dataPoints: dataPoints
          }
          ]
        });
      chart.render();
    }

    // Here:
    // - Configure Pusher
    // - Subscribe to Pusher events
    // - Update chart
})
  • The dataPoints array is the data source for the chart. The objects in the array have a uniform structure of label which stores the frameworks and y which stores the points.
  • We check if the chartContainer exists before creating the chart because the index.html file doesn’t have a chartContainer.
  • We use the Chart constructor function to create a chart by passing the configuration for the chart which includes the data. The chart is rendered by calling render() on constructor function instance.

We can start listening to Pusher events in the comment placeholder at the end:

// ./app.js
// ...continued
// Allow information to be
// logged to console
Pusher.logToConsole = true;

// Configure Pusher instance
var pusher = new Pusher('<APP_KEY>', {
  cluster: '<APP_CLUSTER>',
  encrypted: true
});

// Subscribe to poll trigger
var channel = pusher.subscribe('poll');
// Listen to vote event
channel.bind('vote', function(data) {
  dataPoints = dataPoints.map(x => {
    if(x.label == data.framework) {
      // VOTE
      x.y += data.points;
      return x
    } else {
      return x
    }
  });

  // Re-render chart
  chart.render()
});
  • First we ask Pusher to log every information about realtime transfers to the console. You can leave that out in production.
  • We then configure Pusher with our credentials by passing the app key and config object as arguments to the Pusher constructor function.
  • The name of our trigger is poll, so we subscribe to it and listen to its vote event. Hence, when the event is triggered, we update the dataPoints variable and re-render the chart with render()

Conclusion

We didn’t spend time building a full app with identity and all, but you should now understand the model for building a fully fleshed poll system. We just made a simple realtime poll app with Pusher showing how powerful Pusher can be. Feel free to use Pusher for free to build amazing solutions and tell us about it.

About Chris Nwamba

Chris is a JavaScript preacher. He also strives to make something out of other languages. Tech Writer. Dev Evangelist. Speaker.