Create a Multi-user Presentation with Reveal.js

Shameer C
Share

Creating impressive presentation is an art. For a long time PowerPoint stood alone as the de facto tool for creating presentations.

Now, things have changed, as the web has become the focal point for all businesses, and as browser capabilities improved tremendously. Modern browsers are now capable of rendering 3-D graphics and animations just like in any other native applications.

Then there came some cool presentation libraries based on HTML5 and CSS3. Reveal.js is a highly popular library for creating stunning presentations.

Websockets is a new standard defined as a part of HTML5 spec, which enables bi-directional, full-duplex communication in browsers. There are number of JavaScript libraries that make working with Websockets easier, of which Socket.IO is a prominent one.

In this article we’ll discuss how to create a Reveal.js presentation that can be controlled by multiple users. We’ll make use of Socket.IO for sending and receiving slide change events in real-time. Reveal.js already comes with a multiplexer plugin, but this is a bit difficult to set up so we’ll ignore that for the time being. Let’s focus on how we can write a Socket.IO server that’ll suit our purpose.

Prerequisites

This article assumes that you have installed and can use the following libraries:

  • Node.js
  • Yeoman
  • Grunt
  • Bower

Initial steps

First we’ll set up an express.js server. Yeoman makes it easy to install and run express.js server with the help of generators. So we’ll first install the yeoman express-generator using npm.

$ npm install –g generator-express

This will install the express-generator in global scope. Now let’s set up the server.

$ yo express

This will ask you which type of express it should install. You can select Basic or MVC; in our case, we need only the basic setup. Then it will install bunch of npm modules along with bower.json and Gruntfile.js files. With the only necessary files, the app directory will look something like:

├── Gruntfile.js
├── app.js
├── bower.json
├── node_modules
│ ├── express
│ ├── grunt
│ ├── grunt-contrib-watch
│ ├── grunt-develop
│ ├── jade
│ └── request
├── package.json
├── public
│ ├── components
│ ├── css
│ ├── img
│ └── js
└── views

Now let’s fire up the express server using grunt.

$ grunt
Running "develop:server" (develop) task
>> started application "app.js".

Running "watch" task
Express server listening on port 3000

Yeoman has created a default app.js file for us, which contains necessary setup to run the server. Also, note that it comes with “watch” library, which will track the changes in the code and auto-reload the server, so that we don’t need to do it manually. Before we move further, we’ll install and setup reveal.js library using bower. Installing reveal.js is pretty simple and straightforward. Just issue the following command in terminal.

$ bower install reveal.js --save

This will fetch the latest stable version of reveal.js library from Github and will install under public/components directory. The --save option automatically updates the dependency section of bower.json file with reveal.js.

Now we have everything that we need to create our presentation server. We’ll start by creating the first slide of our presentation. For this create an HTML file inside the views folder.

<!-- views/index.html -->
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Revealer - Reveal.js multiplexer</title>
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
        <link rel="stylesheet" href="components/reveal.js/css/reveal.min.css">
        <link rel="stylesheet" href="components/reveal.js/css/theme/default.css" id="theme">
    </head>
    <body>
        <div class="reveal">
            <div class="slides">
                <section>
                    <h1>Revealer</h1>
                    <h3>Reveal.js multiplexer</h3>
                </section>
            </div>
        </div>
        <script src="components/reveal.js/js/reveal.min.js"></script>
        <script type="text/javascript">
            Reveal.initialize({history: true});
        </script>
    </body>
</html>

This is the most basic HTML we need to get started with reveal.js. Here we’ve included the Reveal.js CSS and JavaScript files. Reveal.initialize() will make the above HTML a nice looking presentation. Any section inside the div with the class slides will act as a slide.

Before we can start the presentation we need to setup our server to serve this file upon request. So we will update the app.js with the code given below.

var express = require('express')
  , http = require('http')
  , path = require('path')
  , app = express();

app.configure(function(){
  app.use(express.static(path.join(__dirname, 'public')));
});

app.get('/', function(req, res){
  res.sendfile(__dirname + '/views/index.html');
});

var server = http.createServer(app).listen(3000, function(){
  console.log("Express server listening on port 3000");
});

The first few lines require the necessary dependencies for our server and then create an object of express. The next line configures the public folder as a static directory where the server will look for requests to static files. Then we add a route to serve the index.html file and start the server.

Now we can see the presentation in the browser using http://localhost:3000/ url. But this is not what we really need. We need this presentation to be multiplexed, so that when one user changes the slide, it should be reflect on another user’s browser.

Next we’ll install and set up the Socket.io module for enabling bi-directional communication using Websockets.

$ npm install socket.io --save

Once the installation is finished, we are ready to enable websockets in our presentation server. First require the socket.io library in the app.js file by adding the following line in variable declaration section.

var io = require(“socket.io”);

Now we need to pass the express server that we previously created to socket.io and then will tell the server to send a welcome message when a new client is connected.

io.listen(server);
io.sockets.on('connection', function (socket) {
  socket.emit("message", "Welcome to Revealer");
});

The server can respond to clients when they are connected. In the above code, the callback function to the connection event takes the client’s socket as an argument and sends a welcome message back to the client.

Let’s move on to the client side JavaScript which will connect to this server. First we need to include the socket.io client library in our HTML.

<script src="/socket.io/socket.io.js"></script>

Next we’ll connect to the Websocket server that we’ve created.

var socket = io.connect("http://localhost:3000");
socket.on("message", function(data){
    console.log(data);
});

io.connect will connect to the server using the given URL. Upon connection, we know our server will respond with a welcome message, which we’ve logged into the console.

Now that our client and server are ready and we can move on to the real stuff. When the presenter changes the slide, it should notify the server to update all other clients.

notifyServer = function(event){
    data = {
      indexv : Reveal.getIndices().v,
      indexh : Reveal.getIndices().h,
      indexf : Reveal.getIndices().f || 0
    }
    socket.emit("slidechanged" , data);
  }

  Reveal.addEventListener("slidechanged", notifyServer);

  Reveal.addEventListener("fragmentshown", notifyServer);

  Reveal.addEventListener("fragmenthidden", notifyServer);

When a slide change occurs, Reveal.js dispatches a slidechanged event. In the case of slide fragments it creates a fragmentshown or fragmenthidden event. We are handling all these cases here and when such an event occurs it will call the notifyServer callback function. At any point of time Reveal.getIndices() returns the current slide positions- horizontal, vertical and the fragment index. When the notifyServer function is called it will get the slide positions into a data object. Then the client will emit a slidechanged event to the server along with the created data.

In the server side we need the ability to handle the slidechanged event emitted by the client, which should update all the connected clients. In order to do this, add the following code inside the connection handler.

socket.on("slidechanged", function(data){
   socket.broadcast.emit("slidechanged", data);
 });

socket.broadcast.emit will send the data to all clients except the sender. So here when server receives slidechanged event, it will simply forward the slide data to all other clients.

The client should also handle this slidechanged event forwarded by server, by moving to the respective slide or fragment. For this, in the client side add

socket.on('slidechanged', function (data) {
    Reveal.slide(data.indexh, data.indexv, data.indexf);
  });

Reveal.slide() takes three arguments, horizontal index, vertical index and the index of fragment, which will have value in the case of fragmentshown or fragmenthidden events.

Adding security

Now we have created a simple multiuser Reveal.js presentation. But this has a serious problem, in that any user can control the presentation. We can overcome this issue by adding a basic authentication in the server side code, and give an alternate route for non-authenticated users.

var masterUser = 'username'
      , masterPass = 'password';

// Authentication
var auth = express.basicAuth(masterUser, masterPass);

app.get('/', auth, function(req, res){
  res.sendfile(__dirname + '/views/master.html');
});

app.get('/client', function(req, res){
  res.sendfile(__dirname + '/views/client.html');
});

Now when a user requests “/” route, the browser will ask for the authentication credentials. express.basicAuth creates a basic authentication middlware which we have passed into the “/” route. If the login in successful it will send the master.html. Other users can use “/client” route to see the presentation where we won’t send any slide change events to server.

The complete code will now look like this.

// server
var express = require('express')
  , http = require('http')
  , path = require('path')
  , ioServer = require('socket.io')
  , app = express()
  , masterUser = 'username'
  , masterPass = 'password';


app.configure(function(){
  app.use(express.static(path.join(__dirname, 'public')));
});

// Authentication
var auth = express.basicAuth(masterUser, masterPass);

app.get('/', auth, function(req, res){
  res.sendfile(__dirname + '/views/presentation.html');
});

app.get('/client', function(req, res){
  res.sendfile(__dirname + '/views/client.html');
});

var server = http.createServer(app).listen(3000, function(){
  console.log("Express server listening on port 3000");
});

var io = ioServer.listen(server);
io.sockets.on('connection', function (socket) {
  socket.emit("message", "Welcome to Revealer");
  socket.on("slidechanged", function(data){
    socket.broadcast.emit("slidechanged", data);
  });
});


//client
(function(){
  var host = 'http://localhost:3000',
    , socket = io.connect(host);
  Reveal.initialize({
    history: true
  });

  /** start - only in master.js **/
  notifyServer = function(event){
    data = {
      indexv : Reveal.getIndices().v,
      indexh : Reveal.getIndices().h,
      indexf : Reveal.getIndices().f || 0
    }
    socket.emit("slidechanged" , data);
  }
  // listeners for slide change/ fragment change events
  Reveal.addEventListener("slidechanged", notifyServer);
  Reveal.addEventListener("fragmentshown", notifyServer);
  Reveal.addEventListener("fragmenthidden", notifyServer);
  /** end - only in master.js **/

  // Move to corresponding slide/ frament on receiving 
  // slidechanged event from server
  socket.on('slidechanged', function (data) {
    Reveal.slide(data.indexh, data.indexv, data.indexf);
  });
  
})();

You can find all the source code on Github.

Summary

In this article we have seen how to build a simple Reveal.js presentation that can be controlled by more than one user. Here we have used Socket.IO library for updating all connected clients in real-time. We have also added a basic security for preventing un-authorized users from controlling the presentation.

You can add more features and use technologies like WebRTC to make it more ubiquitous, so I hope you can see that this article is just a beginning.