twig, cannot loop hash called by variable name - twig

Why does this not work:
{% set relations = [{'cat':'friends','foo':'bar1'},{'cat':'enemies','foo':bar2},....] %}
{% set friends = [{'firstName':'John', 'lastName':'Goodman'},....] %}
{% set enemies = [{'firstName':'Ron', 'lastName':'Badguy'},....] %}
{% for relCat in relations %}
{% set list = relCat.cat %}
{% for person in list %}
{{ person.firstName }}
{% endfor %}
{% endfor %}
I admit that I 'm pretty new to twig, so I really searched and searched, but cannot find a solution for my problem (that I thought to be trivial) ....
I hope someone can help - I lost all my hair over this, thanks, Rudolph

Order of variables is important
So first: enemies and friends:
{% set friends = [{'firstName':'John', 'lastName':'Goodman'}] %}
{% set enemies = [{'firstName':'Ron', 'lastName':'Badguy'}] %}
Then set relations: again friends not 'friends', first is variable, second a string:
{% set relations = [{'cat':friends,'foo':'bar1'},{'cat':enemies,'foo':bar2}] %}
{% for relCat in relations %}
{% set list = relCat.cat %}
{% for person in list %}
{{ person.firstName }}
{% endfor %}
{% endfor %}
And this should work
See fiddle

You can also use the _context variable if you really need to keep strings:
{% set relations = [{'cat':'friends','foo':'bar1'},{'cat':'enemies','foo':bar2}] %}
{% set friends = [{'firstName':'John', 'lastName':'Goodman'}] %}
{% set enemies = [{'firstName':'Ron', 'lastName':'Badguy'}] %}
{% for relCat in relations %}
{% set list = _context[relCat.cat] %} {# <--- here #}
{% for person in list %}
{{ person.firstName }}
{% endfor %}
{% endfor %}
See fiddle

Related

Shopware 6 App - Twig Script causes memory overflow

We have written us a script that allows us in Shopware 6 badges per categories. With many products crashes sometime after 5 minutes shopware crashes with the log message that the memory at php is not enough.
Have we done anything wrong here?
We have already packed the elements in an array (the graphics) and fetch them bundled from the server.
{# #var services \Shopware\Core\Framework\Script\ServiceStubs #}
{% set page = hook.page %}
{# #var page \Shopware\Storefront\Page\Product\ProductPage #}
{% set products = [] %}
{% if hook.page.cmsPage.type === 'product_list' %}
{% foreach hook.page.cmsPage.sections as section %}
{% foreach section.blocks as sectionBlock %}
{% if sectionBlock.type !== 'product-listing' %}
{% continue %}
{% endif %}
{% foreach sectionBlock.slots as slot %}
{% if slot.type !== 'product-listing' %}
{% continue %}
{% endif %}
{% foreach slot.data.listing.entities as product %}
{% set products = products|merge([product]) %}
{% endforeach %}
{% endforeach %}
{% endforeach %}
{% endforeach %}
{% endif %}
{% set categoryIds = products|reduce((carry, v) => carry|merge(v.categoryTree), []) %}
{% if categoryIds %}
{% set categories = services.repository.search('category', {'ids': categoryIds}) %}
{% set mediaIds = [] %}
{# Loop through all products and collect media ids #}
{% foreach products as product %}
{% set badgeList = [] %}
{% foreach categories.entities.getList(product.categoryTree) as category %}
{% if category.customFields.minds_cd_show_badge %}
{% set badgeList = badgeList|merge([{
'priority': category.customFields.minds_cd_badge_priority,
'mediaId': category.customFields.minds_cd_badge
}]) %}
{% endif %}
{% endforeach %}
{% set badge = (badgeList|sort((a, b) => a.priority <=> b.priority))|last %}
{% if badge %}
{% set mediaIds = mediaIds|merge([ badge.mediaId ]) %}
{% endif %}
{% endforeach %}
{# Get all badge media ids in a performant way #}
{% set mediaCollection = services.repository.search('media', {'ids': mediaIds}) %}
{# Loop through all products again and attach the badge #}
{% foreach products as product %}
{% set badgeList = [] %}
{% foreach categories.entities.getList(product.categoryTree) as category %}
{% if category.customFields.minds_cd_show_badge %}
{% set badgeList = badgeList|merge([{
'priority': category.customFields.minds_cd_badge_priority,
'mediaId': category.customFields.minds_cd_badge
}]) %}
{% endif %}
{% endforeach %}
{% set badge = (badgeList|sort((a, b) => a.priority <=> b.priority))|last %}
{% if badge %}
{% set badgeMedia = mediaCollection.get(badge.mediaId) %}
{% do product.addArrayExtension('mcb', {
'badge': badgeMedia
}) %}
{% endif %}
{% endforeach %}
{% endif %}
I think a real performance killer here might be that you potentially fetch a lot of categories. At least that could be the case if the category tree has many levels, with many categories on each level and products are assigned to multiple categories on different branches.
If I read this correctly you're really only interested in categories that have the custom field minds_cd_show_badge set to true. I assume those would potentially far less categories than the total number of entities you're fetching right now.
You could try setting a filter in your criteria targeting your custom field so you're only fetching the categories that are relevant in your case.
{% set criteria = {
'ids': categoryIds,
'filter': [
{ 'field': 'customFields.minds_cd_show_badge', 'type': 'equals', 'value': true }
]
} %}
{% set categories = services.repository.search('category', criteria) %}

Iterating over Shopware Array and trying to get the key not working

Hey iam currently trying to get the description of the first menu navigation in Shopware 6.
For that i use the array page.header.navigation.active.breadcrumb and use its key in page.header.navigation.tree[key].description, but my key value is empty.
Thats happening due to the key beeing empty for no reason.
Heres my Code:
{% sw_extends "#Storefront/storefront/section/cms-section-sidebar.html.twig" %}
{% set topMenu = null %}
{% for key, value in page.header.navigation.active.breadcrumb %}
{% if loop.index == 2 %}
{% set topMenu = value %}
{# {% set topMenuDescription = page.header.navigation.tree[key].category.description %} #}
{% set topMenuDescription = key %}
{% endif %}
{% endfor %}
{% set currentMenu = page.header.navigation.active.breadcrumb | last %}
{# {% set currentMenu = page.header.navigation.active.name %} #}
{# {% set topMenuDescription = page.header.navigation.active.description %} #}
{# {% if ! topMenuDescription %}
{% set topMenuDescription = page.header.navigation.active.description %}
{% endif %} #}
{% block section_main_content_block %}
<div class="category-top">
<div class="category-banner">
<img src="/media/6a/fd/8b/1632946677/listing-banner.jpg">
<div class="category-banner-headlines">
{% if (currentMenu != topMenu) %}
<h3>{{ topMenu }}<h3>
<h2>{{ currentMenu }}<h2>
{% else %}
<h2 class="sameMenu">{{ currentMenu }}<h2>
{% endif %}
</div>
</div>
<div class="category-description">
<h1>{{ currentMenu }}</h1>
{{ topMenuDescription | trans | raw }}
</div>
</div>
{{ parent() }}
{% endblock %}
Also here is the structure of the key i want to get:
key-i-want-to-get
And heres the description i want to get:
description-i-want-to-get
Sidenote: The description in my example is empty, since i do the showcase in a seperate testing area, where i havent set a description
The reason topMenuDescription is empty is because the variable only exist inside the scope of the {% for %}-loop you've created. Outside this loop the variable doesn't exist.
In order to solve this issue you need to alter the scope of topMenuDescription by defining the variable outside the {% for %}-loop
{% set topMenuDescription = null %}
{% for key, value in page.header.navigation.active.breadcrumb %}
{% if loop.index == 2 %}
{% set topMenu = value %}
{# {% set topMenuDescription = page.header.navigation.tree[key].category.description %} #}
{% set topMenuDescription = key %}
{% endif %}
{% endfor %}
sidenote
You really should enable twig's debug whilst developing as your current snippet would throw a RuntimeError explaining the variable does not exist.

How to break “for loop” in Django template

My code is:
{% for key, value in section.items %}
{% for key_t, value_t in title.items %}
{% if value_t.section_id == key|add:"0" %}
<li class="nav-item-header"><div class="text-uppercase font-size-xs line-height-xs">
{{value.title}}</div> <i class="icon-menu" title="Tables"></i></li>
{% endif %}
{% endfor %}
{% endfor %}
I want to break the for loop when if the condition is true. like as
{% for key, value in section.items %}
{% for key_t, value_t in title.items %}
{% if value_t.section_id == key|add:"0" %}
<li class="nav-item-header"><div class="text-uppercase font-size-xs line-height-xs">
{{value.title}}</div> <i class="icon-menu" title="Tables"></i></li>
{{break}}
{% endif %}
{% endfor %}
{% endfor %}
How is it possible? please help me...
There is no way to break out of a for loop in Django Template. However, you can achieve this by setting a variable and adding an if statement on the top like this.
{% set isBreak = False %}
{% for number in numbers %}
{% if 99 == number %}
{% set isBreak = true %}
{% endif %}
{% if isBreak %}
{# this is a comment. Do nothing. #}
{% else %}
<div>{{number}}</div>
{% endif %}
{% endfor %}
for some additional help check out this link
https://dev.to/anuragrana/for-loops-in-django-2jdi
or check this answer on stack overflow
How to break "for loop" in Django template

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.

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