/ Drupal 8

Only show options in a Views exposed filter that belong to result set

In Drupal 7 we could use Views Selective Filters module to have an exposed filter only show options that belong to result set. The module has not been ported to Drupal 8 yet. So what do we do?

Here is our current view:


As we can see there is no node with the JavaScript tag and therefore we would like to remove that option from the exposed filter.

Ideally we would like a setting that we can configure on the filter, however I did not have the time to implement that. Instead we will make a trade off and alter in the exposed form directly.

First we need somewhere to put the code, so we'll create a custom_views module. I'll use Drupal Console to scaffold this out quickly. In custom_views.module we'll add our code, I've added additional comments to explain what we do.


 * @file
 * Contains custom_views.module.

use Drupal\Core\Database\Database;

 * Implements hook_form_FORM_ID_form_alter().
function custom_views_form_views_exposed_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state)
  // If the exposed filter does not exist on this form, there's nothing we can do here.
  if (!array_key_exists('field_tags_target_id', $form)) {

  // Options are tag entity id => title.
  $options = $form['field_tags_target_id']['#options'];

  // We are querying for tags belonging to at least one node.
  // We group by tag id so we don't get a result for each
  // node the tag is referred by.
  // We also set a condition on the bundle, as we could have
  // other bundles using same field.
  $connection = Database::getConnection();
  $sth = $connection->select('node__field_tags', 'tags');
  $sth->addField('tags', 'field_tags_target_id');
  $sth->condition('bundle', 'article');

  $data = $sth->execute();
  // Flip the result set so the array key is the tag entity id.
  $results = array_flip($data->fetchAll(\PDO::FETCH_COLUMN, 'field_tags_target_id'));

  // Intersects the arrays, giving us back an "filtered" array.
  $options = array_intersect_key($options, $results);

  // Replace the options.
  $form['field_tags_target_id']['#options'] = $options;


That's it! We could definitely improve this by breaking out the logic so we can reuse for additional field or even better, integrate this functionality better in Views (for instance by porting Views Selective Filter to D8 https://www.drupal.org/project/views_selective_filters/issues/2660844)

To quote my colleagues:

Good Enough for Now and Safe Enough to Try