Liquid templates: even/odd items in for loop - jekyll

If I have a for loop in Liquid (using Jekyll), how can I target even (or odd) items only? I have tried:
{% for item in site.posts %}
{% if forloop.index % 2 == 1 %}
but that doesn't seem to work. I have also tried:
(forloop.index % 2) == 1
and:
forloop.index - (forloop.index / 2 * 2) == 1

I think you'll want to use the cycle tag for this. For example:
{% for post in site.categories.articles %}
<article class="{% cycle 'odd', 'even' %}"></article>
{% endfor %}
If you want different HTML markup for each cycle:
{% for item in site.posts %}
{% capture thecycle %}{% cycle 'odd', 'even' %}{% endcapture %}
{% if thecycle == 'odd' %}
<div>echo something</div>
{% endif %}
{% endfor %}
You can find more information on it at Liquid for Designers, although the example there isn't particularly helpful. This Shopify support thread should also help.

In contrast to what the Shopify support thread in Ales Lande's answer says, there is a modulo function in Liquid - in form of the modulo filter.
With it, you can do this:
{% for item in site.posts %}
{% assign mod = forloop.index | modulo: 2 %}
{% if mod == 0 %}
<!-- even -->
{% else %}
<!-- odd -->
{% endif %}
{% endfor %}

Related

Liquid/Jekyll: How to check for no posts when one has two or more conditions?

I am having trouble understanding how I might show a "no posts exist" message for a particular conditional statement with two variables.
In this example, let's say I have a collection of "animals" - on a particular page, I'd like a section that displays "primates that are herbivores":
{% for animal in site.animal %}
{% if animal.species == "primate" and animal.type == "herbivore" %}
{{ animal.content }}
{% endif %}
{% endfor %}
What I'd like to do is something like this (pseudocode):
{% if POSTS_EXIST_FOR_THIS_COMBO: (animal.species == "primate" and animal.type == "herbivore") %}
{% for animal in site.animal %}
{% if animal.species == "primate" and animal.type == "herbivore" %}
{{ animal.content }}
{% endif %}
{% endfor %}
{% else %}
There are no posts for this category.
{% endif %}
Note: This differs from examples like this, because I have two parameters to check. Can someone offer a suggestion about the syntax?
I think you can do the following where you at first filter all by species=primate from site.animal and then filter by type=herbivore from that pool and then check if the result exists.
{% assign animals = site.animal | where:"species","primate" | where:"type","herbivore" %}
{% if animals %}
{% for animal in animals %}
{{ animal.content }}
{% endfor %}
{% endif %}
Hope this helps.

jekyll: combine limit and where and reverse

Using Jekyll i'd like to:
iterate through all the pages
where the page.path is not the current path
where the page.categories contains "featured"
reverse them(most recent first)
limit 3
i'm having problems when getting all those filters together
{% assign posts = site.posts | reverse %}
{% for post in posts limit:3 %}
{% if post.categories contains 'featured' and post.path != page.path %}
{% include card.html post=post %}
{% endif %}
{% endfor %}
right now the limit is not working properly because the inner if will prevent a few items from being rendered.
Assign 0 to a counter variable before entering your loop. Don't set a limit on the loop, but instead set another condition on your counter being below your limit, and increment the counter using Liquid's plus filter every time you meet your criteria and output the card.
{% assign posts = site.posts | reverse %}
{% assign counter = 0 %}
{% for post in posts %}
{% if counter < 3 and post.categories contains 'featured' and post.path != page.path %}
{% include card.html post=post %}
{% assign counter = counter | plus: 1 %}
{% endif %}
{% endfor %}
A more complex example
I'm basing all of this on the looping I use myself. My condition is a little more complex: I check for any shared tag with the current page. I've included this as a further example below.
{% assign counter = 0 %}
{% for post in site.posts %}
{% if counter < 4 %}
{% if post.url != page.url %}
{% assign isValid = false %}
{% for page_tag in page.tags %}
{% for post_tag in post.tags %}
{% if post_tag == page_tag %}
{% assign isValid = true %}
{% endif %}
{% endfor %}
{% endfor %}
{% if isValid %}
{% include article_card.html %}
{% assign counter = counter | plus: 1 %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
Why where fails
Although Liquid has a where filter, it can only do exact comparisons, so you have to reinvent the wheel like this in order to achieve the where-like scenario for more complex conditions. This does make for a lot of looping through site.posts, but Jekyll being a preprocessor means the penalty of using a somewhat inefficient improvised where-type looping is only a compile-time concern, not a runtime one. Even so, to mitigate this cost as much as possible, I opt for having the counter condition be the first that Jekyll calculates.

How to delete the odd attributes while rendering form

I rewrite some widgets in bootstrap_3_layout.html.twig and I don't want the form fields to have some attributes while they are rendered.
I've found out the sequence of some widgets
button_widget → button_row → form_widget → widget_attributes
And I made a little change
{% block widget_attributes %}
{% spaceless %}
{# bla-bla-bla #}
{% for attrname, attrvalue in attr %}
{% if attrname in ['placeholder', 'title'] %}
{{ attrname }}="{{ attrvalue|trans({}, translation_domain) }}"
{% elseif attrname not in ['first','last','data-help'] %}
{{ attrname }}="{{ attrvalue }}"
{% endif %}
{% endfor %}
{% endspaceless %}
{% endblock widget_attributes %}
But it doesn't work for buttons.
I'm not sure what you are achieving but you can intercept the odd index of a twig loop as follow:
{% for attrname, attrvalue in attr %}
{% if loop.index is odd %}
odd
{% else %}
even
{% endif %}
{% endfor %}
More info on loop variable and odd test function.
Hope this help
It's strange, but the form layout in twig_bridge has some widgets to work with attributes.
1. for inputs `widget_attributes`
2. for buttons `button_attributes`
3. for another element `widget_container_attributes`
For each widget, there are next blocks in twig-bridge:
{name_of_widget}_widget - main block to render an element
{name_of_widget}_label
{name_of_widget}_row
And for attributes, there are
widget_attributes
widget_container_attributes
button_attributes
attributes
I just used incorrect one :)
Thank all for your patience.

Looping through all list of posts not in some category in liquid

I want to loop through posts on a site except ones with the category unlisted. I'm able to do this by nesting an if statement inside the for loop, but this breaks down when I want to also specify a limit – the loop will run for 5 times only regardless of whether the post passes the check.
{% for post in site.posts limit: 5 %}
{% unless post.categories contains 'unlisted' %}
<!-- display post -->
{% endunless %}
{% endfor %}
I need to pass an already filtered list to the for loop, but I'm unable to do this mainly because I can't find a way to combine the where filter with contains and negation:
{% for post in site.posts | WHERE CATEGORIES NOT CONTAINS 'UNLISTED' | limit: 5 %}
<!-- display post -->
{% endfor %}
You can use a counter :
<ul>
{% assign postCounter = 0 %}
{% assign maxPost = 5 %}
{% for post in site.posts %}
{% unless post.categories contains 'unlisted' %}
<li>{{ post.title }}</li>
{% assign postCounter = postCounter | plus: 1 %}
{% if postCounter >= maxPost %}
{% break %}
{% endif %}
{% endunless %}
{% endfor %}
</ul>

How to set up related collection items rather than related posts in Jekyll?

I've successfully implemented related posts in my site by using site.related_posts with the following Liquid templating:
The code bellow returns 4 posts related by tags on every post page, so far so good, but what's the best approach to achieve something like this for Jekyll collections?
{% assign hasSimilar = '' %}
{% for post in site.related_posts %}
{% assign postHasSimilar = false %}
{% for tag in post.tags %}
{% for thisTag in page.tags %}
{% if postHasSimilar == false and hasSimilar.size < 4 and post != page and tag == thisTag %}
{% if hasSimilar.size == 0 %}
{% endif %}
<a href="{{ site.baseurl }}{{ post.url }}" class="entry">
<div class="entry-media">
<img src="{{ post.thumbnail | prepend: site.baseurl }}" alt="">
<p class="post-cat">{% for category in post.categories.last %}{{ category }}{% endfor %}</p>
</div>
<h6 class="entry-title">{{ post.title | truncatewords: 70, '...' }}</h6>
<p class="post-date">{{ post.date | date: "%d/%m/%Y" }}</p>{% if post.series %}(Series: {{ post.series }}){% endif %}
</a>
{% capture hasSimilar %}{{ hasSimilar }}*{% endcapture %}
{% assign postHasSimilar = true %}{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
{% if hasSimilar.size > 0 %}
{% endif %}
As per Jekyll's documentation, when you create a collection it becomes available via the site Liquid variable, much like site.pages and site.posts, so in order to create a related collection items for my portfolio collection I replicated the Liquid logic above and assigned site.portfolio as posts, see:
{% assign posts = site.portfolio | sort: "tags" %}
So I set tags: [tag1, tag2, tag3] on every "project.md" document's front-matter within my _portfolio collectionas, as I'd normally do with posts and the liquid logic above returns the related portfolio collection items.
Anyway, although I'm not sure if this is the right path to achieve such functionality, it works as intended. I couldn't find any references/ usage for site.related_posts when it come to collections.
I'd really appreciate any thoughts regarding this issue. Thanks in advance.
site.related_posts are the ten most recent posts.
So, your code is only looking for related in ten posts not all posts.
The following code will looks for tags related items for site.posts or any site.collection
{% assign pageTagsNumber = page.tags | size %}
{% assign related = "" | split: "/" %}<!-- create empty array -->
{% assign relatedCount = 0 %}
{% assign maxRelated = 4 %}<!-- maximum number of related items -->
{% assign minTagMatch = 4 %}<!-- minimum number of tag match to be a related -->
{% if minTagMatch > pageTagsNumber %}
{% assign minTagMatch = pageTagsNumber %}
{% endif %}
{% assign matchedComplete = false %}<!-- flag -->
<!--
numberOfTag to match to be in the related list
Will try to match all page tags, then page tags size - 1, -2, until reaching minTagMatch
-->
{% for numberOfTag in (minTagMatch...pageTagsNumber) reversed %}
<!-- Looping over site.posts or any site.mycollection -->
<!-- here you can do {% for item in site.mycollection %} -->
{% for item in site.posts %}
<!-- !!! ITEM SPECIFIC -->
<!-- for a collection change item.title == page.title if needed-->
{% if related contains item or item.title == page.title %}
<!--
Don't scan an item that is already in related
Don't scan an item if we are on his page
-->
{% continue %}<!-- go to next item -->
{% endif %}
{% assign itemTagsNumber = item.tags | size %}
{% if itemTagsNumber < numberOfTag %}
<!-- Not enough tags {{ itemTagsNumber }} -->
{% continue %}<!-- go to next item -->
{% endif %}
{% assign matchingTags = 0 %}<!-- tag match counter -->
{% for tag in page.tags %}
<!-- Comparing page tag "{{ tag }}" to {{ item.tags | join: ", " }} -->
{% if item.tags contains tag %}
<!-- one matching tag increment matchingTags counter-->
{% assign matchingTags = matchingTags | plus: 1 %}
<!-- We have a match and {{ matchingTags }} matching tags total -->
{% if matchingTags >= numberOfTag %}
<!-- we have enough matching tag, this is a related item -->
{% capture html %}
<li><a href="{{ site.baseurl }}{{ item.url }}">
{{ item.title }}
{% assign pageTagsNumber = item.tags | size %}
- has {{ pageTagsNumber }} tags
- match on {{ matchingTags }} tags
</a></li>
{% endcapture %}
{% assign related = related | push: html %}
{% assign relatedCount = related | size %}
{% if relatedCount >= maxRelated %}
<!-- if we have all our related set the matchedComplete flag to true -->
{% assign matchedComplete = true %}
{% endif %}
{% break %}
{% endif %}
{% endif %}
{% endfor %}
{% if matchedComplete == true %}
<!-- matchedComplete breaking item loop! -->
{% break %}
{% endif %}
{% endfor %}
{% if matchedComplete == true %}
<!-- matchedComplete breaking numberOfTag loop! -->
{% break %}
{% endif %}
{% endfor %}
{% if relatedCount > 0 %}
<!-- print related items -->
<ul>
{% for item in related %}
{{ item }}
{% endfor %}
</ul>
{% endif %}