Eleventy: Is it possible to sort a collection by front-matter values?

Created on 3 Feb 2020  ·  8Comments  ·  Source: 11ty/eleventy

Let's say I have a template located at _items/my-awesome-post.md with the following front matter:

title: My awesome post
order: 1
---

Content

I've loaded this template into a collection using getFilteredByGlob as referenced in the documentation:

eleventyConfig.addCollection("items", function(collection) {
    return collection.getFilteredByGlob("_items/**/*.md");
});

I'm wondering if it's possible to sort these collection items by the order property in my front-matter via my liquid templates as below or whether there's another way to do it via the liquid template engine or the Eleventy API. I've already tried doing the following and it doesn't appear to return anything.

{% assign items_by_order = collections.items | sort: "order" %}
{% for item in items_by_order %}

I also tried using the sort filter to grab the data but that didn't return anything either {% assign items_by_order = collections.items | sort: data.order %}

education

Most helpful comment

Based on how the docs recommend to use collections.foo | reverse rather than collections.foo.reverse(), due to the former mututating the collection, I decided to create a custom filter, and found that it works, and also, subsequent iterations over the collection without this filter use the default ordering (didn't seem to get mutated).

function sortByOrder(values) {
    let vals = [...values];     // this *seems* to prevent collection mutation...
    return vals.sort((a, b) => Math.sign(a.data.order - b.data.order));
}

eleventyConfig.addFilter("sortByOrder", sortByOrder);

I like this approach as I can use it on any of my collections, and I have lots of collections, and I don't want have to do a eleventyConfig.addCollection("orderedFoos")... for each of them.

I do admit I'm a bit nervous since docs go into using the collections API for custom sorting rather than suggesting a simple approach such as this. Do others think this approach is inadvisable?

All 8 comments

Solved it (for now at least)! Ended up using the collections API to sort the array of returned posts, but wasn't sure if there was a way to do it within a template.

eleventy.addCollection('items', function(config) {
    return collection.getFilteredByGlob("_items/**/*.md")
        .sort((a, b) => b.data.order - a.data.order);
});

Additionally in my search I came across #338 which touched on the eleventy data structure some, and also lead me to this area of the documentation: https://www.11ty.dev/docs/collections/#collection-item-data-structure. As someone porting his site over from Jekyll, it was very frustrating to try to figure out what data was available to grab from a returned collection object and how it was namespaced (ie, confusing that item.url isn't namespaced but item.data.title is namespaced.

Is work being currently done to improve the documentation on global variables/data objects available in templates/collections? If so, I'd be happy to contribute where needed to help make their structure clearer.

Sorting via JavaScript is the best way that I've found. One option is directly in the .addCollection method, as you've done above. This works for collections that will always be ordered by a single property, like order or title.

For collections that might need to be sorted by different properties in different contexts, I think you could sort the collection with JavaScript in a JavaScript template, which ends in .11ty.js. I'm not sure if you can directly access the collections object without somehow require()ing it first... I'll post an update if I find a way to do this.

Based on how the docs recommend to use collections.foo | reverse rather than collections.foo.reverse(), due to the former mututating the collection, I decided to create a custom filter, and found that it works, and also, subsequent iterations over the collection without this filter use the default ordering (didn't seem to get mutated).

function sortByOrder(values) {
    let vals = [...values];     // this *seems* to prevent collection mutation...
    return vals.sort((a, b) => Math.sign(a.data.order - b.data.order));
}

eleventyConfig.addFilter("sortByOrder", sortByOrder);

I like this approach as I can use it on any of my collections, and I have lots of collections, and I don't want have to do a eleventyConfig.addCollection("orderedFoos")... for each of them.

I do admit I'm a bit nervous since docs go into using the collections API for custom sorting rather than suggesting a simple approach such as this. Do others think this approach is inadvisable?

I like this approach as I can use it on any of my collections, and I have lots of collections, and I don't want have to do a eleventyConfig.addCollection("orderedFoos")... for each of them.

I do admit I'm a bit nervous since docs go into using the collections API for custom sorting rather than suggesting a simple approach such as this. Do others think this approach is inadvisable?

I like that approach, doing the sorting in Eleventy filters. That makes sense because it's bananas to create new collections for every single sort variation of every single collection. Your filter approach allows any kind of sorting on any kind of collection, as needed. 👍

I think doing the ordering inside the collection creation, as advised in the docs and as I mentioned above, only makes sense when you know a particular collection will _always_ be sorted a particular way.

That's what I think. I'm curious how others are approaching this.

@paulshryock Glad to hear at least one person doesn't think I'm on crack.

What still makes me nervous is, if the solution really is this simple, why didn't someone suggest something similar months ago? I'm definitely not a rocket scientist - there must be at least some downside to my approach.

@paulshryock Glad to hear at least one person doesn't think I'm on crack.

What still makes me nervous is, if the solution really is this simple, why didn't someone suggest something similar months ago? I'm definitely not a rocket scientist - there must be at least some downside to my approach.

It may just be that there are tons of issues, and no one got around to posting a reply about this. 🤷‍♂️

@paulshryock yeah, that's probably the most likely, and also that I didn't close my own issue after solving it 😂

@ckot, that's a sweet solve, love the idea of filters as a method of sorting...

i'll close this now since it seems like there's a lot of good ideas but people can keep discussing if they want! cheers, yall!

I wanted to sort a bunch of pages, each about a different person, by the name of each person. (This is just to add to what others wrote above in the hope that it might help others like myself who happen up this issue.)

I came up with this function:

function sortByName(values) {
  return values.slice().sort((a, b) => a.data.sortName.localeCompare(b.data.sortName))
}

(I'm no JavaScript expert, but slice() seems like a reasonable alternative to the spread (...) mentioned by @ckot. I'm not sure which is better, but l prefer the syntactic simplicity of .slice().)

I added it to the Eleventy configuration (.eleventy.js) with:

module.exports = (config) => {
  config.addFilter('sortByName', sortByName)
}

And I added a sortName field to the frontmatter of each Markdown document in my people-tagged collection. For example:

---
title: Epictetus
tags: people
sortName: Epictetus
---

To test it, I did this:

<ul>
{%- for person in collections.people | sortByName -%}
  <li><a href="{{ person.url }}">{{ person.data.title }}</a></li>
{%- endfor -%}
</ul>

<ul>
{%- for person in collections.people | sortByName | reverse -%}
  <li><a href="{{ person.url }}">{{ person.data.title }}</a></li>
{%- endfor -%}
</ul>

I saw what I expected, so I guess it worked.

Was this page helpful?
0 / 5 - 0 ratings