Can a section's pages be sorted by arbitrary frontmatter variables?

This post from 2015 suggests that it’s not possible to sort by arbitrary frontmatter variables. Since it’s been two years, I was wondering if that’s still the case.

I understand that weight can be used to resolve this by generating the appropriate order, but that’s not particularly user-friendly and it discards important semantics that are already present in the front-matter.

Is there a better way to do this?

Looking at this further, currently you can sort a list of posts like this:

{{ range where ((sort .Site.Pages.ByDate) "Section" "recipe")  }}

This is possible because pageSort.go defines a number of sorting functions you can pass to sort. For example, ByDate is:

func (p Pages) ByDate() Pages {
	key := "pageSort.ByDate"

	date := func(p1, p2 *Page) bool {
		return p1.Date.Unix() < p2.Date.Unix()
	}

	pages, _ := spc.get(key, p, pageBy(date).Sort)

	return pages
}

This suggests that if we wanted to sort by arbitrary frontmatter variables, it could be possible to write something like ByFrontmatter:

func (p Pages) ByFrontmatter(keyName string) Pages {
	key := "pageSort.ByFrontmatter." + keyName

	keyComparator := func(p1, p2 *Page) bool {
		return p1.Params[keyName] < p2.Params[keyName]
	}

	pages, _ := spc.get(key, p, pageBy(keyComparator).Sort)

	return pages
}

Because this method takes a parameter, that presumably means your sort invocation in the template would have to change a little. For example, sorting your recipes by date looks like this:

{{ range where ((sort .Site.Pages.ByDate) "Section" "recipe")  }}

but maybe sorting your recipes by their star rating looks like this:

{{ range where ((sort (.Site.Pages.ByFrontmatter "rating")) "Section" "recipe")  }}

I’m not an experienced Go developer so I wouldn’t mind a sanity check on this. Is this a plausible approach? If so I’d be willing to submit a PR.

@jxf This would be a great feature, and unfortunately I can’t help you much. That said, I would recommend using ByParam rather than ByFrontMatter…

I decided to take a crack at writing this myself. A proposed pull request is up:

https://github.com/spf13/hugo/pull/3022

1 Like

This is a great addition.

Is it then also possible to use nested parameters?
Example:

{{ range (.Data.Pages.ByParam "menu.main.weight") }}

1 Like

@jxf I’d like to understand what this new feature does. Can you please give an example that would not have worked before this PR merge?

This tweet says “sort pages by Top 10”, but it’s not clear how to implement that… What decides which pages should be in Top 10?

Thanks.

Prior to this PR, you could only sort using mechanisms that were explicitly provided: sort by date (.ByDate), sort by title (.ByTitle), and so on. But what if you wanted to sort by some arbitrary frontmatter key? Maybe you have a bunch of recipes and they all have a zero to five star rating, indicated by a key named rating. Hugo had no way of understanding custom key-based sorts like this.

Now you can sort by any arbitrary key that might exist in your frontmatter, using the ByParam sort introduced in this PR. Does that make sense?

You decide! It’s based on the value of whichever frontmatter parameter you specify should be determining the sort order.

[quote=“36928495, post:5, topic:5358, full:true”]
This is a great addition.[/quote]

Thank you for the kind words!

Short answer: yes!

Long answer: Originally Hugo would have interpreted this as a top-level key named menu.main.weight, rather than a top-level key named menu, pointing to a map with a key named main, pointing to a map with a key named weight. However, I also wrote a different PR that addressed this, and Hugo now understands nested parameter references as arguments to Param and similar methods:

1 Like