0

I have found a very strange behavior.

My variable related_nodes contains a list of nodes. How it is displayed by Kint:

related_nodes array(10) '#sorted' => boolTRUE '#pre_render' => array(1) '8' => array(5) '10' => array(5) '11' => array(5) '12' => array(5) '13' => array(5) '14' => array(5) '15' => array(5) '16' => array(5)

If I first display the content the variable {{ related_nodes }}, it displays this all related nodes and then them one by one in Divs (with loop index before):

{{ related_nodes }} {% for rel_node in related_nodes[2:] %} <div class="col-md-4 col-xs-6">{{ loop.index }} - {{ rel_node }}</div> {% endfor %}

But when I remove the first display of the variable: {{ related_nodes }}, it does not display related nodes one by one in Divs (it displays only the loop indexes):

        `{% for rel_node in related_nodes[2:] %}
           <div class="col-md-4 col-xs-6">{{ loop.index }} - {{ rel_node }}</div>
        {% endfor %} `

I also have 8 errors like this: " Notice: Undefined index: title in template_preprocess_node() (line 616 of C:\Users\Oksana\Documents\sites\drupal2\core\modules\node\node.module)" when the {{ related_nodes }} is removed.

When I removed the slice [2:], it did not help.

Is there any need to pre-load a varible before I can iterate in it with the twig 'for'? Or what can be a reason for such behavior?

I need to remove this {{ related_nodes }} because it displays all vertically without formating in columns.

Drupal version: 8.5.3.

  • Your related_nodes variable contains a render array. Did you notice the #pre_render key? - This most likely is a callback that is preparing your other items for being shown. (E.g. adding theme wrappers to the items and so on.) Hence, it must be called before you can render the single items. You'll best do so within a theme preprocess hook of your template. - Sorry for brevity. I'm on mobile. ;) – Mario Steinitz Aug 9 '18 at 10:08
  • @MarioSteinitz I've thought about this, but even when this #pre_render key is allowed to be displayed (When I removed the slice [2:]), it did not help. – Oksana Aug 9 '18 at 10:45
  • @Oskana see my answer. You may wish to follow the added links to gain more insights on how render arrays and template preprocessing works, and how to add the example to your solution. – Mario Steinitz Aug 9 '18 at 11:10
3

In Twig you could re-use the pre render callback for each single node, see Cycle comments on field--comment.html.twig, but since you are responsible for the content of the twig variable you can do this when preparing the variable by replacing viewMultiple() with multiple view():

foreach ($related_nodes as $node) {
  $variables['related_nodes'][] = \Drupal::entityTypeManager()
    ->getViewBuilder('node')
    ->view($node, $view_mode, $current_node->language()->getId());
}

Then in Twig you can use a normal loop:

        {% for rel_node in related_nodes %}
           <div class="col-md-4 col-xs-6">{{ loop.index }} - {{ rel_node }}</div>
        {% endfor %}
  • A nice shortcut and surely working great having nodes as children of the related_nodes. Yet, his first debug output suggests arrays as children? - Therefore the suggested mimicking of the Renderer class's pre_render callback calls. – Mario Steinitz Aug 9 '18 at 11:33
  • 1
    @MarioSteinitz, processing single entities by mimicking the pre_render callbacks is a great idea. Another way is to build single render arrays re-using #pre-render in Twig, see the link for comments. But if you have access to the code responsible for this output of viewMultiple(), because it was you who put it there, then you can just remove it :-) – 4k4 Aug 9 '18 at 12:04
1

Your related_nodes variable contains a render array. Once your entire template is rendered, the content of your variable will first be rendered and the rendered output inserted to your template.

The #pre_render key can contain a number of preprocessing functions that receive the contents of the render array just before rendering occurs and add additional information to it. This can be caching information, theme wrappers, or any other preprocessing that is required so the render engine knows how to create markup from the array information.

After you rendered the field first, all this information has been added to the render array. That is why you can print the single elements afterwards.

If you don't render your entire render array, you have to ensure that the preprocessing occurs before you loop through the single entities.

The best place to doing so is the hook_preprocess_HOOK() of your template, which you can place in your theme's *.theme file:

use Drupal\Drupal\Core\Render\Element;

/**
 * Implements hook_preprocess_HOOK() for my-template.html.twig.
 */
mytheme_preprocess_my_template(array &$variables) {
  $related_nodes = $variables['related_nodes'];

  if (isset($related_nodes['#pre_render'])) {
    foreach ($related_nodes['#pre_render'] as $callable) {
      if (is_string($callable) && strpos($callable, '::') === FALSE) {
        $callable = \Drupal::service('controller_resolver')->getControllerFromDefinition($callable);
      }
      $related_nodes = call_user_func($callable, $related_nodes);
    }
  }

  $variables['processed_nodes'] = array_intersect_key($related_nodes, array_flip(Element::children($related_nodes)));
}

You can then print the single items in your template by looping through the newly added {{ processed_nodes }} variable:

{% for rel_node in processed_nodes %}
  <div class="col-md-4 col-xs-6">{{ loop.index }} - {{ rel_node }}</div>
{% endfor %}

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.