Switch Drupal Views Output in Code

Posted by on November 1, 2013
 

I was working on a page that had a sidebar block showing a list of content with the same topic as the main page. In Drupal terms, the sidebar block shared the taxonomy term. It’s pretty easy to create such a block using Views by adding a contextual filter that gets its value from the page path. Contextual filters are very powerful. You can learn more about them in the Drupal documentation here.

Of course on some pages, if Views finds no matches, the block will not display. Our client came back and asked if it could show the full list on pages where no matches were found with the same topic. After pondering it a bit, I realized that this is possible using one of the hooks Views makes available. As you can see from the full list of hooks, you can affect how a view is built at many points along the way. You can accomplish this through the Views UI, by embedding a view in the “No Results” behavior. This example will show you how to do the same in code, but could also be extended to do more complicated replacements, like render one or more blocks programatically.

Figuring out which hook to use can be challenging, especially if you’re unfamiliar with the build pipeline and its phases. In this case, we’ll use the post_render hook, but first, let’s look at our block view which has the identifier “block_1″. I had added two contextual filters that get their values based on the current path of the page. A sample path to the page could be /lists/12/4, where 12 is a topic term id and 4 is an audience term id. Note that you can use term names as the identifier when you configure a contextual filter, but support for this is buggy, especially if your terms include ampersands or other punctuation (see this issue).

views-hook-context

The first thing to do is to clone our filtered display and remove the contextual filters for the cloned block. The new block will have an identifier like “block_2″. You’ll also need to know the machine name of the view we are targeting. Once the blocks are set up in Views, we need to create our view hook in a custom module. The hook code looks like the following:

/**
 * Implements hook_views_post_render(&$view, &$output, &$cache)
 *
 * @param $view
 * @param $output
 * @param $cache
 */
function mymodule_views_post_render(&$view, &$output, &$cache) {
  // test for the view we want to change
  if ('sidebar_lists' == $view->name) {
    switch ($view->current_display) {
      case 'block_1': // our filtered list      
        if (empty($view->result)) {
          // show unfiltered results
          $output = views_embed_view('sidebar_lists', 'block_2');

          // fake a non-empty result for views to render the block
          $view->result = array(1 => true);
        }
        break;
    }
  }
}

That’s all we need. The post_render hook allows us to check if our first block found any matches. The views_embed_view() function is used to programmatically get the output of our fall back view and set that as our output. When I first tried this, nothing displayed. It turns out that you need $view->result to have a non-empty value for Views to render the block.

Of course, I’ve introduced a bit of a performance hit to our page loads by executing another query if the filtered view query is empty. If you’re not caching the whole page, you’d want to cache the output of the block.

The Views UI let’s you build some very complicated filtered lists. Still, in some cases you will need to write custom code. I hope this example shows you how the View module’s hooks can be used to get it to do what you want.


About the author—Oscar still remembers downloading an early version of the Apache HTTP server at the end of 1995, and promptly asking "Ok, what's this good for?" He started learning PHP in 2000 and hasn't stopped since. He's worked with Drupal, WordPress, Zend Framework, and bespoke PHP, to name a few. Follow him on Google+.