Jekyll forloop.last --> before last? - arrays

I made a dummy "related posts" in Jekyll 3.2.1 with the following solution:
{% for post in site.posts limit: 4 %}
{% if page.author == post.author and page.title != post.title %}
<div class="post-stream-item">
<a href="{{ post.url | prepend: site.baseurl }}" title="{{ post.title }}"><div class="post-stream-content">
<img src="{{ post.thumbnail }}" width="80" height="auto" /><span class="post-stream-item-meta"><h3>{{ post.title }}</h3><p>{{ post.author }} on {{ post.date | date: "%b %-d, %Y" }} • {% assign words = post.content | number_of_words %}
{% if words <= 160 %}
1 min
{% else %}
{{ words | plus: 159 | divided_by:160 }} mins
{% endif %} read</p></span></div></a></div>{% if forloop.last == false %}<hr>{% endif %}
{% endif %}
{% endfor %}
The for loop iterates through the posts list in the site and gives
it a limit
If the author of the current post is the same as the author of the
iterated post, but the title is not the same, then it fills out the
jinja bindings.
The problem is with the {% if forloop.last == false %}<hr>{% endif %} part, cause if there is more iterable (post) in the forloop, it will display the <hr> tag, even if it's the last element shown to the user.
Is there any attribute to refer to the penultimate element of the list or any better solution to this?

There isn't going to be a simple out-of-the-box one-line solution for this. Think about it this way: you're essentially asking for a feature that looks ahead in time and figures out whether this is the last time the if statement will evaluate to true. Jekyll is great, but it can't predict the future!
You could do this yourself by using two loops: one that loops through and counts how many <hr> elements you should show. Then another that actually prints stuff out, checking against the count you came up with to decide whether to print the <hr> element.
Or you could just use CSS to hide the last <hr> element. Google is your friend here.

Print a certain number of posts with no print condition
Solution : use loop limit
{% for post in site.posts limit: 4 %}
... output code here
{% endfor %}
You will print exactly 4 posts and forloop.last always works.
Print a certain number of posts with a print condition in the loop
Solution : use where filter, a counter and break
Now that you include a conditional printing :
you don't know which and how many posts will be printed.
if you don't print last post, you have an HR at the end of your list.
If you want to know how many posts you can print, you can use {% assign authorsPosts = site.posts | where: "author", page.author %} and authorsPosts.size.
This code will do it nicely, even if available posts number is less than your limit.
{% comment %} +++++ Max number of posts to print +++++ {% endcomment %}
{% assign limit = 4 %}
{% comment %} +++++ Select authors posts +++++{% endcomment %}
{% assign authorsPosts = site.posts | where: "author", page.author %}
{% comment %} +++++ If author's Posts number is less than limit, we change the limit +++++ {% endcomment %}
{% if limit >= authorsPosts.size %}
{% comment %} +++++ Number of "listable" posts is author's posts number less 1 (the one actually printed) +++++ {% endcomment %}
{% assign limit = authorsPosts.size | minus: 1 %}
{% endif %}
{% assign postsCounter = 0 %}
{% for post in authorsPosts %}
{% if page.author == post.author and page.title != post.title %}
{% assign postsCounter = postsCounter | plus: 1 %}
<h3>{{ post.title }}</h3>
{% comment %} +++++ Prints hr only if we are not printing the last post +++++ {% endcomment %}
{% if postsCounter < limit %}<hr>{% endif %}
{% comment %} +++++ Exit for loop if we reached the limit +++++ {% endcomment %}
{% if postsCounter == limit %}{% break %}{% endif %}
{% endif %}
{% endfor %}

Related

Using Liquid, is there a way to filter an array and ignore non matching items

Using Liquid for a store.
I want to look through an array of values and check to see if any of those values match another list of values. If they match i want to display them. if they do not match i want to ignore them.
I'm currently able to do it in reverse by looking at all the values then 'remove' the ones i don't want individually but that is a terrible way to do this.
{% for 'field' in [metafield.key.value] | split: ", " %}
{% if field == 'value 1' or field == 'value 2' or field == 'value 3'%}
<div>
field
</div>
{% else %}
{% continue %}
{% endif %}
for future weary travelers:
this works you may need to adjust where you're splitting the string:
{% assign metafield_str = product.metafields.yada.yada | split: "," %}
{% assign control_str = 'value, something, test, test2' | split: "," %}
{%- capture variable -%}
{% for c_str in control_str %}
{% for m_str in metafield_str %}
{% if c_str == m_str %}
{{m_str}}
{% endif %}
{% endfor %}
{% endfor %}
{%- endcapture -%}

Loop through products in collection and find lowest price (excluding $0 prices)

I have a Shopify site with multiple categories. Some products do not have a price (Shopify interests this as $0). All collections are displayed on the front page. I want to loop through all products in each collection and output the minimum price (excluding $0 values).
The current array I'm using is:
{% assign startingFrom = collection.products | sort: 'price' %}
{{ startingFrom[0].price_min | money_without_currency }}
Unfortunately, this captures and outputs $0 values.
What is the best way to loop through values that are higher than $0 and output the lowest price?
I have tried to exclucde zero values:
{% assign startingFrom = collection.products | sort: 'price' %}
{% if 'price' != 0.00 %}
{{ startingFrom[0].price_min | money_without_currency }}
{% endif %}
This outputs $0.00.
{% for product in collection.products %}
{% if forloop.first %}
{% assign lowest_price = product.price %}
{% endif %}
{% assign new_price = product.price %}
{% if new_price < lowest_price and new_price != 0 %}
{% assign lowest_price = new_price %}
{% endif %}
{% endfor %}
it is not tested but it should work.
You can't rely on that way unless you have fewer than the pagesize limit of products in your collection.
See the discussion and solution under Shopify: Getting highest price item from collection

Compare Id's of an array in twig

I'm currently comparing two array's and their id's. If they match it should output a count value. It is totally working but I'm thing if there is another way in doing this rather than:
{% for count in product_count %}
{% if count.term_id == child._menu_item_object_id %}
{{ count.count }}
{% endif %}
{% endfor %}
Thanks for any support! :)
Thanks to #DarkBee
{% for count in product_count if count.term_id == child._menu_item_object_id %}

How to add a new index to an existing array on Volt?

So I have an existing array, I want to run a for loop through it and recreate new arrays. I am trying to figure out how to create my own array directly on volt. Here is my code:
{% set oNomesAgendaAmigos = [], oNomesAgendaRecomendado = [], oNomesAgendaAmigosRecomendado = [] %}
{% for oNomeAgenda in oNomesAgenda %}
{% set oNomesAgendasTotal = oNomeAgenda.cliente_nome %}
{% if oNomeAgenda.ind_amigo == 1 %}
{% set oNomesAgendaAmigos = oNomeAgenda %}
{% endif %}
{% if oNomeAgenda.ind_recomendado == 1 %}
{% set oNomesAgendaRecomendado = oNomeAgenda.cliente_nome %}
{% endif %}
{% if oNomeAgenda.ind_recomendado == 1 AND oNomeAgenda.ind_amigo == 1 %}
{% set oNomesAgendaAmigosRecomendado = oNomeAgenda.cliente_nome %}
{% endif %}
{% endfor %}
Last time i have checked there were no mechanism for setting table bit by bit in Volt. The walk-around would be to use array_merge() or implement own filter/method into Volt engine.
Anyway it's a bit against MVC principles. You should set all tables you need over your PHP part of code.
To loop ever array with indexes inside loop you use that trick:
{% for index, value in numbers %}
{{ index }}: {{ value }}
{% endfor %}
I also really appreciate this part of Volt Documentation.

Twig - How to loop particular number of times

I need to be able to generate the links certain number of times (stored in int variable)Is there a way to do it out of the box with twig's for loop?
{% for i in numberOfLoops %}
{{ i }}. Some data
{% endfor %}
The above example do not work. I googled it but did not find actual solution. Any support would be very appreciated.
EDIT:
I also tried:
{% set k = 10 %}
{% for i in 0..k %}
{{ i }}
{% endfor %}
but this generates an exception:
com.lyncode.jtwig.exception.ParseException: Wrong binary operation syntax
Explanation: Input position (line 15, pos 27):
{% for i in 0..k %}
^
I found the working example:
{% set k = 10 %}
{% for i in range(1, k) %}
{{ i }}
{% endfor %}
Source: http://twig.sensiolabs.org/doc/templates.html (not very intuitive to find indeed).
I already had a loop in place to iterate over, I solved this for myself with the slice filter.
{% for link in links|slice(0, 12) %}
http://twig.sensiolabs.org/doc/tags/for.html#iterating-over-a-subset
Try this:
{% set k = 10 %}
{% for i in 0..k %}
{{ i }}
{% endfor %}
Documentation: http://twig.sensiolabs.org/doc/tags/for.html

Resources