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”
Adding the views display style plugin code
Now lets create the rest of the files with the following file system structure:
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:
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.
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.
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:
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:
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
Thank you so much for the tip about the row content being undefined! I also have no idea _why_ you need to jump through those hoops but following your advice I at least have it working!