Creating "Next" and "Previous" Links on Node Views To Related Content Based on A View

Summary

The client wanted a node type that is listed by a view, taking you a detailed view of the node. That node includes links to the ‘previous’ and ‘next’. Those are quoted because it’s not absolute ordering. The view itself defines 3 exposed filters that must be carried into the chain of next and previous.

Implementation Notes

I’ve created a standard style view through the admin, and exposed the three required fields. The view itself puts out the desired node type as a ‘grid’ view mode, but any view mode other than full would do. Because the node view itself has no idea it’s being rendered inside a view, I added preprocess logic in the custom module for the node. The preprocess node mirrors the view’s defined filters from the url, adding them to the node as query string parameters. This allows those to be passed in, but that’s not enough.

In order to determine where this result is in the desired filtering I needed pass an additional parameter $offset. The offset allows me to quickly query when in detailed view to pull “Limit 3 Offset ($offset – 1)” getting the previous and next efficiently. My “query” is actually the view used for listing. I programmatically load, filter, and execute it during full node views to determine the proper filters, orders, etc always matching whatever the view does. The next and previous urls are then programmed to also include the same filters and offsets.

Bonus problem. Long tailed entry to one of these nodes directly will include no filters or offset, but I still had to figure out the next and previous. By disabling paging on the view and pulling back all results I can determine the offset and get people back into the stronger performance flow using next and previous.

Code

sites/all/modules/custom/creative_design/creative_design.module.inc

Note that not all of the required code is included, just the important bits to this process.

  1. /**
  2.  * Modifies non-full node urls to respect filter parameters in node urls
  3.  * @param array $variables
  4.  */
  5. function _custom_preprocess_node_creative_design_node_url(&$variables){
  6.   static $rendered = 0;
  7.   static $creative_design_view = NULL;
  8.   static $creative_design_view_pager_options = NULL;
  9.   if( $variables['view_mode'] == 'full' ){
  10.     return;
  11.   }
  12.  
  13.   $query_string_additions = array();
  14.   // Additional crazy
  15.   // We need to be provide next and previous on the full view
  16.   // so additional parameter should be added to nodes displayed
  17.   // on the creative design view that exposes the offset parameter
  18.   // from views. This can be tracked by using a static variable
  19.   // in this funciton that is incremented only when on the views page
  20.   // and added to node urls.
  21.   if( $creative_design_view === NULL ){
  22.     $menu_item = menu_get_item();
  23.     if(
  24.       $menu_item['page_callback'] == 'views_page'
  25.       && !empty($menu_item['page_arguments'][0])
  26.       && $menu_item['page_arguments'][0] == 'creative_designs'
  27.     ){
  28.       $creative_design_view = TRUE;
  29.       // Load the view and set it to page display to retrieve the items per page...
  30.       $view = views_get_view('creative_designs');
  31.       $view->set_display('page_1');
  32.       $creative_design_view_pager_options = $view->display_handler->get_option('pager');
  33.       unset($view);
  34.     } else {
  35.       $creative_design_view = FALSE;
  36.     }
  37.   }
  38.   if( $creative_design_view ){
  39.     // Add the number processed
  40.     $offset = $rendered;
  41.     $rendered++;
  42.     // Add the items per page * page if required
  43.     if(
  44.       in_array($creative_design_view_pager_options, array('full', 'mini') )
  45.       && !empty($creative_design_view_pager_options['options']['items_per_page'])
  46.     ){
  47.       $page = !empty($_GET['page'])? (int)$_GET['page'] : 0;
  48.       $ofset += $page * $creative_design_view_pager_options['options']['items_per_page'];
  49.     }
  50.     $query_string_additions['offset'] = $offset;
  51.   }
  52.  
  53.   $variables['node_url'] = creative_design_get_url($variables['node'], $query_string_additions);
  54. }
  55.  
  56. /**
  57.  * Prepares a next and previous link for full nodes
  58.  * @param unknown $variables
  59.  */
  60. function _custom_preprocess_node_creative_design_next_and_previous(&$variables){
  61.   if( $variables['view_mode'] != 'full' ){
  62.     return;
  63.   }
  64.   $variables['previous'] = NULL;
  65.   $variables['previous_url'] = NULL;
  66.   $variables['next'] = NULL;
  67.   $variables['next_url'] = NULL;
  68.  
  69.   // Gather defined filters
  70.   $filters = array('field_industries_tid', 'field_special_features_tid', 'field_products_target_id');
  71.   $defined_filters = array();
  72.   foreach($filters as $filter){
  73.     if( array_key_exists($filter, $_GET) && !empty($_GET[$filter]) ){
  74.       $defined_filters[$filter] = $_GET[$filter];
  75.     }
  76.   }
  77.  
  78.   // Load the view and execute it
  79.   $view = views_get_view('creative_designs');
  80.   $view->set_display('default');
  81.   $display = $view->display_handler;
  82.   $filters = $display->get_option('filters');
  83.   foreach($filters as &$filter){
  84.     if( !array_key_exists('exposed', $filter) || !$filter['exposed'] ){
  85.       continue;
  86.     }
  87.     $filter_id = $filter['id'];
  88.     if( !empty($defined_filters[ $filter_id ]) ){
  89.       if( !$filter['is_grouped'] ){
  90.         // Assign this to the value's value
  91.         $filter['value']['value'] = $defined_filters[ $filter_id ];
  92.       } else {
  93.         // TODO: Handle, should it ever be required
  94.         watchdog('custom'
  95.           ,'Grouped field handling for creative design filters NYI. Field info:<br /><pre>!field</pre>'
  96.           ,array( '!field' => print_r($field, TRUE) )
  97.           ,WATCHDOG_ERROR
  98.           ,$_GET['q']
  99.         );
  100.       }
  101.     }
  102.   }
  103.   // Respect offset if defined
  104.   $offset = ( array_key_exists('offset', $_GET) && is_numeric($_GET['offset']) )? (int)$_GET['offset'] : NULL;
  105.   if( $offset !== NULL ){
  106.     // Target exactly
  107.     $display->set_option('pager'
  108.       ,array(
  109.         'type' => 'some',
  110.         'options' => array(
  111.           'items_per_page' => 3,
  112.           'offset' => ( $offset - 1 )
  113.         )
  114.       )
  115.     );
  116.   } else {
  117.     // Grab all elements
  118.     $display->set_option('pager'
  119.       ,array(
  120.         'type' => 'none',
  121.         'options' => array(
  122.           'items_per_page' => 0,
  123.         )
  124.       )
  125.     );
  126.   }
  127.   $view->execute();
  128.   $results = $view->result;
  129.   if( $offset !== NULL ){
  130.     // Use the offset if provided performance
  131.     $variables['previous'] = !empty($results[0]->nid)? node_load($results[0]->nid) : NULL;
  132.     $variables['next'] = !empty($results[2]->nid)? node_load($results[2]->nid) : NULL;
  133.   } else {
  134.     // Handle the offset not defined, search all the available results and take the previous next
  135.     $previous = NULL;
  136.     $next = NULL;
  137.     foreach($results as $id => $result){
  138.       if( $result->nid == $variables['nid'] ){
  139.         $offset = $id;
  140.         $previous = $id - 1;
  141.         $next = $id + 1;
  142.         break;
  143.       }
  144.     }
  145.     if( !empty($previous) || !empty($next) ){
  146.       if( array_key_exists($previous, $results) ){
  147.         $variables['previous'] = node_load($results[$previous]->nid);
  148.       }
  149.       if( array_key_exists($next, $results) ){
  150.         $variables['next'] = node_load($results[$next]->nid);
  151.       }
  152.     }
  153.   }
  154.   if( !empty($variables['previous']) ){
  155.     $variables['previous_url'] = creative_design_get_url($variables['previous'], array( 'offset' => ($offset - 1) ));
  156.   }
  157.   if( !empty($variables['next']) ){
  158.     $variables['next_url'] = creative_design_get_url($variables['next'], array( 'offset' => ($offset + 1) ));
  159.   }
  160.   unset($result);
  161.   unset($view);
  162. }
  163.  
  164. /**
  165.  * Returns a creative design url that includes the active filters
  166.  * @param object $node
  167.  * @param array $query_string_additions
  168.  */
  169. function creative_design_get_url($node, $query_string_additions = array()){
  170.   $query_string_additions += creative_design_get_defined_filters();
  171.   return url('node/' . $node->nid, array('query' => $query_string_additions));
  172. }
  173.  
  174. /**
  175.  * Returns a creative design nodes defined filters from the url
  176.  * @return array
  177.  */
  178. function creative_design_get_defined_filters(){
  179.   $filters = array('field_industries_tid', 'field_special_features_tid', 'field_products_target_id', 'page');
  180.   $query_string_additions = array();
  181.   foreach($filters as $filter){
  182.     if( array_key_exists($filter, $_GET) && !empty($_GET[$filter]) ){
  183.       $query_string_additions[$filter] = $_GET[$filter];
  184.     }
  185.   }
  186.   return $query_string_additions;
  187. }

sites/all/themes/{theme_name}/templates/node--creative-design--full.tpl.php

And use it like this:

  1. <?php if( !empty($previous) ): ?>
  2.   <div id="previous" class="navigation-direction">
  3.     <a href="<?php print $previous_url; ?>">
  4.       <i
  5.         class="fa fa-chevron-left"
  6.         title="<?php print t('View previous: !title', array(
  7.                 '!title' => check_plain($previous->title),
  8.         )); ?>"
  9.       >
  10.       </i>
  11.     </a>
  12.   </div>
  13. <?php endif; ?>
  14. <?php if( !empty($next) ): ?>
  15.   <div id="previous" class="navigation-direction">
  16.     <a href="<?php print $next_url; ?>">
  17.       <i
  18.         class="fa fa-chevron-right"
  19.         title="<?php print t('View next: !title', array(
  20.                 '!title' => check_plain($next->title),
  21.         )); ?>"
  22.       >
  23.       </i>
  24.     </a>
  25.   </div>
  26. <?php endif; ?>
Tags: 

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.