What are some good ways to implement breadcrumbs on a Jekyll site? - breadcrumbs

I'm aware that there are single-level breadcrumbs in http://raphinou.github.com/jekyll-base/ but I'm looking for some good ways to have breadcrumbs on a Jekyll site when directories get to a depth of four or five levels.
(Yes, I'm well aware that Jekyll is primarily a blogging engine and that perhaps I shouldn't use it for a general purpose website, especially with many directory levels. I'm also aware of http://octopress.org but haven't found a suitable plugin.)
Based heavily on http://forums.shopify.com/categories/2/posts/22172 I came up with the following Jekyll layout for breadcrumbs, a variation of which you can see in action at http://crimsonfu.github.com/members/pdurbin . You should see the breadcrumbs "home » members »" at the top.
Here's my layout. Yes, it's ugly. I haven't studied Liquid much. Can you suggest a better way?
<html>
<head>
<title>{{ page.title }}</title>
<style type="text/css">
#bread ul {
padding-left: 0;
margin-top: 2px;
margin-bottom: 2px;
}
#bread ul li {
display: inline;
font-size: 70%;
}
</style>
</head>
<body>
<div id="bread">
<ul>
{% assign url = {{page.url}} %}
{% assign delimiter = '/' %}
{% capture allparts %}{{ url | replace: delimiter, ' ' }}{% endcapture %}
{% capture myFirstWord %}{{ allparts | truncatewords: 1 | remove: '...' }}{% endcapture %}
{% capture minusFirst %}{{ allparts | replace_first: myFirstWord, '' }}{% endcapture %}
{% capture mySecondWord %}{{ minusFirst | truncatewords: 1 | remove: '...' }}{% endcapture %}
{% capture minusSecond %}{{ minusFirst | replace_first: mySecondWord, '' }}{% endcapture %}
{% capture myThirdWord %}{{ minusSecond | truncatewords: 1 | remove: '...' }}{% endcapture %}
{% capture minusThird %}{{ minusSecond | replace_first: myThirdWord, '' }}{% endcapture %}
{% capture myFourthWord %}{{ minusThird | truncatewords: 1 | remove: '...' }}{% endcapture %}
{% capture minusFourth %}{{ minusThird | replace_first: myFourthWord, '' }}{% endcapture %}
{% capture myFifthWord %}{{ minusFourth | truncatewords: 1 | remove: '...' }}{% endcapture %}
{% if myFirstWord contains '.html' %}
<li>home </li>
{% elsif mySecondWord contains '.html' %}
<li>home » </li>
{% unless mySecondWord == 'index.html' %}
<li>{{myFirstWord}} » </li>
{% endunless %}
{% elsif myThirdWord contains '.html' %}
<li>home » </li>
<li>{{myFirstWord}} » </li>
{% unless myThirdWord == 'index.html' %}
<li>{{mySecondWord}} » </li>
{% endunless %}
{% elsif myFourthWord contains '.html' %}
<li>home » </li>
<li>{{myFirstWord}} » </li>
<li>{{mySecondWord}} » </li>
{% unless myFourthWord == 'index.html' %}
<li>{{myThirdWord}} » </li>
{% endunless %}
{% elsif myFifthWord contains '.html' %}
<li>home » </li>
<li>{{myFirstWord}} » </li>
<li>{{mySecondWord}} » </li>
<li>{{myThirdWord}} » </li>
{% unless myFifthWord == 'index.html' %}
<li>{{myFourthWord}} » </li>
{% endunless %}
{% else %}
<li>home » </li>
<li>{{myFirstWord}} » </li>
<li>{{mySecondWord}} » </li>
<li>{{myThirdWord}} » </li>
<li>{{myFourthWord}} » </li>
{% endif %}
</ul>
</div>
<h1>{{ page.title }}</h1>
{{ content }}
</body>
</html>

I have improved slightly on the answers given earlier. I have removed the unordered list and seperated the items with a character (forward slash). I have added a filter for 'index.html' and '.html', so urls like 'mysite.com/path/index.html' and 'mysite.com/path/item-name.html' are also supported. Finally I have capitalized the titles. This results in something that looks like this:
Home / Path / Item name
{% assign crumbs = page.url | remove:'/index.html' | split: '/' %}
Home
{% for crumb in crumbs offset: 1 %}
{% if forloop.last %}
/ {{ crumb | replace:'-',' ' | remove:'.html' | capitalize }}
{% else %}
/ {{ crumb | replace:'-',' ' | remove:'.html' | capitalize }}
{% endif %}
{% endfor %}
PS. I have created an online resource for snippets like this: jekyllcodex.org/without-plugins

This should give breadcrumbs at any depth (with a caveat, see end). Unfortunately, the Liquid filters are fairly limited, so this is an unstable solution: any time /index.html appears, it is deleted, which will break URLs that have a folder that starts with index.html (e.g. /a/index.html/b/c.html), hopefully that won't happen.
{% capture url_parts %} {{ page.url | remove: "/index.html" | replace:'/'," " }}{% endcapture %}
{% capture num_parts %}{{ url_parts | number_of_words | minus: 1 }}{% endcapture %}
{% assign previous="" %}
<ol>
{% if num_parts == "0" or num_parts == "-1" %}
<li>home </li>
{% else %}
<li>home » </li>
{% for unused in page.content limit:num_parts %}
{% capture first_word %}{{ url_parts | truncatewords:1 | remove:"..."}}{% endcapture %}
{% capture previous %}{{ previous }}/{{ first_word }}{% endcapture %}
<li>{{ first_word }} » </li>
{% capture url_parts %}{{ url_parts | remove_first:first_word }}{% endcapture %}
{% endfor %}
{% endif %}
</ol>
How it works is:
separates the URL, ignoring index.html (e.g. /a/b/index.html becomes a b, /a/b/c.html becomes a b c.html),
successively takes and removes the first word of url_parts, to iterate through all but the last word (e.g. it goes a b c.html -> (a, b c.html) -> (b, c.html); then we stop).
at each step, it makes the breadcrumb link using the current first_word, and previous which is all the previous directories seen (for the example above, it would go "" -> "/a" -> "/a/b")
NB. the page.content in the for loop is just to give something to iterate over, the magic is done by the limit:num_parts. However, this means that if page.content has fewer paragraphs than num_parts not all breadcrumbs will appear, if this is likely one might define a site variable in _config.yml like breadcrumb_list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] and use site.breadcrumb_list as the placeholder instead of page.content.
Here is an example (it doesn't use precisely the same code as above, but it's just a few little modifications).

I made the following breadcrumbs with liquid templates, its as elegant as I could manage :) It uses jQuery to set the active class on the li last li item.
<ul class="breadcrumb">
<li><i class="icon-home"></i>Home </li>
{% assign crumbs = page.url | split: '/' %}
{% assign crumbstotal = crumbs | size %}
{% for crumb in crumbs offset:2 %}
{% unless crumb == 'index.html' %}
<li><span class="divider">»</span> {{ crumb | remove: '.html' }} </li>
{% endunless %}
{% endfor %}
</ul>
<script>$(".breadcrumb li").last().addClass('active');</script>

This plugin seems a tad more robust.

I thought of an automatic breadcrumb also displaying nice breadcrumb element text from the frontmatter.
It works perfectly on GitHub Pages.
Check it out here:
http://blog.comsysto.com/2015/04/25/automatic-breadcrumb-for-jekyll-on-github-pages/
If you have this setup:
/mypage1/index.html
---
layout: default
title: My Page 1 - My Homepage
breadcrumb: My Page 1
---
<div class="container">
<div class="row">
<div class="col-md-12">
peace yo!
</div>
</div>
</div>
/mypage1/subpage1/index.html
---
layout: default
title: My Sub Page 1 - My Homepage
breadcrumb: My Sub Page 1
---
<div class="container">
<div class="row">
<div class="col-md-12">
foobar!
</div>
</div>
</div>
The breadcrumb will render the following
<ol class="breadcrumb">
<li>Home</li>
<li>My Page 1</li>
<li class="active">My Sub Page 1</li>
</ol>

This is how I implemented breadcrumbs in a site I inherited, which is based off some of the other versions here. In our case, some of our folders didn't contain an index.html page. Therefore, clicking on a breadcrumb that linked to a folder with no index.html in it would cause an error. This could have been eliminated with a better file structure, but I wasn't able to change that.
Here is what I came up with.
<ol class="pull-right breadcrumb">
<li>Home</li>
{% assign crumbs = page.url | split: '/' %}
{% assign crumbs_total = crumbs | size | minus: 1 %}
{% for crumb in crumbs offset: 1 %}
{% if forloop.index == crumbs_total %}
<li class="active">{{page.title}}</li>
{% else %}
{% assign crumb_limit = forloop.index | plus: 1 %}
{% capture crumb_url %}{% for c in crumbs limit: crumb_limit %}{{ c | append: '/' }}{% endfor %}{% endcapture %}
{% capture crumb_with_index %}{{ crumb_url | append: 'index.html' }}{% endcapture %}
{% capture current_page %}{{ site.baseurl }}{{ page.url }}{% endcapture %}
{% for p in site.pages %}
{% if crumb_with_index != current_page and crumb_with_index == p.url %}
<li>{{ crumb | replace:'-',' ' | capitalize}}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
</ol>

Here's my solution, which works with Jekyll 3.1.3 and GitHub Pages as of today. Like some solutions others have given, it simply inspects the page's URL and builds the breadcrumbs from it.
{% unless page.hide-breadcrumbs %}
<ul class="breadcrumb">
<li>{{site.title}} </li>
{% assign crumbs = page.url | split: '/' %}
{% for crumb in crumbs offset:1 %}
<li {% if forloop.last %}class="active"{% endif %}>
{% unless forloop.last %}
<a href="/{% for crumb in crumbs offset:1 limit:forloop.index %}{{crumb}}/{% endfor %}">
{{ crumb | capitalize }}
</a>
{% else %}
{{ crumb | capitalize }}
{% endunless %}
</li>
{% endfor %}
</ul>
{% endunless %}

I found an alternative technique that is not entirely automatic but that works on GitHub pages.
It consists of creating a data file with the list of possible paths.
For example, here is _data/breadcrumbs.csv for my site:
url,title
/,Home
/api/,API
/api/jsonarray/,JsonArray
/api/jsonbuffer/,JsonBuffer
/api/jsonobject/,JsonObject
/api/jsonvariant/,JsonVariant
/doc/,Manual
/example/,Examples
/news/,News
/faq/,FAQ
Then, a simple loop creates the breadcrumb:
<ol class="breadcrumb">
{% for crumb in site.data.breadcrumbs %}
{% assign url_prefix = page.url | slice: 0, crumb.url.size %}
{% if (url_prefix == crumb.url) and (page.url != crumb.url) %}
<li class="breadcrumb-item">
<a href="{{ crumb.url | prepend: site.baseurl }}">
{{ crumb.title }}
</a>
</li>
{% endif %}
{% endfor %}
<li class="breadcrumb-item active">
<a href="{{ page.url | prepend: site.baseurl }}">
{{ page.title }}
</a>
</li>
</ol>
See this article for details and links to the implementation.

Thought I might throw this into the mix. This is based on Davelab6's example above, with a few improvements. The active class is set by the last entry in the loop - also contains permalinks for each crumb.
I haven't tested it with posts yet - but it should work. Let me know if any issues.
<ul class="breadcrumbs">
<li>Home</li>
{% assign crumbs = page.url | split: '/' %}
{% assign crumbs_total = crumbs | size | minus: 1 %}
{% for crumb in crumbs offset: 1 %}
{% if forloop.index == crumbs_total %}
<li class="active">{{ crumb | replace:'-',' ' }}</li>
{% else %}
<li>{{ crumb | replace:'-',' ' }}
{% endif %}
{% endfor %}
</ul>

I managed to simply it even further. Here is what I'm using now:
{% assign crumbs = page.url | split: '/' %}
<ul class="lv-breadcrumbs">
<li>Home</li>
{% for crumb in crumbs offset: 1 %}
{% if forloop.last %}
<li class="active">{{ crumb | replace:'-',' ' }}</li>
{% else %}
<li>{{ crumb | replace:'-',' ' }}</li>
{% endif %}
{% endfor %}
</ul>

And I have improved JoostS's response to have the following capabilities:
Replace the "/" path separator with a rightward-pointing triangle (▶ ▶)
If one exists, use the breadcrumb: from the front matter of the final page
{% assign crumbs = page.url | remove:'/index.html' | split: '/' %}
Home
{% for crumb in crumbs offset: 1 %}
{% if forloop.last %}
{% if page.breadcrumb != '' %}
▶ {{ page.breadcrumb }}
{% else %}
▶ {{ crumb | replace:'-',' ' | remove:'.html' | capitalize }}
{% endif %}
{% else %}
▶ {{ crumb | replace:'-',' ' | remove:'.html' | capitalize }}
{% endif %}
{% endfor %}

Here's another way to approach this: rather than generating the breadcrumbs for a post from its URL, instead use the post's collection, categories, date (year, month and day) and title to form the breadcrumbs. This means that the breadcrumbs don't have to match with the URL (although they can still do if you want them to). And it means that the breadcrumbs for categories, years, etc can find and link to pages for those categories, years, etc if the pages exist.
The generated breadcrumbs look like this:
Home > Posts > Example Category > 2019 > Dec > 23 > Breadcrumbs in Jekyll
There are options for omitting each of the home, collection, categories, date and title components, so you can customize the breadcrumbs to look how you want.
Breadcrumbs will automatically link to a corresponding page, if the page exists. For example the home breadcrumb will link to a page at URL "/". A breadcrumbs for a "foo" category will link to a page at "/foo/" if one exists (otherwise the breadcrumb will just not be linked). A breadcrumb for the year 2019 will link to a "/2019/" page. And so on.
The templates are pasted below. Usage: {% include breadcrumbs.html %}. See my gist and blog post for more detail.
_includes/breadcrumbs.html:
{% assign omit_home = include.omit_home %}
{% if omit_home == nil %}
{% assign omit_home = site.breadcrumbs_omit_home %}
{% endif %}
{% assign omit_collection = include.omit_collection %}
{% if omit_collection == nil %}
{% assign omit_collection = site.breadcrumbs_omit_collection %}
{% endif %}
{% assign omit_categories = include.omit_categories %}
{% if omit_categories == nil %}
{% assign omit_categories = site.breadcrumbs_omit_categories %}
{% endif %}
{% assign omit_date = include.omit_date %}
{% if omit_date == nil %}
{% assign omit_date = site.breadcrumbs_omit_date %}
{% endif %}
{% assign omit_year = include.omit_year %}
{% if omit_year == nil %}
{% assign omit_year = site.breadcrumbs_omit_year %}
{% endif %}
{% assign omit_month = include.omit_month %}
{% if omit_month == nil %}
{% assign omit_month = site.breadcrumbs_omit_month %}
{% endif %}
{% assign omit_day = include.omit_day %}
{% if omit_day == nil %}
{% assign omit_day = site.breadcrumbs_omit_day %}
{% endif %}
{% assign breadcrumbs = "" | split: "" %}
{% if page.url == "/" %}
{% assign is_front_page = true %}
{% else %}
{% assign is_front_page = false %}
{% endif %}
{% unless is_front_page %}
{% assign page = include.page | default: page %}
{% unless omit_home %}
{% capture breadcrumb_text %}{% include breadcrumb_text.html url="/" default="Home" %}{% endcapture %}
{% assign breadcrumb_text = breadcrumb_text | split: "🗾" %}
{% assign breadcrumbs = breadcrumbs | concat: breadcrumb_text %}
{% endunless %}
{% endunless %}
{% unless omit_collection or page.collection == nil %}
{% assign collection_slug = page.collection | replace: " ", "-" | downcase | url_encode %}
{% assign collection_page_url = "/" | append: collection_slug | append: "/" %}
{% capture breadcrumb_text %}{% include breadcrumb_text.html url=collection_page_url default=page.collection %}{% endcapture %}
{% assign breadcrumb_text = breadcrumb_text | split: "🗾" %}
{% assign breadcrumbs = breadcrumbs | concat: breadcrumb_text %}
{% endunless %}
{% unless omit_categories %}
{% for category in page.categories %}
{% assign category_slug = category | replace: " ", "-" | downcase | url_encode %}
{% assign category_page_url = "/" | append: category_slug | append: "/" %}
{% capture breadcrumb_text %}{% include breadcrumb_text.html url=category_page_url default=category %}{% endcapture %}
{% assign breadcrumb_text = breadcrumb_text | split: "🗾" %}
{% assign breadcrumbs = breadcrumbs | concat: breadcrumb_text %}
{% endfor %}
{% endunless %}
{% unless omit_date or page.date == nil %}
{% assign year = page.date | date: "%Y" %}
{% assign year_page_url = "/" | append: year | append: "/" %}
{% assign month_int = page.date | date: "%m" %}
{% assign month_page_url = year_page_url | append: month_int | append: "/" %}
{% assign day_int = page.date | date: "%d" %}
{% assign day_page_url = month_page_url | append: day_int | append: "/" %}
{% unless omit_year %}
{% capture breadcrumb_text %}{% include breadcrumb_text.html url=year_page_url default=year %}{% endcapture %}
{% assign breadcrumb_text = breadcrumb_text | split: "🗾" %}
{% assign breadcrumbs = breadcrumbs | concat: breadcrumb_text %}
{% endunless %}
{% unless omit_month %}
{% assign month_str = page.date | date: "%b" %}
{% capture breadcrumb_text %}{% include breadcrumb_text.html url=month_page_url default=month_str %}{% endcapture %}
{% assign breadcrumb_text = breadcrumb_text | split: "🗾" %}
{% assign breadcrumbs = breadcrumbs | concat: breadcrumb_text %}
{% endunless %}
{% unless omit_day %}
{% assign day_str = page.date | date: "%e" %}
{% capture breadcrumb_text %}{% include breadcrumb_text.html url=day_page_url default=day_str %}{% endcapture %}
{% assign breadcrumb_text = breadcrumb_text | split: "🗾" %}
{% assign breadcrumbs = breadcrumbs | concat: breadcrumb_text %}
{% endunless %}
{% endunless %}
{% unless is_front_page %}
{% if page.title == nil %}
{% assign title = page.name %}
{% else %}
{% assign title = page.title %}
{% endif %}
{% capture breadcrumb_text %}<strong>{{ title }}</strong>{% endcapture %}
{% assign breadcrumb_text = breadcrumb_text | split: "🗾" %}
{% assign breadcrumbs = breadcrumbs | concat: breadcrumb_text %}
{% endunless %}
{% if breadcrumbs != empty %}
<nav style="display: inline-block;">
{% for breadcrumb in breadcrumbs %}{{ breadcrumb }}{% endfor %}
</nav>
{% endif %}
_includes/breadcrumb_text.html:
{% assign breadcrumb_page = nil %}
{% for page in site.pages %}
{% if page.url == include.url %}
{% assign breadcrumb_page = page %}
{% break %}
{% endif %}
{% endfor %}
{% assign breadcrumb_text = breadcrumb_page.breadcrumb_text | default: breadcrumb_page.title | default: include.default %}
{% unless breadcrumb_page == nil %}
{{ breadcrumb_text }}
{% else %}
{{ breadcrumb_text }}
{% endunless %}
>

Related

Is there a way to set up pagination for Collections in a Jekyll site?

As per Jekyll's doc, pagination for collections is not supported. I've tried to mimic Jekyll posts folder structure for my portfolio collection and then apply a slightly modified pagination Liquid syntax to work with a portfolio collection with no avail.
Is there a way/workaround in order to set up pagination for Collections in a Jekyll site?
There is a way to assign prev and next tags to "fake" pagination for your collections. Here is how anjesh did it:
{% for c in site.tripcollection %}
{% if c.title == page.title %}
{% assign thisPost = c %}
{% if forloop.index == 1 %}
{% assign prevflag = 0 %}
{% assign nextflag = 1 %}
{% elsif forloop.index == forloop.length %}
{% assign prevflag = 1 %}
{% assign nextflag = 0 %}
{% else %}
{% assign prevflag = 1 %}
{% assign nextflag = 1 %}
{% endif %}
{% endif %}
{% endfor %}
{% for c in site.tripcollection %}
{% if c.title == page.title %}
{% assign prevflag = 0 %}
{% endif %}
{% if prevflag == 1 %}
{% assign prevPost = c %}
{% assign page.previous = c %}
{% endif %}
{% endfor %}
{% if nextflag == 1 %}
{% for c in site.tripcollection %}
{% if foundPost == 1 %}
{% assign getNext = 1 %}
{% endif %}
{% if c.title == page.title %}
{% assign foundPost = 1 %}
{% endif %}
{% if getNext == 1%}
{% assign nextPost = c %}
{% assign page.next = c %}
{% assign foundPost = 0 %}
{% assign getNext = 0 %}
{% endif %}
{% endfor %}
{% endif %}
<div id="post-nav">
<div >
{% if prevPost.url %}
<a class="prev" href="{{prevPost.url}}">
<span>< {{prevPost.title}}</span>
</a>
{% endif %}
{% if nextPost.url %}
<a class="next" href="{{nextPost.url}}">
<span>{{nextPost.title}} ></span>
</a>
{% endif %}
</div>
</div>
You can read his whole post here: Get Pagination working in Jekyll Collection in Github pages
jekyll-paginate only paginate posts.
If you want to paginate collections, you can use Octopress Paginate but it's not supported by github (for now).

Jekyll and liquid - Show related posts by amount of equal tags >= 2

i'd like to add a bar called "related posts" at the bottom of each post and the criteria for posts to be related and to appear there should be that there is an amount of minimum 2 equal tags in both posts.
My approach so far:
{% for tag in page.tags %}
{% assign currentTag = tag | first %}
{% for post in site.posts | limit:3 %}
{% if post.tags contains currentTag | plus:1 %}
<div>
<a href="{{ post.url }}">
<img src="{{site.baseurl}}/asset/img/{{ post.img-thumb }}">
</a>
<h5> {{ post.title }}</h5>
</div>
{% endif %}
{% endfor %}
{% endfor %}
Thanks for your help!
This code does the trick :
<div class="relatedPosts">
<h3>Related post</h3>
{% comment %}---> the maximum number of related to posts
to be printed {% endcomment %}
{% assign maxRelated = 5 %}
{% comment %}---> the minimum number of common tags
to have for a post to be considered
as a related post {% endcomment %}
{% assign minCommonTags = 3 %}
{% assign maxRelatedCounter = 0 %}
{% for post in site.posts %}
{% assign sameTagCount = 0 %}
{% assign commonTags = '' %}
{% for tag in post.tags %}
{% comment %}---> Only compare if post is
not same as current page {% endcomment %}
{% if post.url != page.url %}
{% if page.tags contains tag %}
{% assign sameTagCount = sameTagCount | plus: 1 %}
{% capture tagmarkup %} <span class="label label-default">{{ tag }}</span> {% endcapture %}
{% assign commonTags = commonTags | append: tagmarkup %}
{% endif %}
{% endif %}
{% endfor %}
{% if sameTagCount >= minCommonTags %}
<div>
<h5>{{ post.title }}{{ commonTags }}</h5>
</div>
{% assign maxRelatedCounter = maxRelatedCounter | plus: 1 %}
{% if maxRelatedCounter >= maxRelated %}
{% break %}
{% endif %}
{% endif %}
{% endfor %}
</div>
Edit : Added 'configuration' for maxRelated and minCommonTags, plus a test to avoid putting a post in is own related post list.

Related Products by Tag in Shopify

I've built out a liquid template to compare the tags of a current products with the tags of all the other products in the store and display four as related products at the bottom of the page.
It works, but I think I've done it quite inefficiently. Is there a way to make this work a little better?
{% if settings.products_per_row == "4" %}
{% assign number_of_related_products_to_show = 4 %}
{% elsif settings.products_per_row == "3" %}
{% assign number_of_related_products_to_show = 3 %}
{% else %}
{% assign number_of_related_products_to_show = 2 %}
{% endif %}
{% assign number_of_related_products_to_fetch = number_of_related_products_to_show | plus: 1 %}
{% assign current_product_tags = product.tags %}
{% for c in collections %}
{% if c.handle == 'all' %}
{% assign collection_all = c %}
{% endif %}
{% endfor %}
{% assign found_first_match = false %}
{% assign found_second_match = false %}
{% paginate collection_all.products by 1000 %}
{% for product in collection_all.products %}
{% for tag in product.tags %}
{% if current_product_tags contains tag and found_first_match == false and tag != 'Made in USA' %}
{% assign found_first_match = true %}
{% assign first_match = tag %}
{% endif %}
{% if current_product_tags contains tag and found_first_match == true and tag != first_match and tag != 'Made in USA' %}
{% assign found_second_match = true %}
{% assign second_match = tag %}
{% endif %}
{% endfor %}
{% endfor %}
{% endpaginate %}
{% assign matches_found = false %}
{% assign current_product = product %}
{% assign current_product_found = false %}
{% paginate collection_all.products by 1000 %}
{% for product in collection_all.products %}
{% if product.handle == current_product.handle %}
{% assign current_product_found = true %}
{% else %}
{% if product.tags contains first_match %}
{% unless current_product_found == false and forloop.last %}
{% assign matches_found = true %}
{% endunless %}
{% endif %}
{% if product.tags contains second_match and matches_found == false %}
{% unless current_product_found == false and forloop.last %}
{% assign matches_found = true %}
{% endunless %}
{% endif %}
{% endif %}
{% endfor %}
{% endpaginate %}
{% if matches_found == true %}
<div class="row">
<div class="span12">
<h3 class="collection-title">Related products</h3>
</div>
</div>
<div class="row products">
{% paginate collection_all.products by 1000 %}
{% for product in collection_all.products %}
{% if product.handle == current_product.handle %}
{% assign current_product_found = true %}
{% else %}
{% if product.tags contains first_match %}
{% unless current_product_found == false and forloop.last %}
{% include 'related-product-loop' with collection.handle %}
{% assign matched_product = product.title %}
{% endunless %}
{% endif %}
{% if product.tags contains second_match %}
{% unless current_product_found == false and forloop.last or matched_product == product.title %}
{% include 'related-product-loop' with collection.handle %}
{% endunless %}
{% endif %}
{% endif %}
{% endfor %}
{% endpaginate %}
</div>
{% endif %}
{{ 'jquery.pick.js' | asset_url | script_tag }}
<script type="text/javascript" charset="utf-8">
//<![CDATA[
var howMany = {{ number_of_related_products_to_show }};
jQuery(function() {
jQuery('.products .product').pick(howMany);
});
//]]>
</script>
I'm using jquery.pick.js to randomly display four of the products.
Thoughts?
I would suggest taking a look at this article on the Shopify wiki: Related Products.
Perhaps the approach used in section 3. Finding a relevant collection would be a cleaner way to implement related products. However if you need to use product tags, there's an explanation on how to do that as well in section 4. Using product tags.
EDIT: Perhaps your code could be simplified a bit to something like this. It's very similar to what you have above, but just reduces it down to one loop through all the products instead of three.
{% if settings.products_per_row == "3" or settings.products_per_row == "4" %}
{% assign number_of_related_products_to_show = settings.products_per_row | times: 1 %}
{% else %}
{% assign number_of_related_products_to_show = 2 %}
{% endif %}
{% assign current_product = product %}
{% assign current_product_tags = product.tags %}
{% assign found_first_match = false %}
{% assign found_second_match = false %}
{% assign first_related_product = true %}
{% paginate collections.all.products by 1000 %}
{% for product in collections.all.products %}
{% unless product.handle == current_product.handle %}
{% for tag in product.tags %}
{% if current_product_tags contains tag and tag != 'Made in USA' %}
{% if found_first_match == false %}
{% assign found_first_match = true %}
{% assign first_match = tag %}
{% elsif found_second_match == false %}
{% assign found_second_match = true %}
{% assign second_match = tag %}
{% endif %}
{% endif %}
{% endfor %}
{% if found_first_match == true %}
{% if first_related_product == true %}
{% assign first_related_product == false %}
<div class="row">
<div class="span12">
<h3 class="collection-title">Related products</h3>
</div>
</div>
<div class="row products">
{% endif %}
{% if product.tags contains first_match or product.tags contains second_match %}
{% include 'related-product-loop' with collection.handle %}
{% endif %}
{% endif %}
{% endunless %}
{% endfor %}
{% if first_related_product == false %} </div> {% endif %}
{% endpaginate %}
{{ 'jquery.pick.js' | asset_url | script_tag }}
<script type="text/javascript" charset="utf-8">
//<![CDATA[
var howMany = {{ number_of_related_products_to_show }};
jQuery(function() {
jQuery('.products .product').pick(howMany);
});
//]]>
</script>
This code is in a gist here. I included 2 files, the second option uses 2 loops through the products but is perhaps a little more readable. (I couldn't decide between the two, so I included both.)

Symfony2 form - Customize birthday type

How can I customize birthday type in Symfony2? There is no
{% block birthday_widget %}
in form_div_layout
The birthday form type gets rendered with the block choice_widget.
{% block choice_widget %}
{% spaceless %}
{% if expanded %}
{{ block('choice_widget_expanded') }}
{% else %}
{{ block('choice_widget_collapsed') }}
{% endif %}
{% endspaceless %}
{% endblock choice_widget %}
In the end, the block doing the output is named choice_widget_collapsed. Just make your changes in there.
{% block choice_widget_collapsed %}
{% spaceless %}
{% if required and empty_value is none and not empty_value_in_choices %}
{% set required = false %}
{% endif %}
<select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}>
{% if empty_value is not none %}
<option value=""{% if required and value is empty %} selected="selected"{% endif %}>{{ empty_value|trans({}, translation_domain) }}</option>
{% endif %}
{% if preferred_choices|length > 0 %}
{% set options = preferred_choices %}
{{ block('choice_widget_options') }}
{% if choices|length > 0 and separator is not none %}
<option disabled="disabled">{{ separator }}</option>
{% endif %}
{% endif %}
{% set options = choices %}
{{ block('choice_widget_options') }}
</select>
{% endspaceless %}
{% endblock choice_widget_collapsed %}

How to customize form_label block in form template in Symfony2?

I'm trying to customize form_label in a template which already extends one template.
I'm using example in the Symfony2 documentation:
{% use 'form_div_layout.html.twig' with form_label as base_form_label %}
{% block form_label %}
{{ block('base_form_label') }}
{% if required %}
<span class="required" title="This field is required">*</span>
{% endif %}
{% endblock %}
but nothing change!
Can you help me?
Here is my solution.
In top of my form.html.twig file:
{% form_theme form with 'MyBundle:Activity:Form/fields.html.twig' %}
and now into fields.html.twig, I custom the form_label:
{% extends 'form_div_layout.html.twig' %}
{% block form_label %}
{% spaceless %}
{% if not compound %}
{% set label_attr = label_attr|merge({'for': id}) %}
{% endif %}
{% if required %}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %}
{% endif %}
{% if label is empty %}
{% set label = name|humanize %}
{% endif %}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ label|trans({}, translation_domain) }}{% if attr.note is defined %} <span style="font: 11px normal; font-family: arial;">({{ attr.note }})</span>{% endif %}</label>
{% endspaceless %}
{% endblock form_label %}