Drupal – Add Previous and Next Links for Node Navigation

When you are viewing a node, it would be nice to have the previous and next navigation buttons so that you can browse other nodes of the same content type more easily. This is exactly the recent task i need to complete. Luckily i found a very good post showing how to add the navigation buttons in just 2 steps.

1. Add the following function in your theme template.php

function <theme>_prev_next($current_node = NULL, $op = 'p') {
  // Node types to include in paging
  $node_types = array('blog');

  if ($op == 'p') {
    $sql_op = '<';
    $order = 'DESC';
  }
  elseif ($op == 'n') {
    $sql_op = '>';
    $order = 'ASC';
  }
  else {
    return NULL;
  }

  $output = NULL;
  foreach($node_types as $type) {
    $quoted_types[] = "'" . $type . "'";
  }
  $sql = "SELECT nid, title, created FROM {node} n
    WHERE created $sql_op %s
    AND type IN (" . implode(',', $quoted_types) . ")
    AND status = 1
    ORDER BY created $order
    LIMIT 1";
  $result = db_query($sql, $current_node->created, $type);
  $data = db_fetch_object($result);
  if (!isset($data->nid) || !$data->nid) {
    return NULL;
  }
  return l($data->title, "node/$data->nid", array('html' => TRUE));
}

 

2. Add the following piece of code to node.tpl.php or node-<content-type>.tpl.php

<!-- Node paging start. Add this to your node.tpl.php and/or node-TYPE.tpl.php -->
<?php if (!$teaser) : ?>
  <div class="navpn">
    <div class="pnlaquo">&laquo;</div>
    <div id="pnprev"> <?php print <theme>_prev_next($node, 'p'); ?> </div>
    <div id="pnmain"> | <a href="<?php print base_path(); ?>">Home</a> | </div>
    <div id="pnnext"> <?php print <theme>_prev_next($node, 'n'); ?> </div>
    <div class="pnraquo">&raquo;</div>
  </div>
<?php endif; ?>
<!-- Node paging end -->

 

The navigation buttons should work now. Wait… i am using Panels for my node view which contains some Views content panes. How could i load the navigation buttons in views?

That’s easy, in your view, probably you will take the node id as argument to show the content of a specific node and the second argument of the URL will be the node id. Let’s change the code above as follow and paste it in the view template file.

<?php $blog= node_load(arg(1)); ?>
<div class="navpn">
  <div id="pnprev"> <?php print <theme>_prev_next($blog, 'p'); ?> </div>
  <div id="pnmain"> | <a href="<?php print base_path(); ?>">Home</a> | </div>
  <div id="pnnext"> <?php print <theme>_prev_next($blog, 'n'); ?> </div>
</div>

 

Rescan the template file and the navigation buttons are ready.

Done =)

Reference:

31 thoughts on “Drupal – Add Previous and Next Links for Node Navigation”

  1. Hmm. I got this error:

    Recoverable fatal error: Argument 2 passed to db_query() must be an array, string given, called in /var/www/vhosts/example.com/httpdocs/sites/all/themes/THEME/template.php on line 147 and defined in db_query() (line 2282 of /var/www/vhosts/example.com/httpdocs/includes/database/database.inc).

    Drupal 7.4
    Zen Sub-theme

    Like

  2. Hi This is really awesome !!! Hey is there anyway to get this to print out different classes for the 2 pager links like something like this

    <a href class="prev"><span class="arrowLeft"></span></a>
    <a href class="next"><span class="arrowRight"></span></a>
    

    right now they both are identical except for the link to the next node

    thanks again

    Like

    1. About the classes, you can refer to the api specification of the l() function. probably passing the $attributes array with class as key.
      Drupal API – l()

      Or you can customize the return string in function <theme>_prev_next(), sth like

      ...
      // Instead of using the l()
      //return l($data->title, "node/$data->nid", array('html' => TRUE));
      
      // You can return the HTML string
      if ($op == 'p') {
        return '<a href="node/' . $data->nid . '" class="prev"><span class="arrowLeft"></span></a>';
      } else {
        return '<a href="node/' . $data->nid . '" class="next"><span class="arrowRight"></span></a>';
      }
      

       

      About the looping, probably you need to add some logic if the sql returns no record.

        if (!isset($data->nid) || !$data->nid) {
          //return NULL;
          
          // If no node is found, you need to setup a new sql to return the first or latest node depending $op.
        }
      

      Hope this help.

      Like

  3. Hi ,

    It totally helps for sure for the markup, it is correct now !

    but for some reason its still returning the exact same classes some reason this still returns 2 identical strings – I tried this

    if ($op = 'p') {
      return '<a>nid . '" class="prev">' . $data->title .'</a>';
    }
    
    elseif ($op = 'n') {
      return '<a>nid . '" class="next">' . $data->title .'</a>';
    }
    

    However the output is always the same

    <a href="node/129" rel="nofollow">title1</a>       
    <a href="node/132" rel="nofollow">title2</a>
    

    Is there anyway to get it to have it return a second set of classes ? for the other link?

    Thanks very much for your help !! Your awesome !!

    Like

  4. Hi ,

    Yes I did clear the cache it totally works but the output looks like this
    Its basically making the 2 links have identical classes but still different links to nodes

    <a href="node/129" class="prev"><span class="arrowLeft">title1</span></a>       
    <a href="node/132" class="prev"><span class="arrowLeft">title2</span></a>    
    

    Once again thanks for your help

    Like

    1. O, my typo… should be “==”

      if ($op == 'p') {
        return '<a href="node/' . $data->nid . '" class="prev"><span class="arrowLeft"></span></a>';
      } else {
        return '<a href="node/' . $data->nid . '" class="next"><span class="arrowRight"></span></a>';
      }
      

      Like

      1. Ok cool I will let you know wif I have any issue thanks – Also do you have any experience with views pager override in template.php well this one specifically

        function mytheme_views_mini_pager__myiew($tags = array(), $limit = 10, $element = 0, $parameters = array(), $quantity = 9)
        

        I was trying to a similar thing with this function but instead I was trying to return a slightly different html string

        Here is the full function here – http://pastebin.com/n9BNBi8c

        The html string I am looking for is this

        <div class="articlePagination">
        <a href="" class="action"><span>Newer Posts</span></a>
        <a href="" class="action back"><span>Older Posts</span></a>
        </div>
        

        I feel like returning this views pager in a more specific html string should be the exact same process but for some reason I cannot get it it seems like the classes of l() are being controlled by the $items array above and I was wondering if you had any experience with this type of pager or if this is a completely different thing.

        thanks again for all the help

        Like

  5. Hi ykyuen,

    I was able to figure it out for sure I should post my solution there – Basically I just used a if else statement on my existing views pager.

    thanks for the help for sure !

    I am still struggling with creating a loop though if you have any pointers for that ?

    thanks again for all your help !

    function mytheme_prev_next($current_node = NULL, $op = 'p') {
      // Node types to include in paging
      $node_types = array('node_type');
    
      if ($op == 'p') {
        $sql_op = '<';
        $order = 'DESC';
      }
      elseif ($op == 'n') {
        $sql_op = '>';
        $order = 'ASC';
      }
    
    
      $output = NULL;
      foreach($node_types as $type) {
        $quoted_types[] = "'" . $type . "'";
      }
      $sql = "SELECT nid, title, created FROM {node} n
        WHERE created $sql_op %s
        AND type IN (" . implode(',', $quoted_types) .")
        AND status = 1
        ORDER BY created $order
        LIMIT 1";
      $result = db_query($sql, $current_node->created, $type);
      $data = db_fetch_object($result);
      if (!isset($data->nid) || !$data->nid) {
        return NULL;
      }
    
      // Instead of using the l()
    //return l($data->title, "node/$data->nid", array('html' => TRUE));
    
    $newTitle = str_replace(" ", "<br>", $data->title);
    
    // You can return the HTML string
    if ($op =='p') {
      return '<a href="node/' . $data->nid . '" class="prev"><span class="arrowLeft">' . $newTitle .'</span></a>';
    }
    else {
      return '<a href="node/' . $data->nid . '" class="next"><span class="arrowRight">' . $newTitle .'</span></a>';
    }
    
    }
    

    Like

    1. Try this

      if (!isset($data->nid) || !$data->nid) {
        $sql = "SELECT nid, title, created FROM {node} n
          WHERE type IN (" . implode(',', $quoted_types) .")
          AND status = 1
          ORDER BY created $order
          LIMIT 1";
        $result = db_query($sql, $current_node->created, $type);
        $data = db_fetch_object($result);
      }
      

      Like

  6. I am really not sure what is happening but it still won’t work on this one content type but it works on 3 others for some reason, the only thing I really could get it to do is print a single node from the set when I do return NULL; it prints nothing ?

    Like

    1. ...
      // Node types to include in paging
      $node_types = array('node_type');
      ...
      

      this array stores those content type which are available for the sql query. you could either remove this node type condition in the query or you add your new content type to it.

      Like

  7. It’s Work!

    function lafam_prev_next($current_node = NULL, $op = 'p') {
      // Node types to include in paging
      $node_types = array('blog');
    
      if ($op == 'p') {
        $sql_op = '';
        $order = 'ASC';
      }
    
      $output = NULL;
      foreach($node_types as $type) {
        $quoted_types[] = "'" . $type . "'";
      }
    
      $sql = "SELECT nid, title, created FROM {node} n
              WHERE created $sql_op :created
              AND type IN (" . implode(',', $quoted_types) .")
              AND status = 1
              ORDER BY created $order
              LIMIT 1";
    
      $result = db_query($sql, array(':created' =&gt; $current_node-&gt;created));
    
      foreach ($result as $row) {
        $options = array('absolute' =&gt; TRUE);
        $url = url('node/' . $row-&gt;nid, $options);
      }
    
      // You can return the HT(ML string
        if ($op == 'p' and isset($url)) {
          return '<a href="' . $url . '" rel="nofollow">
                    
                    Ver anterior post
                  </a>';
        }
        if ($op == 'n' and isset($url)) {
          return '<a href="' . $url . '" rel="nofollow">
                    Ver siguiente post
                    
                  </a>';
        }
    }
    

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.