Implementing Categories

 •   •  collections eleventy

I want there to be Categories in Eleventy to organize articles into broad domains.[1] The idea is that all articles about books will go into the Culture category, articles about tech go into the Tech category, and so on.

Tags, on the other hand, can be in any category. A weird book and some weird code would be in different categories, but they could both be tagged weird.

How we’ll use them

Let’s look at how we’ll use categories:

Specifying the category

Use the category property like this to specify the category an article belongs to:

---
date: 10/30/2018
title: Loomings
category: Tech
tags:
- tools
- git
- eleventy
---

Using the category in a template

In a template, refer to the category property in the usual way:

<a href="/categories/{{category}}">{{category}}</a>

Working with articles in the same category

We can work with articles in the same category by creating a categories collection.[2] To list all of the articles in the Tech category, you could do it this way:

<ul>
{%- for article in collections.categories["Tech"] -%}
<li>{{ article.data.title }}</li>
{%- endfor -%}
</ul>

Just as collections is an object that has a property for each tag, so collections.categories has a property for each category. Each property refers to an array of articles. It looks something like this:

collections: {
all: [ items ],
categories: {
Culture: [ items ],
Life: [ items ],
Thinking: [ items ]
}
}

The implementation

We want:

Creating a list of categories

To get the list of categories, we iterate over all of the rendered templates. This code creates a collection called categoryList that contains the names of all the categories.

getCatList = function(collection) {
let catSet = new Set()

collection.getAllSorted().forEach(item =>
typeof item.data.category === "string"
&& catSet.add(item.data.category))

return [...catSet]
}

eleventyConfig.addCollection("categoryList", getCatList)

Creating a list of articles for each category

To get lists of each article in a category, we want to create an object that has a property for each category, and each property contains a list of articles of that category. In other words, we want to end up with an object that looks like this:

categories {
Culture: [article_1, article_4],
Tech: [article_3],
Life: [article_1, article_3]
}

We can use the makeCategories() function as a callback to to addCollection() to create this object. We iterate over each item that has a category property in its front matter and add it to the list for that category:[3]

makeCategories = function(collection) {
let categories = {}

// Every rendered page

collection.getAllSorted().forEach(item => {
let category = item.data.category

// Ignore the ones without a category

if (typeof category !== "string")
return

if (Array.isArray(categories[category]))
// category array exists? Just push
categories[category].push(item)
else
// Otherwise create it and
// make `item` the first, uh, item.
categories[category] = [item]
})

return categories
}

Since we want to call our collection of categories categories, we’d build it like this:

addCollection("categories", makeCategories)

So now we have a way to make a collection that has collections of its own. I use it to segregate my posts into unique buckets.


  1. That's what I say now. The original impetus for this was to figure out how collections work. ↩︎

  2. You can use any name you like. I happen to like "categories". ↩︎

  3. This if (Array.isArray(categories[category])) thing is really stupid. Isn’t there a way to push to an array, creating it if it doesn’t exist. ↩︎

← back to articles