How To Build A Real-Time Commenting System

Advertisement

The Web has become increasingly interactive over the years. This trend is set to continue with the next generation of applications driven by the real-time Web. Adding real-time functionality to an application can result in a more interactive and engaging user experience. However, setting up and maintaining the server-side real-time components can be an unwanted distraction. But don’t worry, there is a solution.

Cloud hosted Web services and APIs have come to the rescue of many a developer over the past few years, and real-time functionality is no different. The focus at Pusher1, for example, is to let you concentrate on building your real-time Web applications by offering a hosted API which makes it quick and easy to add scalable real-time functionality to Web and mobile apps. In this tutorial, I’ll show how to convert a basic blog commenting system into a real-time engaging experience where you’ll see a comment made in one browser window "magically" appear in a second window.

Why Should We Care About The Real-Time Web?

Although the Real-Time Web2 is a relatively recent mainstream phrase, real-time Web technologies have been around for over 10 years. They were mainly used by companies building software targeted at the financial services sector or in Web chat applications. These initial solutions were classed as "hacks". In 2006 these solutions were given an umbrella term called Comet3, but even with a defined name the solutions were still considered hacks. So, what’s changed?

In my opinion there are a number of factors that have moved real-time Web technologies to the forefront of Web application development.

Social Media Data

Social media, and specifically Twitter, has meant that more and more data is becoming instantly available. Gone are the days where we have to wait an eternity for Google to find our data (blog posts, status updates, images). There are now platforms that not only make our data instantly discoverable but also instantly deliver it to those who have declared an interest. This idea of Publish/Subscribe4 is core to the real-time Web, especially when building Web applications.

Increased User Expectations

As more users moved to using applications such as Twitter and Facebook, and the user experiences that they deliver, their perception of what can be expected from a Web application changed. Although applications had become more dynamic through the use of JavaScript, the experiences were seldom truly interactive. Facebook, with it’s real-time wall (and later other realtime features) and Twitter with it’s activity stream centric user interface, and focus on conversation, demonstrated how Web applications could be highly engaging.

WebSockets

HTML5 and WebSockets Rock!

Earlier on I stated that previous solutions to let servers instantly push data to Web browsers were considered "hacks". But this didn’t remove the fact that there was a requirement to be able to do this in a cross-browser and standardised way. Our prayers have finally been answered with HTML5 WebSockets5. WebSockets represent a stardardized API6 and protocol7 that allows realtime server and client (web browser) two way communication over a single connection. Older solutions could only achieve two-way communication using two connections so the fact the WebSockets use a single connection is actually a big deal. It can be a massive resource saving to the server and client, with the latter being particularly important for mobile devices where battery power is extremely valuable.

How are Real-Time Technologies being used?

Real-time Web technologies are making it possible to build all sorts of engaging functionality, leading to improved user experiences. Here are a handful of common use cases:

  • Realtime Stats – The technology was first used in finance to show stock exchange information so it’s no surprise that the technology is now used more than ever. It’s also highly beneficial to sports, betting and analytics.
  • Notifications – when something a user is interested in becomes available or changes.
  • Activity Streams – streams of friend or project activity. This is commonly seen in apps like Twitter, Facebook, Trello, Quora and many more.
  • Chat – the 101 or real-time Web technology but still a very valuable function. It helps delivery instant interaction between friends, work colleagues, customers and customer service etc.
  • Collaboration – Google docs offers this kind of functionality within its docs, spreadsheets and drawing applications and we’re going to see similar use cases in many more applications.
  • Multiplayer Games – The ability to instantly deliver player moves, game state changes and score updates is clearly important to multiplayer gaming.

In the rest of this tutorial I’ll cover building a basic blog commenting system, how to progressively enhance it using jQuery and finally I’ll also progressively enhance it using the real-time Web service I work for, Pusher, which will demonstrate not just how easy it can be to use real-time Web technology, but also the value and increased engagement that a real-time factor can introduce.

Creating Generic Blog Commenting System

Start from a Template

I want to focus on adding real-time commenting to a blog post so let’s start from a template8.

This template re-uses the HTML5 layout defined in the post on Coding An HTML 5 Layout From Scratch9 and the file structure we’ll start with is as follows (with some additions that we don’t need to worry about at the moment):

  • css (dir)
    • global-forms.css
    • main.css
    • reset.css
  • images (dir)
  • index.php

HTML

The template HTML, found in index.php, has been changed from the HTML5 Layout article to focus on the content being a blog post with comments. You can view the HTML source here10.

The main elements to be aware of are:

  • <section id="content"> – the blog post content
  • <section id="comments"> – where the comments are to appear. This is where the majority of our work will be done

Comments

Now that we’ve got the HTML in place for our blog post and for displaying the comments we also need a way for our readers to submit comments, so let’s add a <form> element to collect and submit the comment details to post_comment.php. We’ll add this at the end of the <section id="comments"> section wrapped in a <div id="respond">.

<div id="respond">

  <h3>Leave a Comment</h3>

  <form action="post_comment.php" method="post" id="commentform">

    <label for="comment_author" class="required">Your name</label>
    <input type="text" name="comment_author" id="comment_author" value="" tabindex="1" required="required">

    <label for="email" class="required">Your email;</label>
    <input type="email" name="email" id="email" value="" tabindex="2" required="required">

    <label for="comment" class="required">Your message</label>
    <textarea name="comment" id="comment" rows="10" tabindex="4"  required="required"></textarea>

    <-- comment_post_ID value hard-coded as 1 -->
    <input type="hidden" name="comment_post_ID" value="1" id="comment_post_ID" />
    <input name="submit" type="submit" value="Submit comment" />

  </form>

</div>

Comment Form CSS

Let’s apply some CSS to make things look a bit nicer by adding the following to main.css:

#respond {
  margin-top: 40px;
}

#respond input[type='text'],
#respond input[type='email'], 
#respond textarea {
  margin-bottom: 10px;
  display: block;
  width: 100%;

  border: 1px solid rgba(0, 0, 0, 0.1);
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  -o-border-radius: 5px;
  -ms-border-radius: 5px;
  -khtml-border-radius: 5px;
  border-radius: 5px;

  line-height: 1.4em;
}

Once the HTML structure, the comment form and the CSS are in place our blogging system has started to look a bit more presentable.

Screenshot of blog post and commenting system

Handling Comment Submission

The next step is to write the PHP form submission handler which accepts the request and stores the comment, post_comment.php. You should create this file in the root of your application.

As I said earlier I’m keen to focus on the real-time functionality so a class exists within the template that you’ve downloaded which encapsulate some of the standard data checking and persistence functionality. This class is defined in Persistence.php (you can view the source here11), is in no way production quality, and handles:

  • Basic validation
  • Basic data sanitization
  • Very simple persistence using a user $_SESSION. This means that a comment saved by one user will not be available to another user.

This also means that we don’t need to spend time setting up a database and all that goes with it and makes post_comment.php very simple an clean. If you wanted to use this functionality in a production environment you would need to re-write the contents of Persistence.php. Here’s the code for post_comment.php.

<?php
require('Persistence.php');

$db = new Persistence();
if( $db->add_comment($_POST) ) {
  header( 'Location: index.php' );
}
else {
  header( 'Location: index.php?error=Your comment was not posted due to errors in your form submission' );
}
?>

The above code does the following:

  1. Includes the Persistence.php class which handles saving and fetching comments.
  2. Creates a new instances of the Persistence object and assigns it to the variable $db.
  3. Tries to add the comment to the $db. If the comment is successfully added it redirects back to the blog post. If it fails the redirection also occurs but some error text is appended to an error query parameter.

Displaying the Comments with the Blog Post

The final thing we need to do to have our Generic Blog Commenting System up and running is to update the blog page, index.php, to fetch and display the comments from the Persistence object.

  • Since this isn’t a real blogging system we’ll hard code the $comment_post_ID value to be 1.
  • Include the Persistence.php class and fetch the comments from it. Comments are associated with a blog post using a unique $comment_post_ID.
<?php
require('Persistence.php');
$comment_post_ID = 1;
$db = new Persistence();
$comments = $db->get_comments($comment_post_ID);
$has_comments = (count($comments) > 0);
?>

Since we now have the $comment_post_ID accessible via PHP we should update the HTML for the comment form to use this value.

<input type="hidden" name="comment_post_ID" value="<?php echo($comment_post_ID); ?>" id="comment_post_ID" />

We now have all the comments related to the blog post referenced by the $comments variable we need to display them on the page. To do this we need to update the PHP in index.php to iterate through them and create the required HTML.

<ol id="posts-list" class="hfeed<?php echo($has_comments?' has-comments':''); ?>">
  <li class="no-comments">Be the first to add a comment.</li>
  <?php
    foreach ($comments as $comment) {
      ?>
      <li><article id="comment_<?php echo($comment['id']); ?>" class="hentry">  
        <footer class="post-info">
          <abbr class="published" title="<?php echo($comment['date']); ?>">
            <?php echo( date('d F Y', strtotime($comment['date']) ) ); ?>
          </abbr>

          <address class="vcard author">
            By <a class="url fn" href="#"><?php echo($comment['comment_author']); ?></a>
          </address>
        </footer>

        <div class="entry-content">
          <p><?php echo($comment['comment']); ?></p>
        </div>
      </article></li>
      <?php
    }
  ?>
</ol>

You’ll notice that if the value of $has_comments is true an additional CSS class is applied to the ordered list called has-comments. This is so we can hide the list item with the "Be the first to add a comment" message when comments are being displayed using CSS:

#posts-list.has-comments li.no-comments {
  display: none;
}

Now that all this is in place we have a functional blog commenting system. If you would like to start writing your code from this basic functioning blog commenting system you can also download the code completed up to here12.

Screenshot of the functioning blog commenting system

Progressive Enhancement With jQuery

The first step in making our blog commenting system feel less like a Web page and more like an application is to stop page reloads when a user submits a comment. We can do this by submitting the comments to the server using an AJAX request. Since jQuery is probably the defacto standard for cross browser JavaScript functionality we’ll use it here. Although I’m using jQuery here, I’d also like to highlight that it’s a good idea to not always use jQuery13. Instead, analyze your scenario and make a considered decision because there are some cases14 where you are best not to.

In an attempt to try and keep as much scripting (PHP and JavaScript) from the index.php file we’ll create a new folder for our JavaScript and in there a file for our application JavaScript. The path to this fill should be js/app.js. This file should be included after the jQuery include.

<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
<script src="js/app.js"></script>

Capture the Comment Form Submission

When the page is ready bind to the submit event of the form.

$(function() {
  $('#commentform').submit(handleSubmit);
});

When the form is submitted and the handleSubmit function is called the comment data we want to send to the server is extracted from the form. There are more elegant ways of fetching the data from the form but this approach clearly shows where we’re getting the values from and the data object we are creating.

function handleSubmit() {
  var form = $(this);
  var data = {
    "comment_author": form.find('#comment_author').val(),
    "email": form.find('#email').val(),
    "comment": form.find('#comment').val(),
    "comment_post_ID": form.find('#comment_post_ID').val()
  };

  postComment(data);

  return false;
}

function postComment(data) {
  // send the data to the server
}

POST the Comment with AJAX

Within the postComment function make a POST request to the server passing the data that we’ve retrieved from the form. When the request is made an additional HTTP header will be sent to identify the request as being an AJAX request. We want to do this so that we can return a JSON response if it is an AJAX request and maintain our basic functionality if it isn’t (for more information on this see Detected AJAX events on the Server15). We also define two handlers; postSuccess for handling the comment being successfully stored and postError to handle any failures.

function postComment(data) {
  $.ajax({
    type: 'POST',
    url: 'post_comment.php',
    data: data,
    headers: {
      'X-Requested-With': 'XMLHttpRequest'
    },
    success: postSuccess,
    error: postError
  });
}

function postSuccess(data, textStatus, jqXHR) {
  // add comment to UI
}

function postError(jqXHR, textStatus, errorThrown) {
  // display error
}

Dynamically Updating the User Interface with the Comment

At this point the comment data is being sent to the server and saved, but the AJAX response isn’t providing any meaningful response. Also, the comments section isn’t being updated to show the newly submitted comment so the user would have to refresh the page to see it. Let’s start by writing the code to update the UI and test that functionality.

If you are thinking "Hang on a minute! We don’t have all the data we need from the Web server to display the comment" then you are correct. However, this doesn’t stop us writing the code to update the UI and also testing that it works. Here’s how:

function postSuccess(data, textStatus, jqXHR) {
  $('#commentform').get(0).reset();
  displayComment(data);
}
  
function displayComment(data) {
  var commentHtml = createComment(data);
  var commentEl = $(commentHtml);
  commentEl.hide();
  var postsList = $('#posts-list');
  postsList.addClass('has-comments');
  postsList.prepend(commentEl);
  commentEl.slideDown();
}

function createComment(data) {
  var html = '' +
  '<li><article id="' + data.id + '" class="hentry">' +
    '<footer class="post-info">' +
      '<abbr class="published" title="' + data.date + '">' +
        parseDisplayDate(data.date) +
      '</abbr>' +
      '<address class="vcard author">' +
        'By <a class="url fn" href="#">' + data.comment_author + '</a>' +
      '</address>' +
    '</footer>' +
    '<div class="entry-content">' +
      '<p>' + data.comment + '</p>' +
    '</div>' +
  '</article></li>';

  return html;
}

function parseDisplayDate(date) {
  date = (date instanceof Date? date : new Date( Date.parse(date) ) );
  var display = date.getDate() + ' ' +
                ['January', 'February', 'March',
                 'April', 'May', 'June', 'July',
                 'August', 'September', 'October',
                 'November', 'December'][date.getMonth()] + ' ' +
                date.getFullYear();
  return display;
}

The code above does the following:

  • Within the postSuccess function we clear the form values and call displayComment.
  • displayComment first calls the createComment function to create the list item (<li>) HTML as a String.
  • We then convert the HTML to a jQuery object using $(commentHtml) and hide the element.
  • The comment list item is then prepended to the comments ordered list (<ol>). The list also has a class called has-comments added to it so we can hide the first list item which contains the "Be the first to comment" statement.
  • Finally, we call commentEl.slideDown() so that the comment is shown in what is becoming the standard "here’s a new item" way.

The functionality is now implemented but we want to test it out. This can be achieved in two ways:

  • The displayComment is a global function so we can call it directly using the JavaScript console of the browser.
  • We can bind to an event on the page that triggers a fake update which calls the displayComment function

Let’s go with the latter and bind to the "u" key being released by binding to the keyup event. When it is, we’ll create a fake data object containing all the information required to create a new comment and pass it to the displayComment function. That comment will then be displayed in the UI.

Hit the "u" key a few times and see the comments appear.

$(function() {

  $(document).keyup(function(e) {
    e = e || window.event;
    if(e.keyCode === 85){
      displayComment({
        "id": "comment_1",
        "comment_post_ID": 1,
        "date":"Tue, 21 Feb 2012 18:33:03 +0000",
        "comment": "The realtime Web rocks!",
        "comment_author": "Phil Leggetter"
      });
    }
  });

});

Great! We now know that our displayComment function works exactly as we expect it to. Remember to remove the test function before you go live or you’ll really confuse your user every time they press "u".

Screenshot of a bunch of fake comments

Detect and Responding to the AJAX request

All that’s left to do is update the post_comment.php file to detect the AJAX call and return information about the newly created comment.

Detecting the AJAX request is done by checking for the X-Requested-With header:

$ajax = ($_SERVER[ 'HTTP_X_REQUESTED_WITH' ] === 'XMLHttpRequest');

Once we know the request is an AJAX request we can update the code to respond with an appropriate status code and the data representing the comment. We also need to ensure that the original functionality is maintained. The post_comment.php code now looks as follows:

<?php
require('Persistence.php');

$ajax = ($_SERVER[ 'HTTP_X_REQUESTED_WITH' ] === 'XMLHttpRequest');

$db = new Persistence();
$added = $db->add_comment($_POST);

if($ajax) {
  sendAjaxResponse($added);
}
else {
  sendStandardResponse($added); 
}

function sendAjaxResponse($added) {
  header("Content-Type: application/x-javascript");
  if($added) {
    header( 'Status: 201' );
    echo( json_encode($added) );
  }
  else {
    header( 'Status: 400' );
  }
}

function sendStandardResponse($added) {
  if($added) {
    header( 'Location: index.php' );
  }
  else {
    header( 'Location: index.php?error=Your comment was not posted due to errors in your form submission' );
  }
}
?>

Key points from the above code are:

  • The $db->add_comment($_POST) call returns the data from the added comment which is assigned to the $added variable.
  • By setting the response Content-Type to “application/json” we tell jQuery to convert the returned string into a JavaScript object. For more information on calling JSON Web services see A Beginner’s Guide To jQuery-Based JSON API Clients16.
  • The 201 status code indicates a successful call and also that a resource (the comment) was created by the call.

The blog commenting system now works in a much more dynamic way, instantly showing the user that their comment has been posted without refreshing the page. In addition, the way the we’ve added the JavaScript based functionality to the page means that if JavaScript is disabled or a JavaScript file fails to load that the system will fallback to the standard functionality we first implemented.

Getting Real-Time—Finally!

As with any "from scratch" tutorial it can take a bit of time to get to the really interesting part, but we’re finally here. However, all the work we’ve up in has been worth it. Because we’ve built our commenting system up in a progressively enhanced way, plugging Pusher into it is going to be really easy

What is Pusher?

At the start of the tutorial we said that we would use Pusher to add the realtime functionality to the application. But what is Pusher?

Pusher is a hosted service for quickly and easily adding realtime features into Web and mobile applications. It offers a RESTful API that makes it really easy to publish events from any application that can make a HTTP request and a WebSocket API for realtime bi-directional communication. You don’t even need to use the APIs directly as there are server17 (PHP, Ruby, node.js, ASP.NET, Python and more) and client18 (JavaScript, iOS, Android, .NET, ActionScript, Arduino and more) libraries available in a host of technologies which means you can add realtime functionality to an app within minutes ‐ I’m confident you’ll be surprised just how easy!

Sign up for Pusher and get your API Credentials

In order to add Pusher-powered real-time functionality to a Web application you first need to sign up for a free Sandbox account19. After you have signed up you’ll be taken to the Pusher dashboard where you’ll see that a "Main" application has been created for you. You’ll also see you are in the "API Access" section for that application where you can grab your API credentials.

Screenshot of API Access section in Pusher Dashboard

For ease of access create a pusher_config.php file and define the credentials in there so we can refer to them later:

<?php
define('APP_ID', 'YOUR_APP_ID');
define('APP_KEY', 'YOUR_APP_KEY');
define('APP_SECRET', 'YOUR_APP_SECRET');
?>

In your version of pusher_config.php be sure to replace the values which being ‘YOUR_ with your actual credentials.

You should also require this in your index.php file. We should also make the APP_KEY available to the JavaScript runtime as we are going to need it to connect to Pusher.

<?php
require('pusher_config.php);
?>

<script>
var APP_KEY = '<?php echo(APP_KEY); ?>';
</script>

Real-time JavaScript

The first thing you need to do when adding Pusher to a Web application is include the Pusher JavaScript library and connect to Pusher. To connect you’ll need to use the key which you grabbed from the Pusher dashboard. Below you can see all that is required to hook up the front-end application to Pusher.

Include the Pusher JavaScript library after the app.js include:

<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
<script src="http://js.pusher.com/1.11/pusher.min.js"></script>
<script src="js/app.js"></script>

Add the Pusher functionality to app.js:

var pusher = new Pusher(APP_KEY);
var channel = pusher.subscribe('comments-' + $('#comment_post_ID').val());
channel.bind('new_comment', displayComment);

This probably looks too easy to be true, so here are the details about what the above code does:

  • var pusher = new Pusher(APP_KEY);
    Creates a new instance of a Pusher object and in doing so connects you to Pusher. The application to use is defined by the APP_KEY value that you pass in and that we set up earlier.
  • var channel = pusher.subscribe('comments-' + $('#comment_post_ID').val());
    Channels20 provide a great way of organizing streams of real-time data. Here we are subscribing to comments for the current blog post, uniquely identified by the value of the comment_post_ID hidden form input element.
  • channel.bind('new_comment', displayComment);
    Events21 are used to further filter data and are ideal for linking updates to changes in the UI. In this case we want to bind to an event which is triggered whenever a new comment is added and display it. Because we’ve already created the displayComment function we can just pass in a reference to the call to bind.

Sending Real-Time Events using the Event Creator

We can also test out this functionality without writing any server-side code by using the Event Creator for your app which can also be found in the Pusher dashboard. The Event Creator lets you publish events on a channel through a simple user interface. From the code above we can see that we want to publish an event named "new_comment" on the "comments-1" channel. From the earlier test function we also have an example of the test data we can publish.

Screenshot of the Event Creator in Pusher Dashboard

Real-time PHP

Again, we’ve proven that our client-side functionality works without having to write any server-side code. Now lets add the PHP code we need to trigger the new comment event as soon as a comment is posted in our comment system.

Pusher offers a number of server-side libraries22 which make it easy to publish events in addition to helping with functionality such as private channel23 authentication and providing user information for presence channels24. We just want to use the basic event triggering functionality in the post_comment.php file so we need to download the Pusher PHP library25 (direct zip file download26).

Once you’ve downloaded this zip file, unzip it into the directory along with your other files. Your file structure will now look something like this:

  • index.php
  • css (dir)
  • images (dir)
  • post_comment.php
  • pusher_config.php
  • Persistence.php
  • squeeks-Pusher-PHP (dir)
    • lib (dir)
      • Pusher.php

An event can be triggering in just a few lines of code:

<?php
require('squeeks-Pusher-PHP/lib/Pusher.php');
require('pusher_config.php');

$pusher = new Pusher(APP_KEY, APP_SECRET, APP_ID);
$pusher->trigger('comments-1', 'new_comment', array(
  "comment_post_ID" => 1,
  "date" => "Tue, 21 Feb 2012 18:33:03 +0000",
  "comment" => "The realtime Web rocks!",
  "comment_author" => "Phil Leggetter"
));
?>

However, we need to apply a some additional logic before we trigger the event:

  • Check that the comment was added.
  • Extract the unique comment identifier from the $added array.
  • Build the text to identify a channel name for the submitted comment.
  • Trigger a new_comment event on the channel with the $added data. Note: The library automatically converts the $added array variable to JSON to be sent through Pusher.

Therefore the full post_comment.php file ends up looking as follows:

<?php
require('Persistence.php');
require('squeeks-Pusher-PHP/lib/Pusher.php');
require('pusher_config.php');

$ajax = ($_SERVER[ 'HTTP_X_REQUESTED_WITH' ] === 'XMLHttpRequest');

$db = new Persistence();
$added = $db->add_comment($_POST);

if($added) {
  $channel_name = 'comments-' . $added['comment_post_ID'];
  $event_name = 'new_comment';

  $pusher = new Pusher(APP_KEY, APP_SECRET, APP_ID);
  $pusher->trigger($channel_name, $event_name, $added);
}

if($ajax) {
  sendAjaxResponse($added);
}
else {
  sendStandardResponse($added); 
}

function sendAjaxResponse($added) {
  header("Content-Type: application/json");
  if($added) {
    header( 'Status: 201' );
    echo( json_encode($added) );
  }
  else {
    header( 'Status: 400' );
  }
}

function sendStandardResponse($added) {
  if($added) {
    header( 'Location: index.php' );
  }
  else {
    header( 'Location: index.php?error=Your comment was not posted due to errors in your form submission' );
  }
}
?>

If you run the app now in two different browser windows you’ll see that as soon as you submit a comment in one window that comment will instantly ("magically") appear in the second window. We now have a real-time commenting system!

But…, we’re not done quite yet. You’ll also see that the comment is shown twice in the window of the user who submitted it. This is because the comment has been added by the AJAX callback, and by the Pusher event. Because this is a very common scenario, especially if you’ve built an application in a progressively enhanced way as we have, the Pusher server libraries expose a way of excluding a connection/user27 from receiving the event via Pusher.

In order to do this you need to send a unique connection identifier called a socket_id from the client to the server. This identifier can then be used to define who will be excluded.

function handleSubmit() {
  var form = $(this);
  var data = {
    "comment_author": form.find('#comment_author').val(),
    "email": form.find('#email').val(),
    "comment": form.find('#comment').val(),
    "comment_post_ID": form.find('#comment_post_ID').val()
  };

  var socketId = getSocketId();
  if(socketId !== null) {
    data.socket_id = socketId;
  }

  postComment(data);

  return false;
}

function getSocketId() {
  if(pusher && pusher.connection.state === 'connected') {
    return pusher.connection.socket_id;
  }
  return null;
}

The changes we’ve made are:

  • A new function called getSocketId has been added to get the socket_id. It wraps a check to ensure that the pusher variable has been set and also that the client is connected to Pusher.
  • The handleSubmit has been updated to check to see if a socket_id is available. If it is, this information is posted to the server along with the comment data.

On the server we need to use the socket_id parameter if it is present and therefore exclude the connection and user who submitted the comment, or pass in null if it’s not:

$channel_name = 'comments-' . $added['comment_post_ID'];
$event_name = 'new_comment';
$socket_id = (isset($_POST['socket_id'])?$_POST['socket_id']:null);

$pusher = new Pusher(APP_KEY, APP_SECRET, APP_ID);
$pusher->trigger($channel_name, $event_name, $added, $socket_id);

And as simple as that we have a fully realtime enabled blog commenting system and we only send messages to users who really need to see them. As with the AJAX functionality the realtime functionality has been added in a progressively enhancing way, to ensure it doesn’t impact on any other functionality. You can find the a demo running here28 and the completed solution in the realtime commenting repository29 in github.

Good Real-Time App Development Practices

Real-time application development shares common good development practices with general Web development. However, I thought I would share a couple of tips that can come in particularly handy.

Use Browser Developer Tools

When you start doing a lot of JavaScript development the browser developer tools becomes your best friend. It’s the same when adding realtime functionality to your Web app, not only because you are using JavaScript, but also because the JavaScript library you are using is likely to be doing some reasonably complex things internally. So, the best way of understanding what is going on and if your code is using it as expect is to enable logging which usually goes to the developer console. All major browser vendors now offer good developer tools which include a console:

The Pusher JavaScript library provides a way to hook into the logging functionality. All you need to do is assign a function to the Pusher.log35 static property. This function will then receive all log messages. You can do what you like within the function but best practice is to log the messages to the developer console. You can do this as follow, ensuring the code it executed after the Pusher JavaScript library include:

Pusher.log = function(msg) {
  if(window.console && window.console.log) {
    window.console.log(new Date().getTime() + ': ' + msg);
  }
};

The code above checks to make sure the console and log function is available – it’s not in older browsers – and then logs the messages along with a timestamp to the JavaScript console. This can be incredibly handy in tracking down problems.

Screenshot of Pusher logging in the Chrome Developer Tools Console

Check Connectivity

Any good real-time technology will maintain a persistent connection between the client (web browser) and the Web server or service. Sometimes the client will lose connectivity and when the client isn’t connected to the Internet the real-time functionality won’t work. This can happen a lot with applications running on mobile devices which rely on mobile networks. So, if your application relies on that connectivity and functionality then it’s important to deal with scenarios where the client isn’t connected. This might be by displaying a message to tell the user they are offline or you might want to implement some alternative functionality.

The Pusher JavaScript library exposes connectivity state36 via the pusher.connection object, which we briefly saw earlier when fetching the socket_id. Binding to state changes and implementing your required functionality is quite simple as it follows the same mechanism as binding to events on channels:

var pusher = new Pusher(APP_KEY);

pusher.connection.bind('state_change', function(states) {
  Pusher.log('Connection state changed from: ' + states.previous + ' to ' + states.current);
});

Conclusion

We’re seeing real-time functionality appearing in a large number of high profile applications: some have it at the core of what they offer whilst others use it sparingly. No matter how it is used the end goal is generally the same; to enhance the user experience and keep users engaged with an application. By converting a basic blog commenting system into a more engaging communication platform I hope I’ve demonstrated that the functionality and experience can easily be layered on existing application.

The ease of access to this technology is a relatively new thing and we’ve only just touched the potential uses for the technology. Real-time stats, instant news updates, activity streams, social interaction, collaboration and gaming are just a few common uses but as more developers become aware of, and comfortable with, the technology I’m confident that we’re going to see some truly amazing innovation. An "Internet of Real-Time Things?"?

Footnotes

  1. 1 http://pusher.com
  2. 2 http://en.wikipedia.org/wiki/Real-time_web
  3. 3 http://en.wikipedia.org/wiki/Comet_(programming)
  4. 4 http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern
  5. 5 http://pusher.com/websocket
  6. 6 http://www.w3.org/TR/websockets/
  7. 7 http://tools.ietf.org/html/rfc6455
  8. 8 https://github.com/pusher/realtime-commenting/zipball/start
  9. 9 http://www.smashingmagazine.com/2009/08/04/designing-a-html-5-layout-from-scratch/
  10. 10 https://github.com/pusher/realtime-commenting/blob/start/index.php
  11. 11 https://github.com/pusher/realtime-commenting/blob/start/Persistence.php
  12. 12 https://github.com/pusher/realtime-commenting/zipball/basic
  13. 13 http://www.leggetter.co.uk/2012/02/19/jquery-uk-2012-event-dont-always-use-jquery.html
  14. 14 http://www.leggetter.co.uk/2012/02/12/considerations-when-updating-the-dom-to-display-realtime-data.html
  15. 15 http://www.learningjquery.com/2010/03/detecting-ajax-events-on-the-server
  16. 16 http://www.smashingmagazine.com/2012/02/09/beginners-guide-jquery-based-json-api-clients/
  17. 17 http://pusher.com/docs/rest_libraries
  18. 18 http://pusher.com/docs/client_libraries
  19. 19 http://pusher.com/signup
  20. 20 http://pusher.com/docs/channels
  21. 21 http://pusher.com/docs/client_api_guide/client_events
  22. 22 http://pusher.com/docs/rest_libraries
  23. 23 http://pusher.com/docs/private_channels
  24. 24 http://pusher.com/docs/presence
  25. 25 https://github.com/squeeks/Pusher-PHP
  26. 26 https://github.com/squeeks/Pusher-PHP/zipball/master
  27. 27 http://pusher.com/docs/publisher_api_guide/publisher_excluding_recipients
  28. 28 http://www.leggetter.co.uk/pusher/realtime-commenting/
  29. 29 https://github.com/pusher/realtime-commenting
  30. 30 http://getfirebug.com/
  31. 31 http://code.google.com/chrome/devtools/docs/overview.html
  32. 32 http://msdn.microsoft.com/en-us/library/gg589507(v=VS.85).aspx
  33. 33 http://www.opera.com/dragonfly/documentation/
  34. 34 https://developer.apple.com/library/safari/#documentation/appleapplications/Conceptual/Safari_Developer_Guide/2SafariDeveloperTools/SafariDeveloperTools.html
  35. 35 http://pusher.com/docs/client_api_guide/client_global_configuration#pusher_log
  36. 36 pusher.com/docs/connection_states

↑ Back to topShare on Twitter

Phil Leggetter is a Developer Evangelist at Pusher. He's been involved in developing and using realtime web technologies for over 10 years. His focus is to help developers use these technologies to build the next generation of interactive and engaging realtime web applications. Follow him at @leggetter.

Advertising
  1. 1

    Nice one :)
    Earlier looked for some similar and easy to understood tut, but couldn’t find it.

    Thanks

    9
  2. 2

    Just one question. Why are CSS and Image files in different folders?

    7
  3. 3

    Roger Martinez

    May 9, 2012 5:26 am

    Really interesting, I’ll try this right now =)

    -1
  4. 4

    I *just* got my client to sign up for Pusher, to replace our old real-time data provider (x-stream.ly). We’re going to use it, among other things, to build an activity stream for the site. It’ll probably be used for a real-time notification system a little bit later on as well.

    Great article, even if it took quite a while to get to the interesting part.

    3
  5. 5

    Phil Leggetter

    May 9, 2012 8:54 am

    Tommy – Great news! Sorry it took so long to get to the good part. But hopefully the progressive way the app was developed will be useful in demonstrating the realtime functionality is the 4th step in progressively enhancing an application; HTML, CSS, JavaScript and then realtime web functionality for a really interactive and engaging experience. Awesome to have you and your clients using Pusher.

    3
  6. 6

    Awesome tutorial, thanks!

    Would be interesting to see it implemented in a website like Smashing Magazine to see how it impacts performance.

    0
  7. 7

    Stephane Bourbeau

    May 9, 2012 6:03 pm

    Hi
    I was wondering, to leverage the cache system of the server (and client), wouldn’t it be better to load the comments on a page after the documentReady event with an ajax call? This way, the ‘article’ can be cached on the server (and the client), available to the user slightly faster, with comment stills avaible a few ms later. Just saying, haven’t been coding for a while, trying to catch up ;-)

    1
  8. 8

    We probably won’t see an answer coming from you, Phil – but how would I be able to create something like that if I wasn’t ready to put all my development effort in using a service provided by a 3rd party company that could be gone in a year or two. I have seen too many services come and go over time.

    6
  9. 9

    Just so you know, all browsers support border-radius without prefix nowadays :)

    -20
  10. 10

    Dominik, you can set this up in nodejs quite easily. No need for third party. There are even php websocket servers. The key is getting your client to degrade to poll etc. Look up Socket.IO.

    1
  11. 11

    Phil Leggetter

    May 10, 2012 1:40 am

    Dominik – I’m pretty unbiased. I’d rather be seen as honest and fair and then that reflects well on Pusher. I’ve actually compiled a list of realtime web technologies which you can find here (but it does need a serious tidy-up):
    http://www.leggetter.co.uk/real-time-web-technologies-guide

    socket.io does appear to be the ‘go to’ option when considering a self hosted solution. Personally I think it’s because it was one of the first solutions available and it’s riding the node.js wave. Obviously, it must also be a good solution or nobody would use it. But, as you can see from the link above, there are whole host of other options available.

    I’m not aware of a good PHP option that will run on standard hosting and through Apache. Apache wasn’t built with persistent connections in mind and the resource allocation it gives per request isn’t at all efficient for realtime communication (it doesn’t horizontally scale very well). Quite a few of the PHP solutions you’ll find out there will need to be run standalone – acting as their own server process. e.g. via the php executable.

    Also, when it comes to shared hosting it’s unlikely the hosting provider will want applications holding open connections so they probably won’t allow HTTP Long-Polling, HTTP Streaming or WebSocket solutions on their hosting platform. For example, Heroku don’t support WebSockets at the moment – the only solution right now is polling. See: https://devcenter.heroku.com/articles/using-socket-io-with-node-js-on-heroku

    Anyway, the self hosted solutions that I believe are worth looking at are:

    * socket.io – node.js and ports e.g. ruby, python, erlang, java and probably more.
    * Faye – node.js and ruby
    * Tornado – python (i’m sure there are ports available too)
    * SockJS – node.js, lua, erlang, python (with tornado)
    * Atmosphere – scala, groovy, java
    * XSockets – .NET
    * signarR – .NET with Microsoft backing
    * Ratchet – PHP standalone

    11
  12. 12

    You could just serialize the form using $(‘#form’).serialize() before sending it with AJAX if you don’t need to alter the data. This has the added benefit for example in frameworks like Codeigniter that allow CSRF protection by adding a CSRF key and value. By serializing you also get that so the request can be accepted by the controller.

    4
  13. 13

    At a glance, the different code in this post looks so freakin’ scary to me :)

    0
  14. 14

    This is shocking….

    Doesnt teach you how to develop a real-time commenting system, it just advises you to use a third party service (which the article writer develops) to handle the actual core ‘real-time’ functionality.

    You also seem to be duplicating and adding in extra stuff which could be done much simpler with core functionality of the libraries you use (jQuery for example).

    Shame!

    -2
  15. 15

    Okay, cool. Thanks. node.js is all I need to hear. So far I have only used it for internal caching and APIs, but I guess it’s time to have a closer look ;)

    1
  16. 16

    Awesome tutorial! Slowly working my way through it. I love our dev community, thanks!

    1
  17. 17

    It explains how to progressively enhance a blog commenting application from a standard request/response architecture, then by applying AJAX and eventually using a realtime web technology to show the comments in realtime.

    The idea of publish/subscribe, channels and events are applicable to many other realtime web hosted and self-hosted options so although I used the technology belonging to the company I work for (which I made very clear) these other technologies could easily be used instead of Pusher.

    The purpose is to demonstrate how easily realtime functionality can be added. Sorry if you found the blog title misleading.

    Not sure what you mean with your jQuery comment.

    9
  18. 18

    Yeah, I did point out that there were better ways of extracting the values from the form in the post. I really just wrote things as I did for clarity. The point about the CSRF is interesting.

    0
  19. 19

    nice,checking to is this real time one

    0
  20. 20

    It’s kind of sad to imagine nerdy smirks of everyone who thumbed down a simple question. I keep my css, sprite images and icons in one folder, called “design”. Separating by filetype is a good way to go, but is not common sense neither a requirement.

    46
  21. 21

    I might have missed something, but does this solution scale back, i.e. for browsers that do not support HTML5?

    0
  22. 22

    Nice Tut!

    Great to see other Real Time stuff come around.

    I personally work with/at Realtime.co, that uses xRTML. you should check it out ^^

    Ould love to hear some feedback about it.

    -1
  23. 23

    Phil Leggetter

    May 11, 2012 4:22 am

    The Pusher JavaScript library uses web-socket-js (https://github.com/gimite/web-socket-js) as a polyfill. So, Pusher falls back to using a Flash Socket connection. Other realtime web technologies fallback to Comet-style connections such as HTTP Long-Polling and HTTP Streaming.

    1
  24. 24

    Thanks for the share. Really simple and easy to understand.

    -2
  25. 25

    Suleiman Leadbitter

    May 13, 2012 10:43 am

    This is pathetic. Someone asks a genuine question and get ridiculed. Oh I forgot everyone that thumb downed that comment obvious popped out of their mothers womb holding on the Andy Clarke’s latest book. I’m surprised anyone can learn anything new anymore with attitudes like that.

    41
  26. 26

    Phew..Great article. Need to implement.

    0
  27. 27

    Very interesting article, but COMET is more easy, to develop real-time-web apps, isn’t it? :)

    -2
  28. 28

    Smashing seems to have a knack of posting articles on topics I’m just starting using – great, we must be part of the same zeitgeist! This morning I started work on real-time notifications in my web app platform, using node.js initially as it’s quick to get running. What would be really interesting would be another article on the UX considerations, i.e. what, how, when and why notifications should occur in a previously ‘quiet’ app, to be an added benefit to the user without distracting them.

    -1
  29. 29

    I’m curious, did the author of the article read any books such as Clean Code by Robert C. Martin or Real-world Solutions for Developing High-quality PHP Frameworks and Applications by Sebastian Bergmann and Stefan Priebsch?

    If you’re looking into building the skill sets for becoming a back-end developer, I’d strongly advise that you start there. You need to approach PHP scripting with a different mentality from UX design, in fact most of the best code I’ve seen had a coder developing the back end PHP scripts with a designer putting pretty markup on the front.

    -8
  30. 30

    Phil Leggetter

    May 15, 2012 2:07 pm

    COMET is a an umbrella term for various ‘hacks’ that use HTTP to and achieve realtime communication between a client and a server. WebSockets are the technology that supersedes the ideas behind COMET. For more information, and for clarification you can see Alex Russell’s opinion on this here:
    http://www.leggetter.co.uk/2012/04/22/websockets-realise-comet-theyre-not-an-alternative.html#comment-518850026

    Since he invented the term ‘COMET’ I’m happy to take his word as final :)

    2
  31. 31

    Phil Leggetter

    May 15, 2012 2:16 pm

    I understand where you are coming from with this comment. If I were moving an application into production I wouldn’t be entirely happy with the code in this article. However, the code is structured in the way it is because it was developed in a progressive way to fit the flow of the tutorial. I didn’t want to have a discussion about initial code structure, the practices and principles I might follow, or go back to the existing code and deal with refactoring. I’m sorry if you’re not happy with the code ‘smells’. Feel free to submit a pull request to the github repo.

    2
  32. 32

    Hey Phil,
    Great tutorial. It’s pretty awesome to see the end result! I was wondering what the major tweaks I would have to make to ‘Persistence.php’ to have it production ready so that all users can see the comments. I’m fairly new to writing PHP and would love to hear your (or any) feedback on this.

    (FYI, this part in the article)
    “Very simple persistence using a user $_SESSION. This means that a comment saved by one user will not be available to another user.”

    Thanks!

    0
  33. 33

    Phil Leggetter

    May 16, 2012 5:34 am

    Hi Nick,

    It’s actually reasonably simple, it just distracted from building the app.

    The first thing you need is a database engine. With PHP you are likely to be using MySQL (you’ll often hear people talk about the ‘LAMP stack’ and that’s Linux, Apache, MySQL and PHP).

    Once you’ve got MySQL running you’ll need to create an actual database containing a schema into which the comments will be stored. For the moment a `comments` table with the following columns would do:

    * comment_post_ID
    * comment_author
    * email
    * comment
    * date

    `Persistence.php` has the following methods that would need to be updated to query the database:

    * get_comments($comment_post_ID) – query the database for all comments where the comment ID is $comment_post_ID. A ‘SELECT’ query.
    * get_all_comments() – get all comments. I’m not sure this would ever actually be used. Maybe an admin thing. A ‘SELECT’ query.
    * add_comment($vars) – save the comment to the database. Be sure to sanitize the values and check for/avoid SQL injection (see: http://php.net/manual/en/security.database.sql-injection.php). Also make sure that `validate_input($input)` is updated accordingly. An ‘INSERT’ query.
    * delete_all() – again, unlikely to be used other than for admin.
    * sync() – you would probably remove this

    I hope this provides you with a basics you need to get started.

    Regards,

    Phil @leggetter

    -1
  34. 34

    when i enter comment, i get this message:NaN undefined NaN
    By undefined
    undefined

    where is the problem? but, when i use event creater, it does work normally..

    -2
  35. 35

    this is my debug console, but i don’t understand why i get “occupied” http://s15.postimage.org/4se83b14r/aaa.jpg

    -2
  36. 36

    Phil Leggetter

    May 19, 2012 2:35 pm

    It turns out Denny didn’t have the Apache php_curl module installed so his AJAX calls to `post_comment.php` were failing.

    1
  37. 37

    Devsign Web Design and Web Development

    June 21, 2012 1:56 am

    Nice tutorial…

    0
  38. 38

    Agreed! This doesn’t teach us anything about server-side websockets. The title should clearly state this is merely an advertisement for this third party api.

    -3
  39. 39

    I love how people comment “Nice Tut, huh huh” do you people truly take the time to understand the thorough workings of the programatic process discussed here? or copy the code and use accordingly? I expect the latter!

    -1
  40. 40

    This is interesting tutorial. Thank you so much!

    0
  41. 41

    Captcha is not a nice thing for visitors, specially unreadable one’s.

    -1
  42. 42

    Great Content !!!!

    -1
  43. 43

    I liked reading through this and writing it all out. I have no problem with anything working outside of the double comments. I’m still in school for web design development, I am assuming what I’m missing is easy to fix and I know it has to do with where to place this:

    $channel_name = ‘comments-‘ . $added[‘comment_post_ID’];
    $event_name = ‘new_comment';
    $socket_id = (isset($_POST[‘socket_id’])?$_POST[‘socket_id’]:null);

    $pusher = new Pusher(APP_KEY, APP_SECRET, APP_ID);
    $pusher->trigger($channel_name, $event_name, $added, $socket_id);

    I’m looking to learn if anyone wants to send me in the right direction.

    -1
  44. 45

    good and nice thing for begineer..

    -2
  45. 46

    I guess I have selected a mind blowing and interesting blog.

    0
  46. 47

    i have an issue with ajax while i am using it dosent displays output in internet explorer but works fine in google chrome and in firefox

    -1
  47. 48

    I thought this was a pretty good article. Thanks!

    -1
  48. 49

    I just came across this article and really just skimmed it, and noticed this bit here…

    headers: {
    ‘X-Requested-With': ‘XMLHttpRequest’
    },

    You do realize that the $.ajax() function sets this already right? A better use of the headers object would be to set items such as a signed oAuth value (for the one time pad, app id etc) or a cross site request forgery token.

    0
  49. 50

    oh it is really good post to me.Thank you for sharing this post.

    0
  50. 51

    I have a question about Mysql.
    Can you tell me that what database name is, and what is table name
    please

    1
  51. 52

    Hey Jas,

    You’re right. jQuery adds the header when you use `$.ajax`. I didn’t actually know this when I wrote the article.

    Regards,

    Phil @leggetter

    1
  52. 53

    I hope it is useful to all people Thanks to share this for It software jobs

    -1
  53. 54

    This is a great tutorial Phil.. but it function is more like a chat room.. it would be nice to see it work like a blog.. for a blog the message should stay there for everyone to see, not just when i have the application opened but i think you already know that :). Anyway a big thanks!

    -1
  54. 55
  55. 56
  56. 57

    Hi Denny,

    I’m getting the same issue as you in regards to getting “undefined” when posting a comment. Did you manage to resolve this?

    Thanks

    – Obito

    1
  57. 58

    Hello Sir,
    I want to learn something from u, i like your teaching style :)

    my query is ::
    how can we update the page content without refresh like FACEBOOK wall if a User Updates some statue it appears on his wall as well as it appears on his FRIENDS wall Too ??

    i used jQuery but the problem is that if user updates any statue it just appears on just his OWN wall not his friend’s wall at that time.
    we have to refresh friends wall to get the new updates on his friends wall too.

    How can we Update User’s and his Friends’s wall togather without any refresh as soon as the User updates somthing??

    it can be possible by using HttpRequest and MySql data base both ? How ??

    Will u please teach me this with any Demo ?? :)
    Please help me sir, I am new to HttpRequests & Realtime.
    It will vry helpful to me.

    Thank you in advance
    Muhammad

    -3
  58. 59

    What would be more efficient for a large scale commenting system ?
    SQL or NOSQL ?

    -2
  59. 60

    hi, why when i put app.js in the head pusher will not work? but near the it will be okay,

    when i put pusher js code in the jQuery ready function ajax will not work but pusher will work (if i put app.js in the head)

    -2
  60. 61

    How then do I delete the inserted comments?

    -2
  61. 62

    How can i add a subject field that can be in bold letters to a wordpress comment box. ?

    -2
  62. 63

    nice one

    -2
  63. 64

    Gayle Sorenson

    May 17, 2014 8:27 pm

    Thank you, Thank you…I am an online IT student, 2yrs., into a degree in web development, and I have been looking, for 2 weeks, for something like this to add to a capstone project….

    With no luck, broken links, messy, incomprehensible code, forgotten tags, incomplete functions; I have done nothing but, debug, debug, debug….Until, I stumbled onto this…..

    Clean, beautiful, code. logical structure, clearly explained…..I have incorporated it into my project…and, It worked perfectly the first time around…

    I have gotten the books, tagged all your sites, and will follow with avid interest…..Thanks again…..Gayle S.

    -3
  64. 65

    I was thinking of building a real-time commenting system but finally have chosen to use a simple chat. While looking for any code samples I found some nice ones as http://quickblox.com/developers/Web_XMPP_Chat_Sample – hope this helps.

    -1
  65. 66
  66. 67

    Nice work! :)
    but how can you put this comment in this script template? can you maybe? I tried but it did not work.
    Thanks.
    the file is on http://www.id-files.esy.es/comment/
    css and all the index.html index.txt (to show)

    0
  67. 68

    Thank you so much I will try this!

    0
  68. 69

    BT Helpline Customer Care Number

    November 25, 2014 9:59 am

    Nice blog and the tutorial is very useful to understand the coding with commenting skill. thanks for share.

    0
  69. 70

    Tauno – just so you understand why there are -19 thumbs-down – the answer is simply to keep like modules/code segregated and organised. It’s common sense for any type of documenting/coding.

    -10
  70. 71

    Agreed. This is truly pathetic. What kind of example does this teach people?

    “Join the design community, we’ll mock you for your work and call you stupid!”

    Is that really the kind of image you want to advertise for yourself? The guy might be new to design, he might still be learning, and down-voting his comment without even telling him why is an absolute ridiculously pathetic action.

    17

Leave a Comment

Yay! You've decided to leave a comment. That's fantastic! Please keep in mind that comments are moderated and rel="nofollow" is in use. So, please do not use a spammy keyword or a domain as your name, or else it will be deleted. Let's have a personal and meaningful conversation instead. Thanks for dropping by!

↑ Back to top