'How to create NavMenu with collapsible submenu in .Net Core Blazor app
I am trying to create a blazor navmenu which has a shape like this
item a
item b
when I click on item b it expands with sub menu like this and clicking on subitems, new pages open
item a
item b
subitem 1
subitem 2
I just edited the original blazor app but no success. The button appears but it doesn't collapse submenu. any idea?
<div class="@NavMenuCssClass" @onclick="@ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch Data
</NavLink>
</li>
<li class="dropdown">
<button data-toggle="collapse" data-target="#demo">Collapsible</button>
<div id="demo" class="collapse">
<ul>
<li class="nav-item px-3">
<NavLink class="nav-link" href="meeting">
<span class="oi oi-plus" aria-hidden="true"></span> Meetings
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="conference">
<span class="oi oi-list-rich" aria-hidden="true"></span> Conferences
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="event">
<span class="oi oi-list-rich" aria-hidden="true"></span> Events
</NavLink>
</li>
</ul>
</div>
</li>
</ul>
Solution 1:[1]
Do not use the data-toggle and data-target for it.
These are used by boostrap.js however you do not want to modify the DOM in that way.
What you do instead is to use an if statement and thus let Blazor take care of the rendering:
<NavLink class="nav-link" @onclick="()=>expandSubNav = !expandSubNav">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
@if (expandSubNav)
{
<NavLink class="expand-menu" href="">
<span>Sub1</span>
</NavLink>
<NavLink class="" href="">
<span>Sub2</span>
</NavLink>
}
And put the expandSubNav field into your code section:
@code {
private bool expandSubNav;
}
Solution 2:[2]
<div class="nav-link" @onclick="()=>expandSubNav = !expandSubNav">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</div>
@if (expandSubNav)
{
<NavLink class="expand-menu" href="">
<span>Sub1</span>
</NavLink>
<NavLink class="" href="">
<span>Sub2</span>
</NavLink>
}
use div instead of NAVLINK. Navlink reloads the page and reset the expandSubNav.
Solution 3:[3]
My Solution, after problems with not closing on click the submenu:
works on Mobile:
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" href="">MyPrgramm</a>
<button class="navbar-toggler" type="button" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<NavLink class="nav-link text-dark" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item">
<NavLink class="nav-link text-dark" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Menu-II
</NavLink>
</li>
<li class="nav-item dropdown show">
<NavLink class="nav-link dropdown-toggle" @onclick="() => expandSubNavSettings = !expandSubNavSettings" id="navbarDropdown" >
<span class="oi oi-list-rich" aria-hidden="true"></span> Menu III
</NavLink>
@if (expandSubNavSettings)
{
<li class="dropdown-menu show" aria-labelledby="navbarDropdown" @onclick="() => expandSubNavSettings = !expandSubNavSettings">
<li class="nav-item">
<NavLink class="nav-link text-dark" href="fetchdata">
<span class="oi oi-fork" aria-hidden="true"></span> Fetch
</NavLink>
</li>
<li class="nav-item">
<NavLink class="nav-link text-dark" href="counter">
<span class="oi oi-command" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item">
<NavLink class="nav-link text-dark" href="home">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
</li>
}
</li>
</ul>
</div>
</div>
</nav>
@code {
bool collapseNavMenu = true;
private bool expandSubNavSettings;
string baseMenuClass = "navbar-collapse d-sm-inline-flex flex-sm-row-reverse";
string NavMenuCssClass => baseMenuClass + (collapseNavMenu ? " collapse" : "");
void ToggleNavMenu()
{
if(!expandSubNavSettings)
{
collapseNavMenu = !collapseNavMenu;
}
}
}
Solution 4:[4]
You can try this solution if you have more submenus
<div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">System</a>
<button class="navbar-toggler" @onclick="() => ToggleNavMenu(navSubmenu)">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="@NavMenuCssClass">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" @onclick="() => ToggleNavMenu(NavSubmenu.None)" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" @onclick="() => TogleSubmenu(NavSubmenu.First)">
<span class="oi oi-list-rich" aria-hidden="true"></span> Submenu 1
</NavLink>
</li>
@if (navSubmenu == NavSubmenu.First)
{
<li class="nav-item px-5">
<NavLink class="nav-link" @onclick="() => ToggleNavMenu()" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-5">
<NavLink class="nav-link" @onclick="() => ToggleNavMenu()" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch Data
</NavLink>
</li>
}
<li class="nav-item px-3">
<NavLink class="nav-link" @onclick="() => TogleSubmenu(NavSubmenu.Second)">
<span class="oi oi-list-rich" aria-hidden="true"></span> Submenu 2
</NavLink>
</li>
@if (navSubmenu == NavSubmenu.Second)
{
<li class="nav-item px-5">
<NavLink class="nav-link" @onclick="() => ToggleNavMenu()" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-5">
<NavLink class="nav-link" @onclick="() => ToggleNavMenu()" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch Data
</NavLink>
</li>
}
</ul>
</div>
@code {
private enum NavSubmenu
{
None,
First,
Second
}
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private NavSubmenu navSubmenu = NavSubmenu.None;
private bool collapseNavMenu = true;
private void ToggleNavMenu(NavSubmenu? submenu = null)
{
collapseNavMenu = !collapseNavMenu;
navSubmenu = submenu ?? navSubmenu;
}
private void TogleSubmenu(NavSubmenu submenu)
{
if (navSubmenu == submenu)
navSubmenu = NavSubmenu.None;
else
navSubmenu = submenu;
}
}
Solution 5:[5]
(End result) I struggled quite a bit with submenus and ended up using this in my project:
<div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">Submenu master</a>
<button class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="@NavMenuCssClass">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" @onclick="()=>ToggleNavMenu()" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" @onclick="()=>ToggleSubmenu()">
<span class="oi oi-menu" aria-hidden="true"></span> Menu
</NavLink>
</li>
@if (expandMenu)
{
<li class="nav-item px-4" @onclick="ToggleNavMenu">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-4" @onclick="ToggleNavMenu">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</li>
}
</ul>
</div>
@code {
private bool collapseNavMenu = true;
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private bool expandMenu;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
expandMenu = false;
}
private void ToggleSubmenu()
{
expandMenu = !expandMenu;
}
}
Solution 6:[6]
If you would like to have perfect look of sub menu items, here is the example:
<li class="nav-item px-3">
<NavLink class="nav-link" href="cart" @onclick="()=>expandSubMenu= !expandSubMenu">
<span class="oi oi-cart" aria-hidden="true"></span> Cart
</NavLink>
@if (expandSubMenu)
{
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="cart/1">
<span class="oi oi-file" aria-hidden="true"></span> Sub-1
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="cart/2">
<span class="oi oi-bar-chart" aria-hidden="true"></span> Sub-1
</NavLink>
</li>
</ul>
}
</li>
you also need to add expandSubMenu property to the code block
@code {
private bool collapseNavMenu = true;
private bool expandSubMenu;//add
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
Solution 7:[7]
Implement Sub-menu by using Bootstrap
Default template of Blazor generate collapse
class inside @media
in NavMenu.razor.css
which interferes with Bootstrap's submenu collapse
so we need to rename it to something else.
/* NavMenu.razor.css */
/* Add */
.sidebar-collapse {
display: none;
}
/* Modify */
@media (min-width: 641px) {
...
.sidebar-collapse {
/* Never collapse the sidebar for wide screens */
display: block;
}
}
On NavMenu.razor
replace the same class name inside code block
/* NavMenu.razor */
private string? NavMenuCssClass => collapseNavMenu ? "sidebar-collapse" : null;
Remove @onclick="ToggleNavMenu"
from nav
parent div
<div class="@NavMenuCssClass">
<nav class="flex-column">
...
</nav>
</div>
Add Sub-Menu Items.
Classes has-submenu
, nav-submenu
and nav-subitem
are optional but will be used for styling later
<div class="nav-item has-submenu px-3">
<NavLink class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#userSubMenu"> <!-- <= this id -->
<span class="oi oi-list-rich" aria-hidden="true"></span> Users
</NavLink>
<ul class="nav-submenu collapse list-unstyled" id="userSubMenu"> <!-- <= and this id must match -->
<li class="nav-subitem">
<NavLink href="users/add">
<span class="oi oi-plus" aria-hidden="true"></span> Add
</NavLink>
</li>
<li class="nav-subitem">
<NavLink href="users/list">
<span class="oi oi-people" aria-hidden="true"></span> View
</NavLink>
</li>
</ul>
</div>
Bootstrap 4.x
Add js files in index.html
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
Bootstrap 5.x
Add js files in index.html
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
On NavLink
Replace data-toggle
with data-bs-toggle
and data-target
with data-bs-target
Sub-menu should be working now.
Additional Styling
Add these styles to NavMenu.razor.css
/* Custom Styles */
.nav-item ::deep .nav-link i {
width: 2rem;
font-size: 1.1rem;
}
::deep .nav-item.has-submenu > a {
position: relative;
}
::deep .nav-item.has-submenu > a:after {
content: "\e02f";
position: absolute;
right: 15px;
color: white;
font-family: "Icons";
font-weight: 400;
font-size: 13px;
transform: rotateZ(90deg);
transform-origin: left;
transition: transform .5s ease;
}
::deep .nav-item.has-submenu > a.collapsed:after {
transform: rotateZ(0deg);
}
.nav-item ::deep .nav-submenu .nav-subitem a {
padding-left: 3rem;
text-decoration: none;
}
.nav-item ::deep .nav-submenu .nav-subitem a i {
width: 2rem;
font-size: 1.1rem;
}
Solution 8:[8]
I took a approach with jQuery and also made the menu responsive :
A complete sample can be downloaded from here
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Diego Venâncio |
Solution 2 | Shahzad Ahmad |
Solution 3 | Wolfgang Schorge |
Solution 4 | |
Solution 5 | dinozaver |
Solution 6 | J S |
Solution 7 | Uzair Ali |
Solution 8 | Steef |