New site part 5 - post categories
Posted on 11 November, 2023
Part of a series about setting up the new 11ty version of this site
One of the most common problems encountered when moving from WordPress to 11ty — apparently
— is tagging posts, not least because 11ty already has the concept of tags.
There are some solutions out there but they all seemed fairly complicated, so I've written my own. I've added the concept of categories for posts, which are generated at build time.
We have to make an edit in the config file to add a new collection called categories:
// eleventy.js - a new require and a new collection
const { generateCategoriesCollection } = require('./utils/categories');
module.exports = function(eleventyConfig) {
...
// add the categories collection
eleventyConfig.addCollection('categories', (collection => {
const categoriesCollection = generateCategoriesCollection(collection);
return categoriesCollection;
}))
...
}
And then we need to create the generateCategoriesCollection() function:
The process is:
- Generate an array of the unique category names
- Use that array to generate an array of category objects, which have a title (the category name) and a posts property which is an array of all the posts tagged with that category
// src/utils/categories.js
const generateCategoriesCollection = (collection) => {
// get all the posts ...
const allPosts = collection.getFilteredByTag("post");
// create an empty array for the category name strings
let categoryNames = [];
// run through all the posts, get category names, turn to lower text
// and add them to the array
for (let post of allPosts) {
const catNames = post.data.categories;
if (catNames && catNames.length > 0) {
for (let i = 0; i < catNames.length; i++) {
categoryNames.push(catNames[i].toLowerCase())
}
}
}
// generate an array from a set of the array (needlessly
// complicated way to get unique values only)
categoryNames = Array.from(new Set(categoryNames)).sort();
// create an empty array for the category objects
const categories = [];
// now we have an array of unique category names, cycle through
// them to make category objects
for (let categoryName of categoryNames) {
// first make a new object with the property title
const category = {
title: categoryName
}
// generate an array of all the posts that are 'tagged'
// with this category
const posts = allPosts.filter(post => {
// get the categories from that post
let catNames = post.data.categories;
// if there are categories ...
if (catNames && catNames.length > 0) {
// map categories to lower-case to ensure no clashes
catNames = catNames.map(item => item.toLowerCase());
// if our category is in the categories, true; otherwise false;
if(catNames.indexOf(categoryName) >= 0) {
return true;
} else {
return false;
}
// no categories, so post can't be in THIS category
} else {
return false;
}
});
// add the array of posts (reversed date order) as a property
// of the category object ...
category.posts = posts.reverse();
// ... and add the new object to the array of category objects
categories.push(category)
}
return categories;
}
Using categories
We now have a categories collection which is available throughout our 11ty project. This is used
in two places:
- the 'tag cloud' (more on that another time); and
- category pages
I wanted to generate a page for every category with a list of all the posts that are linked to that
category. To do that, I added a file src/pages/categories.njk which paginates over the
categories collection:
// src/pages/categories.njk - pt1
// Paginating with size 1 means that each page will cover one category;
// I've aliased collections.categories as 'tag' (just to confuse things).
// Each page gets one category object, aliased as 'tag', which has a
// title (string) and a posts (array of posts)
---
title: "Categories"
layout: layouts/pages.njk
pagination:
data: collections.categories
size: 1
alias: tag
permalink: "/tags/{{ tag.title }}/index.html"
---
// A header with the category name
<h1 class="as-h2 page-header">
Tag: <span class="highlight">{{ tag.title }}</span>
</h1>
<br/>
// Iterate over each post in the posts array however you want
{%- for post in tag.posts -%}
// do something with the post ...
{%- endfor -%}