Create a Custom Drupal Views Style module

What problem are we trying to solve here?

Views is a Drupal tool for organising blocks of information that repeat: it’s great for rows in a table, items in a list, and panels in a grid. But our project needed something that didn’t neatly fit any of these models, and in a sense was a bit of all three. So, rather than try to bend the defaults to our will and wrangle with the inevitable compromises, the obvious thing to do was create our own option.

I struggled to find a straightforward tutorial on this, so this is how I went about it.

What you need to know already

This is an advanced topic, aimed at Drupal 9 or 10 developers struggling to find an tutorial on a niche task. You need be very comfortable with Drupal’s UI, and Views in particular, but it’s also essential to know your way around Drupal from within a Terminal. This is not a copy-paste code solution, because the assumption is that you have your own objectives; you need to be at least an intermediate coder comfortable with the rudiments of Object Oriented PHP.

Surely there's already module for this?

Not really, no. When I came to look for direction on customising Views Styles I was actually quite surprised to find that someone hadn’t already nailed this problem. To be fair, I’m not that person either: I'm trying to solve a problem for a specific task, not a generalised one for the Drupal community. But hopefully, you’ll get something from my labour.

Building your Drupal module

Naming conventions are really important, and I chose snake_case following the directions under Naming and placing your Drupal module. Note that everything below as my_custom_views_format should be substituted for your own module name.

Following the article linked above, I discovered that manually placing my module directory under either of the suggested paths didn’t result in it being auto-detected by the list under Extend. I recommend going to Extend > Add new module and uploading your project as a Zip archive. In the end, my custom module was auto-placed directly under /web/modules/ and that was fine for my purposes. (If you have a lot of modules, that may be a bit arbitrary or untidy for you.) NB: see the note at the end about running updates on your module.

Your custom module directory tree must contain the following folders and files as a minimum. Note the spelling and cases: yes, that’s definitely /Plugin, and your Twig filename needs to be in kebab-case not snake_case.

/my_custom_views_format/
    my_custom_views_format.info.yml
    src/Plugin/views/style/my_custom_views_format.php
    templates/views-view-my-custom-views-format.html.twig

my_custom_views_format.info.yml is the only essential config file. If you’ve looked at other tutorials on this topic, note that the core: key was dropped because we are a Drupal 9 project: it crashed our site, so absolutely check the .info.yml documentation for the declarations pertinent to your project.

name: My custom Views format
type: module
description: 'Provides a custom Views style.'
package: Views
core_version_requirement: ^9
dependencies: -drupal:views

Open up my_custom_views_format.php. This is the file that creates the instance of your custom format; essentially letting Drupal know it exists. We’re really just extending the default class, and declaring our required settings.

To begin with, you can basically copy-paste the following, changing all the phrases containing “My Custom View Format” to fit your own module name. Note that the name of your file and the new class name must be in the same case, so CustomViewsFormat.php must be CustomViewsFormat.

This will result in a proof of setup: you'll be able to see your custom format as an option, and make one “dummy” setting available.

<?php
namespace Drupal\gdm_views_format\Plugin\views\style;
use Drupal\core\Form\FormStateInterface;
use Drupal\views\Plugin\views\style\StylePluginBase;

/**
 * My Custom View Format
 *
 * @ingroup views_style_plugins
 *
 * @ViewsStyle(
 *     id = "MyCustomViewsFormat",
 *     title = @Translation("My Custom Views format"),
 *     help = @Translation("My Custom Views format"),
 *     theme = "views_view_my_custom_views_format",
 *     display_types = { "normal" }
 * )
 **/

class custom_views_format extends StylePluginBase {
    /**
     * Allow Row plugins.
     * @var bool
     **/
    protected $usesRowPlugin = TRUE;

    /**
     * Allow grouping of rows.
     **/
    protected $usesGrouping = TRUE;

    /**
     * {@inheritdoc}
     **/
    public function buildOptionsForm(&$form, FormStateInterface $form_state) {
        parent::buildOptionsForm($form, $form_state);
        $form['dummy'] = array(
            '#type' => 'textfield',
            '#title' => t('My custom setting'),
            '#default_value' => 'Dummy',
            '#description' => t('Dummy setting.'),
            /**
             * This next array is simply a demo
             * of how to instantiate in-style settings.
             * You don't need to include this if you don't
             * plan to offer any UI-facing config.
             **/
            '#options' => array(
                'One' => $this->t('One'),
                'Two' => $this->t('Two'),
            )
        );
    }

    /**
     * Defines default values.
     * Again, this function is optional
     * if you don't plan on UI config.
     **/
    protected function defineOptions() {
        $options = parent::defineOptions();
        $options['one'] = array('default' => 'One');
        $options['two'] = array('default' => 'Two');
        return $options;
    }
}

Note this line:

theme = "views_view_my_custom_views_format"

which is the name of your twig template; the template file name itself must be kebab case, however. Open up your views-view-my-custom-views-format.html.twig and add something like the following:

{#
/**
 * @file
 * Default theme implementation for a custom Views format.
 **/
#}
<div class="my_custom_views_format">
{% for row in rows %}
    <div>{{ row }}</div>
{% endfor %}
<div>

This should at least output something to demonstrate that you’re at the starting block. Clearly this module isn’t finished: our custom style doesn’t really do anything yet. But at least getting to the point where you can see where to go next.

Updating the module: beware!

Our module isn't managed by Composer: it’s simply imported from a local development project. We hit a snag with updating it: being in an early design and development phase, we discovered that, if we assign our new, custom Style to a View, then run an update on the Style module itself, the whole View will get deleted! This is potentially very serious, because it means that any and all configuration done on the module is also gone.

When we solve this conundrum, we’ll add an update to this article.

Add new comment

CAPTCHA