tags:

views:

204

answers:

4

I'm trying to get a good idea on how to modify certain parts of the page based on what page I'm looking at. I can set certain elements with the page's controller but I'm thinking more about the global navigation menu's (currently being rendered with a RenderAction in the application's MasterPage) active states.

Like if I have some navigation links at the top of the screen (using SO's as an example)

Questions | Tags | Users | ...

If I'm in the "Questions" area or page then I want the Questions link to be active with a different color.

I don't want to have to manage this on every page and plus I don't want to be sending in values to my master page and then sending it through the RenderAction as I think that'd be messy. What I want is the Action to just know what area the rendered page is in and highlight the necessary elements.

+5  A: 

The ViewMasterPage has a ViewContext property. The ViewContext contains the RouteData. The RouteData should have an entry for the name of the controller and current action, if they aren't the default. You could use these in your logic on the master page to determine which navigation elements to highlight.

Similarly, if you used a partial view for the navigation, you would have access to the RouteData through the ViewContext property on the ViewUserControl.

EDIT: I don't think it needs to be complicated.

<%
   var current = this.ViewContext.RouteData.Values["controller"] as string ?? "home";
%>

<ul>
  <li><%= Html.ActionLink( "Home", "index", "home", null, new { @class = current == "home" ? "highlight" : "" } %></li>
  ...
</ul>

In fact, I might even refactor it to an HTML extension to make it easier. Turns out that the helper already has a reference to the ViewContext so you don't even need to determine the current controller in the view. Note that I'm only showing one signature, you can add other signatures to handle additional route data and html attributes (these would need to be merged) as needed.

<ul>
  <li><%= Html.NavLink( "Home", "index", "home" ) %></li>
  ...
</ul>

public static class HtmlHelperExtensions
{
    public static string NavLink( this HtmlHelper helper,
                                  string text,
                                  string action,
                                  string controller )
    {
          string current = helper.ViewContext.RouteData.Values["controller"] as string;
          object attributes = null;
          if (string.Equals( current, controller, StringComparison.OrdinalIgnoreCase ))
          {
              attributes = new { @class = "highlight" };
          }

          return this.ActionLink( text, action, controller, null, attributes );  
    }
}
tvanfosson
Very cool, I didn't know that. Let me give that a go and see if that will work for me.
rball
that's way more complicated than it needs to be.
Will
Change ViewContext.RouteData["controller"] to ViewContext.RouteData.Values["controller"] and it worked for me :) Thanks for the help.
rball
SO needs intellisense... :-)
tvanfosson
A: 

This is something that should be controlled from the view itself. If you don't want to modify every view, you can create an user view control (.ascx files) and add it to the master page.

despart
Ok, and then in the user view control, how would I then tell what page I'm on? That's my question.
rball
+3  A: 

Good question!

In the past I used to solve this with checking the RouteData values from the controller and action. But now i'm using the MvcContrib MenuBuilder to do this kind of job. look on their sample code to see how to work with this.

Ok good suggestion, I'll take a look.
rball
Got a link to their sample code? I googled with no sucess
rball
http://mvccontrib.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=37422, you'll find the example in the source code package, MvcContrib.Samples.UI solution, in Samples folder.
+1  A: 

Given the following in Page.Master:

<head runat="server">
    <link href="Standard.CSS" rel="stylesheet" type="text/css" />
    <asp:ContentPlaceHolder ID="header" runat="server" />
</head>
<!-- and later on in the file -->
<ul>
  <li>
    <a href="/questions" class="question">Questions</a>
  </li>
  <li>
    <a href="/questions" class="user">Users</a> 
  </li>
</ul>

Within the view Users.aspx:

<asp:Content ID="header" ContentPlaceHolderID="header" runat="server">
    <title>User's Page</title>
    <style type="text/css">
        .user
        {
            background-color:yellow;
        }
    </style>
</asp:content>

So you don't have to do any kind of weird route parsing or hurring this or durring that. You just add a ContentPlaceHolder in your master page's header, then in each view provide some additional CSS definition within this content placeholder that makes the page look how it should for that particular view.

Will
Wow. Talk about brittle -- you have to do this on every single page or it's broken. I'd much rather write a little code that I keep with the navigation system than have all of this embedded style stuff scattered through all of my views. What if you decide to change how the highlighting is done? Then you need to go and change it everywhere. Yuck.
tvanfosson
Brittle? Lol. At least you can change your views without *having to recompile*. Also, this allows **designers** to control how your views look (which is their job, btw) instead of programmers.
Will
tvanfosson
Well I'm trying to get away from having to set it from every view. I suppose I could, but if I have 20 (sub)pages underneath "User" and for each of them I want to just highlight that link in a way I don't really want to do it for every page. I could just be lazy though I suppose, but I have 4 main sections and 80 pages that just fit underneath those sections...
rball
I think the other issue with this, and it's probably a small thing but if you have multiple things that need to change states then you're going to have to have a lot of different page level style css that's not cached or whatever.
rball