Loop trough collections of products - html

I'm trying to loop through all the collections that a set of products is part of. This is my code:
<div class="container model-collection">
<h1>{{ collection.title }}</h1>
{% paginate collection.products by 12 %}
<div class="grid collection-products-container">
<ul>
{% for product in collection.products %}
{% for collection in product.collections %}
<li>{{ collection.title }}</li>
{% endfor %}
{% endfor %}
</ul>
</div>
{% if paginate.pages > 1 %}
{% include 'pagination' %}
{% endif %}
{% endpaginate %}
</div>
This works fine, however if two products are part of the same collection it lists that collection twice. So I need to limit the loop that it only shows each collection once.
I've tried to do it like this:
<div class="container model-collection">
<h1>{{ collection.title }}</h1>
{% assign model = collection.title %}
<div class="grid collection-products-container">
<ul>
{% for product in collection.products %}
{% assign seen_collections = "" %}
{% for collection in product.collections %}
{% unless seen_collections contains collection %}
{% assign seen_collections = seen_collections | append: "," | append: collection %}
<li>{{ collection.title }}</li>
{% endunless %}
{% endfor %}
{% endfor %}
</ul>
</div>
</div>
But this only returns one of the collections twice and none of the others. Any ideas how to do this?

You can get an aggregate list of nested properties by using the map filter in your Liquid code, and the map filter lets you drill into nested objects quite efficiently.
So to get an array of all the unique collection handles used by all the products in your collection, we can quickly get the info we want as:
{% assign collection_handles = collection.products | map: 'collections' | map: 'handle' | uniq %}
This creates an array of all the collection handles of all the products inside the collection, then reduces them to just the unique ones (using the uniq filter). Note: uniq needs to work with a number, string, or other simple field - which is why we needed an array of collection handles, not an array of collection objects.
So now you can do your loop as:
{% for handle in collection_handles %}
{% assign collection = collections[handle] %}
<!-- All your awesome stuff here -->
{% endfor %}
Hope this helps!

Related

Twig set reusable piece of html

I'm creating some templates with Twig and have an issue.
I'm trying to load a piece of html that is used several times troughout a webshop. So my idea is to create a reusable piece of code that I can load everytime when needed.
The problem I'm facing is that when I do a for loop and then include that piece of code I get an empty return. With other words my code doesn't recognize the data that need to be loaded for each product in the for loop. It returns empty info boxes.
To clarify:
I have a main template index.html which calls a snippet to include some products (don't look at rain extension!!):
{% if featured %}
{% include 'snippets/products.rain' with {'products': featured, 'type': 'grid'} %}
{% endif %}
My products.rain snippet looks like this:
{% if type %}
{% if type == 'grid' %}
{% for product in products %} {# Products in this case = feautured products #}
<li class="item clearfix">.... etc etc .... </li>
{% endfor %}
{% elseif type == 'other-layout' %}
<div class="item">.... etc etc .... </div>
{% endif %}
{% endif %}
In the for loop there's html that's for 95% the same as in each for loop. I want to place that code inside a block that can be included in the for loops.
So what I did was:
{% set product_html %}
.... a lot of html ....
<a href="{{ product.url | url }}" title="{{ product.fulltitle }}">
<img src="{{ product.image }}" width="100" height="100" alt="{{ product.fulltitle }}"/>
</a>
{% endset %}
And then included in the for loop, like so:
{% if type %}
{% if type == 'grid' %}
{% for product in products %} {# Products in this case = feautured products #}
<li class="item clearfix">{{ product_html | raw }}</li>
{% endfor %}
{% elseif type == 'other-layout' %}
<div class="item">{{ product_html | raw }}</div>
{% endif %}
{% endif %}
However this returns the html that is set however with empty product.image and empty product.fulltitle.
I tried the same with set block, but that has the same result.
Is there anything I'm doing wrong....??
When you are using {% set %}, content inside your variable is not dynamic, it will only use data in your current context, see it live.
You can achieve your goal using 2 ways: using include or using macros.
As your piece of code for a product is small and not reused somewhere else, I suggest you to use macros:
{% macro product_html(product) %}
Current product is: {{ product }}
{% endmacro %}
{% import _self as macros %}
{% for product in products %}
{{ macros.product_html(product) }}
{% endfor %}
See it live

How to loop through categories in a Jekyll collection

I'm trying to loop through categories that have been added to collection posts. For the default 'posts' section it's as easy as:
{% for category in site.categories %}
{{ category }}
{% endfor %}
But I can't seem to get this working for my collection. I thought it would be something along the lines of:
{% for category in my_collection.categories %}
{{ category }}
{% endfor %}
But that doesn't seem to work. Any help would be appreciated.
for anyone needing the answer to this...I've managed to solve this by adding all unique 'my_collection' categories to an array then looping through that. Here's the code:
<!-- create categories array-->
{% assign categories_array = "" | split:"|" %}
<!--Add each unique 'my_collection' category to the array-->
{% for post in site.my_collection %}
{% for category in post.categories %}
{% assign categories_array = categories_array | push: category | uniq %}
{% endfor %}
{% endfor %}
<!--Output the categories-->
{% for category in categories_array %}
{{ category }}
{% endfor %}
you can grab the name of each category like so:
{% for category in site.categories %}
{{ category | first | strip_html }}
{% endfor %}
You first have to declare the collection
{%a assign col = site.COLLECTIONNAME %}
Then you can loop inside the collection
{% for cat in col %}
{{ col.name }}
{% endfor %}

display data in liquid

I'm looking to display information from a csv file on a jekyll-generated site. I need to search for the appropriate category in the csv file, then display four of them on the page. Filtering to the selected category is no problem, but I'm having difficulty limiting the output to four.
Is there a way to apply a limit to an if statement? Or is there any other way to write this? I'm not that savvy in Liquid, so it's extremely likely that I'm missing an obvious solution.
Basic code to make all the applicable data show up on the screen:
{% for study in site.data.studies %}
{% if study.category contains "foo" %}
<div class="col-sm-3">
<h3>{{ study.title }}</h3>
<div class="list-of-attributes">
<h6>Attributes: </h6>
{{ study.attributes }}
</div>
</div>
{% else %}
{% continue %}
{% endif %}
{% endfor %}
I've also tried unless and tablerow, neither of which worked at all. Am I at least on the right track? How can I limit this forloop to stop at four items?
Thank you!
Ideally data should be filtered before rendering however you can also create a variable in liquid to hold the number of stuff rendered
{% assign rendered = 0 %}
{% for study in site.data.studies %}
{% if study.category contains "foo" %}
<div class="col-sm-3">
<h3>{{ study.title }}</h3>
<div class="list-of-attributes">
<h6>attributes: </h6>
{{ study.attributes }}
</div>
</div>
{% assign rendered = rendered | plus: 1 %}
{% if rendered == 4 %}
{% break %}
{% endif %}
{% endif %}
{% endfor %}
The ideal solution as I said would be to create your own filter which does all the work (filter by category and limit the number of results)
{% assign filtered = site.data.studies | my_custom_filter %}
{% for study in filtered %}
<div class="col-sm-3">
<h3>{{ study.title }}</h3>
<div class="list-of-attributes">
<h6>attributes: </h6>
{{ study.attributes }}
</div>
</div>
{% endfor %}
Presuming that your category is a string, not an array, you can do :
{% assign selected = site.data.studies | where: 'category','foo' %}
{% for study in selected limit:4 %}
<div class="col-sm-3">
<h3>{{ study.title }}</h3>
<div class="list-of-attributes">
<h6>Attributes: </h6>
{{ study.attributes }}
</div>
</div>
{% endfor %}
And if your category is a string like "foo, bar, baz" or and array of strings you can use the jekyll 3.2 where_exp filter like this :
{% assign selected = site.data.studies | where_exp:"item", "item.category contains 'foo'" %}

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>

Sorting/grouping collections for an archive

I am building my first site and am having a very difficult time grouping/sorting collections. The only success I have had is a list of files sorted alphabetically by subdirectory, then by files by their dates (seems to be the default) using:
<ul>
{% for page in site.collection_name | sort: weight %}
<h3>{{ page.title }}</h3>{{ page.category }}{{page.excerpt}}
{% endfor %}
</ul>
Weight (above) does not work. Neither did type, category, or any other variable I substituted.
My files are in subdirectories, and have permalinks as:
/collection_name/category_name/file_name/
Front matter includes:
title,
category,
layout,
type,
(tried several others)
The collection will have several file types such as:
articles,
videos,
research
What I want to accomplish is something that loops through my collection first for category, then by type. In very bad pseudo code:
<ul>
{% for page in site.{{category_name}} %}
<li><h2>{{category01}}</h2></li>
<ul>
{% for type in site.{{category_name.type}} | sort: date reverse%}
<li><h3>Articles</h3></li>
<li>{{ page.title }}{{page.excerpt}}</li>
<li><h3>Videos</h3></li>
<li>{{ page.title }}{{page.excerpt}}</li>
<li><h3>Research</h3></li>
<li>{{ page.title }}{{page.excerpt}}</li>
</uL>
<li><h2>{{category02}}</h2></li>
. . .
. . . {% endfor %}
Any help or direction will be appreciated.
Try to use group_by :
{% assign byCategory = site.collection_name | group_by: 'category' | sort: 'name' %}
{% for cat in byCategory %}
<h2>{{ cat.name | capitalize }}</h2>
{% assign byType = cat.items | group_by: 'type' %}
{% for type in byType %}
<h3>{{ type.name | capitalize }}</h3>
<ul>
{% for item in type.items %}
<li>{{ item.title }}</li>
{% endfor %}
</ul>
{% endfor %}
{% endfor %}
Note: this works with category: mycategory not with categories: [one, two]
If you want to specifically order types, you can do :
_config.yml
# this array determine types order
# a collection itm with non matching type will not be listed
types :
- articles
- videos
- research
code
{% assign byCategory = site.area | group_by: 'category' %}
{% for cat in byCategory %}
<h2>{{ cat.name }}</h2>
{% assign byType = cat.items | group_by: 'type' %}
{% for type in site.types %}
{% assign currentType = byType | where:"name", type | first %}
<h3>{{ currentType.name | capitalize }}</h3>
<ul>
{% for item in currentType.items %}
<li>{{ item.title }}</li>
{% endfor %}
</ul>
{% endfor %}
{% endfor %}