0

I want to create three or more levels menu like

- Category_main
    - Category_sub1
        - Category_sub1_sub1
        - Category_sub1_sub2
    - Category_sub2
        - category_sub2_sub1
        - Category_sub2_sub2

I try to edit two files; config.toml and header.html

config.toml is

[[menu.main]]
URL = "main"
name = "main"
identifier = "main"
weight = 1

[[menu.main]]
URL = "sub1"
name = "sub1"
identifier = "sub1"
weight = 1
parent = "main"

[[menu.main]]
URL = "sub2"
name = "sub2"
identifier = "sub2"
weight = 2
parent = "main"

[[menu.main]]
URL = "sub1sub1"
name = "sub1sub1"
identifier = "sub1sub1"
parent = "sub1"
weight = 1

[[menu.main]]
URL = "sub1sub2"
name = "sub1sub2"
identifier = "sub1sub2"
parent = "sub1"
weight = 2

[[menu.main]]
URL = "sub2sub1"
name = "sub2sub1"
identifier = "sub2sub1"
parent = "sub2"
weight = 1

[[menu.main]]
URL = "sub2sub2"
name = "sub2sub2"
identifier = "sub2sub2"
parent = "sub2"
weight = 2

header.html is

<header class="navigation fixed-top">
  <nav class="navbar navbar-expand-lg navbar-dark">
    <a class="navbar-brand" href="{{ site.BaseURL }}">
      {{ if site.Params.logo }}
      <img src="{{ site.Params.logo | absURL }}" alt="{{site.Title}}">
      {{ else }}
      <h3 class="text-white font-secondary">{{site.Title}}</h3>
      {{ end }}
    </a>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navigation"
      aria-controls="navigation" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>

    <div class="collapse navbar-collapse text-center" id="navigation">
      <ul class="navbar-nav ml-auto">
        <li class="nav-item">
          <a class="nav-link" href="{{ site.BaseURL }}">{{ with site.Params.Home }} {{ . }} {{ end }}</a>
        </li>
        {{ $current := . }}
        {{ range site.Menus.main }}
        {{ $active := or ($current.IsMenuCurrent "nav" .) ($current.HasMenuCurrent "nav" .) }}
        {{ $active = or $active (eq .Name $current.Title) }}
        {{ if .HasChildren }}
        <li class="nav-item dropdown">
          <a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
            {{ .Name }}
          </a>
          <div class="dropdown-menu">
            {{ range .Children }}
            <a class="dropdown-item dropdown-toggle" href="{{ .URL | absURL }}" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> {{ .Name }} </a>
            <div class="dropdown-menu">
              {{ range .Children }}
              <a class="dropdown-item"> {{ .Name }} </a>
              {{ end }}
            </div>
            {{ end }}
          </div>
        </li>
        {{ else }}
        <li class="nav-item {{ if $active }}active{{ end }}">
          <a class="nav-link" href="{{ .URL | absURL }}">{{ .Name }}</a>
        </li>
        {{ end }}
        {{ end }}
      </ul>
    </div>
  </nav>
</header>

Result I want is here.
Whatever I press sub1 or sub2, always show sub1 dropdown.
Link is result when i click "sub2".

When I press F12, html source does not have problem.
I guess that javascript or jquery has problem.

What should I edit in code?
Or What can I do for multi-level (three or more) menus in Hugo.

kyukwonee
  • 1
  • 2

1 Answers1

0

Auto-collapsing menu

A lot of websites use an auto-collapsing menu. It is a great and compact way to show a lot of content in a structured way, but it requires a hierarchical view of all your pages. This post explains how to do that.

Different approaches

I tried some different approaches. I started out with the assumption that I was not allowed to use folders in sections. I was under the impression that a folder in a section would create a subsection. Therefore I started with a parent reference in the front matter. Once I found out that I actually WAS allowed to use subfolders, as long as I refrained from using an ‘index.md’ or an ‘_index.md’ file, I rewrote the script. I assumed that the folder structure would be reflected in my permalinks. This, however, turned out to be a false assumption as well. Therefore I rewrote the script a second time. This time I made it look at the filepath. It is fully independent of the permalinks used and therefore works in a multilingual setup with custom permalinks defined in the config file and the front matter.

The code

First we start with a list of the pages that have children. This allows us to set the correct classname. We do this by looping over all pages and ‘collecting’ their path in a scratch variable. Then we create our unordered list that uses a recursive partial to loop over the items.

{{ with .Site.GetPage (print "/" .Section "/_index.md") }}

    {{ $.Scratch.Set "haschildren" "" }}
    {{ range .RegularPages }}
        {{ $urlparts := split (print .File.Dir .File.BaseFileName) "/" }}
        {{ range $index, $value := (first (len $urlparts) $urlparts) }}
            {{ $.Scratch.Add "haschildren" (print " " (delimit (first $index $urlparts) "/") "/") }}
        {{ end }}
    {{ end }}
    {{ $.Scratch.Set "haschildren" (uniq (split ($.Scratch.Get "haschildren") " ")) }}

    <ul class="nestedmenu">
        {{ partial "nested-menu-partial.html" (dict "context" . "pagecontext" $.Page "regularpages" .RegularPages) }}
    </ul>

{{ end }}

The recursive partial

The recursive partial loops over the items in the list (all items in the section, also called ‘regular pages’).

{{ range .regularpages }}

    {{ $filepath := replace (print .File.Dir (replace .File.BaseFileName "_index" "") "/") "//" "/" }}
    {{ $contextfilepath := replace (print $.context.File.Dir (replace $.context.File.BaseFileName "_index" "") "/") "//" "/" }}
    {{ $pagecontextfilepath := replace (print $.pagecontext.File.Dir (replace $.pagecontext.File.BaseFileName "_index" "") "/") "//" "/" }}

    {{ if eq (len (split $filepath "/")) (add (len (split $contextfilepath "/")) 1) }}
      
        {{ if and (in $filepath $contextfilepath) (ne $contextfilepath $filepath) }}
            <li class="{{ if in $pagecontextfilepath $filepath }}active{{ end }} {{ if in ($.pagecontext.Scratch.Get `haschildren`) $filepath }}haschildren{{ end }}">
                <a href="{{ .RelPermalink }}">{{ .Title }}</a>
                <ul>
                    {{ partial "nested-menu-partial.html" (dict "context" . "pagecontext" $.pagecontext "regularpages" $.regularpages) }}
                </ul>
            </li>
        {{ end }}
    {{ end }}
{{ end }}

Adding some CSS

I have added some CSS to make it look good (and for the auto-collapsing to work):

ul.nestedmenu {margin-left: 0;}
ul.nestedmenu li {list-style: none;}
ul.nestedmenu li > ul {display: none;}
ul.nestedmenu li > a::before {
    content: "•"; 
    display: inline-block; 
    margin-right: 0.25rem; 
    width: 0.5rem; 
    text-align: center;
}
ul.nestedmenu li.haschildren > a::before {content: "›";}
ul.nestedmenu li.haschildren.active > a::before {transform: rotate(90deg);}
ul.nestedmenu li.active > ul {display: block;}
ul.nestedmenu li > a {color: #444444!important;}
ul.nestedmenu li.active > a {color: rgb(247, 44, 114)!important;}

If you want to see this in action, you can view a demo here: https://hugocodex.org/blog/creating-a-menu-with-nested-pages/

Mr. Hugo
  • 11,887
  • 3
  • 42
  • 60