Khanh Hoang - Kenn
Kenn is a user experience designer and front end developer who enjoys creating beautiful and usable web and mobile experiences.
At certain scales, the ability of views to provide multiple block or page displays can become a hindrance to the performance of the UI. I recently tackled a problem with a view that contained 45 nearly identical displays and was growing. It was nearly impossible to continue managing this via the views UI.
A simple fix could have been to split the view into multiple displays with fewer displays per view. This would only add to the management complexity, however, as configuration changes would need to be replicated through each view in the set.
The displays only varied in a few ways:
Using context and a little bit of code, all of these items can be altered before the view is executed or provided as variables for use in a view template and allows the view to have only a single block and page display. This technique can also be used when you want your administrators users to be able to control parts of a view but you don’t want to expose them to the views UI interface.
The first step is to provide a context handler that can be used to set the values you want to use in your view. Some of these, like the item counts, are used to alter a view before it is executed. Others, the labels and titles, are used in templates and in drupal_set_title calls.
/** * Expose ability to set variables for use in a reaction * Define this in a .inc file in your module directory */ class context_view_reaction_vars extends context_reaction { /** * Provide form elements for setting data, they will be stored * under a key matching the registry key using this handler */ function options_form($context) { $values = $this->fetch_from_context($context); return array( 'block_title' => array( '#title' => t('Block Title'), '#description' => t('Title to use for block'), '#type' => 'textfield', '#default_value' => $values['block_title'], ), 'block_item_count' => array( '#title' => t('Block Number of Items'), '#description' => t('Number of items to display in block'), '#type' => 'textfield', '#default_value' => $values['block_item_count'], ), 'link_text' => array( '#title' => t('Link Text'), '#description' => t('Text to use when rendering read more link'), '#type' => 'textfield', '#default_value' => $values['link_text'], ), // note that this has special handling in the execute method 'page_title' => array( '#title' => t('Page Title'), '#description' => t('Title to use on list page'), '#type' => 'textfield', '#default_value' => $values['page_title'], ), 'page_item_count' => array( '#title' => t('Page Number of Items'), '#description' => t('Number of items to display on list page'), '#type' => 'textfield', '#default_value' => $values['page_item_count'], ), ); } /** * pull the stored variables from the context and set them in the vars * so they are available in templates and preprocessor functions * * context_view_vars comes from the value used in hook context_registry * * context_view_vars_ . $key is a convention used so that these variables * can be set for use in templates */ function execute(&$vars) { $contexts = context_active_contexts(); foreach ($contexts as $context) { if (!empty($context->reactions['context_view_vars'])) { foreach ($context->reactions['context_view_vars'] as $key => $value) { $vars['context_view_vars_' . $key] = $value; // special handling for setting page title if ($key == 'page_title' && $value) { drupal_set_title($value); } } } } } }
Once this is done you have to tell context about your handler by putting the following in your module file
/** * hook context_plugins */ function context_view_context_plugins() { $plugins = array(); $plugins['context_view_reaction_vars'] = array( 'handler' => array( 'path' => drupal_get_path('module', 'context_view'), 'file' => 'context_view_reaction_vars.inc', 'class' => 'context_view_reaction_vars', 'parent' => 'context_reaction', ), ); return $plugins; } /** * hook context_registry */ function context_view_context_registry() { return array( 'reactions' => array( // note that this key must match what the execute portion of the plugin // is using 'context_view_vars' => array( 'title' => t('View Variables'), 'plugin' => 'context_view_reaction_vars', ), ), ); }
At this point you should be able to set view variables as a reaction in a context. The remaining step is to use your set vars to alter your view. The views_pre_execute handler is used to alter the view, the views_view_list and views_view handlers are used to make the variables you set available to views templates. You may need to add or remove view hooks depending on where you need to use the variables you are setting in your reaction.
/** * hook views_pre_execute * pull data from context view vars and use it to set the number of items * per page for the block and page display */ function context_view_views_pre_execute(&$view) { if (_context_view_apply_reaction_vars($view) && isset($view->pager)) { $plugin = context_get_plugin('reaction', 'context_view_vars'); $vars = array(); $plugin->execute($vars); if ($vars['context_view_vars_block_item_count'] && strstr($view->current_display, 'block_') !== FALSE) { $view->pager['items_per_page'] = $vars['context_view_vars_block_item_count']; } else if ($vars['context_view_vars_page_item_count'] && strstr($view->current_display, 'page_') !== FALSE) { $view->pager['items_per_page'] = $vars['context_view_vars_page_item_count']; } } } /** * Implementation of hook_preprocess_views_view_list() * get context_view_vars reaction plugin and allow it to * set variables for use in the template * * In the view template, the variables are available as * $context_view_vars_ + key * For example, $context_view_vars_block_title */ function context_view_preprocess_views_view_list(&$vars) { if (_context_view_apply_reaction_vars($vars['view'])) { $plugin = context_get_plugin('reaction', 'context_view_vars'); $plugin->execute($vars); } } /** * Implementation of hook_preprocess_views_view * get context_view_vars reaction plugin and allow it to * set variables for use in the template * * In the view template, the variables are available as * $context_view_vars_ + key * For example, $context_view_vars_block_title */ function context_view_preprocess_views_view(&$vars, $hook) { if (_context_view_apply_reaction_vars($vars['view'])) { $plugin = context_get_plugin('reaction', 'context_view_vars'); $plugin->execute($vars); } } /** * This function is responsible for determining if the view and display you * are on should be manipulated by your contextually set variables */ function _context_view_apply_reaction_vars($view) { return $view->name == 'contextually_alterable_view_name' && ($view->current_display == 'block_1' || $view->current_display == 'page_1'); }
Though there is a lot of code here, the basic concept is simple. Provide a context reaction that lets you set some variables and then pull those values for use during the appropriate hook provided by views or template preprocessing.
For additional information about the hooks available in views be sure to install the advanced help module so you can see the documentation included with the views module.