2

I have a custom field formatter (in Drupal 8) that displays a list of taxonomy terms on a node. Some terms are not meant to be displayed, so the field formatter removes them from the items list.

Occasionally, all items are removed -- yet the HTML elements that wrap the field are still rendered. How do I hide the field completely when all items are removed?

EDIT FOR CLARITY:

I have a field with machine name field_tags that is an entity reference to multiple taxonomy terms.

To render this field, I wrote a custom formatter as a field formatter plugin in a custom module. The code is stored in the standard location in mymodule/src/Plugin/Field/FieldFormatter/TermListFormatter.php

My class extends EntityReferenceFormatterBase.

In my class, my viewElements() method strips out some items as needed and sometimes returns an empty array if no items should be rendered.

  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = [];

    // Loop through each of the terms in this field.
    foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) {
      $label = $entity->label();

      // ONLY show taxonomy terms that begin with "Test".
      if (mb_substr($label, 0, 5) === 'Test') {
        $options = [
          'query' => [
            'tags' => [$entity->id()],
          ],
        ];

        // Output terms as links to /test?tags[]={tid}
        $elements[$delta] = [
          '#type' => 'link',
          '#title' => $label,
          '#url' => Url::fromRoute('view.test.page_1'),
          '#options' => $options,
        ];

        if (!empty($items[$delta]->_attributes)) {
          $elements[$delta]['#options'] += ['attributes' => []];
          $elements[$delta]['#options']['attributes'] += $items[$delta]->_attributes;
          // Unset field item attributes since they have been included in the
          // formatter output and shouldn't be rendered in the field template.
          unset($items[$delta]->_attributes);
        }

        $elements[$delta]['#cache']['tags'] = $entity->getCacheTags();
      }
    }

    // This will be an empty array if there were no terms beginning with "Test"
    return $elements;
  }

When all the terms are stripped out, the field wrapper elements are still output as follows:

<span class="views-field views-field-field-tags">
  <span class="field-content"></span>
</span>

How do I stop these elements from appearing when there are no terms to show?

  • How did you build the custom formatter? Twig template? You can use conditions to print output only if the output isn't empty. – prkos Feb 22 at 23:36
  • I built the custom formatter as a field formatter plugin in a custom module. The viewElements() method strips out some items as needed and sometimes returns an empty array if no items should be rendered. – confusedGFCI Feb 22 at 23:56
  • 1
    You should add such information to your question, probably even with code snippets, your template, an example of the output you get when your fields are emptied, and what output you intend to have instead. This would enable us to give you a fast and to the point answer for your issue instead of having to highlight all possible places where you can influence rendered output (which I'd consider too broad for Drupal Answers). – Mario Steinitz Feb 23 at 2:17
  • I'm confused, too. Drupal hides the field wrapper for empty fields automatically. – 4k4 Feb 23 at 10:09
  • 1
    Given your edit, the wrappers added to your output result from the Views module (compare the views-field views-field-field-tags classes). Did you try locating this view and altering it by checking the "NO RESULTS BEHAVIOR/Hide if empty", and/or "STYLE SETTINGS" or "REWRITE RESULTS" to remove the wrappers entirely? – Mario Steinitz Feb 25 at 3:19
2

Drupal adds the field wrapper only if the result returned by viewElements() contains at least one renderable child.

FormatterBase::view():

  public function view(FieldItemListInterface $items, $langcode = NULL) {
    ...
    $elements = $this->viewElements($items, $langcode);

    // If there are actual renderable children, use #theme => field, otherwise,
    // let access cacheability metadata pass through for correct bubbling.
    if (Element::children($elements)) {
      $entity = $items->getEntity();
      $entity_type = $entity->getEntityTypeId();
      $field_name = $this->fieldDefinition->getName();
      $info = [
        '#theme' => 'field',
        '#title' => $this->fieldDefinition->getLabel(),
        '#label_display' => $this->label,
        '#view_mode' => $this->viewMode,
        '#language' => $items->getLangcode(),
        '#field_name' => $field_name,
        '#field_type' => $this->fieldDefinition->getType(),
        '#field_translatable' => $this->fieldDefinition->isTranslatable(),
        '#entity_type' => $entity_type,
        '#bundle' => $entity->bundle(),
        '#object' => $entity,
        '#items' => $items,
        '#formatter' => $this->getPluginId(),
        '#is_multiple' => $this->fieldDefinition->getFieldStorageDefinition()->isMultiple(),
      ];

      $elements = array_merge($info, $elements);
    }

    return $elements;
  }

This means for the question if the field formatter removes all items the field is hidden.

However, field formatter and field preprocessing handle empty fields differently. If you only remove the first field item it is possible that the condition for an empty field has different results. See How to print all values of a multi-value field, except the first?

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.