Idiomatic means to determine if PAGE.TableOfContents is empty?

I wanted to make a TableOfContents render (or not) based on whether or not it exists. For that, I created a partial that I can call in my articles, but I was wondering if there was a more efficient way.

Right now, I can detect an ‘empty’ table of contents through this little bit of code:

{{ $toc := .TableOfContents -}}
{{ if eq (len ($toc | plainify)) 0 }}
  {{ $toc = false }}
{{ end }}
{{ with $toc }}
...

But, I’m left feeling that it’s not a great way to detect whether or not the table of contents is actually empty. Sure, an empty table of contents should plainify to an empty string, but I don’t see anything that would guarantee it. And it still generates a nav container even if empty, so I can’t just directly test the truthiness of it; and I don’t like the idea of testing if ($toc | plainify) directly.

I wasn’t sure if there was a way to alter the template that is actually produced by .TableOfContents, or iterate through the headings so that I can generate my own. I suppose in theory I could modify my _markup/render-heading.html to log all of the headings in the scratchpad.

Has anyone else done something similar, and if so, what?

You may find this helpful: Fragments

1 Like

untested, right from the bat:

.Page.Fragments.Headings returns a list of headings.

no headings = empty slice = false

so {{ if .Page.Fragments.Headings }} schould do the trick.

1 Like

I use this code in my website:

{{ if in .TableOfContents "li" }}
    <aside class="toc">
        <details>
        <summary>Table of contents</summary>
            {{ page.TableOfContents }}
        </details>
    </aside>
{{ end }}
1 Like

The approach described by @nfriedli is the simplest and most accurate in that it honors the start and end levels defined in the TOC configuration.

If you use PAGE.Fragments you will need to hardcode the start and end levels; those config settings are not exposed to the templates, e.g.,

{{ $headingsInTOC := where (sort .Fragments.HeadingsMap) "Level" "in" (slice 2 3 4) }}

{{ if gt (len $headingsInTOC) 2 }}
  {{ .TableOfContents }}
{{ end }}

The first line is equivalent to:

{{ $headingsInTOC := slice }}
{{ range .Fragments.HeadingsMap }}
  {{ if in (slice 2 3 4) .Level }}
    {{ $headingsInTOC = $headingsInTOC | append . }}
  {{ end }}
{{ end }}

The shorthand version works because the sort function eats first-level keys.

2 Likes

Thanks all for the suggestions. Originally, I had put this in ‘Tips & Tricks’ because I’m not necessarily looking for a ‘right’ answer, seems there are quite a few ways to do it.

Personally, I like the idea of checking the .Fragments.Headings. If it’s an empty slice then there are no headings. @jmooring pointed out that PAGE.Fragments won’t pay attention to the TOC config, but it seems unlikely that you’d have a level 4+ heading without a 2/3 heading (although there is the more likely case that you include a level 1 heading).

Admittedly though, I’m inclined to effectively re-implement the TOC rendering entirely as a template/partial instead, as the PAGE.TableOfContents method doesn’t seem to allow customization whatsoever (other than choosing an ordered/unordered list, and start/stop heading levels). Right now, I have to modify its display via CSS, but I’d rather apply those styles directly (i.e. via Tailwind utility classes, which I realize is still CSS).