active menu item - asp.net mvc3 master page
Asked Answered
S

5

65

I've been scanning around trying to find an appropriate solution for assigning "active/current" class to menu items from the master page. The line is split down the middle with regards of whether to do this client vs server side.

Truthfully I'm new to both JavaScript and MVC so i don't have an opinion. I would prefer to do this in the "cleanest" and most appropriate way.

I have the following jQuery code to assign the "active" class to the <li> item...the only problem is the "index" or default view menu item will always be assigned the active class, because the URL is always a substring of the other menu links:

(default) index = localhost/
link 1 = localhost/home/link1
link 2 = localhost/home/link1

$(function () {
 var str = location.href.toLowerCase();
  $('#nav ul li a').each(function() {
   if (str.indexOf(this.href.toLowerCase()) > -1) {
    $(this).parent().attr("class","active"); //hightlight parent tab
   }
});

Is there a better way of doing this, guys? Would someone at least help me get the client-side version bulletproof? So that the "index" or default link is always "active"? Is there a way of assigning a fake extension to the index method? like instead of just the base URL it would be localhost/home/dashboard so that it wouldn't be a substring of every link?

Truthfully, i don't really follow the methods of doing this server-side, which is why I'm trying to do it client side with jQuery...any help would be appreciated.

Snack answered 18/1, 2011 at 20:39 Comment(0)
G
113

A custom HTML helper usually does the job fine:

public static MvcHtmlString MenuLink(
    this HtmlHelper htmlHelper, 
    string linkText, 
    string actionName, 
    string controllerName
)
{
    string currentAction = htmlHelper.ViewContext.RouteData.GetRequiredString("action");
    string currentController = htmlHelper.ViewContext.RouteData.GetRequiredString("controller");
    if (actionName == currentAction && controllerName == currentController)
    {
        return htmlHelper.ActionLink(
            linkText,
            actionName,
            controllerName,
            null,
            new {
                @class = "current"
            });
    }
    return htmlHelper.ActionLink(linkText, actionName, controllerName);
}

and in your master page:

<ul>
    <li>@Html.MenuLink("Link 1", "link1", "Home")</li>
    <li>@Html.MenuLink("Link 2", "link2", "Home")</li>
</ul> 

Now all that's left is define the .current CSS class.

Ger answered 19/1, 2011 at 9:4 Comment(10)
Worth noting htmlHelper.ActionLink() needs "using System.Web.Mvc.Html;"Quartile
You also need to import the namespace in your view, if using Razor in MVC3 you can do this by simply adding @using <NAMESPACE> to your viewDiondione
I had one problem with that. What if in "link2" page I have a link to "link3" which points to another controller and action but it is a part of the "link2" section. The "link2" is no longer active as controller and action in request does not match what we pass to the helper. I have worked out a clean solution: no sessions, no ugly code in views. arturito.net/2011/08/03/… The extra part is a dictionary with route mappings.Loki
I spent ages trying to work this out myself before giving up and checking the old Google. This was the top result. I would never have found ViewContext.RouteData on my own!Gunmaker
In conjunction with the answer, this was also useful: msdn.microsoft.com/en-us/…Trussell
While custom helper is useful, it appears it would only be useful for trivial, one-level navigation. What about the cases where you have a top level navigation, and then for one of the categories in the top, you have a second sub-level of navigation?Trussell
@mg1075, then just adapt helper to suit your needs.Ger
Hello friends. I have just one question regarding this custom link. If I would like to include route values with my custom link, could I also compare them in the helper class in a similar way as the action and controller (how would I retrieve the route value)? I have dynamic tab links on my master page and therefore I generate tab links with foreach loop and if I use the sample above all tabs that are generated within my foreach loop turn all "active" instead of the one I clicked.Alible
@DarinDimitrov, how would I retrieve the required string for the route value? (like when we retrieved "action" and "controller")Alible
@gardarvalur, you retrieve them from RouteData, as you would would do with any other parameter: RouteData.Value["foobar"].Ger
C
5

Added support for areas:

public static class MenuExtensions
{
    public static MvcHtmlString MenuItem(this HtmlHelper htmlHelper, string text, string action, string controller, string area = null)
    {

        var li = new TagBuilder("li");
        var routeData = htmlHelper.ViewContext.RouteData;

        var currentAction = routeData.GetRequiredString("action");
        var currentController = routeData.GetRequiredString("controller");
        var currentArea = routeData.DataTokens["area"] as string;

        if (string.Equals(currentAction, action, StringComparison.OrdinalIgnoreCase) &&
            string.Equals(currentController, controller, StringComparison.OrdinalIgnoreCase) &&
            string.Equals(currentArea, area, StringComparison.OrdinalIgnoreCase))
        {
            li.AddCssClass("active");
        }
        li.InnerHtml = htmlHelper.ActionLink(text, action, controller, new {area}, null).ToHtmlString();
        return MvcHtmlString.Create(li.ToString());
    }
}
Canales answered 16/5, 2012 at 17:48 Comment(0)
U
4

Here is my solution of this issue.

I created following extension method of HtmlHelpers class:

public static class HtmlHelpers
{
    public static string SetMenuItemClass(this HtmlHelper helper, string actionName)
    {
        if (actionName == helper.ViewContext.RouteData.Values["action"].ToString())
            return "menu_on";
        else
            return "menu_off";
    }

Then I have my menublock. It looks like this:

<div id="MenuBlock">
    <div class="@Html.SetMenuItemClass("About")">
        <a>@Html.ActionLink("About", "About", "Home")</a></div>
    <img height="31" width="2" class="line" alt="|" src="@Url.Content("~/Content/theme/images/menu_line.gif")"/>
    <div class="@Html.SetMenuItemClass("Prices")">
        <a>@Html.ActionLink("Prices", "Prices", "Home")</a></div>
</div>

So my method returns class name to every div according to current action of Home controller. You can go deeper and add to the method one parameter, which specifies the name of the controller to avoid problems, when you have actions with the same name but of different controllers.

Ury answered 9/3, 2011 at 10:3 Comment(0)
P
2

Through JQuery u can do like this:

$(document).ready(function () {
    highlightActiveMenuItem();
});

highlightActiveMenuItem = function () {
    var url = window.location.pathname;
    $('.menu a[href="' + url + '"]').addClass('active_menu_item');
};

.active_menu_item {
    color: #000 !important;
    font-weight: bold !important;
}

Original: http://www.paulund.co.uk/use-jquery-to-highlight-active-menu-item

Perdurable answered 15/9, 2013 at 10:56 Comment(0)
I
-1

What I usually do is assign a class to the body tag that's based on the path parts. So like, if you do a String.Replace on the path to turn /blogs/posts/1 to class="blogs posts 1".

Then, you can assign CSS rules to handle that. For example, if you have a menu item for "blogs", you can just do a rule like

BODY.blogs li.blogs { /* your style */}

or if you want a particular style if your on a post only vice if you're on the blog root page

BODY.blogs.posts li.blogs {/* your style */}
Iridic answered 18/1, 2011 at 20:47 Comment(1)
not crazy about this solution...requires creating a class for every single menu item, doesn't it?Snack

© 2022 - 2024 — McMap. All rights reserved.