Drupal Creating 'Next' and 'Previous' Links on Node Views To Related Content Based on A View
Published:
Heads up! This content is more than six months old. Take some time to verify everything still works as expected.
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.
/** * Modifies non-full node urls to respect filter parameters in node urls * @param array $variables */ function _custom_preprocess_node_creative_design_node_url(&$variables){ static $rendered = 0; static $creative_design_view = NULL; static $creative_design_view_pager_options = NULL; if( $variables['view_mode'] == 'full' ){ return; } $query_string_additions = array(); // Additional crazy // We need to be provide next and previous on the full view // so additional parameter should be added to nodes displayed // on the creative design view that exposes the offset parameter // from views. This can be tracked by using a static variable // in this funciton that is incremented only when on the views page // and added to node urls. if( $creative_design_view === NULL ){ $menu_item = menu_get_item(); if( $menu_item['page_callback'] == 'views_page' && !empty($menu_item['page_arguments'][0]) && $menu_item['page_arguments'][0] == 'creative_designs' ){ $creative_design_view = TRUE; // Load the view and set it to page display to retrieve the items per page... $view = views_get_view('creative_designs'); $view->set_display('page_1'); $creative_design_view_pager_options = $view->display_handler->get_option('pager'); unset($view); } else { $creative_design_view = FALSE; } } if( $creative_design_view ){ // Add the number processed $offset = $rendered; $rendered++; // Add the items per page * page if required if( in_array($creative_design_view_pager_options, array('full', 'mini') ) && !empty($creative_design_view_pager_options['options']['items_per_page']) ){ $page = !empty($_GET['page'])? (int)$_GET['page'] : 0; $ofset += $page * $creative_design_view_pager_options['options']['items_per_page']; } $query_string_additions['offset'] = $offset; } $variables['node_url'] = creative_design_get_url($variables['node'], $query_string_additions); } /** * Prepares a next and previous link for full nodes * @param unknown $variables */ function _custom_preprocess_node_creative_design_next_and_previous(&$variables){ if( $variables['view_mode'] != 'full' ){ return; } $variables['previous'] = NULL; $variables['previous_url'] = NULL; $variables['next'] = NULL; $variables['next_url'] = NULL; // Gather defined filters $filters = array('field_industries_tid', 'field_special_features_tid', 'field_products_target_id'); $defined_filters = array(); foreach($filters as $filter){ if( array_key_exists($filter, $_GET) && !empty($_GET[$filter]) ){ $defined_filters[$filter] = $_GET[$filter]; } } // Load the view and execute it $view = views_get_view('creative_designs'); $view->set_display('default'); $display = $view->display_handler; $filters = $display->get_option('filters'); foreach($filters as &$filter){ if( !array_key_exists('exposed', $filter) || !$filter['exposed'] ){ continue; } $filter_id = $filter['id']; if( !empty($defined_filters[ $filter_id ]) ){ if( !$filter['is_grouped'] ){ // Assign this to the value's value $filter['value']['value'] = $defined_filters[ $filter_id ]; } else { // TODO: Handle, should it ever be required watchdog('custom' ,'Grouped field handling for creative design filters NYI. Field info:<br /><pre>!field</pre>' ,array( '!field' => print_r($field, TRUE) ) ,WATCHDOG_ERROR ,$_GET['q'] ); } } } // Respect offset if defined $offset = ( array_key_exists('offset', $_GET) && is_numeric($_GET['offset']) )? (int)$_GET['offset'] : NULL; if( $offset !== NULL ){ // Target exactly $display->set_option('pager' ,array( 'type' => 'some', 'options' => array( 'items_per_page' => 3, 'offset' => ( $offset - 1 ) ) ) ); } else { // Grab all elements $display->set_option('pager' ,array( 'type' => 'none', 'options' => array( 'items_per_page' => 0, ) ) ); } $view->execute(); $results = $view->result; if( $offset !== NULL ){ // Use the offset if provided performance $variables['previous'] = !empty($results[0]->nid)? node_load($results[0]->nid) : NULL; $variables['next'] = !empty($results[2]->nid)? node_load($results[2]->nid) : NULL; } else { // Handle the offset not defined, search all the available results and take the previous next $previous = NULL; $next = NULL; foreach($results as $id => $result){ if( $result->nid == $variables['nid'] ){ $offset = $id; $previous = $id - 1; $next = $id + 1; break; } } if( !empty($previous) || !empty($next) ){ if( array_key_exists($previous, $results) ){ $variables['previous'] = node_load($results[$previous]->nid); } if( array_key_exists($next, $results) ){ $variables['next'] = node_load($results[$next]->nid); } } } if( !empty($variables['previous']) ){ $variables['previous_url'] = creative_design_get_url($variables['previous'], array( 'offset' => ($offset - 1) )); } if( !empty($variables['next']) ){ $variables['next_url'] = creative_design_get_url($variables['next'], array( 'offset' => ($offset + 1) )); } unset($result); unset($view); } /** * Returns a creative design url that includes the active filters * @param object $node * @param array $query_string_additions */ function creative_design_get_url($node, $query_string_additions = array()){ $query_string_additions += creative_design_get_defined_filters(); return url('node/' . $node->nid, array('query' => $query_string_additions)); } /** * Returns a creative design nodes defined filters from the url * @return array */ function creative_design_get_defined_filters(){ $filters = array('field_industries_tid', 'field_special_features_tid', 'field_products_target_id', 'page'); $query_string_additions = array(); foreach($filters as $filter){ if( array_key_exists($filter, $_GET) && !empty($_GET[$filter]) ){ $query_string_additions[$filter] = $_GET[$filter]; } } return $query_string_additions; } </php> <h3>sites/all/themes/{theme_name}/templates/node--creative-design--full.tpl.php</h3> And use it like this: <php> <?php if( !empty($previous) ): ?> <div id="previous" class="navigation-direction"> <a href="<?php print $previous_url; ?>"> <i class="fa fa-chevron-left" title="<?php print t('View previous: !title', array( '!title' => check_plain($previous->title), )); ?>" > </i> </a> </div> <?php endif; ?> <?php if( !empty($next) ): ?> <div id="previous" class="navigation-direction"> <a href="<?php print $next_url; ?>"> <i class="fa fa-chevron-right" title="<?php print t('View next: !title', array( '!title' => check_plain($next->title), )); ?>" > </i> </a> </div> <?php endif; ?>