I have a data files with different items. Each item have nested tasks. I am trying to loop the nested tasks and present each task by the task type.
YML DATA
- name: Outside
description: Description
tasks:
- type: Food
name: Eat it outside
status: working
- type: Drinks
name: Drink it outside
status: working
- name: Inside
description: Description
tasks:
- type: Food
name: Eat it inside
status: pending
- type: Drinks
name: Drink it inside
status: working
Liquid
{% for item in site.data.info %}
{% assign grouped-tasks-by-type = item.tasks | group_by: "type" %}
{% for task in grouped-tasks-by-type %}
<h2 class="task-type">{{ task.type }}</h2>
<ul>
{% for task in item.tasks %}
{% if task.status == 'working' %}
<li>{{ item.name }}: {{ task.name }}</li>
{% endif %}
{% endfor %}
</ul>
{% endfor %}
{% endfor %}
Expected result (HTML)
<h2 class="task-type">Food</h2>
<ul>
<li>Outside: Eat it outside<li>
</ul>
<h2 class="task-type">Drinks</h2>
<ul>
<li>Outside: Drink it outside<li>
<li>Inside: Drink it inside<li>
</ul>
However, I am getting a full blank result. Is this possible to do with group_by?
I hope my algorithm below serves you well. I was able to achieve your expect end result with the algorithm. I was unable to utilize the group_by in my solution. The sample code contains Liquid comment blocks to explain my thought process.
Testing
I used the minima git repo, with command jekyll s. I placed your YML data in a file with path _data/info.yml in my locally cloned minima git repo. I used post.html as the code sandbox.
You can print out the Liquid variables to the DOM by doing {{ all_food_types | json }} in the code.
Solution
{%- comment -%}
End Goal: Find all the food types for <h2>.
1. Use map: "tasks" to gather all the tasks into a single array.
This will cause the loss of Outside/Inside information for each task
2. Use map: "type" to create a list of food types ("Food", "Drinks")
3. Use uniq to remove duplicate food types from the array
{%- endcomment -%}
{% assign all_food_types = site.data.info | map: "tasks" | map: "type" | uniq %}
{%- comment -%}
End Goal: Loop through all the data looking
for a specific food type (Food, Drinks)
and group them together
{%- endcomment -%}
{% for food_type in all_food_types %}
<h2 class="task-type">{{ food_type }}</h2>
<ul>
{% for item in site.data.info %}
{% for task in item.tasks %}
{% if task.status == 'working' and task.type == food_type %}
<li>{{ item.name }}: {{ task.name }}</li>
{% endif %}
{% endfor %}
{% endfor %}
</ul>
{% endfor %}
An aside, for whatever reason, my liquid ends up using a lot of arrays and for-loops.
Related
In my posts' Front Matter, I have "categories" which is an array.
I'm looking for a way to filter the posts based on the first element in the categories array.
For example, if I had two posts' Front Matter like:
title: Post Number One
categories:
- first post ever
- cool stories
and
title: Post Two
categories:
- cool stories
I want a way to filter on categories where "cool stories" would return only "Post Two" because "cool stories" shows up as the first element of the array.
This is an Information Architecture (IA) question.
any post must be categorized in a main category
post can be categorized in more than one "categorie"
Let's use Jekyll's category/categories inner working to represent our IA.
If you define a post like this :
---
title: "My post"
category: "main category"
categories:
- other
- wat!
# ... more front matter variables
---
Category/categories will be available as :
post.category => main category
post.categories =>
- other
- wat!
- main category
Now if you want to use category to filter your posts, using group_by and where_exp filters, you can do :
{% assign category = "main category" %}
{% comment %} #### Grouping posts by 'main' category {% endcomment %}
{% assign grouped = site.posts | group_by: "category" %}
{{ grouped | inspect }}
{% comment %}#### Get our category group{% endcomment %}
{% assign categoryPosts = grouped | where_exp: "group", "group.name == category" | first %}
{{ categoryPosts | inspect }}
{% comment %} #### All interesting posts are now in categoryPosts.items {% endcomment %}
{{ categoryPosts.items | inspect }}
{% comment %} #### We can now sort and loop over our posts {% endcomment %}
{% assign sorted = categoryPosts.items | sort: "whateverKeyYouWantToSortOn" %}
<ul>
{% for post in sorted %}
<li>
{{ post.title }}
<br>Category : {{ post.category }}
<br>Categories :
<br><ul>{% for c in post.categories %}
<li>'categorie' {{ forloop.index }} - {{ c }}</li>
{% endfor %}</ul>
</li>{% endfor %}
</ul>
There are several ways to implement this feature. One of which is:
Create a new include file in _includes named first-category.html with the following code:
{% assign chosen_category = include.category %}
{% for post in site.posts %}
{% for category in post.categories.first %}
{% if category == chosen_category %}
{{ post.title }}
{% endif %}
{% endfor %}
{% endfor %}
Then, in the page where you're listing the post which have the first category as the one in question, simply include the above file and pass the chosen category name:
## Post that have the first category of "cool stories"
{% include first-category.html category = "cool stories" %}
## End
The above code will only show posts which have "cool stories" as the first category in the posts' front-matter.
I have a collections of projects. Part of each project is a list of people who have worked on that project:
---
layout: project
name: Important Project
participants:
- name: julia
role: owner
- name: paul
role: manager
- name: chris
role: implementer
---
Each of these people are in a collection themselves and have a page where their details are listed. I'd like to include the projects they've been working on and what their role was. This is my best effort but doesn't work:
{% for project in site.projects %}
{% if project.participants['name'] == {{ page.name }} %}
<p>{{ project.name }} - {{ project.participants['role'] }}</p>
{% endif %}
{% endfor %}
Any suggestions are most welcome.
Assuming this structure:
# _config.yml
collections:
- people
- projects
Then directory structure like:
_projects/project1.md
_people/person1.md
_people/person2.md
With person1.md front-matter like:
---
name: julia
---
And the above front-matter you provided, this is how to display the list of projects and roles of each person:
{% for person in site.people %}
Person {{person.name}}
{% for project in site.projects%}
{% assign person_project = project.participants | where:"name",person.name | first %}
Project: {{project.name}}
Role: {{person_project.role}}
{% endfor %}
{% endfor %}
Then the output looks like:
Person julia
Project: Important Project
Role: owner
Then you can improve the output as you wish, for example with a table:
{% for person in site.people %}
<table>
<caption>{{person.name}} projects</caption>
<tr>
<th>Project</th>
<th>Role</th>
</tr>
{% for project in site.projects%}
{% assign person_project = project.participants | where:"name",person.name | first %}
<tr>
<td>{{project.name}}</td>
<td>{{person_project.role}}</td>
</tr>
{% endfor %}
</table>
{% endfor %}
output:
julia projects
Project Role
Important Project owner
I imagine that your participants items look like this :
---
short: julia
firstname: Julia
lastname: Last
layout: participant
---
Content
In your participant layout you can do :
<h2>Participant : {{ page.firstname }} {{ page.lastname }}</h2>
{{ content }}
<h2>Projects :</h2>
<ul>
{% for project in site.projects %}
{% for participant in project.participants %}
{% if participant.name == page.short %}
<li>{{ participant.role }} in {{ project.name }}</li>
{% endif %}
{% endfor %}
{% endfor %}
</ul>
In participant
I would like to show a menu that displays only the distinct categories.
Imagine to have the following structure:
_folder1
com1.html
com2.html
com3.html
Now, let me focus only about three files.
For every file contained in the _folder1 you have the following YAML MATTER
TITLE: 1File
type: y
project: 1
TITLE: 2File
type: y
project: 1
TITLE: 3File
type: y
project: 2
Now, I'd like to show the following list:
Projects
1
2
and I don't want to have a double 1.
What is the best practice to reach it in Jekyll ?
It's possible, but you need some very ugly string manipulation hacks to achieve it.
As far as I know, there's no proper way in Liquid to create arrays by yourself.
So 90% of the following solution consists of abusing strings in order to create arrays.
<!-- Step 1: create an array with all projects (with duplicates) -->
{% for page in site.pages %}
{% if page.project %}
{% capture tmp %}{{ tmp }}#{{ page.project }}{% endcapture %}
{% endif %}
{% endfor %}
{% assign allprojects = tmp | remove_first: '#' | split: '#' | sort %}
<!-- Step 2: create an array of unique projects (without duplicates) -->
{% for project in allprojects %}
{% unless tmp2 contains project %}
{% capture tmp2 %}{{ tmp2 }}#{{ project | strip }}{% endcapture %}
{% endunless %}
{% endfor %}
{% assign uniqueprojects = tmp2 | remove_first: '#' | split: '#' | sort %}
<!-- Step 3: display unique projects -->
<h1>Projects:</h1>
<ul>
{% for project in uniqueprojects %}
<li>{{project}}</li>
{% endfor %}
</ul>
In the end, step 3 will generate the following HTML...exactly as requested:
<h1>Projects:</h1>
<ul>
<li>1</li>
<li>2</li>
</ul>
I moved from Jekyll pre-1.0 to 2.0 recently.
In my original code, on each blog post it will list all the title of posts that belongs to the same category as the current post being viewed. Previously this code worked:
{% for post in site.categories.[page.category] %}
<li {% if page.title == post.title %} class="active" {% endif %}>
{{ post.title }}</li>
{% endfor %}
However in the new version this does not work and I have to specify the category individually like so:
{% for post in site.categories.['NAME_OF_CATEGORY'] %}
Why can't I dynamically check for the category as before? And is there a work around for this instead of using if statements?
I figured it out. I had, in each post, my YAML front-matter category variables in uppercase or Camel case. Example: category: ABC or category: Zyx.
Doing page.category will always return the the actual category as it was written in the front-matter, which is ABC or Zyx. However site.categories.[CAT] only accepts CAT in lower cases (down case in liquid language).
Hence this will work site.categories.['abc'] or site.categories.['xyz'].
But this will fail site.categories.['ABC'] or site.categories.['Xyz']. It is the same as doing site.categories.[page.category].
Solution. Assign the current page category in lower case like so:
{% assign cat = page.category | downcase %}
{% for post in site.categories.[cat] %}
<li {% if page.title == post.title %} class="active" {% endif %}>
{{ post.title }}</li>
{% endfor %}
I want to loop over all posts that are assigned category "foo" and category "bar" ..
{% for post in site.categories.foo and in site.categories.bar %}
Is this possible?
In my case "foo" as a "parent" category to "bar" ... /foo/bar/_posts
Thanks
Instead of looking through every post and matching with an or, you can filter by the first tag and then look for the second (and third, fourth, fifth...) tags:
{% for post in site.categories.fizz%}
{% if post.categories contains "buzz" and post.categories contains "bang" %}
<!--post has categories fizz AND buzz AND bang-->
<li>{{ post.title }}</li>
{% endif %}
{% endfor %}
This is a little more efficient than iterating over every single post, and it sets up an and relationship instead of an or relationship.
It is fully possible: loop over all posts, and then select the wanted posts:
{% for post in site.posts %}
{% if post.categories contains "foo" or post.categories contains "bar" %}
<li>{{ post.title }}</li>
{% endif %}
{% endfor %}
Using the Where Expression filter in Jekyll.
Jekyll Liquid Filter Docs
Where Expression,
Select all the objects in an array where the expression is true.
3.2.0
{{ site.members | where_exp:"item", "item.projects contains 'foo'" }}
So on my site I did:
_includes/clippings.html
...
{% capture _filter %}item.tags contains '{{ include.tag }}'{% endcapture %}
{% for clip in site.clippings | where_exp: 'item', _filter %}
{{ clip.do_stuff }}
# more html and stuff
{ endfor }
{% include clippings.html tag='foo' %}
In this case I need to specify the filter tag dynamically. And clippings is just a collection like posts.
If you want to filter by multiple static tags you could do something like:
{% for post in site.posts | where_exp: 'item', "item.tags contains 'foo'" | where_exp: 'item', "item.tags contains 'bar'" %}
{{ post.do_stuff }}
{ endfor }
If you want to do multiple dynamic filter tags then you will need to do something similar to the capture stuff I did above.
I have not tested this, but it should filter posts by an arbitrary amount of filter tags.
{% assign posts = site.posts %}
{% filter_tags = 'foo, bar, buzz' | slipt: ', ' %}
{% for tag in filter_tags %}
{% capture _filter %}item.tags contains '{{ tag }}'{% endcapture %}
{% assign posts = posts | where_exp: 'items', _filter %}
{% endfor %}
{% for post in posts %}
{{ post.do_stuff }}
{% endfor %}
However looping over the whole thing once and checking each post might be more efficient at that point.