I’m working on a wikipedia-based site and I needed to replicate its nested categories system, like in here Category:Philosophy - Wikipedia.
It isn’t something Hugo or any other SSG provides out of the box. I did some research and found some posts here asking about related things, but none provided a solution.
I thought it was not possible to do something like that in the current build.
Fast forward a few days, I had an epiphany, read the taxonomies documentation and set out to code. After some tests it seems to be functional and I decided to share with the community. Here’s how it’s built.
Ps: it nests taxonomy terms, and not the taxonomies themselves. This was something I took a while to realize I needed to do.
Create a data file with an entry for the taxonomies (as many as you need), and under it map the tree of relations between terms, like this:
data/terms_trees.yaml
categories:
termA:
termA1:
termB:
termB2:
termB11:
termB111:
termB1:
termB21:
termB22:
aeste:
Important!
The taxonomy entry has to be its exact plural, and each term must have a “:” after it’s name to indicate it’s a map, otherwise the code will break.
And if you put the data file inside a folder in your data folder you will have to modify the code accordingly to point it to the right place, which shouldn’t be hard.
Then create a partial to work like sort of a recursive function.
partials/functions/list-subterms.html
{{ $target := .target }}
{{ $page := .page }}
{{ $singular := index .page.Data "Singular" }}
{{ $plural := index .page.Data "Plural" }}
{{ $existingterms := .existingterms }}
{{ $scratch := newScratch }}
{{ range $key, $item := .start }}
{{ if eq $key $target }}
{{ $scratch.Add "targeted branch" $item }}
{{ break }}
{{ end }}
{{- partial "functions/list-subterms.html" (dict "start" $item "target" $target "page" $page "existingterms"
$existingterms) -}}
{{ end }}
{{ $targetedbranch := $scratch.Get "targeted branch" }}
{{ with $targetedbranch }}
{{ range $key, $item := . }}
{{ range $existingterms }}
{{ if eq (lower $key) . }}
{{ $scratch.Add "final list" (slice (site.GetPage (printf "/%s/%s" $plural ($key | urlize)))) }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{- $final := ($scratch.Get "final list") -}}
<!-- create a list with all uppercase letters -->
{{ $letters := split "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "" }}
<!-- range all pages sorted by their title -->
{{ range $index, $item := $final.ByTitle }}
<!-- get the first character of each title. Assumes that the title is never empty! -->
{{ $firstChar := substr $item.Title 0 1 | upper }}
<!-- in case $firstChar is a letter -->
{{ if $firstChar | in $letters }}
<!-- get the current letter -->
{{ $curLetter := $scratch.Get "curLetter" }}
<!-- if $curLetter isn't set or the letter has changed -->
{{ if ne $firstChar $curLetter }}
<!-- update the current letter and print it -->
{{ $scratch.Set "curLetter" $firstChar }}
{{ if ne $index 0 }}</div>{{ end }}
<div class="subcategory-group">
<h3>{{ $firstChar }}</h3>
{{ end }}
<ul><li><a href="{{ $item.Permalink }}">{{ $item.Title }}</a></li></ul>
{{ end }}
{{ end }}
</div>
It’s currently set up to separate the terms by initial letter, with I achieved with a very handy code I found in this same forum but I don’t remember the original author (sorry).
Then in your _default/list.html or in whatever list template you want place this little ugly monster of a code:
{{ if eq .Page.Kind "term" }}
{{ $thistax := index $.Page.Data "Plural"}}
{{ $thisterm := index $.Page.Data "Term"}}
{{ range $index, $item := .Site.Taxonomies }}
{{ if eq $index $thistax }}
{{ range $i, $j := $item }}
{{ $.Scratch.Add "existingterms" (slice $i) }}
{{ end }}
{{ end }}
{{ end }}
{{ range $datatax, $map := .Site.Data.terms_trees }}
{{ if eq $datatax $thistax }}
<h2>Sub{{ index $.Page.Data "Plural" }}</h2>
<p>This {{ index $.Page.Data "Singular" }} has the following {{ len . }} sub{{ index $.Page.Data "Plural" }}.</p>
{{- partial "functions/list-subterms.html" (dict "start" $map "target" $thisterm "page" $.Page "existingterms"
($.Scratch.Get "existingterms") ) -}}
{{ end }}
{{ end }}
{{ end }}
And that’s it! Please pardon my spaghetti code, I’m just an amateur…
Unfortunately my site isn’t ready to be put online, but if the forum allows me to edit the post later I will provide a working example here.