Dynamic block name in TWIG - twig

I need to add multiple blocks in my template, every with different name.
{% for item from items %}
{% block item.name %}sometext{% endblock %}
{% endfor %}
But I get error. How can I do this ?
In

Dynamic block names are not possible with Twig. There has been a discussion about it over at GitHub.

You can load blocks dynamically using the block function.
{% for item in items %}
{{ block( item.name )|raw }}
{% endfor %}
Twig documentation for the block function

If I understood the question correctly, you can do this (use parent context):
parent.html.twig
{% for item from items %}
{% set currentLoopItemName = item.name %}
{% block item_loop %}sometext{% endblock %}
{% endfor %}
override.html.twig
{% extends "base.html" %}
{% block item_loop %}
{% if item.name == 'custom' %}
// do something
{% else %}
{{ parent() }}
{% endif %}
{% endblock %}

I was attempting to do the same thing and found a solution using the template_from_string function.
_items.html.twig
{% for item in items %}
{{ '{% block ' ~ item.name ~ ' %}'}}
sometext
{{ '{% endblock %}' }}
{% endfor %}
enter code here
page.html.twig
{% embed template_from_string(include('_items.html.twig')) %}
{% block someItemName %} someDifferentText {% endblock %}
{% endembed %}
What's happening is the block tags are initially being created as text. Then we use the include function to get the rendered content of _items, as a string. Finally, we convert that string to a working template (which we can embed or extend).
This works because the template_from_string function will create and compile a template at runtime, where as normally twig is compiled before hand and unchanged at runtime.

Related

Twig embed and variable scope

I'm running into some problems when nesting twig embeds, more particularly when both embeds have the same variable defined.
An example: a simple code excerpt from a form layout with a fieldset, some rows and input elements (removed some variables for clarity):
{% embed 'components/frmGroup' with {'id':'myFieldset'} %}
{% block main %}
{% embed 'components/frmRow' with {'id':'mySpecialFormRow'} %}
{% block main %}
{% include 'components/inpText' %}
...
{% endblock %}
{% endembed %}
{% embed 'components/frmRow' %}
{% block main %}
{% include 'components/inpText' %}
...
{% endblock %}
{% endembed %}
{% endblock %}
{% endembed %}
As you can see, both frmGroup (fieldset) and frmRow (row) components can take a variable id. The first frmRow embed is fine since it defines its own id, the problem arises with the second frmRow which doesn't need a specific id (and so doesn't define one). But when I look at the outputted html code, I see the formrow carries #myFieldset, the id set on the frmGroup embed. Not what I wanted to accomplish :)
I've tried toying with the "only" keyword but that gave me some very weird results. I could use different variable names (frmGroupId and frmRowId) but that feels lame (and creates clutter), I could also explicitly define and empty id on the second frmRow, but then I have to remember what variables to define on what nested embeds, which isn't pretty either.
So how do I solve this (and can it be solved in Twig)?
I tried your example and I have perfectly normal results:
{% set id = "1" %}
{# id is 1 #}
{% embed "_test.html.twig" with { id: "2"} only %}
{# id is 2 for the scope #}
{% block main %}
{% embed "_test.html.twig" with { id: "3"} %}
{# id is 3 for the scope #}
{% endembed %}
{% embed "_test.html.twig" %}
{# id value is still 2 #}
{% endembed %}
{% endblock %}
{% endembed %}
{% embed "#_test.html.twig" %}
{# id value is 1 #}
{% endembed %}
With _test.html.twig:
{% block main %}
{{ id|default('null') }}
{% endblock %}
It returns
3 2 1
The only think i can think of it for you to use temporary variable:
{% embed "#InddigoMain/_test.html.twig" with { id: "2"} %}
{% block main %}
{% embed "#InddigoMain/_test.html.twig" with { id: "3"} %}
{% endembed %}
{% set temp = id %}
{% set id = null %}
{% embed "#InddigoMain/_test.html.twig" %}
{% endembed %}
{% set id = temp %}
{% endblock %}
{% endembed %}
{% embed "#InddigoMain/_test.html.twig" %}
{% endembed %}
It returns
3 null 1
I have go throught the twig bundle and I don't think it exist.

Pass include path from variable with Twig?

I need to include child-1.twig and child-2.twig in component.twig, and include component.twig in page.twig.
In my page.twig:
{% set items = [
'{% include "child-1.twig" %}',
'{% include "child-2.twig" %}'
] %}
{% include "component.twig" with items %}
In component.twig:
<div class="component">
{% for item in items %}
{{ item }}
{% endfor %}
</div>
The complexity comes from the fact that I cant modify component.twig, only page.twig. My code above would work if {% include "child-1.twig" %} and {% include "child-2.twig" %} were rendered but instead they are printed onto the page as a string of text.
Can I do something similar to my approach but make the child include actually run?
Can I suggest you add an empty block in that file (component.twig)
{% block includes %}{% endblock %}
Then you will be able to do this:
{% embed "component.twig" with items %}
{% block includes %}
{% include "child-1.twig" %}
{% include "child-2.twig" %}
{% endblock %}
{% endembed %}

Twig variable variable

There are some Twig arrays:
feeds, where a feed gets category_name;
events, news, announces with posts.
Therefore, I can get posts for a category that way:
{% for feed in feeds %}
{% if feed.category_name == "events" %}
{% for post in events %}
{{post.title}}
{% endfor %}
{% endif %}
{% endfor %}
Can I get the same output (as above one loop returns) with category_name string set as array name?
Here feed.category_name returns events:
{% for feed in feeds %}
{% for post in feed.category_name %} {# feed.category_name == "events" #}
{{post.title}}
{% endfor %}
{% endfor %}
I think what the question author means is – access the array using a name derived from another variable. So that extra conditions are not necessary (and most answers here do propose extra conditions).
Based on my several-minute-research, Volt alone won't let you do it. However, since you can embed PHP code in Volt templates and twig files are compiled to PHP later on anyway, you could do something like:
{% for feed in feeds %}
<?php foreach (${$feed.category_name} as $post) { ?>
{{post.title}}
<?php } ?>
{% endif %}
{% endfor %}
I have already tested this – it does work. You may want to add an extra check if the array exists, to avoid warnings:
{% for feed in feeds %}
<?php
if (!empty(${$feed.category_name})) {
foreach (${$feed.category_name} as $post) {
?>
{{post.title}}
<?php } } ?>
{% endif %}
{% endfor %}
If you don't like the idea of embedding PHP in your template – don't forget that your template is going to be compiled as PHP anyway!
The global variable _context holds all variables in the current context, so you can do this:
{% for feed in feeds %}
{% for post in _context[feed.category_name]|default(null) %}
{{ post.title }}
{% endfor %}
{% endfor %}
The |default(null) is required to prevent Twig from throwing an exception if the variable is not found.
See TwigFiddle
You want the conditional to be "added" to the loop? I think you mean this in Twig Documentation:
<ul>
{% for user in users if user.active %}
<li>{{ user.username|e }}</li>
{% endfor %}
</ul>
Edit
Your main issue was with the "variable variable". You can solve this with the attribute() function and combining the different feeds into one assoc array (categories?).
So perhaps something like (untested):
{% for feed in feeds %}
{% for post in attribute(categories, feed.category_name) %}
{{post.title}}
{% endfor %}
{% endfor %}
Based on your comment :
{% set new_array = news|merge(events) %}
{% for feed in feeds if attribute(feed.category_name, ['events', 'news']) %}
{% for post in new_array %}
{{post.title}}
{% endfor %}
{% endif %}
{% endfor %}

How to reuse block in an included template using twig

I have a userDashboard.html.twig template like this:
{% extends "AcmeDemoBundle::base.html.twig" %}
{% block content %}
<h1>Name</h1>
{% endblock %}
{% include "AcmeDemoBundle::statistics.html.twig" %}
The controller call this template(userDashboard).
And a statistics.html.twig where I try to override or extend the content block:
{% extends "AcmeDemoBundle::userDashboard.html.twig" %}
{% block content %}
{{ parent() }}
Something
{% endblock %}
My problem is that I can't do this way. Can somebody recommend a solution?
There's embed which basically lets you include templates while overriding some of their blocks:
{% embed "AcmeDemoBundle::userDashboard.html.twig" %}
{% block content %}
{{ parent() }}
Something
{% endblock %}
{% endembed %}

Is it possible to turn on spaceless mode only in certain situations in twig?

I want to do something like this:
{% if compress %}{% spaceless %}{% endif %}
...
{% if compress %}{% endspaceless %}{% endif %}
I'm trying to pass ['compress' => true] to the template from PHP to turn on spaceless mode. But it causes an error; template tags need to be nested properly.
Is there any technique that would let me turn spaceless on/off from PHP?
You would have to restructure your template to do something like this instead.
{% import _self as example %}
{% macro stuff(obj) %}
output stuff with {{ obj.name }}, etc...
{% endmacro %}
{% if compress %}
{% spaceless %}
{{ example.stuff(bla) }}
{% endspaceless %}
{% else %}
{{ example.stuff(bla) }}
{% endif %}
Using macros avoids you have to duplicate the content. The import statement at the top is important, so don't forget it.
page.twig:
{% block page %}
page content
{% endblock %}
index.twig:
{% extends 'page.twig' %}
{% block page %}
{% if compress %}
{% spaceless %}
{{ parent() }}
{% endspaceless %}
{% else %}
{{ parent() }}
{% endif %}
{% endblock %}

Resources