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

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

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!

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?

Capturing Item Index in Collection

I was wondering what is the proper way of finding the index of an item in an array is in a Liquid template, and selected related items based off of the index. Currently I'm able to calculate the value, but it seems to be a string and I can't then find other items in the array with the string. For example in a CMS:
{% for site_page in site.pages.all %}
{% if site_page.id == page.id %}
{% assign page_index = forloop.index0 %}
{% capture previous_page_index %}
{{ page_index | minus: 1 }}
{% endcapture %}
{% break %}
{% endif %}
{% endfor %}
The expected value can be found in previous_page_index (in this case 0) however, if i try to do something like site.pages.all[previous_page_index] I receive no output. If I do the same thing with a hardcoded index value: site.pages.all[0] it does yield an output. Does anyone have an idea/example on how this is supposed to be done in liquid?
Best I can figure is to use {% for item in array limit:1 offset:forloop.index0 %}. For example:
require 'liquid'
chars = %w[a b c]
names = %w[alpha bravo charlie]
puts Liquid::Template.parse(<<DONE).render( 'chars'=>chars, 'names'=>names )
{% for c in chars %}
{{c}} is
{% for n in names limit:1 offset:forloop.index0 %}{{n}}{% endfor %}
{% endfor %}
DONE
…which produces…
a is
alpha
b is
bravo
c is
charlie
Editorial aside: ouch. What an ugly tempting language. I understand its goals, but the burden it places on users is heinous.

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