Customizing Views With Context

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:

Chris Johnson, VP of Engineering
#Drupal | Posted

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:

  • Title of block
  • Number of items in block
  • Wording of read more link
  • Title of linked page display
  • Number of items in linked page display

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.

  1. /**
  2.  * Expose ability to set variables for use in a reaction
  3.  * Define this in a .inc file in your module directory
  4.  */
  5. class context_view_reaction_vars extends context_reaction {
  6. /**
  7.   * Provide form elements for setting data, they will be stored
  8.   * under a key matching the registry key using this handler
  9.   */
  10. function options_form($context) {
  11. $values = $this->fetch_from_context($context);
  12.  
  13. return array(
  14. 'block_title' => array(
  15. '#title' => t('Block Title'),
  16. '#description' => t('Title to use for block'),
  17. '#type' => 'textfield',
  18. '#default_value' => $values['block_title'],
  19. ),
  20. 'block_item_count' => array(
  21. '#title' => t('Block Number of Items'),
  22. '#description' => t('Number of items to display in block'),
  23. '#type' => 'textfield',
  24. '#default_value' => $values['block_item_count'],
  25. ),
  26. 'link_text' => array(
  27. '#title' => t('Link Text'),
  28. '#description' => t('Text to use when rendering read more link'),
  29. '#type' => 'textfield',
  30. '#default_value' => $values['link_text'],
  31. ),
  32. // note that this has special handling in the execute method
  33. 'page_title' => array(
  34. '#title' => t('Page Title'),
  35. '#description' => t('Title to use on list page'),
  36. '#type' => 'textfield',
  37. '#default_value' => $values['page_title'],
  38. ),
  39. 'page_item_count' => array(
  40. '#title' => t('Page Number of Items'),
  41. '#description' => t('Number of items to display on list page'),
  42. '#type' => 'textfield',
  43. '#default_value' => $values['page_item_count'],
  44. ),
  45. );
  46. }
  47.  
  48. /**
  49.   * pull the stored variables from the context and set them in the vars
  50.   * so they are available in templates and preprocessor functions
  51.   *
  52.   * context_view_vars comes from the value used in hook context_registry
  53.   *
  54.   * context_view_vars_ . $key is a convention used so that these variables
  55.   * can be set for use in templates
  56.   */
  57. function execute(&$vars) {
  58. $contexts = context_active_contexts();
  59. foreach ($contexts as $context) {
  60. if (!empty($context->reactions['context_view_vars'])) {
  61. foreach ($context->reactions['context_view_vars'] as $key => $value) {
  62. $vars['context_view_vars_' . $key] = $value;
  63. // special handling for setting page title
  64. if ($key == 'page_title' && $value) {
  65. drupal_set_title($value);
  66. }
  67. }
  68. }
  69. }
  70. }
  71. }

Once this is done you have to tell context about your handler by putting the following in your module file

  1. /**
  2.  * hook context_plugins
  3.  */
  4. function context_view_context_plugins() {
  5. $plugins = array();
  6. $plugins['context_view_reaction_vars'] = array(
  7. 'handler' => array(
  8. 'path' => drupal_get_path('module', 'context_view'),
  9. 'file' => 'context_view_reaction_vars.inc',
  10. 'class' => 'context_view_reaction_vars',
  11. 'parent' => 'context_reaction',
  12. ),
  13. );
  14. return $plugins;
  15. }
  16.  
  17. /**
  18.  * hook context_registry
  19.  */
  20. function context_view_context_registry() {
  21. return array(
  22. 'reactions' => array(
  23. // note that this key must match what the execute portion of the plugin
  24. // is using
  25. 'context_view_vars' => array(
  26. 'title' => t('View Variables'),
  27. 'plugin' => 'context_view_reaction_vars',
  28. ),
  29. ),
  30. );
  31. }

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.

  1. /**
  2.  * hook views_pre_execute
  3.  * pull data from context view vars and use it to set the number of items
  4.  * per page for the block and page display
  5.  */
  6. function context_view_views_pre_execute(&$view) {
  7. if (_context_view_apply_reaction_vars($view) && isset($view->pager)) {
  8. $plugin = context_get_plugin('reaction', 'context_view_vars');
  9. $vars = array();
  10. $plugin->execute($vars);
  11. if ($vars['context_view_vars_block_item_count'] && strstr($view->current_display, 'block_') !== FALSE) {
  12. $view->pager['items_per_page'] = $vars['context_view_vars_block_item_count'];
  13. } else if ($vars['context_view_vars_page_item_count'] && strstr($view->current_display, 'page_') !== FALSE) {
  14. $view->pager['items_per_page'] = $vars['context_view_vars_page_item_count'];
  15. }
  16. }
  17. }
  18.  
  19. /**
  20.  * Implementation of hook_preprocess_views_view_list()
  21.  * get context_view_vars reaction plugin and allow it to
  22.  * set variables for use in the template
  23.  *
  24.  * In the view template, the variables are available as
  25.  * $context_view_vars_ + key
  26.  * For example, $context_view_vars_block_title
  27.  */
  28. function context_view_preprocess_views_view_list(&$vars) {
  29. if (_context_view_apply_reaction_vars($vars['view'])) {
  30. $plugin = context_get_plugin('reaction', 'context_view_vars');
  31. $plugin->execute($vars);
  32. }
  33. }
  34.  
  35. /**
  36.  * Implementation of hook_preprocess_views_view
  37.  * get context_view_vars reaction plugin and allow it to
  38.  * set variables for use in the template
  39.  *
  40.  * In the view template, the variables are available as
  41.  * $context_view_vars_ + key
  42.  * For example, $context_view_vars_block_title
  43.  */
  44. function context_view_preprocess_views_view(&$vars, $hook) {
  45. if (_context_view_apply_reaction_vars($vars['view'])) {
  46. $plugin = context_get_plugin('reaction', 'context_view_vars');
  47. $plugin->execute($vars);
  48. }
  49. }
  50.  
  51. /**
  52.  * This function is responsible for determining if the view and display you
  53.  * are on should be manipulated by your contextually set variables
  54.  */
  55. function _context_view_apply_reaction_vars($view) {
  56. return $view->name == 'contextually_alterable_view_name' &&
  57. ($view->current_display == 'block_1' ||
  58. $view->current_display == 'page_1');
  59. }

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.

Chris Johnson

Chris Johnson

VP of Engineering