Cannot get index of item in array with Nunjucks for loop - for-loop

I'm having some trouble getting indexes of a items in an array from a Nunjucks {% for %} loop.
The array I am targeting is simple and looks like this
pages[1,2,3]
And this is the Nunjucks loop
{% for i,p in data.tickets.pages %}
{{ i }} : {{ p }}
{% endfor %}
The problem is
{{ p }} outputs 1,2,3 but {{ i }} doesn't output anything.
If anyone can tell me how to fix this, I'd much appreciate it.

To get the index in a for loop use loop.index (loop.index starts with a 1)
To get the standard behavior (start with a 0) use loop.index0
data.tickets.pages = [1, 2, 3];
Template code (loop.index)
{% for page in data.tickets.pages %}
{{loop.index}}: {{ page }}
{% endfor %}
Output
1:1
2:2
3:3
Template code (loop.index0)
{% for page in data.tickets.pages %}
{{loop.index0}}: {{ page }}
{% endfor %}
Output
0:1
1:2
2:3
See Also the nunjucks docs
loop.index: the current iteration of the loop (1 indexed)
loop.index0: the current iteration of the loop (0 indexed)
loop.revindex: number of iterations until the end (1 indexed)
loop.revindex0: number of iterations until the end (0 based)
loop.first: boolean indicating the first iteration
loop.last: boolean indicating the last iteration
loop.length: total number of items

Tipically nunjucks wait single iterator for array.
When you use multi-iterator and pass array, nunjucks split each array element by iterator set.
{% set pages = [[10, 11], [12, 13]] %}
{% for a, b in pages %}
{{a}},{{b}}
{% endfor %}
---
10:11
12:13
You can use range, convert array to object (element order can be lost) or use loop.index0'/loop.index
var nunjucks = require('nunjucks');
var env = nunjucks.configure();
// range
var res = nunjucks.renderString(`
{% for i in range(0, items.length) %}
{% set item = items[i] %}
{{i}}: {{item}}
{% endfor %}`,
{items: [10, 12]}
);
console.log(res);
// array to object
res = nunjucks.renderString(`
{% for i, item in items %}
{{i}}: {{item}}
{% endfor %}`,
{items: Object.assign({}, [10, 12])}
);
console.log(res);
// loop.index0
res = nunjucks.renderString(`
{% for item in items %}
{{loop.index0}}: {{item}}
{% endfor %}`,
{items: [10, 12]}
);
console.log(res);

For anyone coming here by ways of Google or whatever. I was able to use the method with range loop described above directly in my template, and even nesting loops.
Here's part of my code directly in a nunjucks template:
<ul class="index">
{% for x in range(0, dbReport.sections.length) %}
{% set section = dbReport.sections[x] %}
<li>
<strong>{{ x + 1 }}</strong> {{ section.sectionTitle }}
<ul>
<li>
<strong>{{ x + 1 }}.1</strong> Section components:
<ul>
{% for y in range(0, section.subSections.length) %}
{% set subSection = section.subSections[y] %}
<li>
<strong>{{ x + 1 }}.1.{{ y + 1 }}</strong> {{subSection.subSectionTitle}}
</li>
{% endfor %}
</ul>
</li>
</ul>
</li>
{% endfor %}
</ul>

with array.entries()
{%- for pageIndex, page in collections.pages.entries() -%}
<div class="page" id="page-{{ pageIndex + 1 }}">
{{ page.templateContent | safe }}
</div>
{%- endfor -%}

Related

How create loop in twig template?

I want made loop like of this on twig template:
for($i=1;$i<100;$i++) {
echo $i;
}
I solved this problem as:
{% if k > 0 %}
{% for i in 0..k - 1 %}
<div></div>
{% endfor %}
{% endif %}
if k = 0 -> no loop
if k = 1 -> 1 loop
if k = 100 -> 100 lopp
May be is other solve?
You can use range to reproduce loop output similar to the first php example
{% for i in range(1, 100-1) %}
{{ i }}
{% endfor %}
You can add the if inside the loop
{% for i in 0..k if k > 0 %}
{{ i }}
{% endfor %}
demo
edit: This doesn't work anymore in twig 3.X. You have to place the if inside the codeblock

Calculate needed spaces in Jinja2 / Ansible [duplicate]

This question already has an answer here:
Pushing items to a var while looping over another var in Ansible Jinja2 template
(1 answer)
Closed 4 years ago.
I'm trying to calculate the needed space based on the longest word in a dictionary.
It seems though that the variable num doesn't transfer it's value to the second inner loop.
I'm basically trying to caculate the amount of spaces to align the columns correctly.
{% for module in modules %}
module "{{ module.name }}" {
source = "{{ module.source }}"
{% set num = 1 %}
{% for n in module.vars.keys() %}
{% if num < n|length %}
{% set num = n|length %}
{% endif %}
{{ num }}: {{ n }}
{% endfor %}
{% for m in module.vars %}
{{ num }}
{{ m }} {{ '= "' + module.vars[m]|indent(width=num) }}"
{% endfor %}
}
You're right, you can't get variables out of loops this way. See "Scoping behavior" in the docs.
One option is to use what they suggest and create a namespace:
{% set ns = namespace(num=0) %}
{% for n in module.vars.keys() %}
{% if ns.num < n|length %}
{% set ns.num = n|length %}
{% endif %}
{{ ns.num }}: {{ n }}
{% endfor %}
In your case, there is an easier and cleaner solution though: you can calculate the maximum width in an expression. Use map() to get a list of lengths, and use max filter to get the biggest one:
{% set indent_width = module.vars.keys() | map("length") | max %}
{% for m in module.vars %}
{{ m }} {{ '= "' + module.vars[m]|indent(width=indent_width) }}"
{% endfor %}

Is there a way to sort lists in Jekyll by two fields?

Is there a way to sort lists by two fields with Jekyll/Liquid? For example, sorting first by year and then title. I have tried:
{% assign list = site.data.papers.papers | sort: 'year' | sort: 'title' %}
but that left it sorted only based on the last field, in this case the title. There was some discussion on this, but it seems to have been frozen without being merged: https://github.com/jekyll/jekyll/issues/1802
Thanks!
group_by to the rescue :
{% assign years = site.data.papers.papers | group_by: "year" %}
{% assign yearsSorted = years | sort: "name" %}
<ul>
{% for y in yearsSorted %}
<li>{{ y.name }}
<ul>
{% assign yearTitlesSorted = y.items | sort: "title" %}
{% for t in yearTitlesSorted %}
<li>{{ t.title }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
If you start with a single array of hashes and you want to end up with the same array in the same structure (i.e. not grouped, just multi-sorted), use:
{% assign grouped = array | group_by: 'field1' %}
{% for item in grouped %}
{% assign sorted = item['items'] | sort: 'field2' %}
{% if forloop.first %}
{% assign array = sorted %}
{% else %}
{% assign array = array | concat: sorted %}
{% endif %}
{% endfor %}

Shopify: how to get value of forloop.index outside of the loop?

When I attempt to assign the forloop index (or anything else) to a variable in a for-loop and then use it outside (after) the loop, the assigned value is lost. The code below is one of about 20 different approaches I have tried. None of them have worked. I just need to know if x contains y (so the variable can either be boolean or an integer or anything).
{% assign has_y = 0 %}
{% for x in collection %}
{% if x contains y %}
<span style="display: none">{{ has_y | plus: 1 }}</span>
{% endif %}
{% endfor %}
{% if has_y < 1 %}
THIS DOESN'T WORK AS EXPECTED
{% endif %}
I'm confused about Shopify's scoping rules...
The problem is you are outputting {{ has_y | plus: 1 }}, but not assigning anything to has_y inside the for loop.
Try this:
{% assign has_y = 0 %}
{% for x in collections %}
{% if x contains y %}
{% assign has_y = has_y | plus: 1 %}
<span style="display: none">{{ has_y }}</span>
{% endif %}
{% endfor %}
{% if has_y < 1 %}
...
{% endif %}
Or, if you want to use boolean values instead:
{% assign has_y = false %}
{% for x in collections %}
{% if x contains y %}
{% assign has_y = true %}
<span style="display: none">{{ has_y }}</span>
{% endif %}
{% endfor %}
{% if has_y == false %}
...
{% endif %}
Also, you may want to check your for loop {% for x in collection %}. collection is an object. Maybe you meant to iterate over collection.products or collections instead?

select previous item in twig for loop

I use twig and have some data in array. I use for loop to access all data like this:
{% for item in data %}
Value : {{ item }}
{% endfor %}
Is it possible to access previous item in loop? For example: when I'm on n item, I want to have access to n-1 item.
There's no built-in way to do that, but here's a workaround:
{% set previous = false %}
{% for item in data %}
Value : {{ item }}
{% if previous %}
{# use it #}
{% endif %}
{% set previous = item %}
{% endfor %}
The if is neccessary for the first iteration.
In addition to #Maerlyn 's example, here is code to provide for next_item (the one after current one)
{# this template assumes that you use 'items' array
and each element is called 'item' and you will get
'previous_item' and 'next_item' variables, which may be NULL if not availble #}
{% set previous_item = null %}
{%if items|length > 1 %}
{% set next_item = items[1] %}
{%else%}
{% set next_item = null %}
{%endif%}
{%for item in items %}
Item: {{ item }}
{% if previous_item is not null %}
Use previous item here: {{ previous_item }}
{%endif%}
{%if next_item is not null %}
Use next item here: {{ next_item }}
{%endif%}
{# ------ code to udpate next_item and previous_item elements #}
{%set previous_item = item %}
{%if loop.revindex <= 2 %}
{% set next_item = null %}
{%else%}
{% set next_item = items[loop.index0+2] %}
{%endif%}
{%endfor%}
My solution:
{% for item in items %}
<p>item itself: {{ item }}</p>
{% if loop.length > 1 %}
{% if loop.first == false %}
{% set previous_item = items[loop.index0 - 1] %}
<p>previous item: {{ previous_item }}</p>
{% endif %}
{% if loop.last == false %}
{% set next_item = items[loop.index0 + 1] %}
<p>next item: {{ next_item }}</p>
{% endif %}
{% else %}
<p>There is only one item.</p>
{% endif %}
{% endfor %}
I had to make endless image gallery where before first item goes last one and after last item goes first one. It could be done this way:
{% for item in items %}
<p>item itself: {{ item }}</p>
{% if loop.length > 1 %}
{% if loop.first %}
{% set previous_item = items[loop.length - 1] %}
{% else %}
{% set previous_item = items[loop.index0 - 1] %}
{% endif %}
{% if loop.last %}
{% set next_item = items[0] %}
{% else %}
{% set next_item = items[loop.index0 + 1] %}
{% endif %}
<p>previous item: {{ previous_item }}</p>
<p>next item: {{ next_item }}</p>
{% else %}
<p>There is only one item.</p>
{% endif %}
{% endfor %}

Resources