Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough

Theming Views in Drupal 8 – Custom Style Plugins

Parent Feed: 

Drupal 8 logo

Views is in Drupal 8 core. We all know that by now. Twig is the new templating engine in Drupal 8. This we also know. But do we know how to interact programmatically with the first in order to theme a View using the second? Aside from overriding View templates like with any other subsystem, we have available a more powerful alternative in the form of Views plugins (Display, Style, Row and Field).

In this article, we are going to look at how we can create a custom Style plugin for Views in Drupal 8. We will use the Bootstrap tab markup as a goal and implement a tabbed output for our View results. In the View configuration, the Style settings will allow us to specify which field will be used as the tab navigation copy, leaving the rest of the fields shown in the respective tab panes. Basically, each View result will represent a tab – so this example is not suited for Views which have more than a few results. The main goal is to illustrate how we can create our own Views Style plugins in Drupal 8.

We will not cover the details on how you can use Bootstrap in your project. However, you can check out the documentation page on assets or even this article on how to make sure anonymous users can benefit from jQuery being loaded on the page. And if you want to see the code we write ahead of time, you can find it in this repository within the Demo module.

What Is the Style Plugin?

The Views Style plugin is the one responsible for rendering the listing. Notable examples of core Style plugins are Unformatted List, HTML List, Table or Grid. They are used by the Display plugin and they in turn use Row plugins that represent one item in the listing.

In Drupal 8, all Views plugin types are built using the new Plugin system and share some common functionality (they always extend from the same Views PluginBase).

Let’s now create our own such Style plugin that can be used by most Display types (Page, Block, etc) and which uses the Field row plugin.

The Bootstrap Tabs Style Plugin

The first step is to create our plugin class located in the Plugin/views/style folder of our module:

namespace Drupal\demo\Plugin\views\style;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\style\StylePluginBase;


class BootstrapTabs extends StylePluginBase {

  
  protected $usesRowPlugin = TRUE;

  
  protected $usesGrouping = FALSE;

  
  protected function defineOptions() {
    $options = parent::defineOptions();
    $options['tab_nav_field'] = array('default' => '');
    return $options;
  }

  
  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    parent::buildOptionsForm($form, $form_state);
    $options = $this->displayHandler->getFieldLabels(TRUE);
    $form['tab_nav_field'] = array(
      '#title' => $this->t('The tab navigation field'),
      '#description' => $this->t('Select the field that will be used as the tab navigation. The rest of the fields will show up in the tab content.'),
      '#type' => 'select',
      '#default_value' => $this->options['tab_nav_field'],
      '#options' => $options,
    );
  }
}

The Drupal plugin type we are creating an instance of is ViewsStyle with some basic configuration passed in the annotation. Leaving aside the obvious ones, we have the theme and display_types keys that are worth mentioning. The first declares which theme function this Style plugin will use to render its data while the second declares which kinds of Display plugins this Style can be used by (in our case all Display types which don’t otherwise specify a custom type: normal). For more information on all the available annotation configuration for this plugin type, check out the Drupal\views\Annotation\ViewsStyle annotation class.

Using the two class properties, we declare that our Style uses row plugins but does not allow grouping. Make sure you check out the parent classes to learn more about what other options can be specified like this. For example, the class we are extending already declares that Views fields can be used with the Style plugin.

As mentioned before, using the two methods we create a plugin option and form element to be able to specify which field should act as the tab navigation. Using the current display handler ($this->displayHandler) we can load up all the available View fields the site builder has added to it. And this new form element will be available on the Style settings form:

Drupal 8 Style Plugins

Since we are extending from the StylePluginBase class, there is nothing more we need to do. For the markup output we can rely on the demo_bootstrap_tabs theme which receives the relevant variables from the executed View. If we want, we can override any of the render methods and add more variables, change the theme, or whatever we need. We are good with the defaults, especially since we will implement a preprocessor to handle the variables that the template receives.

The Theme

It’s time to define the demo_bootstrap_tabs theme as we normally do (inside our .module file):


function demo_theme($existing, $type, $theme, $path) {
  return array(
    'demo_bootstrap_tabs' => array(
      'variables' => array('view' => NULL, 'rows' => NULL),
      'path' => drupal_get_path('module', 'demo') . '/templates',
    ),
  );
}

The Style plugin passes the $view object and the resulting $rows by default to the template. It is up to the preprocessor to do a bit of handling of these variables (if needed) before they are sent to the template:


function template_preprocess_demo_bootstrap_tabs(&$variables) {
  $view = $variables['view'];
  $rows = $variables['rows'];
  $variables['nav'] = array();

   $field = $view->style_plugin->options['tab_nav_field'];
  if (!$field || !isset($view->field[$field])) {
    template_preprocess_views_view_unformatted($variables);
    return;
  }

  $nav = array();
  foreach ($rows as $id => $row) {
    $nav[$id] = array(
      '#theme' => 'views_view_field',
      '#view' => $view,
      '#field' => $view->field[$field],
      '#row' => $row['#row'],
    );
  }

  template_preprocess_views_view_unformatted($variables);
  $variables['nav'] = $nav;
}

So what’s happening here? First, we check the Style plugin options for the field name to be used (the one that was selected when configuring the View). If one is not there, we return, but not before doing a bit of default preprocessing that the template_preprocess_views_view_unformatted function already does well. So we delegate to it. Then, we loop through the Views results and build an array of content for our tab navigation. For this, we use the default Views views_view_field theme function to render the selected field. Finally, we pass this array to the template and also run the default preprocessor of the unformatted list style.

The Template

In Drupal 8 there are no more theme functions, everything is now handled in Twig templates. So let’s see how the demo-bootstrap-tabs.html.twig file looks like in our module’s templates folder:

<div>
    
    <ul class="nav nav-tabs" role="tablist">
        {% for tab in nav %}
            {% set active = '' %}
            {% if loop.index0 == 0 %}
                {% set active = 'active' %}
            {% endif %}
            <li role="presentation" class="{{ active }}"><a href="#tab-{{ loop.index0 }}" aria-controls="profile" role="tab" data-toggle="tab">{{ tab }}</a></li>
        {% endfor %}
    </ul>

    
    <div class="tab-content">
        {% for row in rows %}
            {% set active = '' %}
            {% if loop.index0 == 0 %}
                {% set active = 'active' %}
            {% endif %}
            <div role="tabpanel" class="tab-pane {{ active }}" id="tab-{{ loop.index0 }}">{{ row.content }}</div>
        {% endfor %}
    </div>
</div>

As you can see, this is the necessary markup for the Bootstrap tabs. It won’t work, of course, without making sure the relevant Bootstrap styles and script are loaded in your theme first.

The first thing we render are the tab navigation items (from our nav variable). While looping through this array, we also make use of the loop index value in order to default the first item as active and be able to target the tab content panes below using unique IDs. For the actual value of the items, we just print the render array we created in our preprocessor and Drupal takes care of rendering that. That being said, it is probably a good idea to make sure that the field you use here is relatively short, without a link and plain markup. Titles would probably work just fine. But this is a matter of configuring the View accordingly.

Below the navigation, we print the actual view rows, using the same loop index to default the first row as the active tab pane and identify them uniquely so the navigation above can control their visibility. As for the content, we print the entire row.content variable (which is prepared inside template_preprocess_views_view_unformatted) and which contains all the fields in our View. And if we want to not include the field we used for the navigation, we can just exclude that one from display in the View configuration. It will still appear in the navigation (because we explicitly print it there) but not in the main tab pane.

Conclusion

And there we have it. A Views Style plugin to output the View results as Bootstrap tabs. All we need now is to make sure the Bootstrap assets are loaded and simply configure our View to use the new Style plugin. Do keep in mind that this is not meant for Views with lots of results and it only serves as an example to demonstrate how to create Style plugins.

If you have questions, comments, or suggestions, please leave them below!

Author: 
Original Post: 

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web