0

I have a slightly mind bending question. Here's my setup:

file.twig

{% set entries = craft.entries({ section: 'events').all() %}
{% set tileContent %}
    <p>{{tileEntry.title}}</p>
{% endset %}
{% import "macros.twig" as generalMacros %}
{{ generalMacros.renderTile(entries, tileContent) }}

macros.twig

{% macro renderTile(entries, tileContent) %}
  {%- for tileEntry in entries %}
    <div>
        {{tileContent}}
    </div>
  {% endfor -%}
{% endmacro %}

So my macro variable tileContent contains tileEntry.title, which refers to the tileEntry generated in the macro's for loop.

However, when I run this, the variable of tileEntry is not picked up in the macro's loop, and so nothing is output - in fact I get an error that tileEntry isn't defined... which is correct as it's defined inside the for loop inside macros.twig

I can't seem to pass a variable to a macro that contains a reference to a variable generated by that macro... oy.

Why do I want to do this?

So that we never have to edit macros.twig — all we need to do is edit file.twig and pass it content through the variable {{tileContent}} and use variables inside the macro to customize it.

Basically the contents of tileContent are being interpreted before they are being sent to the macro, whereas I'd like it to be interpreted inside the macro first.

Is what I am trying to do even possible? And if so, how would you go about it?

2 Answers 2

0

Answering my own question... So in my search I was looking for a twig way to do something similar to php's eval and came across the twig function template_from_string()

The sample code from twig's docs looks like this: {{ include(template_from_string("Hello {{ name }}")) }}

So I can create a string in my file.twig that's eval'd only when it's "included" in macros.twig.

Here is what that looks like:

file.twig

{% set entries = craft.entries({ section: 'events').all() %}
{% set tileContent = template_from_string("<p>{{tileEntry.title}}</p>") %}
{% import "macros.twig" as generalMacros %}
{{ generalMacros.renderTile(entries, tileContent) }}

macros.twig

{% macro renderTile(entries, tileContent) %}
  {%- for tileEntry in entries %}
    <div>
        {{include(tileContent)}}
    </div>
  {% endfor -%}
{% endmacro %}

This way the variable string tileContent is not actually evaluated in context until it's "included" in macros.twig when we output {{include(tileContent)}}.

2
  • Feels like you might be over-complicating things here? Have you tried using an embed? twig.symfony.com/doc/3.x/tags/embed.html Sep 19 at 18:39
  • @JamesSmith — Well hot damn!!! That is much simpler. Thank you! — I'll post another answer with a refactored solution using embed
    – Adrian
    Sep 23 at 13:13
0

Another (probably better/simpler) solution, inspired by @JamesSmith's comment, uses embed to avoid includes inside of macros, and has all the same flexibility:

file.twig

{% set entries = craft.entries({ section: 'events').all() %}
{% embed "/_partials/tiles.twig" with {'entries':entries}  %}
    {% block captionContent %}
        <h1>{{entry.title}}</h2>
        <p class='datestamp'>{{entry.eventDate | date('yy/m/d')}}</p>
        <p>{{entry.lead}}</p>
    {% endblock %}  
{% endembed %}

/_partials/tiles.twig

<div>
    {%- for entry in entries %}
        <div>
            {% block captionContent %}
                {# Default Content Overridden by 'embed'/'block' Call  #}
                <p>Hello World!</p>
            {% endblock %}
        </div>
    {% endfor -%}
</div>

This way the block is called on the loop as if it was in scope of the parent page and overrides the default content inside. But if the block is not provided then the default content inside each loop is displayed.

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.