7

Edit: I've created a repository here that tests jibe's answer below. I just end up getting a blank page when I visit /animals, so any help is appreciated!

This is a follow-up to this question: Hierarchical Categories in GitHub Pages

In that question, I found out how to list the posts of a particular hierarchical category. Now I'm trying to figure out how to list the subcategories of a particular hierarchical category.

I'm using Jekyll on GitHub pages, and I want to have hierarchical categories like this:

  • animals -> mammals -> cats -> _posts -> housecat.md, tiger.md
  • animals -> mammals -> dogs -> _posts -> poodle.md, doberman.md
  • animals -> reptiles -> lizards -> _posts -> iguana.md, chameleon.md

I'd like users to be able to visit /animals and see a listing of the subcategories (mammals and reptiles). Then if they go to /animals/mammals, they'd see cats and dogs listed as subcategories.

I'm currently doing this manually by putting an index.html file inside each subcategory. But that makes updating things much more complicated than it probably should be.

I've tried following this answer, but that's meant for single tags, not multiple categories.

The catch is that any particular category might not be unique, so I can have stuff like this:

  • animals -> mammals -> bats -> _posts -> vampire.md, fruit.md
  • sports -> baseball -> bats -> _posts -> wiffle.md, teeball.md

I'd also like to be able to define frontmatter attributes in the subcategories, maybe in the index.html file of each? For example the animals->mammals->bats->index.html file would contain a frontmatter variable thumbnail with a value of "VampireBat.jpg", and sports->baseball->bats->index.html would have a thumbnail of "YellowWiffleBat.png". I'd like to be able to access these variables from the parent level (to show a thumbnail and a link to the subcategory).

My first thought was to access the subcategories directly, like this:

{% for mammalType in site.categories.animals.mammals %}
   <p>{{ mammalType.title }}</p>
   <img src="(( mammalType.thumbnail }}" />
{% endfor %}

Which I'd generalize using the categories from the page itself:

{% for subcategory in page.categories %}
   <p>{{ subcategory.title }}</p>
   <img src="(( subcategory.thumbnail }}" />
{% endfor %}

But that doesn't work at all, since site.categories.whatever is a list of all of the posts in that category, ignoring any hierarchical information.

Is there a better way to approach this other than doing it manually?

Community
  • 1
  • 1
Kevin Workman
  • 41,537
  • 9
  • 68
  • 107

2 Answers2

1

As it was suggested in my deleted answer, I post an improved version of my answer of your previous question. I also add information to answer your new questions (also deleted) :

Thanks for the reply. This almost works, but it's got a few problems. Most importantly, it doesn't support subcategories with the same name (like animals->bats and baseball->bats). It also lists every subcategory and every post under a particular category. I only want to list the subcategories, not the posts. Is there a way to modify your approach to meet those requirements? – Kevin Workman yesterday

Modify your _config.yml accordingly

collections:
    animals:
        output: true
        mammals:
            output: true
            cats:
                output: true
            dogs:
                output: true
        reptiles:
            output: true
            lizards:
                output: true

then created the structure:

mkdir -p _animals/reptiles/lizards
mkdir -p _animals/mammals/cats
mkdir _animals/mammals/dogs

add your md files and all index.html you need to make the list you want. which will index items with filter. From the top directory, your animals collection should look like this (with index.html in each folder) :

cleaner

root/
└── _animals/
    ├── index.html
    ├── mammals
    │   ├── cats
    │   │   ├── housecat.md
    │   │   └── tiger.md
    │   ├── dogs
    │   │   ├── doberman.md
    │   │   └── poodle.md
    │   └── index.html
    └── reptiles
        └── lizards
            ├── chameleon.md
            └── iguana.md

new you can list only subcategories with or without going deeply (with an optional parameters) _includes/list_subcategories.html

 {% assign animals = site.animals| sort:'title' %}
 {% assign from = page.url | remove: '/index.html' %}
 {% assign deep = (page.url | split: '/' | size) + 1 %}
 {% for animal in animals %}
 {% assign d = animal.url | remove: '/index.html' | split: '/' | size %}
 {% if animal.url != page.url and animal.url contains from and animal.url contains "index" and (include.dig or deep == d) %}
 <a  href={{ animal.url | prepend: site.baseurl }}>{{animal.title}}</a>
 {% endif %}
 {% endfor %}

improved similarly to list animals _includes/list_animals.html

{% assign animals = site.animals| sort:'title' %}
{% assign from = page.url | remove: '/index.html' %}
{% assign deep = (page.url | split: '/' | size) + 1 %}
{% for animal in animals %}
{% assign d = animal.url | remove: '/index.html' | split: '/' | size %}
{% if animal.url contains "index" or animal.url == page.url %}
{% else %}
    {% if animal.url contains from  and (include.dig or deep == d) %}
    <a  href={{ animal.url | prepend: site.baseurl }}>{{animal.title}}</a>
    {% endif %}
{% endif %}
{% endfor %}

list all subcategories and all animals in animals/index.html :

---
title: animals
---
{% include list_subcategories.html dig=true %}
<hr>
{% include list_animals.html dig=true %}

For example, to list all mammals and subcategoeries in animals/mammals/index.html :

---
title: animals
---
{% include list_subcategories.html %}
<hr>
{% include list_animals.html %}

Finally the generated structure should look like this (with some more index.html):

cleaner

root/
├─ _animals/
│   └─── ...
└── _site
    └── animals
        ├── index.html
        ├── mammals
        │   ├── cats
        │   │   ├── housecat.html
        │   │   └── tiger.html
        │   ├── dogs
        │   │   ├── doberman.html
        │   │   └── poodle.html
        │   └── index.html
        └── reptiles
            └── lizards
                ├── chameleon.html
                └── iguana.html

it solves your question. I changed from taxonomy to dig, but you could also have distinguished between animals->bats and baseball->bats by putting taxonomy="baseball/bats" or taxonomy="animals/bats".

jibe
  • 578
  • 1
  • 4
  • 12
  • Again, thanks for your reply. I'm trying to get this working, but when I go to `/examples`, I just get a blank page. Do you have this working somewhere? – Kevin Workman Sep 09 '16 at 14:22
  • I've set up a repository that follows your approach, but I still get a blank page if I go to `/animals`: https://github.com/KevinWorkman/GitHubPagesTest – Kevin Workman Sep 09 '16 at 16:01
  • 2 things interfere in your _config.yml. 1. you have to remove `permalink: /:categories/:slug`  2. you need to add `baseurl: "/GitHubPagesTest"`  Moreover it is not required to have the date before the name of the markdown file. – jibe Sep 09 '16 at 16:49
  • Thanks for the pull request. This still doesn't do what I'm trying for though. The `/animals` page should list `/animals/mammals` and `/animals/reptiles`, but instead it lists all of the animals specified in the `.md` files. Also, I need the `permalink` for regular blog posts and whatnot. – Kevin Workman Sep 09 '16 at 17:04
  • If you want /animals to list only animals of the same level, you should not use dig option and it will list animal that are directly in _animals folder. The dig option lists all animals belonging to animals hierarchy. If you want to list reptiles and mammals, you should add index.html in both of them. You could also change dig option to be a parametric (i.e. suppose dig=2 then it will mean start at the current level and go 2 level further), it won't be difficult to make this change from what you have now. – jibe Sep 09 '16 at 17:28
  • If I remove the `dig` option, then I get a blank page when I navigate to `/animals`. To be clear, I'm trying to link to the **subcategories** directly under `animals`, not the posts inside them. I shouldn't see every mammal under `/animals`, I'm trying to just show a single link to the `/animals/mammals` subcategory (which will then show `/animals/mammals/cats/` and `/animals/mammals/dogs`, **not** the posts under `cats` and `dogs`, just the subcategories themselves). I've updated the repository to show that removing dig results in a blank `/animals` page. – Kevin Workman Sep 09 '16 at 17:36
  • My bad I let a too strong condition here in the pasted coded. The new PR corrects it. Then if you only want to list subcategories, you have to not use `{% include list_animals.html %}` in `_animals/index.html`. If you want other subcategories to appear in `/animals` you just have to add a bunch of `index.html` in your hierarchy. like `_animals/mammals/index.html`, `_animals/mammals/cats/index.html`, etc. – jibe Sep 10 '16 at 08:11
  • This is 90% of what I'm looking for, and I've updated the repository. But is there a way to get this working with the `permalink` property set in the `_config.yml` file? This is all a moot point if it's going to mangle my existing urls. – Kevin Workman Sep 10 '16 at 15:11
  • Did you try it ? I just did and it seems to just work unlike the change in my first PR. Sorry about that. Well I think the answer is complete although the bounty is gone now. – jibe Sep 11 '16 at 11:46
  • Yeah I did try it, and I just get blank pages for everything. If something ends up helping me, I'll add another bounty. – Kevin Workman Sep 12 '16 at 00:21
  • for some reason, using permalink replace `index.html` by `index` in page.url. So I proposed you a new pr that solve the problem. – jibe Sep 12 '16 at 14:13
  • I really appreciate all the effort you went to try to solve my problem. I'm upvoting this (and your answer on my other question), but I'm going to go with simpyll's answer because it does what I want without changing my categories into a collection. Thanks again for all your research. – Kevin Workman Sep 13 '16 at 20:56
1

See simpyll.com for demo

See github for website code

assign var page_depth as the current pages depth using the path '/' as a count variable

{% assign page_depth = page.url | split: '/' | size %}

assign var page_parent as the slug of the last directory housing 'index.md'

{% assign page_parent = page.url | split: '/' | last %}

loop through every page in the website

{% for node in site.pages offset:1 %}

skip website root

{% if node.url == '/' %}
{{ continue }}
{% else %}

remove backslashed from each page in website

{% assign split_path = node.url | split: "/" %}

assign var node_last for each page in website

{% assign node_last = split_path | last %}

assign var node_parent as the slug of the last directory housing 'index.md' for each page in website

{% assign node_parent = node.url | remove: node_last | split: '/' | last %}

assign node_url for each page in website

{% assign node_url = node.url %}

loop through each slug in each page path in website

{% for slug in split_path offset:1 %}

assign var slug as the name of each slug therefore giving it a name

{% assign slug = slug %}

assign slug_depth with a forloop.index

{% assign slug_depth = forloop.index %}

close for

{% endfor %}

obtain sub-directories for every page in website comparing depth and parent of current page to that of every other page in website

{% if slug_depth == page_depth and page_parent == node_parent %}<li><a href="{{ node_url }}">{{ slug }}</a></li>{% endif %}

obtain sub-directories for root (which we skipped early in this script). we can use depth alone to define this.

{% if slug_depth == 1 and page.url == '/' and slug != 'search.json' and slug != 'sitemap.xml' %}<li><a href="{{ node_url }}">{{{slug}}</a></li>{% endif %}

close if and for

{% endif %}
{% endfor %}

altogether:

  {% assign page_depth = page.url | split: '/' | size %}
  {% assign page_parent = page.url | split: '/' | last %}
  {% for node in site.pages offset:1 %}
  {% if node.url == '/' %}
  {{ continue }}
  {% else %}
  {% assign split_path = node.url | split: "/" %}
  {% assign node_last = split_path | last %}
  {% assign node_parent = node.url | remove: node_last | split: '/' | last %}
  {% assign node_url = node.url %}
  {% for slug in split_path offset:1 %}
  {% assign slug = slug %}
  {% assign slug_depth = forloop.index %}
  {% endfor %}
  {% if slug_depth == page_depth and page_parent == node_parent %}
  <li><a href="{{ node_url }}">{{ slug }}</a></li>
  {% endif %}
  {% if slug_depth == 1 and page.url == '/' and slug != 'search.json' and   slug != 'sitemap.xml' %}
  <li><a href="{{ node_url }}">{{{slug}}</a></li>
  {% endif %}
  {% endif %}
  {% endfor %}
Simpyll
  • 26
  • 2