Basic Next/Previous Navigation For Nodes

22:18

When viewing a node, I wanted the ability to page through to the next and previous article.

  • Code is abstracted out into 2 different functions.
  • You can specify the text to be used for the generated links.
  • Uses l() instead of hardcoding directly generating the links using $nid. l() uses aliases if available and therefore generated links are consistent with the rest of the site.
  • Uses a few class attributes to generated links so they can be styled.

These 2 functions (plus maybe a lot of other similar functions) can be abstracted out into a module.

  1. Create a file called template.php in your theme's folder if it does not already exist, assuming that theme is PHPTemplate-based.
  2. Copy the 2 functions next_node() and previous_node() into template.php. Copy the functions along with their complete definitions i.e. the whole body not only the function names. Make sure the functions are withing <?php and ?> tags.
  3. Copy the code snippet to the place in node.tpl.php where you want the links to appear. These links should appear only when a node is view in its entirety so put them in a if($page!=0).

For themes using other engines you will have to read the appropriate documentation to know where to put the code.

<?php
   
/**
     * Enables theme developers to include a link to the node of the same type
     * that comes immediately after the the node being currently viewed.
     *
     * A node has to be published and promoted to the front page to
     * qualify as the 'next node'. Unpublished and nodes not promoted
     * to front page are not considered.  Access control is respected.
     *
     * Theme developers would normally use this in the template for a node.
     *   
     * @param $node
     *     node whose next node is to be found.
     * @param $next_node_text
     *   The text for the link that will be created. If no text is given
     *     then the title of the next node is used.
     * @param $prepend_text
     *     Text to be prepended to the created link. It is not a part of the link.
     * @param $append_text
     *     Text to be appended to the created link. It is not a part of the link.
     *
     */  
   
function next_node($node, $next_node_text=NULL, $prepend_text=NULL, $append_text=NULL)
    {  
       
$query = db_rewrite_sql("SELECT nid, title FROM {node} WHERE created > '%s' AND status=1 and promote=1 AND type='%s' ORDER BY created ASC LIMIT 1", "node", "nid");
       
       
$result = db_query($query, $node->created, $node->type);

      
$next_node = db_fetch_object($result);

        if(!
$next_node_text) // If next_node_text is not specified then use the next node's title as the text for the link.
       
{
           
$next_node_text = $next_node->title;      
        }
      
        if(
$next_node->nid!=NULL)
        {
            return
$prepend_text.l($next_node_text, 'node/'.$next_node->nid, array('title'=>'Go to the next post "'.$next_node_text.'"', 'class'=>'goto-previous-node')).$append_text;
        }
        else
// There is no next node for this node...
       
{
            return
NULL;
        }
    }

   
/**
     * Enables theme developers to include a link to the node of same type
     * that comes immediately before the the node being currently viewed.
     *
     * A node has to be published and promoted to the front page to
     * qualify as the 'previous node'. Unpublished and nodes not promoted
     * to front page are not considered.  Access control is respected.
     *
     * Theme developers would normally use this in the template for a node.
     *   
     * @param $node
     *     node whose next node is to be found.
     * @param $previous_node_text
     *   The text for the link that will be created. If no text is given
     *     then the title of the previous node is used.
     * @param $prepend_text
     *     Text to be prepended to the created link. It is not a part of the link.
     * @param $append_text
     *     Text to be appended to the created link. It is not a part of the link.
     *
     */
   
function previous_node($node, $previous_node_text=NULL, $prepend_text=NULL, $append_text=NULL)
    {  

       
$query = db_rewrite_sql("SELECT nid, title FROM {node} WHERE created < '%s' AND status=1 and promote=1 AND type='%s' ORDER BY created DESC LIMIT 1", "node", "nid");
       
       
$result = db_query($query, $node->created, $node->type);

      
$previous_node = db_fetch_object($result);

        if(!
$previous_node_text) // If previous_node_text is not specified then use the previous node's title as the text for the link.
       
{
           
$previous_node_text = $previous_node->title;      
        }
      
        if(
$previous_node->nid!=NULL)
        {
            return
$prepend_text.l($previous_node_text, 'node/'.$previous_node->nid, array('title'=>'Go to the previous post "'.$previous_node_text.'"', 'class'=>'goto-previous-node')).$append_text;
        }
        else
// This node does not have a previous node...
       
{
            return
NULL;
        }
    }
?>

Alternately you use this return statement:
            return $prepend_text.l($next_node_text, 'node/'.$next_node_nid, array('title'=>'Go to the next post "'.$next_node_text.'"', 'class'=>'goto-next-node')).$append_text;

I used the above code in my node.tpl.php just below the node body like this:

<?php
   
if($page!=0)
    {
       
$previous_node_link = previous_node($node, NULL, '&lt;&lt; ', NULL);
       
$next_node_link = next_node($node, NULL, NULL, ' &gt;&gt;');   
       
        print
'<div>';
        if(
$previous_node_link && $next_node_link)
        {
            print
$previous_node_link.' | '.$next_node_link;
        }
        else if(
$previous_node_link)
        {
            print
$previous_node_link;
        }
        else if(
$next_node_link)
        {
            print
$next_node_link;
        }
        print
'</div>';
    }
?>

update:

The above functions, though functional are huge resource hogs. Doing a node_load on every node until you find the correct taxonomy term has a huge performace impact. This can be overcome by with some simple sql joins. Here is an updated function. I've also combined the 2 functions into one.

<?php
function node_sibling($dir = 'next', $node, $next_node_text=NULL, $prepend_text=NULL, $append_text=NULL, $tid = FALSE){
  if(
$tid){
   
$query = 'SELECT n.nid, n.title FROM {node} n INNER JOIN {term_node} tn ON n.nid=tn.nid WHERE '
          
. 'n.nid ' . ($dir == 'previous' ? '<' : '>') . ' %d AND n.type = "%s" AND n.status=1 '
          
. 'AND tn.tid = %d ORDER BY n.nid ' . ($dir == 'previous' ? 'DESC' : 'ASC');
   
$result = db_query($query, $node->nid, $node->type, $tid);
  }else{
   
$query = 'SELECT n.nid, n.title FROM {node} n WHERE '
          
. 'n.nid ' . ($dir == 'previous' ? '<' : '>') . ' %d AND n.type = "%s" AND n.status=1 '
          
. 'ORDER BY n.nid ' . ($dir == 'previous' ? 'DESC' : 'ASC');
   
$result = db_query($query, $node->nid, $node->type);
  }
  if(
$row = db_fetch_object($result)){
   
$text = $next_node_text ? $next_node_text : $row->title;
    return
$prepend_text . l($text, 'node/'.$row->nid, array('rel' => $dir)) . $append_text;
  }else{
    return
NULL;
  }
}
?>

(c)