Combine GroupBy with sort/dictsort - nunjucks

The bounty expires in 7 days. Answers to this question are eligible for a +50 reputation bounty.
CuttingWide wants to draw more attention to this question:
Getting a hint on how to go further with this
I'm trying to get some of my templates in Obsidian working together with using Zotero and for this I want to write a corresponding (adapted) template.
I'm almost happy with it but still struggling with one thing:
Annotations from Zotero are properly read by the Obsidian plugin which extracts them based on their highlighting color and even grouping works as I would like to. However, I would like to have a certain order in which they're imported.
The following is a macro I'm using for a different function and it shows the Order I would like to have the groups sorted:
{%- macro colorValueToName(color) -%}
{%- switch color -%}
{%- case "#ff6666" -%}
Questionable
{%- case "#2ea8e5" -%}
TODO / follow up
{%- case "#ffd400" -%}
Important
{%- case "#a28ae5" -%}
Interesting
{%- case "#5fb236" -%}
Good
{%- default -%}
Interesting but not relevant
{%- endswitch -%}
{%- endmacro -%}
Now, when "Good" (green) comes before "Questionable" in the annotations, the order will be accordingly. I understand, that I'll have to define most likely a dict and use dictsort somehow but I'm unsure how to do so.
The relevant code part where the annotations are fetched and group is this:
## Annotations
{% persist "annotations" %}
{% set annots = annotations | filterby("date", "dateafter", lastImportDate) -%}
{% if annots.length > 0 %}
### Imported on {{importDate | format("YYYY-MM-DD h:mm a")}}
{% for color, annots in annots | groupby("color") -%}
#### {{colorValueToName(color)}}
{% for annot in annots -%}
> [!quote{% if annot.color %}|{{annot.color}}{% endif %}] {{calloutHeader(annot.type)}}
[...removed unrelated code]
{% endfor -%}
{% endfor -%}
{% endif %}
{% endpersist %}
I'm open for thoughts and suggestions. I would think that somewhat has to be done with {% for color, annots in annots | groupby("color") -%}

Related

How to show name specific tag in shopify

I want to display the value of a specific product tag in my shopify store.
The product tag always starts with Color_ followed by the color Color_Green
I want to display only green. (I can use split or slice for that part)
But I can't figure out to only display the tag that starts with Color_ because I have multiple tags added to my products.
This is my code for now:
{%- for tag in product.tags -%} {%- assign tag_prefix = tag %} {%- if tag_prefix == "Color_" -%} {%- endif -%} {%- endfor -%}
Try this snippet
{% for tag in product.tags %}
{% assign tag_prefix = tag %}
{% if tag_prefix contains "Color_" %}
{% assign tagValue = tag_prefix | replace: "Color_", "" %}
{{ tagValue }}
{% endif %}
{% endfor %}

How can I sort a collection before rendering it in liquid on Dawn/Shopify?

1. The problem.
I want to sort sold out products at the bottom of a Shopify collection using liquid, in a customized version of the Dawn theme.
I tried filtering the product loop twice using opposing criteria, and it works to some extent. The main issue is that the pagination renders both loops per page, rather than rendering the whole collection and then paginating it.
So in a collection with 100 products, 30 sold out, and 5 pages with 24 products per page, the goal is for the 30 sold out products to take up pages 4 and 5. With my current code, it only pushes the sold out products in the first 24 spots to the bottom of that page, making the rest of the collection appear sold out when the user scrolls to the bottom.
This is what I have tried so far:
<div id="CollectionProductGrid">
{%- paginate collection.products by section.settings.products_per_page -%}
{%- if collection.products.size == 0 -%}
<div class="collection collection--empty page-width" id="main-collection-product-grid" data-id="{{ section.id }}">
<div class="loading-overlay gradient"></div>
<p class="collection-product-count light" role="status">
{%- if collection.products_count == collection.all_products_count -%}
{{ 'sections.collection_template.product_count_simple' | t: count: collection.products_count }}
{%- else -%}
{{ 'sections.collection_template.product_count' | t: product_count: collection.products_count, count: collection.all_products_count }}
{%- endif -%}
</p>
<div class="title-wrapper center">
<h2 class="title title--primary">
{{ 'sections.collection_template.empty' | t }}<br>
{{ 'sections.collection_template.use_fewer_filters_html' | t: link: collection.url, class: "underlined-link link" }}
</h2>
</div>
</div>
{%- else -%}
<div class="collection page-width">
<div class="loading-overlay gradient"></div>
<ul id="main-collection-product-grid" data-id="{{ section.id }}" class="
grid grid--1-col negative-margin product-grid
{% if collection.products_count < 4 %} grid--{{ collection.products_count }}-col-tablet{% else %}
{% if collection.products_count == 4 %} grid--4-col-desktop{% else %} grid--3-col-tablet grid--one-third-max grid--4-col-desktop grid--quarter-max{% endif %}
{% endif %}">
{% comment %}--Loops through available products first--{% endcomment %}
{%- for product in collection.products -%}
{%- if product.available -%}
<li class="grid__item">
{% render 'product-card',
product_card_product: product,
media_size: section.settings.image_ratio,
show_secondary_image: section.settings.show_secondary_image,
add_image_padding: section.settings.add_image_padding,
show_vendor: section.settings.show_vendor,
show_image_outline: section.settings.show_image_outline,
show_rating: section.settings.show_rating,
show_atc: section.settings.show_atc
%}
</li>
{%- endif -%}{%- endfor -%}
{%- for product in collection.products -%}
{%- unless product.available -%}
<li class="grid__item">
{% render 'product-card',
product_card_product: product,
media_size: section.settings.image_ratio,
show_secondary_image: section.settings.show_secondary_image,
add_image_padding: section.settings.add_image_padding,
show_vendor: section.settings.show_vendor,
show_image_outline: section.settings.show_image_outline,
show_rating: section.settings.show_rating,
show_atc: section.settings.show_atc
%}
</li>
{%- endunless -%}{%- endfor -%}
</ul>
{%- if paginate.pages > 1 -%}
{% render 'pagination', paginate: paginate, anchor: '' %}
{%- endif -%}
</div>
{%- endif -%}
{%- endpaginate -%}
</div>
The issue is the pagination, and it seems to be a difficult one to manipulate for an inexperienced coder like myself, so there are still other options I believe might work.
Either 1: Sorting
Creating a custom sorting order, if that is even possible. Since the sorting works with the pagination, this might be one of the easier ways to achieve this I believe, but I don't know how.
2: Using the filters and re-rendering the collection with javascript
This is far from ideal in terms of performance and UX, but might work as a hack since the filters seem to render the collection very quickly in real-time after filtering.
3: Filtering the loop before rendering it, and before pagination.
This is what I believe will be the best solution.
I have looked into capturing the loop in an array, but I am struggling to putting it all together correctly. This post explains how you can do this: http://www.codeshopify.com/blog_posts/building-arrays-with-liquid-in-shopify
And here is some example code I believe might be helpful:
{% capture products_list %}
{% for product in collection.products%}
{{product.title}}|{{product.url}}|{{product.description}}|{{product.featured_image.src | product_img_url: 'medium' }}
{% if forloop.last == false %}::{% endif%}
{% endfor %}
{% endcapture %}
{% assign products_array = products_list | split: '::'%}
What I need help with for the last option, is to figure out what to capture, how to concatinate it properly, and then how to loop through the new and filtered array. Or, a better solution if there is one.

Jinja2 Namespaces odd result

Im trying to set a global variable within a jinja2 template so i can set variables inside and outside of a loop. I've used this before and its worked but in this case it doesnt and i'm struggling to find out why.
This is my j2 template.
{% set found = namespace() %}
{% set vrf = namespace() %}
{% for group_item in site_master_list -%}
{% for host_item in site.list -%}
{% if group_item.ip_addr != host_item.ip_addr %}
none match {{ group_item.ip_addr }} none match {{ host_item.ip_addr }}
{% set found.tracker = false %}
does it have vrf {{ host_item.vrf }}
{% if host_item.vrf != "" %}
{% set vrf = namespace(id=host_item.vrf) %}
vrf {{ vrf }}
vrf.id {{ vrf.id }}
{% endif %}
{% endif %}
{% endfor %}
bottom vrf {{ vrf }}
{%- endfor %}
This is the output
bottom vrf <Namespace {}>none match 10.180.193.235 none match 10.112.208.11
does it have vrf management
vrf <Namespace {'id': 'management'}>
vrf.id management
bottom vrf <Namespace {}>
As you can see i'm setting host_item.vrf to the namespace vrf so i can use vrf.id outside of the loop further down the template.
Where i've got bottom vrf {{ vrf }} you can see in the output the empty namespace where it shows
bottom vrf <namespace {}>
Returning vrf.id within the loop returns management the correct value and all is good.
Anyone got any suggestions why i cant return vrf.id outside of the inner loop? I was returning vrf.id but Ansible returns the below which is correct because 'id' doesn't exist at that stage in the template.
''jinja2.utils.Namespace object'' has no attribute ''id'''
Thanks
I've fixed this by replacing
{% set vrf = namespace(id=host_item.vrf) %}
With
{% set vrf.value = host_item.vrf %}
Not sure why this worked previously but the latter works.

Jekyll: How to use for loop to generate table row within the same table inside markdown

I am trying to generate a table, based on the posts that I have. Now, the challenge here is that this is inside a markdown file, so for each row that I generate, liquid seems to generate a new table for each generated row. Is there a way to fit all rows inside one single table?
Here is my code:
|Title |Link |
|---|---|
{% for my_post in site.posts %}
{% if my_post.title %}
|{{ my_post.title }} |[Click Here]({{ my_post.url }}) |
{% endif %}
{% endfor %}
The generated output looks like this
As you can see, the outcome is actually a messed-up table header row + two separate tables. Can I really generate rows and fit them all inside one table? or am I better off switching to html code?
The liquid markup introduces line breaks in markdown.
Edit : you can now manage use whitespace control in Liquid
In Liquid, you can include a hyphen in your tag syntax {{-, -}}, {%-, and -%} to strip whitespace from the left or right side of a rendered tag.
|Title |Link |
|---|---|
{% for my_post in site.posts -%}
{% if my_post.title -%}
|{{ my_post.title }} |[Click Here]({{ my_post.url }}) |
{% endif %}
{%- endfor -%}
old answer
If you put your liquid tags on same line you'll have a valid table outputed.
| Title | Link |
|---|---|{% for my_post in site.posts %}{% if my_post.title %}
|{{ my_post.title }} |[Click Here]({{ my_post.url }}) |{% endif %}{% endfor %}

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.

Resources