How to chop inside a for-loop in Twig - twig

This is a refactoring question. The code works as is, I'm just not happy with it in an aesthetical sense.
I would like to know if the conditional inside the loop can be written in a shorter, more readable way or maybe can be stripped away?
{% set i = 0 %}
{% for element in list %}
{% if loop.first %}<div class="row">{% endif %} {# open first row #}
{% if i > 2 %} {# new row every 3 elements #}
{% set i = 0 %}
</div>
<div class="row">
<img src="{{ element.url }}">
{% else %}
{% set i = i+1 %}
<img src="{{ element.url }}">
{% endif %}
{% if loop.last %}</div>{% endif %}
{% endfor %}

As user DarkBee said, have a look into batch.
{% for element in list|batch(3) %}
.....
.....
{% endfor %}
Just to have an example on this page.
Batch-Docs
Regards

Related

How to render HTML to a variable

I need to render a list of HTML elements with content and put it into a variable. How can I do this efficient in twig?
e.g. I need to render the post tracking URLs from an order which can have several shippings / parcels.
{% for delivery in order.deliveries %}
{% for trackingCode in delivery.getTrackingCodes() %}
{{ trackingCode }}<br/>
{% endfor %}
{% endfor %}
Instead of printing this directly into the output I like first to put this rendered output into a variable like
{% set output = ... %}
...
{{ output }}
How can I do this in twig?
Just the concatenate the html to the output variable. Keep in mind you'll need to define to the variable outside the for-loop in order to use it outside the loop.
{% set foo = '' %}
{% for i in 1..10 %}
{% set foo = foo ~ ''~i~'' %}
{% endfor %}
{{ foo|raw }}
demo
After long search I found a better more efficient way
you can use {% set var %} with {% endset %} as a whole output block. Means the whole output will be set to the variable. This makes the life much easier and readable.
e.g.
{% set trackingText %}
{% for delivery in order.deliveries %}
{% for trackingCode in delivery.getTrackingCodes() %}
{{ trackingCode }}<br/>
{% endfor %}
{% endfor %}
{% endset %}
...
{% if trackingText|trim is not empty %}
You can track the delivery by using the following URL:<br/>
{{ trackingText }}
<br/>
{% endif %}

How to manipulate count based on sub-values in TWIG?

I was wondering how to manipulate / render the count based on sub-rules.
In the current situation specifications are counted and when there are more than 5, a link is shown to show more specifications. I wanted to show only specs that have a value like this:
{% for spec in product.specs | limit(5) %}
{% if spec.value %}
<li>
<span>{{ spec.title }}</span>
{{ spec.value }}
</li>
{% endif %}
{% endfor %}
{% if product.specs | length > 5 %}
<li class="more">{{ 'View all specifications' | t }}</li>
{% endif %}
In this case the count is done before checking the values of the sub-items, so the "Show more" link is visible and clickable, but doesn't show more specifications, because they are stripped out, because the value is empty but is counted as item.
The goal is to hide the "Show more" link when there are < 5 items WITH values.
I hope anyone could point me in the right direction :-)
Thank you very much for thinking with me!
You could either use an extra counter to count the valid elements,
{% for spec in product.specs | limit(5) %}
{% if spec.value %}
<li>
<span>{{ spec.title }}</span>
{{ spec.value }}
</li>
{% endif %}
{% endfor %}
{% set cnt = 0 %}
{% for spec in product.specs %}
{% if spec.value %}{% set cnt = cnt + 1 %}{% endif %}
{% endfor %}
{% if cnt >= 5 %}
<li class="more">{{ 'View all specifications' }}</li>
{% endif %}
Or you can use the filter filter
{% if products.specs| filter(spec => spec.value|default) | length >= 5 %}
<li class="more">{{ 'View all specifications' }}</li>
{% endif %}
Update as for your comment (and as you don't have access to filter)
You can't just limit the result before hand. So in the first part you would also need to use a counter
{% set cnt = 0 %}
{% for spec in product.specs %}
{% if cnt < 5 %}
{% if spec.value %}
<li>
<span>{{ spec.title }}</span>
{{ spec.value }}
</li>
{% set cnt = cnt + 1 %}
{% endif %}
{% endif %}
{% endfor %}

How to add "Read More" in text displayed on several lines but only a certain number of lines with twig

I am using the following function in twig to show a part of the content of the description of a news item saved in a database:
{{ new.description|striptags|truncate(300,true)|raw|nl2br }}
With this function inside a p element in the html, I get the text whose characters do not exceed 300 and then I add "Read More" with an element a:
<p >{{ new.description|striptags|truncate(200,true)|raw|nl2br }}
<a class="href_blue" href="{{ path('new', {'id': new.id}) }}">
<strong> [Read More] </strong></a>
</p>
This code works for text that comes in a paragraph with more than 300 characters, but if for example I have another one with several "p" elements that are then changed in twig to elements and I need it to only show me several lines because I have A maximum elevation of the container where it is displayed, I would not know how to do it, since it shows me all line breaks until it does not exceed 300 characters.
To clarify it a little more, I show an image of the result:
What I need is that in the case of Title2 having many line breaks, just show some and add the "Read More" before so that the height of the div is equal to the previous one (to show the example I removed the max- Height and overflow: hidden).
How could I get that?
I greet your help in advance.
You could do something like this in Twig:
{% set paragraphs = new.description|split('</p>') %}
{% set summary = '' %}
{% for i in 1..10 %}
{% set summary = summary ~ paragraphs[i] %}
{% endfor %}
{% set summary = summary ~ '[Read More]' %}
Now you can use the summary variable in your twig file to show the truncated summary.
EDIT #2 based on comments
Then try this instead:
{% set paragraphs = new.description|split('</p>') %}
{% set summary = '' %}
{% for i in 1..(paragraphs|length) %}
{% set summary = summary ~ paragraphs[i] %}
{% if summary|length > 300 %}
{% set shortsummary = summary %}
{% endif %}
{% endfor %}
{% set final_summary = shortsummary|slice(:300) ~ '[Read More]' %}
EDIT #3 Code modified with the solution to the problem
{% set paragraphs = new.description|striptags|truncate(300,true)|raw|nl2br %}
{% set paragraphs = paragraphs|split('<br />') %}
{% set summary = "" %}
{% set cont = 90 %}
{% set type = "" %}
{% if paragraphs|length == 1 %}
{% set summary = paragraphs[0] %}
{% if summary|length <= 300 %}
{% set type = "" %}
{% else %}
{% set type = "anything" %}
{% endif %}
{% else %}
{% for i in 1..(paragraphs|length) %}
{% if summary|length + cont + paragraphs[i-1]|length <= 500 %}
{% set summary = summary ~ "<br>" ~ paragraphs[i-1] %}
{% set cont = cont + 90 %}
{% else %}
{% set type = "anything" %}
{% endif %}
{% endfor %}
{% endif %}
//In the case of a description with less than 300 characters the option "Read More" is not shown
{% if type != "" %}
<p>{{ summary|striptags|truncate(300,true)|raw|nl2br }}<a class="href_blue" href="{{ path('new', {'id': new.id}) }}"> <strong> [Read More] </strong></a></p>
{% else %}
<p>{{ summary|striptags|truncate(300,true)|raw|nl2br }}<a class="href_blue" href="{{ path('new', {'id': new.id}) }}"></a></p>
{% endif %}

A solution for grouping items in loop with Timber and Twig

Often i need to do some tricky layout on dynamic elements like galleries.
Here's one example:
<ul>
<li class="slide">
<img src="img_01.jpg">
<img src="img_02.jpg">
</li>
<li class="slide">
<img src="img_03.jpg">
<img src="img_04.jpg">
</li>
<li class="slide">
<img src="img_05.jpg">
<img src="img_06.jpg">
</li>
</ul>
I've managed to do it with the following snippet. But i wanted some suggestions if possible about how to make it more flexible or more simple, like grouping by any number. Maybe using cycle() or any other method. I was getting strange results using the slice() or array[1:2] notation.
<ul>
{% for image in gallery %}
{% set current = loop.index %}
{% set next = current + 1 %}
{% if current is odd %}
<li class="slide">
{% for image in gallery %}
{% if loop.index in [current,next] %}
{% set th = TimberImage(image) %}
<img src="{{th.src}}">
{% endif %}
{% endfor %}
</li>
{% endif %}
{% endfor %}
</ul>
Any suggestions are welcomed.
Timber becomes very handy for quick in and out fixes with Timber::compile or custom themes with full routing. The purpose of the question is to create some snippet that can be reused.
Kudos to creators.
https://github.com/timber/timber
You can approach with the rest of the division with the following code (Here a working solutions):
{# number of element for every section #}
{% set section = 2%}
<ul>
{% for image in gallery %}
{% if loop.index % section == 1 %}
<li class="slide">
{% endif %}
{% set th = TimberImage(image) %}
<img src="{{th.src}}">
{% if loop.index % section == 0 or loop.last %}
</li>
{% endif %}
{% endfor %}
</ul>
You can easily reuse this code making a Twig macro using as parameter the gallery and the number of element for section (highlighted with the variable section
Here's the final result taking the suggestion of #Matteo for a macro:
https://gist.github.com/lithiumlab/5ee0454b0a77b1cc26fc0ce8ba52fd80
views/single.twig:
{% import 'utils.twig' as utils %}
{{utils.group_collection(gallery,3)}}
views/utils.twig:
{% macro group_collection(collection, groupby) %}
{% set section = groupby|default(2) %}
<ul>
{% for element in collection %}
{% if loop.index % section == 1 %}
<li class="group">
{% endif %}
{% set th = TimberImage(element) %}
<img src="{{th.src}}">
{% if loop.index % section == 0 or loop.last %}
</li>
{% endif %}
{% endfor %}
</ul>
{% endmacro %}

Use loop.index as part of template name to include with Twig?

I have an array of 3 items so in the following I'm including sub-component.twig 3 times:
{% for i in array %}
<div class="my-class">
{% include "sub-component.twig" %}
</div>
{% endfor %}
However I actually have 3 slightly different templates and I would like to load a different one for each iteration over the array:
sub-component-1.twig
sub-component-2.twig
sub-component-3.twig
When I print loop.index in the template the result is "1", "2" and "3". Can I therefore use the index to form the template name?
{% for i in array %}
<div class="my-class">
{{ loop.index }}
{% include ["sub-component-" ~ loop.index ~ ".twig"] %}
</div>
Possibly because I'm using gulp twig I had to break things out into variables for this to work.
https://github.com/zimmen/gulp-twig
{% for i in array %}
<div class="my-class">
{% set sub-component_1 = "sub-component-" %}
{% set sub-component_2 = loop.index %}
{% set sub-component_3 = ".twig" %}
{% set sub-component_full = sub-component_1 ~ sub-component_2 ~ sub-component_3 %}
{% include sub-component_full %}
</div>
{% endfor %}

Resources