Добавление "активного" тега в список навигации на главной странице asp.net mvc
В проекте asp.net mvc по умолчанию в файле Site.Master есть список навигации по меню:
<div id="menucontainer">
<ul id="menu">
<li><%= Html.ActionLink("Home", "Index", "Home")%></li>
<li><%= Html.ActionLink("About Us", "About", "Home")%></li>
</ul>
</div>
В браузере отображается:
<div id="menucontainer">
<ul id="menu">
<li><a href="/">Home</a></li>
<li><a href="/Home/About">About Us</a></li>
</ul>
</div>
Я хочу иметь возможность динамически устанавливать активный элемент списка на основе вызываемого представления. То есть, когда пользователь смотрит на домашнюю страницу, мне нужен следующий HTML-код:
<div id="menucontainer">
<ul id="menu">
<li class="active"><a href="/">Home</a></li>
<li><a href="/Home/About">About Us</a></li>
</ul>
</div>
Я бы ожидал, что способ сделать это будет примерно таким:
<div id="menucontainer">
<ul id="menu">
<li <% if(actionName == "Index"){%> class="active"<%}%>><%= Html.ActionLink("Home", "Index", "Home")%></li>
<li <% if(actionName == "About"){%> class="active"<%}%>><%= Html.ActionLink("About Us", "About", "Home")%></li>
</ul>
</div>
Ключевым битом здесь является строка <% if(actionName == "Index"){%> class="active"<%}%>
. Я не знаю, как определить, что такое текущее имя action.
Любые предложения о том, как это сделать? Или, если я полностью ошибаюсь, есть ли лучший способ сделать это?
Ответы
Ответ 1
Я сделал себя помощником, чтобы справиться с этим типом вещей. В коде моей главной страницы (может быть добавлен метод расширения... вероятно, лучший подход), я поместил следующий код.
protected string ActiveActionLinkHelper(string linkText, string actionName, string controlName, string activeClassName)
{
if (ViewContext.RouteData.Values["action"].ToString() == actionName &&
ViewContext.RouteData.Values["controller"].ToString() == controlName)
return Html.ActionLink(linkText, actionName, controlName, new { Class = activeClassName });
return Html.ActionLink(linkText, actionName, controlName);
}
Затем я просто назову его на моей странице так:
<%= ActiveActionLinkHelper("Home", "Index", "Home", "selected")%>
Ответ 2
Внутри представления вы можете получить текущее имя действия с помощью:
ViewContext.RouteData.Values["action"].ToString()
Ответ 3
Вы также можете попытаться определить, какая текущая выбранная вкладка находится из имени контроллера и имени представления, а затем добавить атрибут класса.
public static string MenuActionLink(this HtmlHelper helper, string linkText, string actionName, string controllerName)
{
var htmlAttributes = new RouteValueDictionary();
if (helper.ViewContext.Controller.GetType().Name.Equals(controllerName + "Controller", StringComparison.OrdinalIgnoreCase))
{
htmlAttributes.Add("class", "current");
}
return helper.ActionLink(linkText, actionName, controllerName, new RouteValueDictionary(), htmlAttributes);
}
Ответ 4
В MVC 3 Razor View Engine вы можете сделать это как:
@{string ctrName = ViewContext.RouteData.Values["controller"].ToString();}
<div id="menucontainer">
<ul id="menu">
<li @if(ctrName == "Home"){<text> class="active"</text>}>@ Html.ActionLink("Home", "Index", "Home")</li>
<li @if(ctrName == "About"){<text> class="active"</text>}>@ Html.ActionLink("About Us", "About", "Home")</li>
</ul>
</div>
Мой образец работал, когда у меня есть две страницы: "Главная/О нас" и его контроллер имеют одно и то же имя "Индекс", поэтому я получаю имя контроллера для разграничения действий. Если вы хотите получить действие, просто замените следующим:
@{string ctrName = ViewContext.RouteData.Values["action"].ToString();}
Ответ 5
Чтобы внести свой собственный ответ (протестированный в MVC4), я взял несколько лучших бит других ответов, исправил несколько проблем и добавил помощника для работы с URL-адресами, которые не обязательно разрешаются с помощью Controller и Action (например, если у вас встроенная CMS, работающая с некоторыми ссылками на страницы и т.д.)
Код также доступен для github: https://gist.github.com/2851684
///
/// adds the active class if the link action & controller matches current request
///
public static MvcHtmlString MenuActionLink(this HtmlHelper htmlHelper,
string linkText, string actionName, string controllerName,
object routeValues = null, object htmlAttributes = null,
string activeClassName = "active")
{
IDictionary htmlAttributesDictionary =
HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
if (((string)htmlHelper.ViewContext.RouteData.Values["controller"])
.Equals(controllerName, StringComparison.OrdinalIgnoreCase) &&
((string)htmlHelper.ViewContext.RouteData.Values["action"])
.Equals(actionName, StringComparison.OrdinalIgnoreCase))
{
// careful in case class already exists
htmlAttributesDictionary["class"] += " " + activeClassName;
}
return htmlHelper.ActionLink(linkText, actionName, controllerName,
new RouteValueDictionary(routeValues),
htmlAttributesDictionary);
}
///
/// adds the active class if the link path matches current request
///
public static MvcHtmlString MenuActionLink(this HtmlHelper htmlHelper,
string linkText, string path, object htmlAttributes = null,
string activeClassName = "active")
{
IDictionary htmlAttributesDictionary =
HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
if (HttpContext.Current.Request.Path
.Equals(path, StringComparison.OrdinalIgnoreCase))
{
// careful in case class already exists
htmlAttributesDictionary["class"] += " " + activeClassName;
}
var tagBuilder = new TagBuilder("a")
{
InnerHtml = !string.IsNullOrEmpty(linkText)
? HttpUtility.HtmlEncode(linkText)
: string.Empty
};
tagBuilder.MergeAttributes(htmlAttributesDictionary);
tagBuilder.MergeAttribute("href", path);
return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal));
}
Ответ 6
Старый вопрос, но, надеюсь, кто-то может найти это очень полезным.
- Поместите что-то, что вы можете использовать для идентификации своей страницы в ViewBag, я использовал ViewgBag.PageName
Например, в index.cshtml поставьте что-то вроде
@{
ViewBag.PageName = "Index";
}
- Добавьте класс к каждому элементу ссылки с условным оператором, чтобы он возвращался активным, если посещаемая страница имеет требуемое значение или возвращает пустую строку в противном случае. Подробнее см. Ниже:
<li class="@((ViewBag.PageName == "Index") ? "active" : "")"><a href="@Url.Action("Index","Home")">Home</a></li>
<li class="@((ViewBag.PageName == "About") ? "active" : "")"><a href="@Url.Action("About","Home")">About</a></li>
<li class="@((ViewBag.PageName == "Contact") ? "active" : "")"><a href="@Url.Action("Contact","Home")">Contact</a></li>
Ответ 7
Использование MVC3 с Razor View предлагает еще один вариант:
_Layout.cshtml:
<li class="@ViewBag.NavClassHome">@Html.ActionLink("Home", "Index", "Home")</li>
<li class="@ViewBag.NavClassAbout">@Html.ActionLink("Disclaimer", "About", "Home")</li>
HomeController:
public ActionResult Index() {
ViewBag.NavClassHome = "active";
return View();
}
public ActionResult About() {
ViewBag.NavClassAbout = "active";
return View();
}
Если вы хотите сохранить это для обратной передачи, вам также нужно назначить значение ViewBag:
[HttpPost]
public ActionResult Index() {
ViewBag.NavClassHome = "active";
return View();
}
[HttpPost]
public ActionResult About() {
ViewBag.NavClassAbout = "active";
return View();
}
Протестировано и работает отлично для меня, но у вас будет имя класса css в коде на стороне сервера.
Ответ 8
Это должно работать с использованием jQuery на стороне клиента, использует Google для обслуживания последней библиотеки jQuery:
<script src="http://www.google.com/jsapi" type="text/javascript" language="javascript"></script>
<script type="text/javascript" language="javascript">google.load("jquery", "1");</script>
<script language="javascript" type="text/javascript">
$(document).ready(function(){
var str=location.href.toLowerCase();
$('#menucontainer ul#menu li a').each(function() {
if (str.indexOf(this.href.toLowerCase()) > -1) {
$(this).attr("class","current"); //hightlight parent tab
}
});
});
</script>
Ответ 9
Я хотел немного контролировать свой макет, и это то, что я сделал.
Создайте модель LayoutModel, которую наследуют другие модели:
public abstract class LayoutModel
{
public CurrentPage CurrentPage { get; set; }
}
Создайте атрибут LayoutAttribute, который наследует ActionFilterAttribute следующим образом:
public class LayoutAttribute : ActionFilterAttribute
{
private CurrentPage _currentPage { get; set; }
public LayoutAttribute(
CurrentPage CurrentPage
){
_currentPage = CurrentPage;
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var result = filterContext.Result as ViewResultBase;
if (result == null || result.Model == null || !(result.Model is LayoutModel)) return;
((LayoutModel)result.Model).CurrentPage = _currentPage;
}
}
Теперь на уровне Action или Controller я могу установить текущую страницу (и другие вещи, если захочу) следующим образом:
[Layout(CurrentPage.Account)]
public class MyController : Controller
{
}
В моем представлении макета теперь у меня есть доступ к текущей странице и все, что я добавляю к LayoutModel.
Ответ 10
То, что ваш взгляд должен знать о ваших действиях контроллера, ломается с шаблоном MVC. Возможно, ваш контроллер может передать некоторую "контрольную" информацию в представление, чтобы в конечном счете позволить ему выполнить одно и то же, единственное различие заключается в том, кто несет ответственность.
Как и в действии вашего контроллера, вы можете:
public ActionResult Index(){
ViewData["currentAction"] = "Index";
//... other code
return View();
}
Затем, по вашему мнению, вы можете:
<% if( ((string)ViewData["currentAction"]) == "Index" {%> <!- some links --><% } %>
<% if( ((string)ViewData["currentAction"]) == "SomethingElse" {%> <!- some links --><% } %>
Однако чем больше я думаю об этом, тем больше сомневаюсь, почему вы используете один и тот же вид для нескольких действий. Является ли представление тем, что похоже?
Если прецедент оправдывает его, то переходите к моему предложению выше. Но в противном случае вы могли бы разбить все на несколько видов (по одному для каждого действия контроллера), и проблема решает сам.
Ответ 11
Основываясь на предыдущих ответах, вот что мое текущее решение для одной и той же проблемы:
На главной странице я даю каждому li id, который соответствует контроллеру и действию, так как это должно быть известно из ActionLink. Ранее я делал это с названием страницы, но это помогает организации.
Site.Master:
<ul id="menu">
<li id="menuHomeIndex" runat="server"><%= Html.ActionLink("Home", "Index", "Home") %></li>
<li id="menuHomeAbout" runat="server"><%= Html.ActionLink("About Us", "About", "Home") %></li>
</ul>
Site.Master.cs:
// This is called in Page_Load
private void SetActiveLink()
{
string action = "" + ViewContext.RouteData.Values["controller"] + ViewContext.RouteData.Values["action"];
var activeMenu = (HtmlGenericControl)Page.Master.FindControl("menu" + action);
if (activeMenu != null)
{
activeMenu.Attributes.Add("class", "selected");
}
}
Это больше работает, чем встроенный код, но я думаю, что он чище, а также позволяет вам иметь действия с тем же именем в разных контроллерах. Поэтому, если вы добавите больше элементов меню с разными контроллерами, в меню будут выделены не все действия с именем Index.
Если кто-либо видит проблемы с этим подходом, пожалуйста, дайте мне знать.
Ответ 12
Используя MVC3 с Razor View, вы можете реализовать это как:
<ul id="menu">
@if (ViewContext.RouteData.Values["action"].ToString() == "Index")
{
<li class="active">@Html.ActionLink("Home", "Index", "Home")</li>
}
else
{
<li>@Html.ActionLink("Home", "Index", "Home")</li>
}
@if (ViewContext.RouteData.Values["action"].ToString() == "About")
{
<li class="active">@Html.ActionLink("About", "About", "Home")</li>
}
else
{
<li>@Html.ActionLink("About", "About", "Home")</li>
}
</ul>
И затем применяя свой стиль вашего "активного" класса, например:
ul#menu li.active
{
text-decoration:underline;
}
Ответ 13
Вот версия, совместимая с текущей версией MVC4.
Я переписал код Адама Карра как метод расширения.
using System;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
namespace MyApp.Web {
public static class HtmlHelpers {
/// <summary>
/// Returns an anchor element (a element) that contains the virtual path of the
/// specified action. If the controller name matches the active controller, the
/// css class 'current' will be applied.
/// </summary>
public static MvcHtmlString MenuActionLink(this HtmlHelper helper, string linkText, string actionName, string controllerName) {
var htmlAttributes = new RouteValueDictionary();
string name = helper.ViewContext.Controller.GetType().Name;
if (name.Equals(controllerName + "Controller", StringComparison.OrdinalIgnoreCase))
htmlAttributes.Add("class", "current");
return helper.ActionLink(linkText, actionName, controllerName, new RouteValueDictionary(), htmlAttributes);
}
}
}
Ответ 14
Попробуйте
Должен работать нормально!!!
ИЗМЕНИТЬ: УДАЛЕН В BETA1
Удалено свойство ViewName из класса ViewContext.