Mastodon Creating collections in 11ty by two keys
  1. Home
  2. About
  3. Blog

Creating collections in 11ty by two keys

I added /movies to my website which lists all the movies I watched thus far. I gathered all the watch information from my plex server and then wrote a small script to turn those into individual markdown files which I could further edit.

The .md files

A movie markdown file looks something like this: ./src/movies/2024/09/src/media/movies/2024/09/20-the-kings-speech.md

---
tags: "movie"
title: "The King's Speech"
date: "2024-09-21T03:53:15.000Z"
poster: "https://image.tmdb.org/t/p/original/pVNKXVQFukBaCz6ML7GH3kiPlQP.jpg"
rating: "4"
---

As you can see, all I have in there is some front matter. I could always add some more content in the future should I wish, but for now all I care to do is record the movie and perhaps add my own rating for it.

To display these movies in 11ty you need to create a collection and then further use that collection in a template. Most tutorials show you how to do this for blog posts, which list out the posts and then have links to the individual post themselves.

The Javascript

I however, would like to show all the movies I watched in a months as a row, separated by month and further separated by year. Here's the magic sauce:

./.eleventy.js

module.exports = function (eleventyConfig) {
    // Your other stuff here

    // Create a new collection
    eleventyConfig.addCollection("moviesByYearAndMonth", (collectionApi) => {
        // Filter all collections by the tag (this is defined in the markdown file front matter)
        const movies = collectionApi.getFilteredByTag("movie");

        // Create an object which we can add to
        const payload = {};

        // Loop over all items tagged as "movie"
        for (let movie of movies) {
            // Grab the year from the date field (also defined in the front matter)
            const yearKey = movie.date.getFullYear();

            // Grab the month
            const monthKey = movie.date.toLocaleString("default", {
                month: "long",
            });

            // If the payload object does not have the year, add it
            if (!payload?.[yearKey]) {
                payload[yearKey] = {};
            }

            // If the payload object has neither the year nor the month within, add it
            if (!payload?.[yearKey]?.[monthKey]) {
                payload[yearKey][monthKey] = [];
            }

            // Now that we're sure the object path exists, add it to the front of the array
            payload[yearKey][monthKey].unshift(movie);
        }

        // Make the nested list available
        return payload;
    });
}

The Template

Now finally, you can use this collection in your template. I'm using Liquid here, but the same idea applies to any other templating markup you choose.

First you need to create your page movies.html with the following front matter:

./src/movies.html

---
layout: base-layout.html
pagination:
    data: collections.moviesByYearAndMonth
    size: 1
title: "Movies"
---

Then add whatever other HTML you need and iterate over your collection like so: ./src/movies.html

{% for year in collections.moviesByYearAndMonth reversed %}
    <div class="year-wrapper">
        <h3>{{ year[0] }}</h3> <!--The [0] here refers to the keys name which is the year -->
        {% for month in collections.moviesByYearAndMonth[year[0]] reversed %}
        <div class="month-wrapper">
            <h3>{{ month[0] }}</h3> <!--Same idea here for month -->
            <ol>
                {% for movie in
                collections.moviesByYearAndMonth[year[0]][month[0]] %} <!--This double selection to get to each movie, for the current month in the current year -->
                <li class="movie-list-item">
                    <!-- You do you here -->
                </li>
                {% endfor %}
            </ol>
        </div>
        {% endfor %}
    </div>
{% endfor %}

En viola! You have A Movie Listing Page