Ryan Jones

Indexing Categories or Tags in Jekyll without a Plugin

Jekyll is a very robust system, and one thing I was trying to find in this system is a way to tag or categorize a post, and then display all the posts pertaining to that tag. Plugins work well, not necessarily for those on github pages. I was inspired by other posts on the subject, and figured it would work for me…

Front Matter

The _config.yml does need to contain your collection. The big difference for me was determining plurality of the object in question.

# _config.yml
collections:
  categories:
    output: true

This was also a good time to default those pages to the category.html layout.

# _config.yml
defaults:
  - scope:
      path: "_categories"
    values:
      layout: "category"

Making the Categories

Adding the path means a I need a folder to story the category details in. The output: true setting above autogenerated items for me. To get the details I want, or to have category names like “Ruby on Rails” with a nice URL of ruby-on-rails, I wanted more control over the process. I will create markdown files for each category needed. While not being difficult, it is an extra step to be aware of for future categories.

I created a _categories folder and added .md files for each category needed, such as for Swift.

# _categories/swift.md
---
tag: swift
title: "Swift"
permalink: "/categories/swift/"
---

Listing Posts in a Category

The default layout was defined in the Front Matter. The categories.html file is where we’ll “take a page” from the _categories markdown files and filter through all the posts on the site; ensuring that the categories assigned to a post contain the tag value (the tag value I made up and corresponds to the URL in the permalink). Then we render out the post in a html snippet that’s reused elsewhere on this site.

1
2
3
4
5
6
7
8
# layouts/category.html
{% for post in site.posts %}
  {% for c in post.categories %}
    {% if page.tag == c %}
      {% include components/excerpt_post.html %}
    {% endif %}
  {% endfor %}
{% endfor %}

While this suffices, I am also forward thinking and could have many categories that I will very unlikely write about. No need for those to show up! This mess of liquid tags makes me long for a Swift map/filter method.

Assigning a Post to a Category

Now setting a category variable in a post markdown file yaml header, will assign that post to the category. For the sake of the code in this article, I use the tag value set in the category md for matching.

# _posts/YYYY-MM-DD-post-file.md
---
categories:
  - jekyll
  - swift
---

Listing the Categories

Almost done, I promise. Linking to these category pages is easy, especially with liquid. Now I want to list all the categories, except only the categories that have associated posts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# categories/index.html
{% for post in site.posts %}
  {% assign c = post.categories | join: "," %}
  {% if c != "" %}
    {% capture categories %}{{categories}},{{c}}{% endcapture %}
  {% endif %}
{% endfor %}

{% assign categories = categories | split: "," | uniq | sort: self %}
{% assign catobjs = site.categories | sort: "tag" %}
{% for category in catobjs %}
  {% for cat in categories %}
    {% if cat == category.tag %}
      <h1><a href="{{category.permalink}}">{{category.title}}</a></h1>
    {% endif %}
  {% endfor %}
{% endfor %}

Lines 2-7 run through all of the posts on the site, and take the array of categories and turn it into a comma separated string. You can easily print out the result with

{{ categories | jsonify }}
# ex output: ",jekyll,swift,ruby-on-rails,xcode,jekyll,update"

We’ve got some duplicates in there, and it is still a string, not an actionable array. We fix that with line 9 that turns the string into an array, separating values by the comma added above, and makes sure to remove the duplicates, and then, finally, alphabetizes them. And then assigns back to the categories variable as an array of sorted categories.

Then on line 10, we get all of the category objects on the site, sort them alphabetically by the tag value, and assign to the catobjs object.

Then the for loop on line 11-17 compares both arrays, and if the array we created (categories) finds a match in a catobjs tag, then we output the link to the category page that will list the posts for a category. Done!

Being Concluded

You can see the result of all of the code above on the categories page on this site. It took less time to make it than it did to write this post about it.