Store Twig loop for multiple reuse - for-loop

I have two arrays that I loop through, but they are nested.
Reduced to the minimum, it looks like this:
{% for item in items %}
<label>{{ item.name }}</label>
<select>
{% for attribute in attributes %}
<option>{{ attribute.name }}</option>
{% endfor %}
</select>
{% endfor %}
The problem is the size of the arrays. There are about 1,100 items and 400 attributes. As one can guess, this is slow. Very slow.
Is it possible to "store" the inner loop, and just reuse the generated/rendered block?

Move the inner loop outside of the outer loop and then store the value generated in a Twig variable. Then run the outer loop supplying the variable between the <select> tags. This way you only generate the inner loop once.
{% set options = '' %}
{% for attribute in attributes %}
{% set options = options ~ '<option>' ~ attribute.name ~ '</option>' %}
{% endfor %}
{% for item in items %}
<label>{{ item.name }}</label>
<select>
{{ options }}
</select>
{% endfor %}

Related

Making a custom collection in shopify liquid - liquid

I want to make a custom collection in collection.liquid based on some conditional scenario and for this I applied concat, append and join but the problem is that it returns ProductDropProductDropProductDropProductDrop... or LazyProductDropCollectionLazyProductDropCollection instead of products. Following is the code snippet
{% assign custom_products = '' %}
{% for product in collections["paneer-easy-indie-bowls"].products %}
{% assign custom_products = custom_products | append: product %}
{% endfor %}
instead of append I tried both join and concat but returns ProductDropProductDropProductDropProductDrop...
{% assign custom_products = custom_products | concat: product %}
then I tried the following:
{% capture custom_products %}
{% for product in collections["paneer-easy-indie-bowls"].products %}
{{ custom_products }},{{ product.handle }}
{% endfor %}
{% endcapture %}
{% assign custom_products = custom_products | split: ',' %}
{% for product in custom_products %}
{{ product}}
{% endfor %}
but this code not appending the products in right way. I want products like same as{{collection.products}}. Any suggestion ?
If I read your pseudo-code correctly, you are trying to build a collection of products from a collection of products. Which leads to the question, why? Since you already have a perfect collection, use it as is!

Cannot get index of item in array with Nunjucks 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 -%}

How do I show a tag to represent multiple products? Shopify Liquid

Hello and thanks for reading my post!
I have a collection with multiple products. On a custom collection template, I want to show the tags only for those that contain multiple products (or when more than 1 product in that collection have the same tag)
I assume it would go something like:
{% for tag in collection.all_tags %}
{% if tag.product.size >= 1 %}
has more than 1 product.
{% endif %}
{% endfor %}
I've answered similar questions here and here.
You want something like this:
{% for tag in collection.all_tags %}
{% assign products_count = 0 %}
{% for product in collection.products %}
{% if product.tags contains tag %}
{% assign products_count = products_count | plus: 1 %}
{% endif %}
{% endfor %}
{% if products_count > 1 %}
{{ tag }}
{% 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