tags:

views:

418

answers:

3

As a base for discussion. Create a standard ASP.NET MVC Web project. It will contain two menu item in the master page:

<div id="menucontainer">
  <ul id="menu">
    <li>
      <%= Html.ActionLink("Home", "Index", "Home")%></li>
    <li>
      <%= Html.ActionLink("About", "About", "Home")%></li>
  </ul>
</div>

How can I set the visual css style indicating the current page. For example, when in the About page/controller, I essentially would like to to this:

<%= Html.ActionLink("About", "About", "Home", new {class="current"})%></li>

And, of course, when on the home page:

<%= Html.ActionLink("Home", "Index", "Home", new {class="current"})%></li>

(Having a css style names current that visually indicates in the menu that this is the current page.)

I could break out the menu div from the master page into a content place holder, but that would mean that I must put the menu on every page.

Any ideas, is there a nice solution to this?

A: 

It might just be that it's the 5th parameter, so slot a null before your html attribute. This post here describes it as such, though you can pass in some stuff on the 4th arguement, the 5th is specifically for HTMLattributes

<%= Html.ActionLink("Home", "Index", "Home", null, new {@class="current"})%>

Amadiere
@class instead of class :)
Arnis L.
+3  A: 

The easiest way is to get the current controller and action from the ViewContext's RouteData. Note the change in signature and use of @ to escape the keyword.

<% var controller = ViewContext.RouteData.Values["controller"] as string ?? "Home";
   var action = ViewContext.RouteData.Values["action"] as string ?? "Index";
   var page = (controller + ":" + action).ToLower();
 %>

<%= Html.ActionLink( "About", "About", "Home", null,
                     new { @class = page == "home:about" ? "current" : "" ) %>
<%= Html.ActionLink( "Home", "Index", "Home", null,
                     new { @class = page == "home:index" ? "current" : "" ) %>

Note that you could combine this an HtmlHelper extension like @Jon's and make it cleaner.

<%= Html.MenuLink( "About", "About", "Home", null, null, "current" ) %>

Where MenuActionLink is

public static class MenuHelperExtensions
{
     public static string MenuLink( this HtmlHelper helper,
                                    string text,
                                    string action,
                                    string controller,
                                    object routeValues,
                                    object htmlAttributes,
                                    string currentClass )
     {
         RouteValueDictionary attributes = new RouteValueDictionary( htmlAttributes );
         string currentController = helper.ViewContext.RouteData.Values["controller"] as string ?? "home";
         string currentAction = helper.ViewContext.RouteData.Values["action"] as string ?? "index";
         string page = string.Format( "{0}:{1}", currentController, currentAction ).ToLower();
         string thisPage = string.Format( "{0}:{1}", controller, action ).ToLower();
         attributes["class"] = (page == thisPage) ? currentClass : "";
        return helper.ActionLink( text, action, controller, new RouteValueDictionary( routeValues ), attributes );
     }
}
tvanfosson
Not a good solution. A view should know nothing about controllers and actions. The data to display should only be supplied by the model.
Developer Art
It's only a short cut to getting the URL -- you could do the same thing using the requested URL but then you'd have to parse it. I'm content with this mechanism not breaking the pattern.
tvanfosson
Is the syntax correct? I get "Cannot apply indexing with [] to an expression of type 'System.Web.Routing.RouteData'"
Magnus Johansson
I forgot that you actually need to look at the Values collection on the RouteData object. Fixed.
tvanfosson
Sorry for being an obnoxious novice here... but this will render the following html a tag: <a Count=\"1\" Keys=\"System.Collections.Generic.Dictionary`2+KeyCollection[System.String,System.Object]\" Values=\"System.Collections.Generic.Dictionary`2+ValueCollection[System.String,System.Object]\" href=\"/\">Home</a>
Magnus Johansson
Do you mean the helper extension? I confess that I didn't actually try it. I'll pull up Snippet Compiler and give it a run.
tvanfosson
Yes, I mean the helper extension.
Magnus Johansson
I needed to convert the routeValues to a RouteValueDictionary as well to force it to choose the correct signature and not recreate a RouteValueDictionary from the htmlAttributes (that caused the weird attributes on the link). Fixed.
tvanfosson
Great. Thanks for your help.
Magnus Johansson
I noticed that the ViewContext is available on the HtmlHelper as well so you could migrate the code to determine the current controller/action to the helper and remove it from the View. My updated code now reflects this and includes the ability to specify the class to use for the current action.
tvanfosson
+1  A: 

I recently created an HTML Helper for this that looks like:

public static string NavigationLink(this HtmlHelper helper, string path, string text)
{
    string cssClass = String.Empty;
    if (HttpContext.Current.Request.Path.IndexOf(path) != -1)
    {
        cssClass = "class = 'selected'";
    }

    return String.Format(@"<li><a href='{0}' {1}>{2}</a></li>", path, cssClass, text);
}

The Implementation looks like this:

  <ul id="Navigation">
  <%=Html.NavigationLink("/Path1", "Text1")%>
  <%=Html.NavigationLink("/Path2", "Text2")%>
  <%=Html.NavigationLink("/Path3", "Text3")%>
  <%=Html.NavigationLink("/Path4", "Text4")%>
  </ul>
Jon
I have a few issues with this. First, it doesn't address string casing and will break if you supply a different casing in the url (remember that users can type these directly). Second, it will be hard to unit test given the reliance on the static HttpContext.Current. Third, it breaks down if you multiple levels of actions/menus as the "top" level will match all of the sub-levels. Granted, my solution has the third problem as well, but since it doesn't rely on string matching it is somewhat easier to modify it to accomodate multiple levels.
tvanfosson
...I do like, and have borrowed the idea -- thank you -- the idea of making it an extension method to make the view code neater.
tvanfosson
Thanks for the feedback! I'm still a novice at all of this and am always looking for ways to improve my code.
Jon