Using a counter in a Twig node template - symfony

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.

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 %}

How to change "if" condition related to a page

I would like to include tab_system.twig file in two differents files : archive.twig and blog_list.twig to have the same html in those two files.
{% include "tab_system.twig" %}
In this tab_system.twig file I have a condition to add the active class :
{% if XXX %}active{% endif %}
But this condition must be different depending on which page the user is.
For blog_list.twig :
{% if loop.first %}active{% endif %}
For archive.twig :
{% if item.term_id == term_page.term_id %}active{% endif %}
I wrote this without success :
{% set addclass = '' %}
{% if is_blog_list %}
{% set addclass = '{% if loop.first %}active{% endif %}' %}
{% elseif is_archive %}
{% set addclass = '{% if item.term_id == term_page.term_id %}active{% endif %}' %}
{% endif %}
In tab_system.twig I have a tab system with menu from a part and content to the other part. I wrote a js loop to display the corresponding content. I need to add active class on the first link and first content tab in blog_list.twig file and to add active class to the link and content tab depending on which category the user is.
<div class="tab-menu-list">
{% for item in all_posts %}
<a href="#" class="tab-menu {{ addclass }}"</a>
{% endfor %}
</div>
<div class="tab-content-list">
{% for item in all_posts %}
<div href="#" class="tab-content {{ addclass }}"</div>
{% endfor %}
</div>
is_archive and is_blog_list are variables defined elsewhere. They work
How can I create a condition ? Thank you in advance.
As I'm assuming you are looping the records, I'd say don't overcomplicate things,
single.twig
{% for i in 1..5 %}
{% include 'article.twig' with { 'active' : loop.first, } %}
{% endfor %}
template_blog.twig
{% for item in items %}
{% include 'article.twig' with { 'active' : item.term_id == term_page.term_id, } %}
{% endfor %}
article.twig
<div class="articles__list tab-content{{ active ? ' active'}}">
foo
</div>
demo - just change the main template
If you really wanted to keep that for inside the article template, which I think is not a great idea, you could still use the variables is_archive and is_blog_list inside article, e.g.
article.twig
{% for item in items %}
{% if is_archive %}
{% set active = loop.first %}
{% elseif is_blog_list %}
{% set active = item.term_id == term_page.term_id %}
{% endif %}
<div class="articles__list tab-content{{ active ? ' active'}}">
{% 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 %}

Is it possible to override default # products in a row in custom Shopify collections template?

I have created an alternate collection.liquid template for a shopify site I'm working on. My struggle is with not being able to control the number of products that appear in a row in the grid. I developed the custom template so that I wouldn't affect the number of products/row that appear on the other collections.
The code that displays the grid in my liquid template is this:
<div class="four columns section_select {% unless settings.collection_sort %}offset-by-four omega{% endunless %}">
{% for tag in collection.all_tags %}
{% if forloop.first %}
<label for="tag_filter" class="inline">
{{ 'collections.sorting.filter' | t }}: </label>
<select name="tag_filter" id="tag_filter">
<option {% unless current_tags %}selected="selected"{% endunless %} value="{% if collection.handle == "all" %}/collections/all{% else %}{{ collection.url }}{% endif %}">{{ 'collections.general.all_collection_title' | t: title: collection.title }}</option>
{% endif %}
{% unless tag contains 'meta-related-collection-' %}
<option {% if current_tags contains tag %}selected="selected"{% endif %} value="/collections/{% if collection.handle != blank %}{{ collection.handle }}{% else %}all{% endif %}/{{ tag | handleize }}">{{ tag }}</option>
{% endunless %}
{% if forloop.last %}
</select>
{% endif %}
{% endfor %}
</div>
Even when I change the class class="four columns" to something else, it is not reflected on my collection.
The problem could be in my code that assigns how many products are pulled in this collection but I can't seem to make a difference.
{% case products_per_row %}
{% when '1' %}
{% assign grid_item_width = 'medium--one-third large--one-whole' %}
{% when '2' %}
{% assign grid_item_width = 'medium-down--one-half large--one-half' %}
{% when '3' %}
{% assign grid_item_width = 'medium--one-third large--one-third' %}
{% when '4' %}
{% assign grid_item_width = 'medium-down--one-half large--one-quarter' %}
{% when '5' %}
{% assign grid_item_width = 'medium-down--one-half large--one-fifth' %}
{% endcase %}
Any help would be great!
It looks like your theme is using Timber grid.
You will find grid structure and classes to use in documentation here: https://shopify.github.io/Timber/
HTH

For loop - iterate from 1 to x

I have loop in Twig template:
{% for item in 1..0 %}
{{ item }}
{% endfor %}
(of course in real life instead of 0 there is variable value). I would expect in this example that loop won't be executed because 0 is lower than 1. But in fact it displays
1 0
Question: is there any way in this example using simple for loop in Twig (or other loop) to tell Twig I want to iterate ++ not -- or I have to add condition to check before loop if 0 is lower than 1
EDIT:
Of course I'm aware I can create array in PHP and use it in Twig template but what about this case - without creating array in PHP
EDIT2:
I want it simple use for generating star rating. Now I need to have code because I had to add extra if checking for each for loop:
{% if full_stars_number >= 1 %}
{% for item in 1..full_stars_number %}
<img src="img/full_star.png" />
{% endfor %}
{% endif %}
{% if half_stars_number >= 1 %}
{% for item in 1..half_stars_number %}
<img src="img/half_star.png" />
{% endfor %}
{% endif %}
{% if empty_stars_number >= 1 %}
{% for item in 1..empty_stars_number %}
<img src="img/empty_star.png" />
{% endfor %}
{% endif %}
Try this:
{% for item in 1..full_stars_number if full_stars_number>0 %}
<img src="img/full_star.png" />
{% endfor %}
{% for item in 1..half_stars_number if half_stars_number>0 %}
<img src="img/half_star.png" />
{% endfor %}
{% for item in 1..empty_stars_number if empty_stars_number>0 %}
<img src="img/empty_star.png" />
{% endfor %}
This is basically the same as what you already did in your EDIT2, but in a slightly more concise way.
1..x generates a collection, with inclusive values. It can be ordered ascending or descending. This is expected.
I'm not sure what you are trying to achieve, but the way you described it is not possible in twig out of the box.
You could write your own twig function, which would generate the values, or return an empty array:
{% set itemsCount = 0 %}
{% for item in my_crazy_function(itemsCount) %}
{{ item }}
{% endfor %}
Edit:
If you simply want to repeat a value number of times you could write a twig filter:
{{ '<img src="img/empty_star.png" />' | repeat(5) }}
A function implementation could just be a call to PHP's str_repeat:
class Project_Twig_Extension extends Twig_Extension
{
public function getFilters()
{
return array(
new Twig_SimpleFilter('repeat', 'str_repeat'),
);
}
// ...
}

Resources