How to keep the active menu item highlighted when visiting pages in ASP.Net MVC 5 application?

11,271

Solution 1

Everytime you click on a link, it does a page redirect, and it doesn't matter what changes you've made with jQuery, it always fetch this partial view, and in this partial view, you have active class on the first link, that is why it always highlights the first link.

What you have to do is write an HtmlHelper that will add the class "active" to current link on each page load. Create a folder called Helpers under your project and add a custom HtmlHelper class.

using System;
using System.Web.Mvc;

public static class ActiveMenuHelper
{
    public static MvcHtmlString MenuLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, string areaName)
    {
        var currentAction = htmlHelper.ViewContext.RouteData.GetRequiredString("action");
        var currentController = htmlHelper.ViewContext.RouteData.GetRequiredString("controller");
        var currentArea = htmlHelper.ViewContext.RouteData.DataTokens["area"];

        var builder = new TagBuilder("li")
        {
            //InnerHtml = htmlHelper.ActionLink(linkText, actionName, controllerName).ToHtmlString()
            InnerHtml = "<a href=\"" + new UrlHelper(htmlHelper.ViewContext.RequestContext).Action(actionName, controllerName, new { area = areaName }).ToString() + "\">" + linkText + "</a>"
        };

        if (String.Equals(controllerName, currentController, StringComparison.CurrentCultureIgnoreCase) && String.Equals(actionName, currentAction, StringComparison.CurrentCultureIgnoreCase))
            builder.AddCssClass("active");

        return new MvcHtmlString(builder.ToString());
    }
}

and then in your partial view:

@using YourProjectName.Helpers

<ul>
    @Html.MenuLink("Resume", "Index", "Resume", "" )
    @Html.MenuLink("Cover Letter", "Index", "CoverLetter", "" )
</ul>

You might not need the area, that is why you can leave it blank, i put it in here just in case.

Solution 2

Try adding this to your shared view . normally "_Layout".

@{ 
    string pageUrl = Request.Url.PathAndQuery.ToString();
}

and instead of adding "active" class from the html markups, add conditional statement that will check the pageUrl just like the code below.

@if (Request.IsAuthenticated)
{
    <ul style="list-style-type:none;">
        <li class="@(pageUrl.ToLower().Contains("/index/Resume") ? "active" : string.Empty) setBtnMargin">
            <a href="@Url.Action("Index", "Resume")" class="btn btn-block">My Resume </a>
        </li>
        <li class="@(pageUrl.ToLower().Contains("/index/CoverLetter") ? "active" : string.Empty) setBtnMargin">
            <a href="@Url.Action("Index", "CoverLetter")" class="btn btn-block">My Cover Letter </a>
        </li>
        <li class="@(pageUrl.ToLower().Contains("/index/Home") ? "active" : string.Empty) setBtnMargin">
            <a href="@Url.Action("Index", "Home")" class="btn btn-block">My Account </a>
        </li>
        <li class="@(pageUrl.ToLower().Contains("/index/Home") ? "active" : string.Empty) setBtnMargin">
            <a href="@Url.Action("Index", "Home")" class="btn  btn-block">Get Rewards </a>
        </li>
    </ul>
}
Share:
11,271

Related videos on Youtube

Unbreakable
Author by

Unbreakable

The fool didn't know it was impossible, so he did it!

Updated on July 13, 2022

Comments

  • Unbreakable
    Unbreakable almost 2 years

    So, I have one ASP.Net MVC 5 application. In that one of page hasfour menu items. When page loads first menu item is selected by default (so it should be highlighted as soon as the page loads). And now as soon as user clicks on any other menu, that other menu should be in highlighted stage, so that user knows which menu he is currently on. Below is my attempt:

    My thinking:

    a. add a click event on the ul tag.

    b. when clicked find "active" class inside ul tag and remove it.

    c. now add active class to the source of click event (which means the li tag on which user clicked).

    Issue:

    The selected/clicked menu gets highlighted for a second, and then automatically first menu gets highlighted. So as soon as the whole page corresponding to the newly clicked menu item reloads the first menu item gets highlighted back.

    My Attempt

    P.S: This is a partial View, And all redirects will load this partial view afresh.

    @if (Request.IsAuthenticated)
    {
        <ul style="list-style-type:none;">
            <li class="active setBtnMargin">
                <a href="@Url.Action("Index", "Resume")" class="btn btn-block">My Resume </a>
            </li>
            <li class="setBtnMargin">
                <a href="@Url.Action("Index", "CoverLetter")" class="btn btn-block">My Cover Letter </a>
            </li>
            <li class="setBtnMargin">
                <a href="@Url.Action("Index", "Home")" class="btn btn-block">My Account </a>
            </li>
            <li class="setBtnMargin">
                <a href="@Url.Action("Index", "Home")" class="btn  btn-block">Get Rewards </a>
            </li>
        </ul>
    }
    
    <script>
    // ATTEMPT 1
        $("ul").on("click", "li", function () {
            $('ul li').removeAttr('active');
            $(this).addClass('active');
        });
    
    // ATTEMPT 2
        //$("li").click(function () {
        //    //$("ul li").removeClass("active");
        //    $(this).addClass("active");
        //});
    </script>
    

    EDIT 1

    So the problem is as the page gets redirected. The active tag again gets appened to the first menu item when entire DOM gets reloaded because all the landing pages use the same partial view.

  • Unbreakable
    Unbreakable over 6 years
    This is a solution that will take some time for me to comprehend. I will definitely try this as I will learn alot from this. I will try this and comment/accept/ask accordingly.
  • Admin
    Admin over 6 years
    This can be improved by adding arguments for route values and html attributes, and by using htmlHelper.ActionLink(...) to generate the InnerHtml (and of course creating various overloads)
  • Mindless
    Mindless over 6 years
    Stephen is the master at this, he helped me alot, you are in safe hands haha
  • Unbreakable
    Unbreakable over 6 years
    So your solution worked, so basically we are adding the active class when the actual landing page is getting loaded. To be honest it might not be one the best solution. but it works like a charm. :) Thank you. :)
  • Unbreakable
    Unbreakable over 6 years
    slight edit is required. The text after .contains all needs to be in lower case. /index/Resume -> /index/resume (all lowered)
  • Unbreakable
    Unbreakable over 6 years
    @Mindless: I am yet to try your solution. I will mostly try it tonight and will accept it accordingly. In the mean time I tried the below solution by JF-Mechs and it worked. What's your take on that solution. Is that a good way too?
  • Unbreakable
    Unbreakable over 6 years
    Also, in your solution you have kept "area", "controller" and "action" text hardcoded. So, those strings are needed to bel ike that by the framework itself right? It has nothing to do with any of our code right?
  • Unbreakable
    Unbreakable over 6 years
    @Mindless Tried your solution now. Worked in first go. Thank you so much.
  • Unbreakable
    Unbreakable over 6 years
    Read the comments by @StephenMuecke of using built in actionlink. I will try that too. It will remove all the extra noise from code. :)
  • Mindless
    Mindless over 6 years
    @Unbreakable, Yea, now that i remembered it, i think the reason why i didn't use actionlink is because i have to create a link like this <a href=""><i class="" /></a>, i modified my code to remove <i class="" /> inside, but as Stephen pointed out, actionlink is preferred. you can see that i commented out actionlink as i was using the previously, i forgot about it.
  • Mindless
    Mindless over 6 years
    @Unbreakable and regarding your hardcode question, yes
  • JF-Mechs
    JF-Mechs over 6 years
    @Unbreakable yes, exactly you only add the active class when the page loaded. Its alright, This was just a quick fix I made for you and for all those who might need it and so I'm leaving it here. Good luck mate :)
  • Unbreakable
    Unbreakable over 6 years
    @StephenMuecke: Sir, I have some button such as "create new" in landing pages of left menu items. But when I used to visit index page then correct tab was getting highlighted, but when I clicked on Create New button inside it, then tab highlighting vanished, because the action won't match now in HTML Helper. So I removed the Action matching element from the HTML Helper, so that as long as controller name is same, I just added the active tab. Hope I did the right thing
  • Unbreakable
    Unbreakable over 6 years
    new code if (String.Equals(controllerName, currentController, StringComparison.CurrentCultureIgnoreCase)) builder.AddCssClass("active");
  • Admin
    Admin over 6 years
    @Unbreakable, That would only work so long as your menu never contains more that one item from the same controller. A better solution may be to provide null to the actionName and then test if its provided or not (and if not, use the code in your previous comment)