Creating a custom Views filter in Drupal 8
In the previous article we've seen how we can interact programatically with Views in Drupal 8 in order to create a custom field in our Views results. Today, we will be looking a bit at how we can create a custom filter you can then add to the View in the UI and influence the results based on that.
Filters in Views have to do with the query being run by Views on the base table. Every filter plugin is responsible with adding various clauses in this query in an attempt to limit the results. Some (probably most) take on configuration parameters so you can specify in the UI how the filtering should be done.
If you remember from the last article, to create our field we extended the FieldPluginBase
class. Similarly, for filters, there is a FilterPluginBase
class that we can extend to create our own custom filter. Luckily though, Views also provides a bunch of plugins that extend the base one and which we can use or extend to make our lives even easier. For example, there is a BooleanOperator
class that provides a lot of the functionality needed for this type of filter. Similarly, there is an InOperator
class, a String
class, etc. You can find them all inside the views/src/Plugin/views/filter
directory of the Views core module.
In this tutorial, we will create 2 custom filters. One will be a very simple one that won't even require creating a new class. The second one will be slightly more complex and for which we will create our own plugin.
The code we write will go in the same module we started in the previous article and that can be found in this repository.
Node type filter
The first filter we will write is very simple. We want to be able to filter our node results by the machine name of the node type. By default, we can use a filter in which we select which node types to be included. Let's say, for the sake of argument, we want a more complex one, such as the one available for a regular text value like the title. The String
class will be perfect for this and will provide actually 100% of our needs.
So let's go to our hook_views_data_alter()
implementation and add a new filter:
...$data['node_field_data']['node_type_filter'] = array( 'title' => t('Enhanced node type filter'), 'filter' => array( 'title' => t('Enhanced node type filter'), 'help' => t('Provides a custom filter for nodes by their type.'), 'field' => 'type', 'id' => 'string' ),);...
Since the table that we are interested in altering the query for is the node_field_data
table, that is what we are extending with our new filter. Under the filter
key we have some basic info + the id of the plugin used to perform this task. Since our needs are very simple, we can directly use the String
plugin without us having to extend it. The most important thing here though is the field
key (under filter
). This is where we specify that our node_type_filter
field (which is obviously a non-existent table column) should be treated as being the type
column on the node_field_data
table. So, by default, the query alter happens on that column. And this way we don't have to worry about anything else, the String
plugin will take care of everything. If we didn't specify that, we would have to extend the plugin and make sure the query happens on the right column.
And that's it. You can clear your cache, create a View with nodes of multiple types and add the Enhanced node type filter
to it. In its configuration you'll have many matching options such as equals
, contains
, does not contain
etc you can use. For example, you can use contains
and specify the letters art
in order to return results whose node type machine name contain these letters.
Node title filter
The second custom filter we build will allow Views UI users to filter the node results by their title from a list of possibilities. In other words, they will have a list of checkboxes which will make it possible to include/exclude various node titles from the result set.
Like before, we need to declare our filter inside the hook_views_data_alter()
implementation:
...$data['node_field_data']['nodes_titles'] = array( 'title' => t('Node titles'), 'filter' => array( 'title' => t('Node titles'), 'help' => t('Specify a list of titles a node can have.'), 'field' => 'title', 'id' => 'd8views_node_titles' ),);...
Since we are filtering on the title column, we are extending again on the node_field_data
table but with the title
column as the real field
to be used. Additionally, this time we are creating a plugin to handle the filtering identified as d8views_node_titles
. Now it follows to create this class:
src/Plugin/views/filter/NodeTitles.php:
<?php/** * @file * Definition of Drupal\d8views\Plugin\views\filter\NodeTitles. */namespace Drupal\d8views\Plugin\views\filter;use Drupal\views\Plugin\views\display\DisplayPluginBase;use Drupal\views\Plugin\views\filter\InOperator;use Drupal\views\ViewExecutable;/** * Filters by given list of node title options. * * @ingroup views_filter_handlers * * @ViewsFilter("d8views_node_titles") */class NodeTitles extends InOperator { /** * {@inheritdoc} */ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) { parent::init($view, $display, $options); $this->valueTitle = t('Allowed node titles'); $this->definition['options callback'] = array($this, 'generateOptions'); } /** * Override the query so that no filtering takes place if the user doesn't * select any options. */ public function query() { if (!empty($this->value)) { parent::query(); } } /** * Skip validation if no options have been chosen so we can use it as a * non-filter. */ public function validate() { if (!empty($this->value)) { parent::validate(); } } /** * Helper function that generates the options. * @return array */ public function generateOptions() { // Array keys are used to compare with the table field values. return array( 'my title' => 'my title', 'another title' => 'another title', ); }}
Since we want our filter to be of a type that allows users to select from a list of options to be included in the results, we are extending from the InOperator
plugin. The class is identified with the @ViewsFilter("d8views_node_titles")
annotation (the id we specified in the hook_views_data_alter()
implementation).
Inside our plugin, we override three methods:
Inside init()
, we specify the title of the set of filter options and the callback that generates the values for options. This callback has to be a callable and in this case we opted for the generateOptions()
method on this class. The latter just returns an array of options to be presented for the users, the keys of which being used in the query alteration. Alternatively, we could have also directly created the options inside the init()
method by filling up the $this->valueOptions
property with our available titles. Using a callback is cleaner though as you can perform various logic in there responsible for delivering the necessary node titles.
The point of overriding the query()
and validate()
methods was to prevent a query and validation from happening in case the user created the filter without selecting any title. This way the filter has no effect on the results rather than returning 0 results. It's a simple preference meant to illustrate how you can override various functionality to tailor your plugins to fit your needs.
And that's it. You can add the Node titles filter and check the box next to the titles you want to allow in the results.
Conclusion
In this article we've looked at how we can create custom filters in Drupal 8 Views. We've seen what are the steps to achieve this and looked at a couple of the existing plugins that are used across the framework and which you can use as is or extend from.
The best way to learn how all these work is by studying the code in those plugin classes. You will see if they are enough for what you want to build or extending them makes sense. In the next article we are going to look at some other Views plugins, so stay tuned.
var switchTo5x = true;stLight.options({"publisher":"dr-8de6c3c4-3462-9715-caaf-ce2c161a50c"});