views:

4428

answers:

5

Hello,

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>
+6  A: 

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
Didn't consider keeping it entirely clientside. Not a bad idea at all to use window.location.href.
Chad Ruppert
nice jquery selector, didn't think of that :-)
Thomas Stock
But I do wonder if this will work with the "/" url..
Thomas Stock
Yes -- that's what I mean by having to massage the url, i.e., it can only end in / if that's the entire url. I have some code at work that's similar to that to use with the Accordion plugin navigation filter, but I'm home sick today. :-(
tvanfosson
Note, you'll also have to remove the protocol, port, and server name.
tvanfosson
I'll just use the serverside thing from Chad, combined with your jquery.
Thomas Stock
@Thomas -- that makes a lot of sense. I hadn't thought of it, but will give it a try, too. Note you may still need to massage it with respect to the trailing slash.
tvanfosson
It works with this: $("#menu li a[href=<%=Request.Url.LocalPath %>]").parent("li").addClass("current");
Thomas Stock
but will probably use the all-serverside solution.. will try that one and then see who I accept :-)
Thomas Stock
+4  A: 

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
nice edit. I'll use the serverside thingy as it looks more waterproof than windows.location.href
Thomas Stock
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
Works with this: $("#menu li a[href=<%=Request.Url.LocalPath %>]").parent("li").addClass("current");
Thomas Stock
spiffy, glad it works for you. :)
Chad Ruppert
A: 

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
Thomas Stock
+1 on not doing this with JavaScript-1 for using a control
John Sheehan
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
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
@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
+12  A: 

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
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
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
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
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
Then you'd need to put an if block to add the attribute if the page section value matches the li element.
John Sheehan
What jQuery plugin and what are you trying to do with it? That's not mentioned in the question.
John Sheehan
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
I use the same approach in PHP, now I can do it in .NET. Thx.
UberNeet
A: 

Check out this link for setting assigning css class to MVC dropdownlist:link text