OK new to MVC. I had asked this question earlier and got an answer but I am wondering if there is a simpler solution.
Say I have a master page with a menu laid out as an
The answer from Jakub Konecki led me in the right direction... here is the controller action I ended up with:
[ChildActionOnly]
public ActionResult MainMenu()
{
var items = new List<MenuItem>
{
new MenuItem{ Text = "Home", Action = "Index", Controller = "Home", Selected=false },
new MenuItem{ Text = "My Profile", Action = "Index", Controller = "Profile", Selected = false},
new MenuItem{ Text = "About", Action = "About", Controller = "Home", Selected = false }
};
string action = ControllerContext.ParentActionViewContext.RouteData.Values["action"].ToString();
string controller = ControllerContext.ParentActionViewContext.RouteData.Values["controller"].ToString();
foreach (var item in items)
{
if (item.Controller == controller && item.Action == action)
{
item.Selected = true;
}
}
return PartialView(items);
}
Hope this helps someone.
Here is another possibility, more simple in my opinion using an HTML Helper:
CSS Class
ul#menu li.selected a {
background-color: #034af3;
color: #e8eef4;
}
HTML Helper
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;
namespace MyWebApp.WebUI.HtmlHelpers
{
public static class HtmlHelperExtensions
{
public static string ActivePage(this HtmlHelper helper, string controller, string action)
{
string classValue = "";
string currentController = helper.ViewContext.Controller.ValueProvider.GetValue("controller").RawValue.ToString();
string currentAction = helper.ViewContext.Controller.ValueProvider.GetValue("action").RawValue.ToString();
if (currentController == controller && currentAction == action)
{
classValue = "selected";
}
return classValue;
}
}
}
Menu on the Layout :
<nav>
<ul id="menu">
<li class="@Html.ActivePage("Home", "Index")">@Html.ActionLink("Home", "Index", "Home")</li>
<li class="@Html.ActivePage("Home", "About")">@Html.ActionLink("About", "About", "Home")</li>
</ul>
</nav>
More informations here : http://bubblogging.wordpress.com/2012/02/12/mvc-add-selected-class-to-menu/
Beside all other solutions you can use this approach to load menus dynamically from Database and make the selected menu active and keep the status across the website.
in the Layout page:
<body>
<header>
@{Html.RenderPartial("_MenuPartial"); }
</header>
@RenderBody()
</body>
Then your partial view contains :
<nav class="navMain">
<ul>
foreach (var menu in menuList)
{
<li id="@menu.Id"><a href="@menu.Url">@menu.Name</a></li>
}
</ul>
</nav>
To activated the clicked menu, you need a script in the partial view. Also 'active' class in nothing but changing the background color in the CSS.
<script>
var selector = '.navMain li';
$(selector).on('click', function () {
$(this).addClass('active').siblings().removeClass('active');
var menuId = $(this).prop("id");
localStorage.setItem('SelectedMenu', menuId);
});
Since we set the HREF for each menu link, we'll lose the active class per post-back. So you need to read the selected menu from localStorage and apply it on the proper menu in partial view:
<script>
$(document).ready(function () {
var activeMenuLocalStorage = localStorage.getItem('SelectedMenu');
if (!isNaN(activeMenuLocalStorage)) {
{
$("#" + activeMenuLocalStorage).addClass('active');
}
});
stephen,
here's my contribution to the party. a recursive inline function to populate the <ul><li>
for as many depths as is required (here's the entire ascx file):
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<List<GBC_Art.Controllers.MenuItemModel>>" %>
<%@ Import Namespace="GBC_Art.Controllers" %>
<%
Action<List<MenuItemModel>, int> printNodesRecursively = null;
printNodesRecursively = (List<MenuItemModel> nodeList, int depth) =>
{
if (nodeList == null || nodeList.Count == 0) return;
%>
<ul<%= depth == 0 ? " id='menu'" : ""%>>
<%
foreach (var menuItem in nodeList)
{
%>
<li><%= Html.ActionLink(menuItem.Text, menuItem.Action, menuItem.Controller, null, new { @class = menuItem.Selected ? "selected" : "" })%>
<%printNodesRecursively(menuItem.SubMenu, depth + 1);%>
</li>
<%
}
%>
</ul>
<%
};
List<MenuItemModel> nodes = Model;
printNodesRecursively(nodes, 0);
%>
usage -> called as a partialview via the controller as per your example above.
cheers
Just learning MVC now, but I came across this problem and I've figured out a much quicker method of achieving this. It relies on consistent URLs, and isn't affected by query strings.
<ul id="menu">
<li @(Request.Path.Equals("/") ? Html.Raw("class=\"selected\"") : Html.Raw(""))>
@Html.ActionLink("Home", "Index", "Home")</li>
<li @(Request.Path.Equals("/about") ? Html.Raw("class=\"selected\"") : Html.Raw(""))>
@Html.ActionLink("About", "About", "Home")%></li>
</ul>
Of course, you could handle multiple URLs showing the same item selected through OR
logic or making a list. But if you're making a list, you'd of course want that in your model. You could have an option highlight for a whole directory by just checking that the start of the string matches.
As is, this would be most suitable to a more static website. If you include this on a LayoutPage, maintenance is pretty easy as you don't have extra classes or functions to maintain. Obviously this wouldn't cut it if you were working off a CMS.
You should pass all relevant information in the Model. Ideally your menu will be rendered as a Partial View by a separate controller method. I have a Navigation controller with actions like MainMenu, FooterMenu, Breadcrumbs, etc that render individual parts.
Your model will be a collection of menu items like:
public class MenuItemModel
{
public MenuItemModel()
{
SubMenu = new List<MenuItemModel>();
}
public string Text { get; set; }
public string Controller { get; set; }
public string Action { get; set; }
public bool Selected { get; set; }
public List<MenuItemModel> SubMenu { get; private set; }
}
Your Controller will create a collection of menu items and pass them to the view with the appropriate item selected. Then the view can be as simple as:
<ul id="menu">
<% foreach(var menuItem in Model.MenuItems) { %>
<li><%: Html.ActionLink(menuItem.Text, menuItem.Action, menuItem.Controller, null, new { @class = menuItem.Selected ? "selected" : "" })%></li>
<% } %>
</ul>