Sorting nuts and bolts

Create a Custom Views Sort Plugin with Drupal 8

Having recently grokked custom views sorting in Drupal 7, I took a leap to investigate how to create this functionality in Drupal 8. The documentation out there is scarce at best. As a starting point, I decided to replicate Dave Vasilevsky’s task where he makes a custom views sort plugin for ordering upcoming and past events in D7. The requirement was to show future events in chronological order (i.e. show future events nearest the current date ahead of later future events) before showing past events in reverse chronological order (i.e. most recent past events first followed by earlier past events).

Screenshot from Vasilevsky's post

Create content type and view:

First order of business is to create an Event content type with a date field and a new view of events using fields in the display format. Here is a gist with the configuration yaml files for importing an Event content type with a body and field_event_date field and an Events view.

New events view

Looking at the results returned from this view as is, all the events are shown in haphazard order:

New events view

Now let’s create a custom views sort plugin. Since we need to alter the query for sorting event results by an event date field in a special way, a new sort handler needs to be added to the view by extending the field table for field_event_date. Using hook_views_data_alter() allows us to add this custom sort handler.

Declare the sort in hook_views_data_alter():

/**
 * Implements hook_views_data_alter().
 */
function mymodule_views_data_alter(array &$data) {
  $data['node__field_event_date']['event'] = array(
    'title' => t('Custom event sort'),
    'group' => t('Content'),
    'help' => t('Sort events by past/future, then distance from now.'),
    'sort' => array(
      'field' => 'field_event_date_value',
      'id' => 'event',
    ),
  );
}

In this particular case, the date field that was added to the Event content type has a machine name of field_event_date. This generated a new table in the database called node__field_event_date that holds the actual value of the datetime field of an Event node. This datetime field is a column called field_event_date_value - this is the field on which we need to perform our custom sorting. Since we added the field_event_date as a field to the view, it will be joined to the base node_field_data table that gets queried for matching Event nodes.

In hook_views_data_alter(), we’re altering the query for the node__field_event_date table by extending it with a new event array that adds some basic information like title, group, and help which we’ll see in the views UI when we go to select the custom sort. The new event array more importantly adds a sort key with the field_event_date_value as the field we want to sort on plus the id of the plugin (in this case a new sort plugin class named ‘event’) we will use to add the custom sorting functionality.

Create the plugin to handle the new sorting:

<?php

namespace Drupal\mymodule\Plugin\views\sort;

use Drupal\views\Plugin\views\sort\Date;

/**
 * Basic sort handler for Events.
 *
 * @ViewsSort("event")
 */
class Event extends Date {

  /**
   * Called to add the sort to a query.
   */
  public function query() {
    $this->ensureMyTable();

    $date_alias = "UNIX_TIMESTAMP($this->tableAlias.$this->realField)";

    // Is this event in the past?
    $this->query->addOrderBy(NULL,
      "UNIX_TIMESTAMP() > $date_alias",
      $this->options['order'],
      "in_past"
    );

    // How far in the past/future is this event?
    $this->query->addOrderBy(NULL,
      "ABS($date_alias - UNIX_TIMESTAMP())",
      $this->options['order'],
      "distance_from_now"
    );
  }
}

This sort handler basically extends the Date sort plugin which can be found in core (/core/modules/views/src/Plugin/views/sort/Date.php). All that we’re overriding is the query() function to add our custom sorting, effectively running two sorts (future and past) on the results based on the current date.

Since we need to sort differently for future and past events, we convert the datetime formatted value of field_event_date_value into a unix timestamp and run comparisons against the current date.

We can then alter the query by adding custom ORDER BY clauses to the SQL statement. When we go to add the new sort, the new query will look like:

Custom query

Let’s see all this in action through the views UI. Back in our view, click on Add next to "SORT CRITERIA". Our new custom sort should be visible in the list:

New custom sort

In the next part of the ajax form, since we didn’t override any of the Date form options, we’ll see the order and granularity options. Just leave all the defaults as is and click "Apply".

Set default options

Now we should see our new custom sort in the UI:

Custom sort in the events view

Finally we’ll see our events sorted with future events in chronological order and past events in reverse chronology:

Custom sorted events

The theory behind how to accomplish this task in Drupal 8 is not that much different from Drupal 7. In both cases, we need to add the custom sort handler in a hook_views_data_alter() and override the query. The difference in D8 is everything uses the plugin system so the syntax for extending the base Date sort class varies from extending the default sort handler in D7.

And there you have it - a custom views sort plugin in D8!

Related Articles