We Do Tech, stuff.

Small business web and system solutions.

Free programs, software reviews, and tech, stuff.

nopCommerce Multilevel Bootstrap Menu

by: Dan Orlovsky
Posted On: 8/9/2017 12:09:21 PM

A nopCommerce project I had taken on incorporated Bootstrap, but the navigation menu ignored child-categories. Here was the fix.


I was hired to take on a nopCommerce project a client had that was only half completed.  The original developer incorporated Twitter's Bootstrap for a responsive design.  One of the problems, though, is the navigation menu ignored child categories.  The original navigation menu was marked-up as so:

<div class="navbar navbar-default">
    <div class="navbar-header">
        <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="@Url.RouteUrl("HomePage")"><i class="fa fa-home"></i></a>
    </div>
    <div id="navbar" class="navbar-collapse collapse">
        <ul class="nav navbar-nav">
            @{
                var categories = Model.Categories.Where(x => x.IncludeInTopMenu).ToList();

            }
            @foreach (var item in categories)
            {
                <li><a href="@Url.RouteUrl("Category", new { SeName = item.SeName })">@item.Name</a></li>
            }
        </ul>

    </div>
</div>

 

What I first needed to do was figure out a good way to display the category whether it had sub-categories (children) or not.  Turns out, the original nopCommerce theme does this by using a recursive helper-function.  So I created something similar:

@helper RenderMenuLine(CategorySimpleModel category, int level)
{

    bool isDrop = (category.SubCategories.Count > 0);
    string dropDownClass;           // The class we assign to the dropdown markup depends on where we are
    if (level > 0)                  // If we are already in a dropdown menu
    {
        dropDownClass = "dropdown-submenu";
    }
    else    // otherwise we are in the root menu, or level = 0
    {
        dropDownClass = "dropdown";
    }
    if (isDrop)
    {
        <li class="@dropDownClass"> 
            <a tabindex="-1" href="@Url.RouteUrl("Category", new { SeName = category.SeName })" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="true">
                @category.Name
                @if (level == 0)
                { <span class="caret"></span> }
                @if (category.NumberOfProducts.HasValue)
                {
                    <text></text> @T("Categories.TotalProducts", category.NumberOfProducts.Value)
                }
            </a>

            @{ var subCats = category.SubCategories.Where(x => x.IncludeInTopMenu); }
            <ul class="dropdown-menu">
                @foreach (var subCat in subCats)
                {
                    @RenderMenuLine(subCat, level++)
                }
            </ul>

        </li>
    }
    else
    {
        <li><a href="@Url.RouteUrl("Category", new { SeName = category.SeName })">@category.Name</a></li>
    }
}

Now, when we call this function from our navigation menu, we send the Category along with 0 for the level, meaning it's the base menu.  We check the category for children, if it has any, we flag isDrop to true.  This tells the function it is a drop-down, and from there it will call this function again, incrementing level by one.  If the category has no children, it renders a line and goes back to our menu foreach loop.

So I fixed the menu like so:

<ul class="nav navbar-nav">
    @{
        var categories = Model.Categories.Where(x => x.IncludeInTopMenu).ToList();
    }
    @foreach (var item in categories)
    {
        @RenderMenuLine(item, 0)
    }
</ul>


Finally, I used CSS to push the sub-menu off to the side:

li.dropdown:hover > ul.dropdown-menu {
    display: block;
}

.dropdown-submenu {

    position:relative;
}
.dropdown-submenu>.dropdown-menu {

    top:0;
    left:100%;
    margin-top:-6px;
    margin-left:-1px;
    -webkit-border-radius:0 6px 6px 6px;
    -moz-border-radius:0 6px 6px 6px;
    border-radius:0 6px 6px 6px;
    display: none;
}
li.dropdown-submenu:hover > ul.dropdown-menu {
    display: block;
}

.dropdown-submenu>a:after {
    display: block;
    content:" ";
    float:right;
    width:0;
    height:0;
    border-color:transparent;
    border-style:solid;
    border-width:5px 0 5px 5px;
    border-left-color:#cccccc;
    margin-top:5px;
    margin-right:-10px;
}

And there we have it.  A Bootstrap version of the nopCommerce navigation menu that supports multi-level dropdowns!


Dan Orlovsky

Self-taught full-stack developer and Visual Studio junkie specializing in C#, ASP.NET (WebForms and MVC), HTML5, and CSS3.  I design custom content management solutions for small-businesses looking to take control of their website.  Each project is built with the technical aptitude of the user in mind.

Currently a junior Software Design Engineer for a data company with a focus in Angular and C#.

 


Comments

Ad Space