2

Our page and region templates need to access fields from the custom blocks inside that region.

Unfortunately, {{ region.block_id.field_x }} doesn't work, because region.block_id only contains a #lazy_builder, and therefore the actual variables inside that render array aren't available until we're in the block template itself (at which point it's not possible to inject variables back into the parent template).

Basically, it looks like what we need is a theme_preprocess(&$variables) that iterates over all child elements and recursively evaluates their lazy builders / pre-render hooks, so that all nested elements are available to parent templates. Is there an easy way to do this, or are we stuck with re-implementing parts of the render API here?

2 Answers 2

3

This seems like an anti-pattern. Normally you would configure and theme separate instances of the block for the fields you want to display in each region.

If you need block fields in the page template, get the fields directly from the custom block content and put them in variables in page preprocess, see Programmatically accessing block content, or use the module Twig Tweak:

{{ drupal_field('field_example', 'block_content', 1) }}
3
  • 1
    I'm ambivalent - it does break some Drupal conventions and is brittle (eg. block must stay in the region), but it's not necessarily an anti-pattern. We can't take all logic out of the front-end, and I've found the more it is tried the more front-end devs will use horrible hacks to get around it. ;) I've used Twig Tweak, and unfortunately that completely bypasses the block layout - we still need to to show or hide blocks conditionally. The best solution here would probably if Drupal gets OO renderables instead of arrays, and lets templates access lazy-built variables on demand. Commented Jun 11, 2017 at 14:41
  • 1
    (Apparently that last thing is being tracked here: drupal.org/node/1843798) Commented Jun 11, 2017 at 14:45
  • 1
    Yes, I agree 100 %, my recommendation is in the first paragraph, what you normally do. The second paragraph is when you need to access block fields bypassing the standard block rendering.
    – 4uk4
    Commented Jun 11, 2017 at 15:16
1

Actually, it seems there's an only moderately hacky way to do this via the renderer.

As the renderer service takes the render array by reference and prepares it, all we need is a twig extension function analogous to render_var() that returns the render array instead of the rendered output.

  function prepareVar($data) {
    $this->renderer->renderRoot($data);
    return $data;
  }

The result of the Twig expression prepare_var(data) will contain basically the same keys as the variable scope available to the template normally responsible for rendering data,

(As this array will also have populated #markup keys, nothing should be rendered more than once, either.)

3
  • If you see a render array with #lazy_builder or #pre_render it is probably rendered asynchron to the page, either the rendering gets delayed and it is put as placeholder in the rendered page or it was built and rendered some time ago and since then is delivered from cache. This means that many page elements are not even built and rendered in the same request as the page.
    – 4uk4
    Commented Jun 7, 2017 at 20:41
  • The renderRoot() should prevent it from outputting placeholder markup, as all placeholders are resolved in the root rendering call. (I don't know if enabling BigPipe breaks it, but BigPipe-using projects probably have well-designed front-ends which don't need hacks like this anyway.) Commented Jun 8, 2017 at 10:44
  • My comment was about how your answer ended, about nothing should be rendered more than once. That is what you are doing by applying a second rendering.
    – 4uk4
    Commented Jun 8, 2017 at 11:36

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.