Custom Views Display Style Plugin

What do we want to achieve?

Say you have built a custom view and now want to output the given fields as well as the wrapper for the whole view in a custom DOM.

Thats where custom view display style plugins come in hand.

Creating the custom Drupal module

First of all lets start with a basic custom Drupal module:

Create the folder docroot/modules/custom/sunlime_custom_view_display

In there create a file “sunlime_custom_view_display.info.yml“:

name: Sunlime Custom Display View
type: module
description: 'Provides a views display style for listings.'
package: Views
core: '8.x'
dependencies:
  - views

With that alone you should see the module appear in the backend under “Extend”

e4054546762eb0ae42d34182bb636b77

Adding the views display style plugin code

Now lets create the rest of the files with the following file system structure:

99b45280bff3f926b0f5c71b03bbf661

The file “SunlimeCustomViewDisplay.php” contains:

<?php
/**
 * @file
 * Definition of Drupal\sunlime_custom_view_display\Plugin\views\style\SunlimeCustomViewDisplay.
 */
namespace Drupal\sunlime_custom_view_display\Plugin\views\style;
use Drupal\core\form\FormStateInterface;
use Drupal\views\Plugin\views\style\StylePluginBase;

/**
 * Style plugin to render listing.
 *
 * @ingroup views_style_plugins
 *
 * @ViewsStyle(
 *   id = "sunlime_custom_view_display",
 *   title = @Translation("Sunlime New"),
 *   help = @Translation("Render a listing of view data."),
 *   theme = "views_view_sunlime_custom_view_display",
 *   display_types = { "normal" }
 * )
 *
 */
class SunlimeCustomViewDisplay extends StylePluginBase {

  /**
   * Set default options
   */
  protected function defineOptions() {
    $options = parent::defineOptions();
    $options['height'] = array('default' => '354');
    return $options;
  }

  /**
   * {@inheritdoc}
   */
  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    parent::buildOptionsForm($form, $form_state);
    $form['height'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Height'),
      '#description' => $this->t('Default height of header.'),
      '#size' => '6',
      '#default_value' => $this->options['height'],
      '#required' => TRUE,
      '#field_suffix' => 'px',
    );
  }
}

With this PHP class you will now see the following option popup in the view formatter selection:

34174f57596a81284de6a7a51ceb6318

The buildOptionsForm(&$form, FormStateInterface $form_state) function defines which fields should be present in the “Settings” modal of the view formatter.

The defineOptions() function defines the default values for the given fields in the buildOptionsForm(&$form, FormStateInterface $form_state) function.

e78cecddb207ccae0fa17c72a5f9ee6f
717c5533368d03da4280da639d037f88

One very important part here is the theme definition in the @ViewsStyle comments of the class. This text defines how the template file has to be named.

Basically as you can see its 1:1 the same name but with the _ replaced with –

I don’t know why that has to be like that but I guess it has some Drupal historical reason.

3d4b20efe6e3d61755187912c6e06949

Outputting the view

So now lets open views-view-sunlime-custom-view-display.html.twig and add the following code:

<h1>Start of view-view-sunlime-custom-view-display.html.twig</h1>

<h2>Settings of View</h2>
{# Options from View Settings #}
{{ dump(view.style_plugin.options) }}

<h2>Rows</h2>
{# View Result #}
{{ dump(rows) }}

<h2>Logged in user</h2>
{# Logged in user #}
{{ dump(user) }}

With that code (and a place where your view is outputted in Drupal like a block or a page) you should see something like this:

dcb6f65f263934249217db89ef30cc34

Preproccessing the view

Just like you can preprocess many other data in Drupal you can of course preprocess your views data as well.

For that first of all open the sunlime_custom_view_display.module file and add the following content:

<?php
/**
 * @file
 * Sunlime Custom Views module help and theme functions.
 */
// Theme functions in a separate .inc file.
\Drupal::moduleHandler()->loadInclude('sunlime_custom_view_display', 'inc', 'sunlime_custom_view_display.theme');

/**
 * Implements hook_theme().
 */
function sunlime_custom_view_display_theme($existing, $type, $theme, $path) {
  return array(
    'sunlime_view_display_module' => array(
      'file' => 'sunlime_custom_view_display.theme.inc',
    ),
  );
}

Basically the first line dictates that the file sunlime_custom_view_display.theme.inc should also be included. Lets add the following code inside the sunlime_custom_view_display.theme.inc

<?php
/**
 * Prepares variables for view template.
 *
 * Default template: views-view-sunlime-custom-view-display.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - view: A ViewExecutable object.
 *   - rows: The raw row data.
 */
function template_preprocess_views_view_sunlime_custom_view_display(&$variables) {
  // View options set by user.
  $options = $variables['view']->style_plugin->options;
  $rows = $variables['rows'];
  echo '<h2> $options in sunlime_custom_view_display.theme.inc</h2>';
  var_dump($options);
  echo '<h2> $rows in sunlime_custom_view_display.theme.inc</h2>';
  var_dump($rows);
  // Apply required logic and add variables here. We can define variable in
  // our hook_theme and use it here.
}

With that in place you should now see the following additional output:

33f443c08a0f25cdc627b02977c7bd02

Depending on your Drupal cache settings as well as your TWIG debug settings you will probably have to do a drush cr more often or not.

I want to use the fields defined in the view backend. How do I do that?

The above example just shows you how to create a basic views formatter and requires you to iterate over the rows object in TWIG to access the nodes data.

But this “version” will not work with the defined fields in the Drupal backend.

The following template file shows a basic view formatter twig output for a grid output:

{% set wrapper_classes = "l-row" %}

<div class="{{ wrapper_classes }}">

  {% if rows|length %}
    {% for row in rows %}
      <div class="l-col {{view.style_plugin.options.grid_class}}">
        {{- row.content -}}
      </div>
    {% endfor %}

  {% elseif empty %}

    <div class="view-empty">
      {{ empty }}
    </div>

  {% endif %}

</div>

As you can see {{- row.content -}} should theoretically output the fields which are defined in the backend.

Instead you get the following error:

Exception: Object of type Drupal\views\ResultRow cannot be printed.

After some researching I found the following Bootstrap issue: https://www.drupal.org/project/views_bootstrap/issues/2871454

Basically you have to do the following modifications to your View Formatter:

class GridDisplay extends StylePluginBase {
   protected $usesOptions = true;
   protected $usesRowPlugin = true;
...

At the start of your custom PHP class you have to add the following 2 variables so you “somehow” tell Drupal to “use the row plugin” which changes “something”.

But not only that, you will also have to add the following preprocessor to your <module>.theme.inc

/**
 * Prepares variables for the view grid display template.
 *
 * Template: views-view-grid-display.html.twig
 *
 * @param array $variables
 *   An associative array containing:
 *   - view: The view object.
 *   - rows: An array of row items. Each row is an array of content.
 */
function template_preprocess_views_view_grid_display(&$variables) {
  template_preprocess_views_view_unformatted($variables);
}

The name of the function has to be adapted to your view display name, but I hope this is clear since it is basically a preprocessor.

I have no idea why this is needed and we just accidentally happened to stumble upon that but as you can see, I have no idea whats happening here. With that, it works, and thats all you will need.

If someone reads this who has a deeper understanding of this and can explain to me why this is needed I would be very happy to adjust this post and cite you.

Multilingual adjustments

Using custom view style plugins in a multilingual page need a little bit of customization to work.

If you read out the fields value hard coded via {{ row._entity.fields.title.value }} you will need to adjust your code to something like that:

{% set current_language = row.node_field_data_langcode %}
{% if row._entity.hastranslation(current_language) %}
  {% set entity = row._entity.translation(current_language) %}
{% else %}
  {# No translation available for this node #}
{% endif %}
{{ entity.fields.title.value }}

A Drupal Core Issue has been created here because in my opinion I don’t see the need for that here: https://www.drupal.org/project/drupal/issues/3111717

Source: https://www.drupal.org/docs/creating-custom-modules/building-a-views-display-style-plugin-for-drupal

Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *