Navbar menu from content structure (sections and pages)

I am attempting to create a navbar menu that is created from the content structure of the site based on the folders and markdown files (sections and pages) but not relying on frontmatter or data files. I have read over the documentation and the forums but haven’t been able to piece together what I am missing so far.

The site I am working on is built with bootstrap 4 and I wanted to create a navbar with dropdown menus for the sections like in the documentation examples.

to save time looking if you can help test, there is a github repo here

I tried at first using the menu templates but found it was unable to show member pages of sections.

I then looked at examples on @bep’s example site and tried to implement a simple version of it but got a bit confused with getting the recursion to work with my navbar template (defining and then calling templates within a range).

I attempted to build using section and page variables to list:

  • a link to the home page
  • single pages in the top-level of the folder structure
  • section pages with their descendants in the dropdown using.IsAncestor

my baseof.html file header block calls {{ partial "site-nav" . }} which has the following:

<nav class="navbar sticky-top navbar-expand-md navbar-light bg-light">
	<a class="navbar-brand" href="{{ .Site.Home.RelPermalink }}">{{ $.Site.Params.client }}</a>
	<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
		<span class="navbar-toggler-icon"></span>
	</button>
	<div class="collapse navbar-collapse" id="navbarNavDropdown">
		<ul class="navbar-nav mr-auto">
			{{- template "section-nav" . -}}
		</ul>
	</div>
</nav>
{{- define "section-nav" -}}
{{ $home := $.Site.Home }}
	{{- range .Site.Pages.ByTitle -}}
		{{- if eq .Kind "home" -}}
		<li class="nav-item "><a class="nav-link" href="{{ $home.RelPermalink }}">Home</a></li>
		{{- else if (.InSection $home) -}}
		<li class="nav-item "><a class="nav-link" href="{{ .RelPermalink }}">{{ .Title }}</a></li>
		{{- else if eq .Kind "section" -}}
		{{ $.Scratch.Set "section" . }}
			<li class="nav-item dropdown ">
			<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenu{{ urlize .Title }}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{ .Title }}</a>
			<div class="dropdown-menu" aria-labelledby="navbarDropdownMenu{{ urlize .Title }}">
			  <h6 class="dropdown-header">{{ .Title }}</h6>
				<div class="dropdown-divider"></div>
				{{- range $.Site.Pages -}}
				{{- if .IsAncestor ($.Scratch.Get "section") }}
				<a class="dropdown-item " href="{{ .RelPermalink }}">{{ .Title }}</a>
		  	{{- end -}}
				{{- end }}
			</div>
			</li>
		{{- end -}}
	{{- end -}}
{{- end -}}

any help is much appreciated, I’ve been stuck on this trying various things for a few days now.

Can you link your repo please. That makes it easy for someone to help.

@RickCogley thanks for the quick reply, I made a github repo here

The above is really all the code thats relevant, other than throwing some filler folders and markdown files in the content folder for testing (also, I forgot to mention that the markdown files have titles set in the frontmatter called in the code with {{ .Title }}). If you need any base html to use, the navbar static example page on the bootstrap site can be used and just replace it with the code from my post above.

If the code isn’t easy to follow, I made comments explaining what I was trying to do :

{{/* Navbar html with a link to the home page calling a client name param from the config.toml as the link text - works normally */}}
<nav class="navbar sticky-top navbar-expand-md navbar-light bg-light">
<a class="navbar-brand" href="{{ .Site.Home.RelPermalink }}">{{ $.Site.Params.client }}</a>
	<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
		<span class="navbar-toggler-icon"></span>
	</button>
	<div class="collapse navbar-collapse" id="navbarNavDropdown">
		<ul class="navbar-nav mr-auto">
		{{/* within the <ul> tags is the template code I am calling written below to list the links to sections/articles */}}
			{{- template "section-nav" . -}}
		</ul>
	</div>
</nav>

{{/* define the template code to insert above */}}
{{- define "section-nav" -}}
{{/* set the parameter $home for the homepage */}}
{{ $home := $.Site.Home }}
	{{/* loop through the pages of the site by the title of the page */}}
	{{- range .Site.Pages.ByTitle -}}

		{{/* 1) conditional statment to find pages of kind "home" (the top level index page) */}}
		{{- if eq .Kind "home" -}}
		{{/* html to set it as a relative link to the hompage */}}
		<li class="nav-item "><a class="nav-link" href="{{ $home.RelPermalink }}">Home</a></li>

		{{/* 2) conditional statment to find top level single pages in the content directory  */}}
		{{- else if (.InSection $home) -}}
		{{/* html to set it as a link to the single page */}}
		<li class="nav-item "><a class="nav-link" href="{{ .RelPermalink }}">{{ .Title }}</a></li>

		{{/* 3) conditional statment to find section pages in order to make dropdown menu items from them */}}
		{{- else if eq .Kind "section" -}}
		{{/* setting a scratch variable to match the current section in the range */}}
		{{ $.Scratch.Set "section" . }}
			{{/* html to make a dropdown list item that can be clicked */}}
			<li class="nav-item dropdown ">
			<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenu{{ urlize .Title }}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{ .Title }}</a>
			{{/* html to make a dropdown menu to list the links in */}}
			<div class="dropdown-menu" aria-labelledby="navbarDropdownMenu{{ urlize .Title }}">
			  <h6 class="dropdown-header">{{ .Title }}</h6>
				<div class="dropdown-divider"></div>
				{{/* loop through all the pages of the site */}}
				{{- range $.Site.Pages -}}
				{{/* try to find just the pages that are members of this section */}}
				{{- if .IsAncestor ($.Scratch.Get "section") }}
				{{/* link the pages that are members of the section */}}
				<a class="dropdown-item " href="{{ .RelPermalink }}">{{ .Title }}</a>
		  	{{- end -}}
				{{- end }}
			</div>
			</li>
		{{- end -}}
	{{- end -}}
{{- end -}}

Didn’t have a ton of time today but, after cloning your repo, adding another test section adds another top level menu item. But, while the dropdown listing of the section name is working, as you know the dropdown bit is not. I’ll have to look at Bep’s code.

As for listing pages in the root, that’s a variation of what’s in the manual, kind of. Something like:

{{ range .Site.Pages }}
       <li><a class="whatever" href="{{ .RelPermalink }}">{{ .Title }}</a></li>
{{ end }}

…gets you just a flat list of all the pages, but I have not had luck filtering them without using frontmatter, as you mention you’d like to. If it’s possible to put the pages you have in the root of content in a section like content/page then, all you’d need to do is filter for that and use a simple list of <li> tags in your nav bar.

Sorry I’m not more help.

Thanks for taking the time and downloading the repo to take a look. Your idea of making a separate section for top-level pages and listing it differently is a clever idea, however another issue you may not have noticed is that all of the pages within each section aren’t being listed in the dropdown for some reason. It seems that only the main section page is a link in the dropdown but none of the single pages within that section are being listed. I guess there is something wrong with a conditional statement or my range statement within the dropdown but I’m not sure what to change.