13

I have the following menu in my masterpage:

<ul id="menu" class="lavaLampBottomStyle">
    <li>
        <%= Html.ActionLink("Employees", "Index", "Employees")%></li>
    <li>
        <%= Html.ActionLink("Customer", "Details", "Account")%></li>
</ul>

I need a way to set the css class of the current active li to "current".

My first guess it to do this with the assistance of javascript.

I would include something like this in the masterpage:

  $("#menu li a").each(){
    if($(this).attr("href") == '<%= *GET CURRENT PAGE* %>'){
       $(this).parent("li").addClass("current");
    }
  }

Is this a good approach?

If it is, how can I get the current URL part like in the href?

If it isn't, what's your suggestion? :-)

FYI, the generated html I'm after:

<ul id="menu" class="lavaLampBottomStyle">
    <li>
        <a href="/KszEmployees/Index">Employees</a></li>
    <li>
        <a class="current" href="/">Customer</a></li>
</ul>
Thomas Stock
  • 10,927
  • 14
  • 62
  • 79
  • What does the final jQuery each loop look like? – Picflight Aug 23 '09 at 16:06
  • Picflight... and anyone looking for it, add the following to the masterpage; – Charlie Brown Jan 28 '10 at 20:21
  • thanks a lot for asking this question :) have the very similar issue, it helped a lot! – Alexander Beletsky Aug 27 '10 at 19:08

5 Answers5

15

If you want to do it all server-side, I've done this before. Create an action filter attribute:

public class PageOptionsAttribute : ActionFilterAttribute
{
    public string Title { get; set; }
    public string Section { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var controller = filterContext.Controller as ControllerBase;
        if (controller != null)
        {
            controller.SetPageSection(this.Section);
            controller.SetPageTitle(this.Title);
        }

        base.OnActionExecuting(filterContext);
    }
}

This calls two methods in my ControllerBase class that all my controllers inherit from:

public class ControllerBase : Controller
{
    public void SetPageSection(string section)
    {
                    // use the section defined or the controller name if none
        ViewData["PageSection"] = section != null ? 
                        section : this.RouteData.Values["controller"].ToString();
    }

    public void SetPageTitle(string title)
    {
        ViewData["PageTitle"] = title;
    }
}

Set the title and page section on you controller methods:

public class HomeController : ControllerBase
{
    [PageOptions(Title="Home Page", Section="Home")]
    public ActionResult Index()
    { }
 }

Then I call the ViewData value from my master page (this won't interfere with ViewData.Model):

<body class="<%=ViewData["PageSection"] %>">

Then to reference via CSS, instead of calling .current, give each nav item an ID and then use the body class in combination with that ID to determine the current page.

body.home #HomeNav { /* selected */  }
body.about #AboutNav { /* selected */  }
John Sheehan
  • 77,456
  • 30
  • 160
  • 194
  • As an alternative, if all your ViewModel inherit from a base ViewModel, you can add the Title/Section props in your base viewmodel and set them when you populate those in your view methods. The above method assumes each controller method returns the same view every time (or similar views that would share a title/section definition), which doesn't always apply. – John Sheehan Jun 03 '09 at 15:11
  • seems quite complex.. I like that :-) I will study this method for future projects, but due to lack of time I'll use the javascript solution I already implemented for now.. Thanks for the effort, much appriciated. – Thomas Stock Jun 03 '09 at 15:20
  • This becomes much less trivial if your menus are dynamically generated, such as, say, a CMS. This was the point of view I answered the question from. If you have hard links, then yes, your solution is best. – Chad Ruppert Jun 03 '09 at 15:21
  • I don't really get the last part about css referencing tho. To continue on my example, how would you create the class "current" on that LI? It needs to be named "current" because the jquery plugin requires that. – Thomas Stock Jun 03 '09 at 15:25
  • Then you'd need to put an if block to add the attribute if the page section value matches the li element. – John Sheehan Jun 03 '09 at 15:49
  • What jQuery plugin and what are you trying to do with it? That's not mentioned in the question. – John Sheehan Jun 03 '09 at 15:50
  • The other benefit to doing it like this is you'll have a finite list of sections to include in your CSS but if you match on URL, you could have infinite URLs to evaluate. – John Sheehan Jun 03 '09 at 15:51
  • I use the same approach in PHP, now I can do it in .NET. Thx. – guzart Jun 29 '10 at 21:11
9

Extract the current location from window.location. Then use a selector that specifies the value of the href attribute to choose just those elements that match (presumably only one).

 var currentLocation = window.location.href;
 // probably needs to be massaged to extract just the path so that it works in dev/prod

$("#menu li a[href$="+currentLocation+"]").addClass("current");
tvanfosson
  • 524,688
  • 99
  • 697
  • 795
6

That's probably the least intensive way of doing it. If you can count on the users to have javascript enabled, I see nothing wrong with this, and have done it myself on occasion.

Request.Url is the object you are interested in to get the current page on the server side. The suggestion to use window.location.href by tvanfosson isn't bad either if you want to keep it entirely clientside.

The advantage of using serverside, is that Request.Url has easily accessible parts of the url, such as Request.Url.Host, etc to help with your link-munging needs.

Chad Ruppert
  • 3,650
  • 1
  • 19
  • 19
  • nice edit. I'll use the serverside thingy as it looks more waterproof than windows.location.href – Thomas Stock Jun 03 '09 at 14:51
  • Actually I hadn't considered using the Request object in rendering phase since I was working with a plugin client-side and only thought about it from that perspective. I'll have to see if this makes my navigation code cleaner. – tvanfosson Jun 03 '09 at 14:56
  • Works with this: $("#menu li a[href=<%=Request.Url.LocalPath %>]").parent("li").addClass("current"); – Thomas Stock Jun 03 '09 at 15:13
0

I had the same problem. Using the following answers, I managed to put together the solution below.

https://stackoverflow.com/a/4733394/280972

https://stackoverflow.com/a/5650735/280972

In HtmlHelpers.cs:

public static class HtmlHelpers
{
    public static HtmlString MenuLink(
        this HtmlHelper htmlHelper,
        string linkText,
        string actionName,
        string controllerName
    )
    {
        var currentAction = htmlHelper.ViewContext.RouteData.GetRequiredString("action");
        var currentController = htmlHelper.ViewContext.RouteData.GetRequiredString("controller");
        var link = htmlHelper.ActionLink(linkText, actionName, controllerName);
        var prefix = (actionName == currentAction && controllerName == currentController) ? String.Format("<li class=\"current\">") : String.Format("<li>");
        const string suffix = "</li>";
        return new HtmlString(prefix + link + suffix);
    }
}

In the layout:

<ul class="nav nav-list">
    <li class="nav-header">Statistics</li>
    @Html.MenuLink("Number of logins", "Logins", "Statistics")
</ul>

Note that the MenuLink-helper creates both the li-tag and the a-tag.

Feedback on this solution is very welcome!

Community
  • 1
  • 1
Adrian Schmidt
  • 1,886
  • 22
  • 35
-2

This should NOT be done with Javascript! Deciding which page you are on is the job of the server side code, it's not a UI behaviour.

Have the menu as a user control and pass a value to it to indicate which part of the menu should be highlighted. I'm front end person, not a .NET developer but something like this:

<yourControl:menuControl runat="server" ID="menu" selectedPage="4" />
edeverett
  • 8,012
  • 33
  • 28
  • 1
    You can combine javascript & serverside code.. I'm not going to make a control for that. Besides, it's asp.net mvc. And in your solution you'd have to include the menu on every page? + it's hard to change the orders of the menu items. A lot of things wrong with your solution. – Thomas Stock Jun 03 '09 at 15:00
  • I dunno. I think it's a trivial thing to fail if JS is disabled. You lose a memory aid, essentially, if it fails. You do not break functionality. – Chad Ruppert Jun 03 '09 at 15:20
  • Fair enough with my implement of .NET, I've no doubt it could be done better. I guess I should have stayed with the point about Javascript being a bad solution. @Chad, the losing usability on a site is not trivial, navigation is hugely important for the user and memory has nothing to do with it if the user has landed on that page. A Javascript solution for this is page bloat - there is no good reason that this logic should not be carried out on the server where it can be done faster and more reliably. – edeverett Jun 03 '09 at 16:14
  • @edeverett You don't use breadcrumbs or other aids beyond just simple menu active state? I HOPE that's not your only usablity cue. – Chad Ruppert Jun 04 '09 at 12:38