three or more level submenu in hugo - hugo

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.

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 }}">
{{ .Title }}
<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/

Related

How to display the html-tag through the condition if hugo?

I have a code in the hugo template engine that should add a script if the parameter in the article is equal to the desired value.
Front Matter articles. Here I need the header_class parameter
---
title: "Replaced header"
header_class: "replaced-header"
---
If header_class = "replaces-header" output the code. I tried to implement it like this:
{{ if isset .Params.header_class "replaced-header" }}
<script>
let replacedHeader = document.querySelector('.replaced-header');
window.onscroll = function() {
let breakPoint = 100;
let scroll = window.pageYOffset || document.documentElement.scrollTop;
if(scroll > breakPoint) {
replacedHeader.classList.add('replaced-header--scrolled');
}
};
</script>
{{ end }}
The code doesn't work. How should a condition be set in this case?
Got a reply on another forum, post it here. Any of these three methods will work:
{{ if isset .Params "header_class" }}
{{ .Params.header_class }}
{{ end }}
{{ with .Params.header_class }}
{{ . }}
{{ end }}
{{ with .Param "header_class" }}
{{ . }}
{{ end }}
Or like this:
{{ with $.Param "header_class" }}
{{ if ( eq . "replaced-header") }}
My script
{{ end }}
{{ end }}

Hugo – How to add different class names based on counter

Using Hugo, I need add unique class names to the first few posts. How can this be done?
My code that's obviously not working…
// if the first post
{{ if eq .Site.GetPage 1 }}
{{ $classname := "class-one" }}
// else the second post
{{ elseif eq .Site.GetPage 2 }}
{{ $classname := "class-two" }}
// else the third post
{{ elseif eq .Site.GetPage 3 }}
{{ $classname := "class-three" }}
{{ else }}
{{ end }}
<li class="{{ $classname }}">
…
</li>
Maybe there's some more efficient way, but this works:
{{ $paginator := .Paginate (where .Data.Pages.ByDate.Reverse "Type" "post") }}
{{ range $index, $element := $paginator.Pages }}
{{ if (eq $index 0) }}
{{ $.Scratch.Set "classname" "class-one" }}
{{ else if (eq $index 1) }}
{{ $.Scratch.Set "classname" "class-two" }}
{{ else }}
{{ $.Scratch.Set "classname" "" }}
{{ end }}
<li class="{{ $.Scratch.Get "classname" | safeHTML }}">
…
</li>
{{ end }}

nested ngrepeat, get the index in the nested array

can you help me with the index in the nested ng-repeat ? I can't find the right way to take the first 8 element of prodata, then the 8 following elements, then the 8 following elements...etc.
<ion-slide-box show-pager="true" does-continue="true" on-slide-changed="slideHasChanged($index)">
<ion-slide ng-repeat="s in [0,1,2,3,4,5,6,7,8,9]">
<ul>
<li ng-repeat="item in prodata | limitTo:8*s+8:8*s+0">
{{s}}
<a class="suggestPro" href="#">
<span><img ng-src="img/boards/{{item.imageName}}" /></span>
<p class="flex-caption"> {{item.model}} - {{item.name}}</p>
</a>
</li>
</ul>
</ion-slide>
</ion-slide-box>
Check your angular version. The ability to specify the begin argument has been added to 1.4 version of angular :
Extract from angular's change log :
limitTo: extend the filter to take a beginning index argument (aaae3cc4, #5355, #10899)
Your code wil be :
<li ng-repeat="item in prodata | limitTo:8:8*s">
The best way IMO would be to reformat the initial array to make it suitable for usage with ng-repeat. Example:
var getSplittedArray = function (array, numberOfElements) {
var newArray = [];
for(var i = 0; i < array.length; i += 1) {
if (i % numberOfElements === 0) {
newArray.push([]);
}
newArray[newArray.length - 1].push(array[i]);
}
return newArray;
}
// Example: var realArray = someService.get();
$scope.splittedArray = getSplittedArray(realArray, 8);
Example usage in the view:
<ion-slide-box show-pager="true" does-continue="true" on-slide-changed="slideHasChanged($index)">
<ion-slide ng-repeat="set in splittedArray">
<ul>
<li ng-repeat="item in set">
{{s}}
<a class="suggestPro" href="#">
<span><img ng-src="img/boards/{{item.imageName}}" /></span>
<p class="flex-caption"> {{item.model}} - {{item.name}}</p>
</a>
</li>
</ul>
</ion-slide>
</ion-slide-box>

Hide and Display Subsections

I have menu with sections and subsections, like this:
Section 1
Sub 1.1
Sub 1.2
Section 2
Sub 2.1
Sub 2.2
I want to hide subsections and show one of them by clicking on section (click on Section 2):
Section 1
Section 2
Sub 2.1
Sub 2.2
Here is my code and JSFiddle:
<div ng-controller="MyCtrl">
<div ng-repeat="(meta, counts) in info">
{{ meta }}
<ul class="subsection">
<li ng-repeat="(group, cnt) in counts">
{{ group }}
</li>
</ul>
</div>
</div>
Controller:
var myApp = angular.module('myApp',[]);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.name = 'Superhero';
$scope.info = { "one": { "a": 1, "b": 2 },
"two" : { "c": 3, "d": 4 }};
$scope.display = function(meta) {
// ????
};
}
CSS:
ul.subsection {
display: none;
}
How can I fix this code to show one of the subsection by click on the section ?
Update: I fixed the link on JSFiddle
Since ng-repeat creates its own scope, you can simply toggle a variable within the loop, and use ng-show on that variable:
<div ng-repeat="(meta, counts) in info">
{{ meta }}
<ul class="subsection" ng-show="display">
<li ng-repeat="(group, cnt) in counts">
{{ group }}
</li>
</ul>
</div>
Edit: If you can only show one function at a time, then you can do what you were trying to do in your original code with a function:
<div ng-repeat="(meta, counts) in info">
{{ meta }}
<ul class="subsection" ng-show="sectionIndex == $index>
<li ng-repeat="(group, cnt) in counts">
{{ group }}
</li>
</ul>
</div>
$scope.display = function(index) {
$scope.sectionIndex = index;
}
You can simply do something like this:
<div ng-repeat="(meta, counts) in info">
{{meta}}
<ul class="subsection" ng-show="$parent.display == meta">
<li ng-repeat="(group, cnt) in counts">
{{ group }}
</li>
</ul>
</div>
Note, that you can refer $parent scope to avoid local scope display property.
In this case you don't need CSS rule. Bonus point is that you can set in controller $scope.display = 'two' and the second item will expand.
However cleaner way would be using a controller function as demonstrated by #tymeJV, this is the best approach.
Demo: http://plnkr.co/edit/zXeWjXLGHMv0BZZRZtud?p=preview

How do I make the carousel indicators in angular ui use thumbnails from a model in a controller?

I'm using the angular ui bootstrap carousel and I want to make the indicators thumbnails. I have a controller that looks like this (from the demo):
function carouselCtrl($scope) {
$scope.myInterval = 5000;
$scope.imgPath="img/slideshow/"
var slides = $scope.slides = [{
'imgName':'iguanas.jpg',
'caption':'Marine iguanas in the Galapagos National Park on Santa Cruz Island, on September 15, 2008.',
'author':'(Reuters/Guillermo Granja)'
},{
'imgName':'eruption.jpg',
'caption':'In June of 2009, the Cerro Azul volcano on Isabela Island was in an active phase, spewing molten lava into the air, spilling it across its flanks, and widening existing lava flows.',
'author':'(Reuters/Parque Nacional Galapagos)'
},{
'imgName':'bluefoot.jpg',
'caption':'A close-up of a pair of Booby feet, photographed in March of 2008. ',
'author':'(CC BY Michael McCullough)'
}];
}
and the template looks like this:
<div ng-mouseenter="pause()" ng-mouseleave="play()" class="carousel">
<ul class="carousel-indicators" ng-show="slides().length > 1">
<li ng-repeat="slide in slides()" class="slide-thumb" ng-class="{active: isActive(slide)}" ng-click="select(slide)"></li>
</ul>
<div class="carousel-inner" ng-transclude></div>
<a ng-click="prev()" class="carousel-control left" ng-show="slides().length > 1">‹</a>
<a ng-click="next()" class="carousel-control right" ng-show="slides().length > 1">›</a>
</div>
I want to do something like this:
<li ng-repeat="slide in slides()" class="slide-thumb" ng-class="{active: isActive(slide)}" ng-click="select(slide)" style="background-image:url({{slide.imgName}});"></li>
but I must be out of scope or something... Does anyone know any angular carousels that have a thumbnail option or how I can get this to work?
The slide array in the carousel template actually don't refer to the slides array you have defined in your app controller.
In the carousel template slides refer to a bunch of dom elements enhanced with internal properties. That's why any access to properties you have defined in our objects will failed when executed (scope issue as you guessed already).
If you want to stick to the carousel from angular-ui I would recommend a slightly different approach and go for css styling something like that:
//Default styles for indicator elements
.carousel-indicators li {
background-size : 42px 22px;
width : 42px;
height: 22px;
background-repeat : no-repeat;
background-position : center;
cursor : pointer;
}
// Then Specify a background image for every slide
.carousel-indicators li:nth-child(1){
background-image: url(http://cache.wallpaperdownloader.com/bing/img/WeddedRocks_20100418.jpg);
}
...
You can see a working Plunker here.
It's very possible and it's very simple. First you must pass the model of every slide in the actual directive as indicated in the docs for the uib-slide settings. And then you must override the template using the template-url directive. Don't forget to declare the template.
So the html should look like this:
<!--Defining the controller scope-->
<div ng-controller="carousel">
<!--Declaring the template for later usage-->
<script id="carousel-with-thumbs.html" type="text/ng-template">
<div ng-mouseenter="pause()" ng-mouseleave="play()" class="carousel" ng-swipe-right="prev()"
ng-swipe-left="next()">
<div class="carousel-inner" ng-transclude></div>
<a role="button" href class="left carousel-control" ng-click="prev()"
ng-class="{ disabled: isPrevDisabled() }"
ng-show="slides.length > 1">
<span aria-hidden="true" class="glyphicon glyphicon-chevron-left"></span>
<span class="sr-only">previous</span>
</a>
<a role="button" href class="right carousel-control" ng-click="next()"
ng-class="{ disabled: isNextDisabled() }"
ng-show="slides.length > 1">
<span aria-hidden="true" class="glyphicon glyphicon-chevron-right"></span>
<span class="sr-only">next</span>
</a>
<ol class="carousel-indicators" ng-show="slides.length > 1">
<li ng-repeat="slide in slides | orderBy:indexOfSlide track by $index"
ng-class="{ active: isActive(slide) }" ng-click="select(slide)">
<!--Showing the thumbnail in a <img> tag -->
<img ng-src="{{slide.slide.actual.thumb}}">
<span class="sr-only">slide {{ $index + 1 }} of {{ slides.length }}<span ng-if="isActive(slide)">, currently active</span></span>
</li>
</ol>
</div>
</div>
</script>
<uib-carousel active="active" interval="interval" template-url="carousel-with-thumbs.html">
<uib-slide ng-repeat="slide in slides track by $index" index="$index" actual="slide">
<!--Passing the slide in the actual directive-->
<img ng-src="{{slide.image}}" style="margin:auto;">
<div class="carousel-caption">
<h4>{{slide.title}}</h4>
<p>{{slide.text}}</p>
</div>
</uib-slide>
</uib-carousel>
Take a moment to analyse the carousel-indicators ordered list:
<ol class="carousel-indicators" ng-show="slides.length > 1">
<li ng-repeat="slide in slides | orderBy:indexOfSlide track by $index" ng-class="{ active: isActive(slide) }"
ng-click="select(slide)">
<img ng-src="{{slide.slide.actual.thumb}}">
<span class="sr-only">slide {{ $index + 1 }} of {{ slides.length }}<span ng-if="isActive(slide)">, currently active</span></span>
</li>
</ol>
Look how each slide model has another slide sub-model which is the transcluded scope of the slide model passed
through the actual directive. And then, nested again, the actual model containing all the data of each slide:
<img ng-src="{{slide.slide.actual.thumb}}">
And of course for the big finale, the controller should look like this:
(function(){
'use strict';
angular
.module('app', ['ui.bootstrap'])
.controller('carousel', carousel);
carousel.$inject = ['$scope'];
function carousel($scope){
$scope.active = 0;
$scope.interval = 5000;
$scope.slides = [
{title: "Any title", text: "This is a text for the slide", image: "path/to/the/image/image.jpg", thumb: "path/to/the/image/thumbs/thumb.jpg"},
{title: "Any title", text: "This is a text for the slide", image: "path/to/the/image/image.jpg", thumb: "path/to/the/image/thumbs/thumb.jpg"},
{title: "Any title", text: "This is a text for the slide", image: "path/to/the/image/image.jpg", thumb: "path/to/the/image/thumbs/thumb.jpg"}
];
}
})();
By the way, I'm using Angular UI Boostrap 1.3.3 along with Angular 1.5.8.
Here's the Fiddle for it https://jsfiddle.net/logus/6mvjpf40/

Resources