1

I have a vocabulary of Districts with nested taxonomy terms like below.

taxnomy terms

I have a view of taxonomy terms in Drupal 8 which has an exposed filter of all terms with parent and child. What I want is that when I add a contextual filter of term parent the exposed filter should only show child terms matching the parent term in the contextual filter.

For example, if the URL contains Deoghar, then the exposed filter (like below filter) should only show the term Karon as option.

enter image description here

1

I've done this recently in a project of mine using MYMODULE_form_views_exposed_form_alter. The code assumes you've a view with the term ID on first position as contextual filter, e.g. domain.com/myview/123

<?php
function MYMODULE_form_views_exposed_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  $view = $form_state->getStorage('view');
  $display_id = $view['view']->current_display;

  if ($form_id == 'views_exposed_form' && $view['view']->id() == 'MY_VIEW' && $display_id == 'MY_VIEWDISPLAY') {
    if (!empty($view['view']->args) && $view['view']->args[0]) {
      //get the term ID from the contextual filter
      $contextTermId = $view['view']->args[0];
      $entriesToRemove = [];

      $currentFilterValue = $form_state->getUserInput()['FIELD_FILTER_TAXONOMY'];
      foreach ($form['FIELD_FILTER_TAXONOMY']['#options'] as $idx => $entry) {
        if ($idx === 'All') {
          //remove the old root '- Any -' option
          $entriesToRemove[] = $idx;
        }
        //the next line is a bit shaky might need rework:
        //sometimes filter entries are a stdClass, 
        //but I've also seen entries as array in other exposed forms (grouped filters?)
        elseif ($entry instanceof stdClass && is_array($entry->option)) {
          $termId = key($entry->option);
          $termName = current($entry->option);
          //the context filter term is our new root, so we change its label to '- Any -'
          if ($termId == $contextTermId) {
            $form['FIELD_FILTER_TAXONOMY']['#options'][$idx]->option = [$termId => t('- Any -')];
            if (empty($currentFilterValue) || $currentFilterValue == 'All') {
              //we've removed the default empty/All entry, reset it to our newly created, faked '- Any -' category
              $form_state->setUserInput(['FIELD_FILTER_TAXONOMY' => $contextTermId]);
            }
          }
          else {
            //this is the core part: for each filter option load the term
            //and check if it is a child of the contextual filter term
            $parents = \Drupal::entityTypeManager()
              ->getStorage('taxonomy_term')
              ->loadParents($termId);
            if (!in_array($contextTermId, array_keys($parents))) {
              $entriesToRemove[] = $idx;
            }
            else {
              //optional: we are in a sub-filter now,so we no longer need the leading '-' to indicate hierarchy
              $form['FIELD_FILTER_TAXONOMY']['#options'][$idx]->option = [$termId => trim(preg_replace('/^-/', '', $termName))];
            }
          }
        }
      }
      foreach ($entriesToRemove as $idx) {
        unset($form['FIELD_FILTER_TAXONOMY']['#options'][$idx]);
      }
      //optional: if there is only 1 entry left, hide the filter
      if (count($form['FIELD_FILTER_TAXONOMY']['#options']) == 1) {
        $form['FIELD_FILTER_TAXONOMY'] = [
          '#type' => 'hidden',
          'value' => $contextTermId
        ];
      }
    }
  }
}

A word of caution: Don't to this on huge taxonomies, my code loads every taxonomy term to decide if it needs to be removed.

|improve this answer|||||
  • Looks good. Will try and update. – Rimi B Dec 12 '19 at 11:49
0

As @Hudri said, you can use MYMODULE_form_views_exposed_form_alter() to alter the form.

Rather than check each term to see if it's parent is your contextual filter argument, another approach might be to use the TermStorage::loadTree() method to load the tree of terms below a term, e.g.

$vocabulary_id = 'tags';
// Get this from your view object's contextual argument, e.g. $view->args[0], etc
$parent_id = 11;

$child_terms = \Drupal::entityTypeManager()
  ->getStorage('taxonomy_term')
  ->loadTree($vocabulary_id, $parent_id);

// Next build an options array of the child terms.
$child_term_options = [];
// Maybe you want an Any option
$child_term_options[''] = '- Any -';
foreach ($child_terms as $term) {
  $child_term_options[$term->tid] = $term->name;       
}

// Next update your filter options with only the child terms:
$form['MY_EXPOSED_FILTER']['#options'] = $child_term_options;
|improve this answer|||||
  • 1
    This indeed looks more performant than my solution, it is better if the view filter is mapped 1:1 to the taxonomy tree. But it doesn't use the actual filter values of the view (e.g. when using grouped filters or when showing the options as indented tree). Anyways, the - Any - option key should be the ID of the term and not blank, this can avoid error messages due invalid filter values. – Hudri Dec 12 '19 at 13:29
  • I think Drupal usually uses '' for the values of - Any - items. However, I can see an argument for setting it to the tid from the contextual arg, but I don't think that's necessary. – sonfd Dec 12 '19 at 13:54
  • That's true, but the real, empty root - Any - is no longer present as input filter option, so Drupal most likely will print a warning. In my project I explicitly had to set the key for the new fake - Any - value to suppress those warnings. – Hudri Dec 12 '19 at 14:08
  • That sounds like a pretty good reason to set it to the parent tid ;) – sonfd Dec 12 '19 at 14:10
  • This actually is better approach i think. Thanks – Rimi B Dec 13 '19 at 5:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.