Darren Mothersele

Software Developer

Warning: You are viewing old, legacy content. Kept for posterity. Information is out of date. Code samples probably don't work. My opinions have probably changed. Browse at your own risk.

Drupal live blogging with AJAX auto-refreshing view

Jul 31, 2012

web-dev

Live blogging, like you see on the Guardian or other news sites during important events is easily achieved on your Drupal sites, with a bit of jQuery AJAX and some EntityFieldQuery magic. Here's the code, with comments:

Create a module

Create your module folder. This module is going to be called Live Refresh, so create a folder called live_refresh in your site's module folder. Then create a file called live_refresh.info in this new folder with the following content:

name = Live Refresh
core = 7.x

That's the minimum you need in an info file to get Drupal to see your module.

Create your menu items

I need one menu item that visitors will see (accessed via the path /live), that can be placed in a menu, and a second menu item (accessed via the path /live-refresh-ajax) that is accessed via AJAX only. Visitors don't directly see this second menu item, so it's defined as type MENU_CALLBACK.

<?php
function live_refresh_menu() {
    return array(
       
'live' => array(
           
'title' => t('Live blog'),
           
'page callback' => '_live_refresh_page',
           
'access arguments' => array('access content'),
        ),
       
'live-refresh-ajax/%' => array(
           
'title' => t('Live refresh'),
           
'page callback' => '_live_refresh_ajax',
           
'page arguments' => array(1),
           
'access arguments' => array('access content'),
           
'type' => MENU_CALLBACK,
        ),
    );
}
?>

Each menu item has a page callback function. These will both be loading nodes from the database using EntityFieldQuery, so let's write a utility function to do this...

<?php
function _live_refresh_get_nids($since = NULL) {
   
$query = new EntityFieldQuery();
   
$query->entityCondition('entity_type', 'node')
      ->
entityCondition('bundle', 'article')
      ->
propertyCondition('status', 1)
      ->
propertyOrderBy('created', 'DESC');
    if (
$since) {
       
$query->propertyCondition('created', $since, '>');
    } else {
       
$query->range(0, 10);
    }
   
$entities = $query->execute();
    return empty(
$entities['node']) ? array() : array_keys($entities['node']);
}
?>

The code above loads nodes of type article. If you provide the function with a $since parameter then it loads all nodes posted since this time. Otherwise it loads the most recent 10 nodes.

Now define the page callback that generates the page at /live that lists the last 10 article nodes. It calls our utility function to load the list of node IDs returned from the EntityFieldQuery. It adds some Javascript to the page using drupal_add_js(), one of these is our Javascript file (defined below), and the other is a javascript setting that states what the last found created date is. This is used by the ajax callback to only load new content. The javascript setting also has a refresh rate in ms (set here to 9000, or 9 seconds). The final stage is generate the node views (using build mode 'teaser') and wrap them in a div.

<?php
function _live_refresh_page() {
   
drupal_add_js(drupal_get_path('module', 'live_refresh') . '/js/live-refresh.js');
   
$nids = _live_refresh_get_nids();
   
$nodes = node_load_multiple($nids);
   
$last_created = $nodes[$nids[0]]->created;
   
drupal_add_js(array('liveRefresh' => array(
       
'lastCreated' => $last_created,
       
'refreshRate' => 9000,
    )),
'setting');
   
$build = node_view_multiple($nodes, 'teaser');
   
$build['#prefix'] = '<div id="live-refresh-wrapper">';
   
$build['#suffix'] = '</div>';
    return
$build;
}
?>

This second page callback is a bit simpler. It is only accessed via AJAX. It loads up the nodes since the provided time, renders them using view mode 'teaser' then encodes the information as JSON and prints it. By printing from the menu callback instead of returning the value Drupal doesn't render the content in a page template and just sends it as is.

<?php
function _live_refresh_ajax($since) {
   
$nids = _live_refresh_get_nids($since);
   
$nodes = node_load_multiple($nids);
   
$last_created = empty($nids) ? FALSE : $nodes[$nids[0]]->created;
   
$build = node_view_multiple($nodes, 'teaser');
    print
json_encode(array('lastCreated' => $last_created, 'build' => render($build)));
}
?>

The only thing left to do is write the Javascript code to actually do the AJAX refresh. This file is included above in the main page callback. The content of the file is a simple Drupal behaviour like this...

(function ($) {

  Drupal.behaviors.liveRefresh = {
    attach: function (context, settings) {
var refreshId = setInterval(function() {
$.ajax({
url: settings.basePath + 'live-refresh-ajax/' + Drupal.settings.liveRefresh.lastCreated,
dataType: 'json',
success: function(data) {
if (data.lastCreated) {
  Drupal.settings.liveRefresh.lastCreated = data.lastCreated;
  $('#live-refresh-wrapper', context).prepend(data.build);
}
},
});
}, settings.liveRefresh.refreshRate);
    }
  };

})(jQuery);

This code uses setInterval to create a function that calls repeatedly after a specified amount of time. In this case the refreshRate that was set in the page callback code. It makes a callback to the server, using $.ajax(), then inserts the returned content into the page and updates the lastCreated variable that is used in the next ajax callback.

As you can see, this is quite a simple bit of code, but quite powerful, and shows of the cool EntityFieldQuery functionality from Drupal 7. It's also really easily themable by swapping out the view mode from 'teaser' to your own view mode, or display settings created using Display Suite.

Grab the code

I've posted the barebones code on my GitHub. You will need to edit the code to make sure the query matches the content type for your site.