Looping in twig - symfony

How can I echo 1, 2, 3, 4, .... with a twig counter? I can accomplish it with dirty code below but is there a better way?
{% set i = 0 %}
{% for brand in brands %}
{% set i = i + 1 %}
{{ i }}
{% endfor %}
I used {{ cycle(["even", "odd"], loop.index) }} but only getting even or odd.
Also checked Twig docs and range

You can use the loop.index0 or loop.index variable, as mentioned in the docs.
{% for brand in brands %}
{{ loop.index }}
{% endfor %}

Just use {{ loop.index }} or {{ loop.index0 }}if you need it to be 0 indexed

Related

Twig3: How to migrate "for item in items if item.foo == 'bar'" with loop bariable

I use the following Twig 2 code:
{% for item in items if item.foo == 'bar' %}
<span class="{% if loop.index % 2 %}special-class{% endif %}">
{{ item.value }}
</span>
{% else %}
Nothing found
{% endfor %}
In the twig docs: https://twig.symfony.com/doc/2.x/deprecated.html
Adding an if condition on a for tag is deprecated in Twig 2.10. Use a filter filter or an "if" condition inside the "for" body instead (if your condition depends on a variable updated inside the loop)
I wonder how I migrate my Twig 2 code to Twig 3. As you see I use the loop variable and else in the for loop. I know that I can use a new parameter and increase it myself... but it that really the intention? How do I rewrite this code using filter?
You have two options to solve this
Place the if-tag inside the loop
{% set i = 0 %}
{% for item in items %}
{% if item.foo == 'foo' %}
<span class="{% if i % 2 %}special-class{% endif %}">
{{ item.value }}
</span>
{% set i = i + 1 %}
{% endif %}
{% else %}
Nothing found
{% endfor %}
With this solution you can't "rely" on the internal loop variable, as the counter keeps going up whether or not the condition was met
Use the filter - filter
{% for item in items | filter(item => item.foo == 'foo') %}
<span class="{% if loop.index % 2 %}special-class{% endif %}">
{{ item.value }}
</span>
{% else %}
Nothing found
{% endfor %}
updated demo
Using the filter filter your code would look something like this (see also https://twigfiddle.com/9hiayc and https://twigfiddle.com/9hiayc/2):
{% for item in items|filter(i => i.foo == 'bar') %}
<span class="{% if loop.index % 2 %}special-class{% endif %}">
{{ item.value }}
</span>
{% else %}
Nothing found
{% endfor %}

Twig use one value in different for loop

Since I'm using a SaaS platform I don't have much space to do things differently.
I have two for loops in Twig:
{% for option in product.options %}
{{ option.title }}
{% if option.values %}
{% for value in option.values %}
{{ value.title }}
{% endfor %}
{% endif %}
{% endfor %}
{% for variant in product.variants %}
{{ variant.stock.level }}
{% endfor %}
What I try to do is to use variant.stock.level value inside the product.options for loop to show some HTML. This value always match the corresponding index value of the other for loop. I also think that's the only way to do this.
So what I mean is.....Let's say both for loops contain 3 elements.
Option1
Option2
Option3
Variant1
Variant2
Variant3
So option1 needs to have the value from variant1.
For the end result I need to know what the value of eg Variant1 is to show some HTML like so:
{% for value in option.values %}
{% check if value from corresponding variant is greater then 0 %}
<li class="on-stock">{{ value.title }}</li>
{% else %}
<li class="out-of-stock">{{ value.title }}</li>
{% endif %}
{% endfor %}
I don't know no other way to explain this :) Any help appreciated....
You are looking for attribute.
The attribute function can be used to access a "dynamic" attribute of a variable
Since the indexes are the same, you can use loop.index0
Example for your case
{% if attribute(option.variants, loop.index0) > 0 %}
// some stuff
{% endif %}
I'm not sure if I understood you correctly but you may use key from first loop to access product.variants with the same index.
{% for key, option in product.options %}
{{ option.title }}
{% if option.values %}
{% for value in option.values %}
{{ value.title }}
{% endfor %}
{% endif %}
{{ product.variants[key].stock.level }}
{% endfor %}

Using a counter in a Twig node template

Setting up a Drupal 8 custom view for Top 4 items, I want a different layout for item 1 than the remaining. I have override files for the custom view, but for this example I'm using base files to keep it simple.
In views-view.html.twig base file we have:
<div class="view-content">
{{ rows }}
</div>
In node.html.twig base file we have:
<div {{ content_attributes.addClass('content') }}>
{{ content }}
</div>
In node.html.twig I am aiming for something like:
{% if row_counter = 1 %}
output this markup / fields
{% else %}
do something boring with the other 3 items.
{% endif %}
I was able to set row_counter in the views-view.twig.html file:
{% for row in rows %}
{% set row_counter = loop.index %}
<div{{ row.attributes }}>
{{ row_counter }}
</div>
{% endfor %}
But I need to check against the value of {{ row_counter }} in the node.html.twig file....
What other properties are available in node.html.twig to check against its position in the list?
From the documentation
The loop variable
Inside of a for loop block you can access some special variables:
Variable Description
-----------------------------------------------------------------
loop.index The current iteration of the loop. (1 indexed)
loop.index0 The current iteration of the loop. (0 indexed)
loop.revindex The number of iterations from the end of the loop (1 indexed)
loop.revindex0 The number of iterations from the end of the loop (0 indexed)
loop.first True if first iteration
loop.last True if last iteration
loop.length The number of items in the sequence
loop.parent The parent context
edit: every variable know in the parent template is also known inside an include as context is passed by default. Only macro's don't know the parent's context
controller
<?php
require __DIR__ . '/../requires/propel_standalone.php';
echo $twig->render('tests/items.html', [
'items' => [
'Abc',
'def',
'ghi',
'jkl',
'mno',
'pqr',
'stu',
'vwx',
'z',
],
]);
items.twig
<!doctype>
<html>
<head><title>Test</title></head>
<body>
{% for item in items %}
{% include "tests/item.html" %}
{% endfor %}
</body>
</html>
item.twig
{% set order = 'Nothing to report' %}
{% if loop.first %}
{% set order = 'I\'m first' %}
{% endif %}
{% if loop.last %}
{% set order = 'I\'m last' %}
{% endif %}
{% if loop.index is even %}
{% set order = 'I\'m even' %}
{% endif %}
{% if loop.index is divisible by(5) %}
{% set order = 'I can be dived by 5' %}
{% endif %}
{% if loop.index is divisible by(3) %}
{% set order = 'I can be dived by 3' %}
{% endif %}
<div>
<b>{{ loop.index }}:</b>{{ order }} - {{ item }}
</div>
You could do something like this if a class is enough:
Your views template:
{% for row in rows %}
<div class="{% if loop.index == 1 %}first_element{% endif %}">
{{ row.content }}
</div>
{% endfor %}
And then just style it appropriately.

Twig - passing a parameter to a function with a dynamic variable name

I have Twig variables:
{{ totalAmountMonth0 }} ... {{ totalAmountMonth10 }}
I have a loop and I want to call this variable for example:
{{ totalAmountMonth5 }}
I want to give this variable to a function like this:
totalAmount.percentFromTotalAmount((totalAmountMonth5))
But this doesn't work:
{% for i in 0..10 %}
{{ totalAmount.percentFromTotalAmount(totalAmountMonth~i) }}
{% endfor %}
This doesn't work either:
{% for i in 0..10 %}
{{ totalAmount.percentFromTotalAmount('totalAmountMonth'~i) }}
{% endfor %}
Untested, but try this:
{% for i in 0..10 %}
{{ totalAmount.percentFromTotalAmount(attribute(_context, 'totalAmountMonth'~i)) }}
{% endfor %}
Just provide to your twig an array, which I suggest, or build it (see below example)
// use line below only if array isn't provided to twig
{% set totalAmounts = { totalAmount1, totalAmount2, ..., totalAmount10 } %} // pseudo-code; you need to declare all variables here
{% for ta in totalAmounts %}
{{ totalAmount.percentFromTotalAmount(ta) }}
{% endfor %}
Try to set that variable before and use it.
{% for i in 0..10 %}
{% set temp = 'totalAmountMonth'~i %}
{{ totalAmount.percentFromTotalAmount(temp) }}
{% endfor %}

Defining custom twig form block for errors rendering

I'm trying to define a specific new block for form field errors rendering, keeping form_errors unchanged for common errors rendering.
# Twig Configuration
twig:
debug: %kernel.debug%
strict_variables: %kernel.debug%
form:
resources:
- 'ApplicationMyBundle:Main:form/customFormTheme.html.twig'
In customFormTheme.html.twig I overwrite a few blocks copied from form_div_layout.html.twig plus I added the folloowing new one.
{% block field_errors %}{% spaceless %}
{% if errors|length > 0 %}
<ul class="errors">
{% for error in errors %}
{% if error.messageTemplate|length %}
<li class="error">{{ error.messageTemplate|trans(error.messageParameters, 'validators') }}</li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
{% endspaceless %}{% endblock %}
Then I expect to be able to use this block in my views like this :
<div>
{{ form_label(form.message, 'message.label'|trans({},'contact')|raw ) }}
{{ form_widget(form.message, {attr: {maxlength:1000, size:1000, rows:8}}) }}
{{ field_errors(form.message) }}
</div>
but I receive the following error :
The function "field_errors" does not exist. Did you mean "form_errors"
I also tried by naming my block text_errors or textarea_errors mentioned here but I haven't been luckier.
Any idea ?
Actually it works by defining the block text_errors or textarea_errors only and still use {{ form_errors(field.name) }} in your template. If a block named after the type of your field exists (according to form field types) it will be used instead of form_errors.
!! But you can't use directly {{ text_errors(field.name) }} in your twig template !!
The same way you can have a custom row for a specific type like this
{% block textarea_row %}{% spaceless %}
<div class="textarea l-field {{ (form_errors(form)?'error':'') }}">
{{ form_label(form) }}
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
{% endspaceless %}{% endblock textarea_row %}
and use it in your template as follow :
{# message has textarea field type #}
{{ form_row(form.message, {
label: 'message.label'|trans({},'contact')|raw ,
attr: {maxlength:1000, size:1000, rows:8}})
}}
You can also pass many custom parameters by using the object attr{}
{% block form_row %}
{% spaceless %}
<div class="form-field {{ (form_errors(form)?'error':'') }}">
{{ form_label(form) }}
{{ form_widget(form) }}
{{ dump(attr) }}
{% if attr.help is defined and not attr.help == '' %}<p class="form-help">{{ attr.help }}</p>{% endif %}
{{ form_errors(form) }}
</div>
{% endspaceless %}
{% endblock form_row %}
and use it like this
{{ form_row(form.message, {
label: 'message.label'|trans({},'contact')|raw ,
attr: {
maxlength:1000, size:1000, rows:8,
help: 'password.help'|trans({})|raw
}
})
}}

Resources