My issue
I wanted to include a tag list on my category pages that only shows the tags that are actually present in [pages in] the category shown.
I found a few topics, but no real answers. In the end, it wasn’t so hard, but as a new Hugo user, dealing with the context shift is a bit of a learning curve. It took me a while to figure out why my tags showed inside my range, but not outside.
TLDR;
<!-- Get the tags for all the pages the current context -->
{{ $.Scratch.Set "tags" (slice) }}
{{ range .Data.Pages }}
{{ range .Params.tags }}
{{ $.Scratch.Add "tags" . }}
{{end}}
{{end}}
<!-- Get all tags and show only the ones we grabbed in the step above -->
{{ range .Site.Taxonomies.tags.ByCount }}
{{ if in ($.Scratch.Get "tags" |sort |uniq) .Name }}
<a href="{{ .Page.Permalink }}">{{ .Page.Title }}</a> {{ .Count }}
{{ end }}
{{ end }}
How I got here
We need to deal with Hugo’s context shift. I found this article very helpful in explaining what happens, but the author mostly talks about passing variables down, and I wanted my tags to go up!
.Scratch
In the Hugo docs, .Scratch
is described as:
.Scratch
is available as a Page method or a Shortcode method and attaches the “scratched” data to the given page. Either a Page or a Shortcode context is required to use.Scratch
.
Cool! So .Scratch
attaches to the page I’m working on, and I can now update .Scratch
, and it will work! No?
Ehhh, no. Actually, it doesn’t work that way. It took me quite a bit longer than I care to admit that “page” refers to something in the current context than and not the Category page that will eventually get rendered by Hugo. So if I call .Scratch as the first thing, it’s at the root of my page. If I call it inside range .Data.Pages
, .Scratch
will live there, and f I call it in the second range, it will live there. And for every time range
loops over an item, I will get a new .Scratch
. Empty. Once that sank in, I understood why my slice ended up empty, and the solution wasn’t too hard to find.
Just give it a dollar!
In the article I mentioned above, there’s a section about the top level context, and how it’s attached to $
. So I added a dollar sign in front of my .Scratch
, like so: $.Scratch
, and lo and behold, it got filled round after round while range
looped over all the pages in my category. Hurray!
Getting the links to tags
This was easy. I started by assembling all the tags in the site (range .Site.Taxonomies.tags.ByCount
). First I thought it was a good idea to get their names and intersect those with the list I had in my .Scratch
, but then I realised I still had to look up the page objects in .Site.Taxonomies.tags.ByCount
to get the links to the tags, and was doing the same thing twice. All I needed was to check if the objects name was in my scratched list, and get the title and link. the in
function did the trick.
And there it is, a tag list for the current category. A lot of learning went into those few lines of code.
Afterthoughts
Pages and dollars
I was seriously thrown off by the meaning of Page and Pages. Just like the dot, their meaning changes with the context. And then what you’re working on is a page too, so that was seriously confusing. Finding that explanation of the $ really helped a lot. I’m sure it’s in the docs somewhere, and once you know it, it’s unlikely you’ll ever forget, but for newbies like me it isn’t an obvious thing.
Edit: found it, it’s in the Templating Introduction.
You actually don’t need .Scratch (or $.Scratch) if you use a recent version of Hugo
Hugo now supports $variables in templates, so you could use those as well. However, I think using .Scratch
and $.Scratch
isn’t a bad thing. Having to add that dollar or not really makes you think about the scope, and also gives an immediate visual clue. I’m a sysadmin and cloud engineer and have used a lot of Bash, zsh and Python. Hugo scoping is a bit different, and that dollar sure helps!