1

I have a content type with a term reference. The terms have child terms. On the admin editing side, I want to show only top level terms until selected, then show only the child terms for that parent.

This to prevent having a ginormously long field.

Parent 1
  Child 1
  Child 2
Parent 2
  Child 3
  Child 4
Parent 3
...

When a user edits a piece of content, I want to only show the parent terms initially. Then show/hide the child terms when the parent is checked.

Parent 1
Parent 2 (checked)
  Child 3
  Child 4
Parent 3
Parent 4
...

I've used conditional fields, state API, and select (other) modules. But this is showing and hiding children on the same field. So it's a bit different.

Anyone have any advice for this scenario?

1

Off the top of my head, I would say that you can do this more cleanly with two '#type' => 'select' elements.

The first one would just contain the parent terms.

The second one would be set to

'#states' => [
  'disabled' => [
    ':input[name="parent_term"]' => ['value' => ''],
  ],
],

Then you could use Drupal Form API #states to conditionally set the value of the second select element to the appropriate child terms, depending on which parent term is selected.

The most obvious alternative approach would be some hacky JavaScript.

1
  • 1
    Yeah I get that. I’ve done that before and i guess i will again. Just seemed nice keeping the terms grouped within one taxonomy with nested relationships.
    – xpersonas
    Apr 22 at 2:56
1

Well, I went the following route. If someone knows a better/cleaner way, let me know. Otherwise this works pretty well.

My field referencing the taxonomy: field_categories .

Preprocess Form

First I'm adding an extra class via hook_preprocess_form_element. I don't love that I have to load each term, but it's only for admin pages.

/**
 * Implements hook_preprocess_form_element().
 */
function admin_helper_preprocess_form_element(&$variables) {
  if ($variables["element"]["#type"] == 'checkbox') {
    $tid = $variables["element"]["#attributes"]["value"];

    // Check if term has parents.
    $parents = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadParents($tid);

    // If parents exist, add classes.
    if (!empty($parents)) {
      $key = array_key_first($parents);
      $variables['attributes']['class'][] = 'child-term--' . $parents[$key]->id();
    }
  }
}

Javascript

Then my not-too-hacky javascript. I could probably combine some of this and clean up a bit. But again, this is just for one content type on the admin side.

(() => {
  const parents = document.querySelectorAll('[id^="edit-field-categories"] input');

  [].forEach.call(parents, el => {
    const id = el.value;
    const children = document.querySelectorAll(`.child-term--${id}`);

    if (el.checked) {
      [].forEach.call(children, el => {
        el.style.display = 'block';
      })
    } else {
      [].forEach.call(children, el => {
        el.style.display = 'none';
      })
    }
  });

  [].forEach.call(parents, el => {
    el.addEventListener('click', (e) => {
      const id = e.currentTarget.value;
      const children = document.querySelectorAll(`.child-term--${id}`);

      if (el.checked) {
        [].forEach.call(children, el => {
          el.style.display = 'block';
        })
      } else {
        [].forEach.call(children, el => {
          el.style.display = 'none';
        })
      }
    });
  });
})();
1
  • Looks good to me. Thanks for sharing your solution! Apr 22 at 17:59

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.