Check if current page is part of a section defined in menu slug

I am trying to find out, if there is an easier way to set my current navigation item to “active”, ie. showing the current page belonging to certain menu item.

The current situation

  1. I have a menu called main
  2. I have a section archive (folder inside of /content with an _index.md and many 2020.md, 2019.md etc.)
  3. In _index.md I define
    ---
    slug: archiv
    menu:
      main:
        identifier: archive
    ---
    
  4. in eg. 2020.md I define
    ---
    menu:
      main:
        parent: archive
    ---
    
  5. in my navigation partial I define the following:
    <li class="nav-item{{if or ($currentPage.IsMenuCurrent "main" .) ($currentPage.HasMenuCurrent "main" .) (and (eq $currentPage.Type "posts") (eq .Identifier "archive" )) (and (eq $currentPage.Kind "taxonomy") (eq .Identifier "archive" )) }} active{{end}}">
    

This is not only chaotic, but right now I don’t even know how my navigation set’s the parent item “active” on all subsequent pages for the archives section. Probably because of the parent: archive part of it’s frontmatter. And remembering to add a frontmatter menu point to the archive sub-page every time I create one (let’s just think I might introduce monthly archive pages or pages by topic/taxonomy etc.) might be problematic with increasing age.

Long story short: what is your preferred way to set a navigation item class (first level, no dropdowns) to active if a current page (on whatever level) belongs to specific section? The section name (folder name) archive is identical to the top menus identifier. I am looking for a “once and done” solution that needs no specific frontmatter or code parts after putting an .md-file into a section.

Just a guess:

Maybe, you can create a shortcode that would pass a parameter as to what the parent is and you could use JavaScript to set class?

How are you rendering the rest of your menus?

What you could do is attach only the section pages to the menu. Then, instead of attaching the child pages via frontmatter menu definition, range through the .Pages instead. See: https://gohugo.io/templates/menu-templates/#using-page-in-menus

Personally, for sites where I want all the pages to be automatically included in the nav menu, I prefer to generate the content tree instead of using Hugo Menus. Have a look at my example here: repo / demo.

hmm. that’s the problem, it’s not for a site per se but a theme that I want to open source.

The full partial (hihi) is this:

<div id="topnavigation" class="container">
  <nav class="navbar navbar-dark bg-dark navbar-expand-md">
    <button class="navbar-toggler" type="button"
      data-toggle="collapse" data-target="#topbarMainNavigation"
      aria-controls="topbarMainNavigation" aria-expanded="false"
      aria-label="Navigation umschalten">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="topbarMainNavigation">
      <ul id="menu-topnavigation" class="nav navbar-nav mr-auto">
        {{ $currentPage := . }}
        {{ range .Site.Menus.main }}
          <li class="nav-item{{if or ($currentPage.IsMenuCurrent "main" .) ($currentPage.HasMenuCurrent "main" .) (and (eq $currentPage.Type "posts") (eq .Identifier "archiv" )) (and (eq $currentPage.Kind "taxonomy") (eq .Identifier "archiv" )) }} active{{end}}">
            <a href="{{.URL}}"{{if eq .Page nil }} target="_blank" rel="noopener noreferrer"{{ end }} class="nav-link">
              {{ with .Pre }}
              <span class="icon larger light" aria-hidden="true">
                {{ partialCached (print .) .}}
              </span>&nbsp;
              {{ end }}
              {{ .Name }}
            </a>
          </li>
        {{ end }}
      </ul>
    </div>
  </nav>
  <div id="readingprogress" class="progress">
    <div class="progress-bar" role="progressbar" id="progressbar"></div>
  </div>
</div>

By the way, there is no subnavigation. That menu code in the single pages is in there just to define affiliation with a parent menu point.

I realized, that these variables might help and will test around with them. I might have over-thought that:

Assuming you have:

content/archive/_index.md
menu:
    main:
        identifier: archive

content/archive/2018.md
content/archive/2019.md
content/archive/2020.md

With only two levels of nav (parent + child), you should be able to do something like

{{ $currentPage := . }}
{{ range .Site.Menus.main }}
    
    {{ .Name }} {{/* parent / archive/_index.md */}} 

    {{ with .Page }} 

    {{ if eq . $currentPage.Parent }} active page's parent {{ end }}
    {{ if eq . $currentPage }} active page {{ end }}

    {{ range .Pages }} {{/* archive/***.md */}}
        {{ if eq . $currentPage }} active child page {{ end }}
    {{ end }}
    {{ end }}

{{ end }}

If the only parent-child relationship is strictly .Section > .Pages, you don’t need to define the menu items in individual md files. Attach the _index.md to the main menu, then range over the .Page.Pages.

1 Like

I’ll try your code. Even better (maybe, sitting on it right now): $currentPage.Section is also “archive”. Maybe it’s just checking that part vs. the current menu items page.

But I think your method might be cleaner if I ever choose to flip the menu name or add sub-menus.

I like your solution, but I decided against it :wink: When I added your code as a partial it increased the build time about five times for five menu items. That’s not worth it. I think the range .Pages is what’s killing this.

I have now a quite simple solution, but it’s not tested on all pages yet, for the ones I checked it works:

{{ $currentPage := . }}
{{ range .Site.Menus.main }}
  <li class="nav-item{{if or (eq $currentPage.Section .Identifier) ($currentPage.IsMenuCurrent "main" .) ($currentPage.HasMenuCurrent "main" .) (and (eq $currentPage.Type "posts") (eq .Identifier "archive" )) (and (eq $currentPage.Kind "taxonomy") (eq .Identifier "archive" ))}} active{{end}}">
    <a href="{{.URL}}"{{if eq .Page nil }} target="_blank" rel="noopener noreferrer"{{ end }} class="nav-link">
      {{ with .Pre }}
        <span class="icon larger light" aria-hidden="true">
          {{ partialCached (print .) .}}
        </span>&nbsp;
      {{ end }}
      {{ .Name }}
    </a>
  </li>
{{ end }}

The (eq $currentPage.Section .Identifier) is the secret stuff: If the current pages section is identical to the menu identifier then it’s a page in that section. For that to work the menu item MUST have an identifier that is the same as the sections slug. But well… better that than nothing :smiley:

By the way: I remembered. The stuff on the right side of that long or-line was for something completely different: to make the archive menu item active when we are in the posts section. This way all blog posts, taxonomy icons and now the archive section too show the archive link as active. Long live documentation.

1 Like