12

When dealing with a renderable array in PHP, I can use Element::children() to access the elements that are not # properties but subordinated renderable elements (form items inside a fieldset, items inside a field widget, etc). For instance, this snippet from file.module:

<?php
if ($element['#multiple']) {
  foreach (Element::children($element) as $name) {
    // ...
  }
}
?>

How can I do the same in a Twig template? If I do {% for child in element %}, it will include also #type, #cache etc.

1

5 Answers 5

25
{% for key, child in element if key|first != '#' %}
  <div>{{ child }}</div>
{% endfor %}
1
  • 2
    Please avoid code-only answers.
    – Mołot
    Commented Sep 17, 2015 at 11:59
4

Use the Twig Tweak module, which, among other wonderful features, has a "children" filter:

{% for item in content.field_name | children(true) %}
  {# loop.length, loop.revindex, loop.revindex0, and loop.last are now available #}
{% endfor %}
0
2

I have created a Twig filter that returns back with the children as an ArrayIterator.

mymodule/mymodule.services.yml

services:
  mymodule.twig_extension:
    arguments: ['@renderer']
    class: Drupal\mymodule\TwigExtension\Children
    tags:
      - { name: twig.extension }


mymodule/src/TwigExtension/Children.php

<?php

namespace Drupal\mymodule\TwigExtension;


class Children extends \Twig_Extension
{

  /**
   * Generates a list of all Twig filters that this extension defines.
   */
  public function getFilters()
  {
    return [
      new \Twig_SimpleFilter('children', array($this, 'children')),
    ];
  }


  /**
   * Gets a unique identifier for this Twig extension.
   */
  public function getName()
  {
    return 'mymodule.twig_extension';
  }


  /**
   * Get the children of a field (FieldItemList)
   */
  public static function Children($variable)
  {
    if (!empty($variable['#items'])
      && $variable['#items']->count() > 0
    ) {
      return $variable['#items']->getIterator();
    }

    return null;
  }

}


in the Twig template:

{% for headline in entity.field_headline|children %}
  {{ headline.get('value').getValue() }}
{% endfor %}
0
2

Since Twig 2.10.0 using "if" in a "for" loop is deprecated, and you'll have to use filter.

{% for key, child in element if key|first != '#' %}
  <div>{{ child }}</div>
{% endfor %}

would become

{% for key, child in element|filter((child, key) => key|first != '#') %}
  <div>{{ child }}</div>
{% endfor %}
1
1

Here's a modification of https://drupal.stackexchange.com/a/236408/67965 that loops through the render children instead of the field #items.

The twig extension:

/**
 * Generates a list of all Twig filters that this extension defines.
 */
public function getFilters() {
  return [
    new \Twig_SimpleFilter('children', array($this, 'children')),
  ];
}

/**
 * Get the render children of a field
 */
public static function children($variable) {
  return array_filter(
    $variable, 
    function($k) { return (is_numeric($k) || (strpos($k, '#') !== 0)); },
    ARRAY_FILTER_USE_KEY
  );
}

In twig, you can then pass through rendered children directly, which helps in atomic design patterns. Define an entity template, for instance:

{% include '@molecules/grid.html.twig' with { 
   head : content.field_title,
   grid_columns: content.field_collection_items|children
} %}

where grid.html.twig is something like:

{% if head %}
<div class="slab__wrapper">
  {{ head }}
</div>
{% endif %}
<div class="grid">          
  {% for col in grid_columns %}
  <div class="grid__column">
    {{ col }}
  </div>
  {% endfor %}
</div>

This is usually more useful than having to render a field template for {{ content.field_collection_items }} because the layout of the children may be controlled in the context of the parent design element.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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