Twig extract FOR loop variables - twig

Say I have a collection of items
$collection = array(
'item1' => array(
'post' => $post,
'category' => $category,
// ...
),
'item2' => array(...)
);
And I have a template:
{% for item in collection %}
Now I can use item data
- {{ item.post.title }}
- {{ item.category.id }}
- {{ item.var1 }}
- {{ item.var2 }}
- and another 20 vars
I want to extract those vars into more global FOR context, and use them as:
{{ post.title }}
{{ category.id }}
{{ var1 }}
... etc
{% endfor %}
Is this possible?
I was thining of defining the loop as a template block and then iterating it with Twig_Template::renderBlock(). But the docs say renderBlock is for 'internal' use only :) So not sure.
EDIT:
Another idea I had:
{% for item in collection %}
{% do extract(item) %}
// extract() would work similar to extract function from php
{% endfor %}
However, it seems that context is passed to twig functions by value, so this would not work.
Lastly I could write a TokenParser and do:
{% for item in collection %}
{% extract item %}
// would probably get direct access to the context, but haven't tried it
{% endfor %}
But this is quite a bit of work.. I am just hoping that twig can already do this natively :)

You can use a macro :
http://twig.sensiolabs.org/doc/tags/macro.html
{% import _self as macro %}
{% macro render(item) %}
{{ item.post.title }}
{{ item.category.id }}
{{ item.var1 }}
{{ item.var2 }}
...
{% endmacro %}
{% for item in collection %}
{{ macro.render(item) }}
{% endfor %}

If you really want to assign variables in the global context :
{% for item in collection %}
{% for var, value in item %}
{% set _context[var] = value %}
{% endfor %}
{{ post.title }}
{{ category.id }}
{{ var1 }}
... etc
{% endfor %}

Related

Conditionnal display with twig

I'm using Twig in Views to rewrite output with condition.
{{ field_illus_lycee }}
{% if field_titre_pour_views is defined %}
{% if field_titre_pour_views is not empty %}
{{ field_titre_pour_views }}
{% endif %}
{% else %}
{{ title }}
{% endif %}
<span class="accroche-admin">{{ body }}</span>
I want to display field_titre_pour_views only if it exists and isn't empty, otherwise the regular title should be displayed. But at this point the regular title isn't displayed.Inspired by this
I don't understand which mistake I've made.
EDIT: correct code
{{ field_illus_lycee }}
{% if field_titre_pour_views is defined %}
{% if field_titre_pour_views is not empty %}
{{ field_titre_pour_views }}
{% else %}
{{ title }}
{% endif %}
{% else %}
{{ title }}
{% endif %}
<span class="accroche-admin">{{ body }}</span>
Sometimes, to ask is to find...this code do the trick:
{% if field_titre_pour_views |default %}
{{ field_titre_pour_views }}
{% else %}
{{ title }}
{% endif %}
Auto fixed :)
Hope it would help someone else.

Loop nested array in twig

Im trying to loop through this (for me) quite complex array with Twig. I want to be able to print out all elements. How would you do that? Below is the array and the code im currently trying.
This is the array: https://pastebin.com/TZSANFpW
I have tried this so far but it gives me error: "Notice: Array to string conversion in "
{% for route in routes %}
<p>
{{ route.admin }}
</p>
{% endfor %}
In order to read out a full array, you would to create some form of recursion.
You could achieve this with a macro in the line of
macros.twig
{% macro readArray(array) %}
{% import _self as macros %}
{% if not array is iterable %}
{{ array }}
{% else %}
{% for k,v in array %}
<ul>
<li>
{{ k }}: {{ macros.readArray(v) }}
</li>
</ul>
{% endfor %}
{% endif %}
{% endmacro %}
main.twig
{% import "macros.twig" as macros %}
{{ macros.readArray(results) }}
example

Why {{ loop.first }} always return when a for is inside another for?

I need to get first item of a loop in twig. The problem is that the loop is inside another loop.
{% for collection in collections %}
{% for innerItem in collection %}
{{ loop.first }}
{% endfor %}
{% endfor %}
This variable always return 1.

Passing Twig variables from array into an include

I have an array I'm iterating over to pull in different types of components into my page:
array(
'content'=> array(
'componentA'=>array(
'val'=>'1',
'title'=>'sample title'
),
'componentB'
)
)
I'm attempting to pass variables through from the array to the included template, but I'm not sure how to turn that string produced by the join into something that the include can understand as an array of variables. When I exclude the "with" from the first #components include, it prints out all the default values I've set in the iterable components like I would expect, but still gives me a white screen when I keep the with attribute in. When I display var itself, it returns this string:
(Note, I've also tried putting quotes around the {{k}} to no avail)
{ val:'1',title:'sample title' }
How can I pass the variables from my array to my component?
{% for key,item in content %}
{% if item is iterable %}
{% set var = [] %}
{% for k,v in item %}
{% set temp %}{% if loop.first %} { {% endif %}{{ k }}:'{{ v }}'{% if loop.last %} } {% endif %}{% endset %}
{% set var = var|merge([ temp ]) %}
{% endfor %}
{% set var = var|join(',') %}
{{ include ("#components/" ~ key ~ ".tmpl",var) }}
{% else %}
{{ include ("#components/" ~ item ~ ".tmpl") }}
{% endif %}
{% endfor %}
Your include statements are incorrect. You are using {{ include ... }}, which should be {% include ... %}.
The following snippet should work, if you only want to provide the data from the array (and not the loop data):
{% for key,item in content %}
{% if item is iterable %}
{% include ("#components/" ~ key ~ ".tmpl") with item %}
{% else %}
{% include ("#components/" ~ item ~ ".tmpl") %}
{% endif %}
{% endfor %}
You can then use {{ val }} and {{ title }} within your component template.
If you want to include the loop data, you can use:
{% for key,item in content %}
{% if item is iterable %}
{% include ("#components/" ~ key ~ ".tmpl") with {item: item, loop: loop} %}
{% else %}
{% include ("#components/" ~ item ~ ".tmpl") %}
{% endif %}
{% endfor %}
You can then use {{ item.val }}, {{ item.title }} and {{ loop.index }} in your component template.

Dynamic block name in 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.

Resources