2008-10-18 12 views
34

Dans le projet asp.net par défaut mvc, dans le fichier Site.master, il y a une liste de navigation de menu:Ajout tag "active" à la liste de navigation dans une page maître mvc asp.net

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

Cette Renders dans le navigateur:

<div id="menucontainer"> 
    <ul id="menu">    
     <li><a href="/">Home</a></li> 
     <li><a href="/Home/About">About Us</a></li> 
    </ul> 
</div> 

Je veux être en mesure de définir dynamiquement l'élément actif de la liste, en fonction de la vue qui est appelé. Autrement dit, lorsque l'utilisateur est à la recherche à la page d'accueil, je veux le code HTML suivant à créer:

<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> 

je me attends à ce que la façon de le faire serait quelque chose comme:

<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> 

Le bit clé ici est la ligne <% if(actionName == "Index"){%> class="active"<%}%>. Je ne sais pas comment déterminer le nom d'action actuel.

Des suggestions sur comment faire cela? Ou, si je suis complètement sur la mauvaise piste, y at-il une meilleure façon de le faire?

Répondre

0

Le fait que votre View connaisse les actions de votre contrôleur rompt avec le modèle MVC. Peut-être que votre contrôleur pourrait transmettre des informations de «contrôle» à la vue pour finalement lui permettre d'accomplir la même chose, la seule différence étant de savoir qui est responsable.

Comme dans l'action de votre contrôleur, vous pouvez:

public ActionResult Index(){ 
    ViewData["currentAction"] = "Index"; 
    //... other code 
    return View(); 
} 

ensuite sur votre vue, vous pouvez:

<% if(((string)ViewData["currentAction"]) == "Index" {%> <!- some links --><% } %> 
<% if(((string)ViewData["currentAction"]) == "SomethingElse" {%> <!- some links --><% } %> 

Cependant, plus je pense plus je me demande pourquoi vous utilisez la même vue pour plusieurs actions. Est-ce que la vue similaire?

Si le cas d'utilisation le justifie alors suivez ma suggestion ci-dessus. Mais sinon, vous pourriez peut-être diviser les choses en plusieurs vues (une pour chaque action du contrôleur) et le problème se résout.

-1

Essayez

devrait fonctionner correctement !!!

EDIT: SUPPRIMEE DANS BETA1

Suppression de la propriété ViewName de la classe ViewContext.

10

intérieur vue, vous pouvez obtenir le nom de l'action en cours avec:

ViewContext.RouteData.Values["action"].ToString() 
0

Sur la base des réponses précédentes, voici ce que ma solution actuelle est la même question:

Dans la page principale Je donne à chaque li un identifiant qui correspond au contrôleur et à l'action, puisque ceci devrait être connu par ActionLink. Je le faisais précédemment avec le titre de la page, mais cela aide à l'organisation.

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"); 
    } 
} 

Il est plus de travail que le code en ligne, mais je pense qu'il est plus propre et permet également d'avoir des actions avec le même nom dans différents contrôleurs. Donc, si vous ajoutez plus d'éléments de menu avec des contrôleurs différents, toutes les actions nommées Index ne seront pas mises en surbrillance dans le menu.

Si quelqu'un voit des problèmes avec cette approche s'il vous plaît faites le moi savoir.

10

Vous pouvez également essayer de détecter quel onglet est actuellement sélectionné à partir de son nom de contrôleur et de son nom, puis ajouter l'attribut de classe.

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); 
} 
+3

Si vous obtenez une erreur sur le retour helper.ActionLink , Ajoutez ceci à vos références en utilisant: using System.Web.Mvc.Html; – Mike

+2

il devrait retourner un MvcHtmlString et pas une chaîne – BjarkeCK

1

Cela devrait fonctionner en utilisant jQuery sur le côté client des choses, utilise Google pour servir la dernière bibliothèque 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> 
+5

S'il vous plaît ne pas le faire. – Brady

+0

@Brady pourquoi pas? Ressemble à une solution solide. –

37

Je me suis fait une méthode d'aide pour gérer ce genre de chose. Dans le code derrière de ma page maître (pourrait être poussé à une méthode d'extension ... probablement une meilleure approche), j'ai mis le code suivant.

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); 
} 

Ensuite, je l'appelle juste dans ma page comme ceci:

<%= ActiveActionLinkHelper("Home", "Index", "Home", "selected")%> 
+0

merci, je vais utiliser votre méthode ... –

+1

le nouveau {Class = "selected"} devrait être nouveau {Class = activeClassName}; P –

+0

Awesome. J'étais juste à la recherche moi-même. – Stevus

0

En utilisant MVC3 avec vue Razor, vous pouvez mettre en œuvre cela comme:

<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> 

Et puis l'application de votre style de votre classe ".active" comme:

ul#menu li.active 
{ 
text-decoration:underline; 
} 
9

Dans MVC 3 Voir Razor Engine, vous pouvez le faire comme:

@{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> 

Mon échantillon travaillé quand j'ai deux pages comme: Accueil/A propos et son contrôleur a même nom d'index, donc j'obtenir le nom du contrôleur pour insteed distinction d'action. Si vous souhaitez obtenir une action, il suffit de remplacer avec ce qui suit:

@{string ctrName = ViewContext.RouteData.Values["action"].ToString();} 
+0

merci simple et efficace! – castors33

+0

Excellente solution, merci! –

+0

également probablement la peine de faire quelque chose comme: @if (ctrName.ToLower() == "home") pour tenir compte de toute personne tapant manuellement dans les URL. Sinon, vous trouverez que quelqu'un peut visiter votre page à/home (plutôt que/Home) et ne pas voir le menu stylé comme actif. Notez, si vous utilisez "ToLower()", assurez-vous que votre variable "ctrName" ne peut jamais être nulle, sinon vous pourriez lancer une exception. Par exemple. définissez "ctrName" comme: string ctrName = ""; if (ViewContext.RouteData.Values ​​["action"]! = Null) { ctrName = ViewContext.RouteData.Values ​​["action"]. ToString(); } –

0

Voici la version compatible avec la version actuelle de MVC4.
J'ai réécrit le code d'Adam Carr en tant que méthode d'extension.

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); 
     } 
    } 
} 
+2

helper.ActionLink ne compile pas –

+0

@AndyBrudtkuhl en utilisant System.Web.Mvc.Html; obtient pour compiler – m4chine

6

Contribuer ma propre réponse (testé MVC4), je pris quelques meilleurs morceaux des autres réponses, fixé quelques questions, et a ajouté une aide pour travailler avec les urls qui ne sont pas résolus nécessairement par l'intermédiaire du contrôleur & action (. par exemple si vous avez un CMS intégré traitant des liens de page, etc.)

Le code est également forkable sur github: https://gist.github.com/2851684

/// 
/// adds the active class if the link's 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's 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)); 
}
1

Je voulais avoir un peu plus de contrôle sur mon mise en page, et c'est ce que j'ai fait.

Créer un LayoutModel que d'autres modèles héritent:

public abstract class LayoutModel 
{ 
    public CurrentPage CurrentPage { get; set; } 
} 

Créer une LayoutAttribute qui hérite de ActionFilterAttribute comme ceci:

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; 
    } 
} 

maintenant sur l'action ou au niveau du contrôleur je peux mettre la page en cours (et d'autres choses si je voulais) comme ceci:

[Layout(CurrentPage.Account)] 
public class MyController : Controller 
{ 

} 

Dans mon l J'ai maintenant accès à la page en cours, et tout ce que j'ajoute à LayoutModel.

3

En utilisant MVC3 avec un rasoir View offre une autre option:

_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(); 
} 

Si vous souhaitez conserver ce pour un postback aussi bien, Vous devez également affecter la valeur ViewBag ici:

[HttpPost] 
public ActionResult Index() { 
    ViewBag.NavClassHome = "active"; 
    return View(); 
} 

[HttpPost] 
public ActionResult About() { 
    ViewBag.NavClassAbout = "active"; 
    return View(); 
} 

Testé et fonctionne bien pour moi, mais vous aurez un nom de classe CSS dans votre code côté serveur.

4

Une vieille question mais j'espère que quelqu'un pourrait trouver cela très utile.

  1. Mettez quelque chose que vous pouvez utiliser pour identifier votre page dans le ViewBag, je ViewgBag.PageName

Par exemple, dans Index.cshtml, mettre quelque chose comme

@{ 
    ViewBag.PageName = "Index"; 
} 
  1. Ajouter une classe à chaque élément de lien avec une condition instruction à retourner active si la page visitée a la valeur requise, ou renvoie une chaîne vide dans le cas contraire. Voir ci-dessous pour plus de détails:

<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>

Je n'ai pas testé juste, j'utiliser cette méthode dans mes projets

+0

Ce fut la seule chose que j'ai trouvé qui a fonctionné. Je vous remercie. J'utilise MVC4. –

Questions connexes