I need to have a submenu(Executive Sections) for a section called 'Executives'. This menu must show up when a user clicks Executives in the main menu and remain as long as any submenu link of Executives is clicked.
Catch is: the submenu links are dynamic and come from the DB/CMS system, so it's not a hard-coded list. Which is where my woe begins.
Currently, I have it in a PartialView that requires a model of IEnumerable so it can build dynamically.
But how I go about making this work as I need it too is a little twisted up in my mind.
#using xxx.Models
#model IEnumerable<xxx.Models.ExecutiveSection>
<ul class="nav navbar-nav">
#foreach (ExecutiveSection es in Model)
{
<li>#Html.ActionLink(es.SectionName, "Section", "Executive", new { id = es.ExecutiveSectionId })</li>
}
I solved it.
Did this in the _Layout
#{ // Sub-Menu for Executive Sections
if (HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString().Contains("Executive"))
{
Html.RenderAction("ExecutiveSubMenu", "Executive");
}
}
And in the controller:
public ActionResult ExecutiveSubMenu()
{
// get sections
var sections = db.ExecutiveSections.ToList();
return PartialView("ExecutiveSubMenuView", sections);
}
and finally the Partial:
#using xxx.Models
#model IEnumerable<xxx.Models.ExecutiveSection>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
#foreach (ExecutiveSection es in Model)
{
<li>#Html.ActionLink(es.SectionName, "Section", "Executive", new { id = es.SectionName.Replace(" ", "-") }, null)</li>
}
</ul>
(I 'replace' the spaces in any section name with dashes '-' and back again for SEO friendliness.)
Related
I have a problem and I don't know if this is the correct forum.
I have to create a system using ASP.NET Core MVC and the system have 10 plans. And in order to know the plan it will be based on the login.
And this is the sample navigation bar for (there will be lots of navigation bar and only some can be found for some plans like the order and report).
Plan 1
a. Orders
b. Reprint Receipt
c. Report
d. Setting
Plan 2
a. Orders
b. Buy
c. Report
d. Setting
Plan 3
a. Orders
b. Buy
c. Cancel Purchase
There are 10 plans and some have different navigations (redirects to a different page).
My plan is to create the pages for each plan.
But how can I populate the navigation bar for each plan?
I don't want to have many if statement in the view to hide some navigation bar for each plan.
How can I solve this? I'm thinking of having a config file. Then after the login read the config file in order to know the navigation for each plan.
Is this a correct solution for this? Or is there a better solution?
Thank you
To populate the navigation menu, first, I suggest you create a NavigationModel model to store the navigation text and the url, the NavigationModel model as below:
public class NavigationModel
{
public int Id { get; set; }
public string NavigationName { get; set; }
public string NavigationUrl { get; set; }
public string PlanType { get; set; }
}
And the following DataRepository contains the test data, in your application, you could store the data into database, then get the navigations via the DbContext:
public class DataRepository : IDataRepository
{
public List<NavigationModel> GetNavigations()
{
return new List<NavigationModel>()
{
new NavigationModel(){ Id =1001, NavigationName="Orders", NavigationUrl="Home/Orders", PlanType="Plan1"},
new NavigationModel(){ Id =1002, NavigationName="Reprint Receipt", NavigationUrl="Home/Reprint", PlanType="Plan1"},
new NavigationModel(){ Id =1003, NavigationName="Report", NavigationUrl="Home/Report", PlanType="Plan1"},
new NavigationModel(){ Id =1004, NavigationName="Setting", NavigationUrl="Home/Setting", PlanType="Plan1"},
new NavigationModel(){ Id =1005, NavigationName="Orders", NavigationUrl="Home/Orders", PlanType="Plan2"},
new NavigationModel(){ Id =1006, NavigationName="Buy", NavigationUrl="Home/Buy", PlanType="Plan2"},
new NavigationModel(){ Id =1007, NavigationName="Report", NavigationUrl="Home/Report", PlanType="Plan2"},
new NavigationModel(){ Id =1008, NavigationName="Setting", NavigationUrl="Home/Setting", PlanType="Plan2"},
new NavigationModel(){ Id =1009, NavigationName="Orders", NavigationUrl="Home/Orders", PlanType="Plan3"},
new NavigationModel(){ Id =10010, NavigationName="Buy", NavigationUrl="Home/Buy", PlanType="Plan3"},
new NavigationModel(){ Id =10011, NavigationName="Cancel Purchase", NavigationUrl="Home/CancelPurchase", PlanType="Plan3"},
};
}
}
Then, you could try to use the following methods to populate the navigation menu.
Use session to store the navigation information.
First, configure the application to use session and add the SessionExtensions (Note the session expired time)
Second, after user login successfully, you could refer the following code to get the relate navigation menu, and store them in the session:
public IActionResult Index()
{
var isUserLogin = true;
var plantype = "Plan2";
if (isUserLogin && _dataRepository.GetNavigations().Any(c => c.PlanType.Contains(plantype)))
{
var navigations = _dataRepository.GetNavigations().Where(c => c.PlanType == plantype).ToList();
if (HttpContext.Session.Get<List<NavigationModel>>("Navigation") == default)
{
HttpContext.Session.Set<List<NavigationModel>>("Navigation", navigations);
}
}
return View();
}
In the _Layout.cshtml page, add the following code in the header:
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor HttpContextAccessor
[Note] In the Startup.ConfigureServices method, use services.AddHttpContextAccessor(); to register the HttpContextAccessor.
Then, we could access the session and populate the navigation menu:
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
#if (HttpContextAccessor.HttpContext.Session.Get<List<NavigationModel>>("Navigation") != default)
{
var navigations = HttpContextAccessor.HttpContext.Session.Get<List<NavigationModel>>("Navigation");
foreach (var item in navigations)
{
<li class="nav-item">
<a class="nav-link text-dark" href="#item.NavigationUrl">#item.NavigationName</a>
</li>
}
}
</ul>
Use JQuery Ajax to call the action method and populate the navigation menu:
In the Home Controller, create an action method to get the navigation menu:
[HttpPost]
public IActionResult GetNavigation(string plantype)
{
//check if user login and get the relate navigation menus.
return Json(_dataRepository.GetNavigations().Where(c => c.PlanType == plantype).ToList());
}
In the _Layout.cshtml page, use the following code to add the navigation menu:
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
</ul>
<script>
$(function () {
$.ajax({
url: "Home/GetNavigation",
method: "Post",
data: { "plantype": "Plan2" },
success: function (items) {
for (var i = 0; i < items.length; i++) {
var li = document.createElement('li');
li.setAttribute('class', 'nav-item');
var a = document.createElement('a');
a.setAttribute('class', 'nav-link text-dark');
a.href = items[i].navigationUrl;
var linkText = document.createTextNode(items[i].navigationName);
a.appendChild(linkText);
li.appendChild(a);
$(".navbar-nav")[0].appendChild(li);
}
}
})
});
</script>
The result as below (display plan 2):
I need some guidance on inserting a menu in the _layout.cshtml file. I have hit two problems:
1) Even when I create an additional model to include two data models, I am unable to pass both the models to the layout file
eg:
Model:
public class IndexModel
{
public tblMenu tblMenu { get; set; }
public tblSite tblSite { get; set; }
}
I need info from the Menu table and the site table on the same page.
2) When I create a partial view to pass the menu data I continually get exceptions telling me that I can't use the model in this way.
My Partialview:
#model mvcSPS.Models.IndexModel
<li>#Model.tblMenu.MenuName</li>
My Controller:
public ActionResult _menu()
{
return PartialView(db.IndexModels.ToList());
}
My _layout.cshtml
<ul id="navigation">
#foreach (var item in Model)
{
#Html.Partial("_menu")
}
</ul>
I have trawled the net and to be quite frank.. I am having a really difficult transition from ASP classic (yes I know) to ASP.net and MVC.
Your gentle guidance would be much appreciated.
Thanks
Andrew
change your #Html.Partial in your _layout.cshtml to call the controller function and render the result of the Action method.
#foreach (var item in Model)
{
Html.RenderAction("_menu", "Home");
}
note: you shouldn't need a prepending '#' since it's in the context of the foreach loop
EDIT: Based on my comment suggestion below
HomeController:
public ActionResult Menu() {
return PartialView("_menu", db.IndexModels.ToList());
}
_layout.cshtml
#{Html.RenderAction("Menu", "Home");} //be sure to fully-qualify the controller since it's layout, otherwise it'll look to the current controller based on route values collection
_menu.cstml
<nav>
<ul>
#foreach(var item in Model) {
Html.Partial("_menuItem", item)
}
</ul>
</nav>
_menuItem.cshtml
#foreach(var item in Model) {
<li>
text
#if(item.Children.Any())
{
<ul>
Html.Partial("_menuItem", item.Children)
</ul>
}
</li>
}
I'm learning Bootstrap and can't get the selected item into an "active" state. The active state remains on the default item. The newly selected/clicked item changes to active briefly, but reverts back. I've read all the posts and still can't get this code to work. I'm using MVC5 and JQuery 2.1.
EDIT:
If I change the li's hrefs to href="#", then the active class gets applied properly. What's happening when a new view gets loaded? I think Sebastian's response is close, but gets messy with Areas.
Markup
<div class="navbar-wrapper">
<div class="container">
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-header">
<a class="navbar-toggle" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="navbar-brand" href="#">Test</a>
</div>
<div class="btn-group pull-right">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<i class="icon-user"></i>Login
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>Profile</li>
<li class="divider"></li>
<li>Sign Out</li>
</ul>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="active">Home</li>
<li>About</li>
<li>Students Sample</li>
<li class="dropdown">
Dropdown <b class="caret"></b>
<ul class="dropdown-menu">
<li>Admin</li>
<li>Another action</li>
<li>Something else here</li>
<li class="divider"></li>
<li>Separated link</li>
<li>One more separated link</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
<!-- /container -->
</div>
<!-- /navbar wrapper -->
Script
<script type="text/javascript">
$(function () {
$('.navbar-nav li').click(function () {
$(this).addClass('active').siblings().removeClass('active');
});
});
</script>
EDIT: Here's what I ended up doing with the help of the posted answers and some research.
public static string MakeActive(this UrlHelper urlHelper,string action, string controller, string area = "")
{
string result = "active";
string requestContextRoute;
string passedInRoute;
// Get the route values from the request
var sb = new StringBuilder().Append(urlHelper.RequestContext.RouteData.DataTokens["area"]);
sb.Append("/");
sb.Append(urlHelper.RequestContext.RouteData.Values["controller"].ToString());
sb.Append("/");
sb.Append(urlHelper.RequestContext.RouteData.Values["action"].ToString());
requestContextRoute = sb.ToString();
if (string.IsNullOrWhiteSpace(area))
{
passedInRoute = "/" + controller + "/" + action;
}
else
{
passedInRoute = area + "/" + controller + "/" + action;
}
// Are the 2 routes the same?
if (!requestContextRoute.Equals(passedInRoute, StringComparison.OrdinalIgnoreCase))
{
result = null;
}
return result;
}
You have to check in your controller or view which menu item is active based on the current url:
I have an extension method similar to this:
public static string MakeActiveClass(this UrlHelper urlHelper, string controller)
{
string result = "active";
string controllerName = urlHelper.RequestContext.RouteData.Values["controller"].ToString();
if (!controllerName.Equals(controller, StringComparison.OrdinalIgnoreCase))
{
result = null;
}
return result;
}
You can use it in your view like this:
<!-- Make the list item active when the current controller is equal to "blog" -->
<li class="#Url.MakeActive("blog")">
....
</li>
The JavaScript isn't working because the page is getting reloaded after it runs. So it correctly sets the active item and then the page loads because the browser is following the link. Personally, I would remove the JavaScript you have because it serves no purpose. To do this client side (instead of the server side code you have), you need JavaScript to set the active item when the new page loads. Something like:
$(document).ready(function() {
$('ul.nav.navbar-nav').find('a[href="' + location.pathname + '"]')
.closest('li').addClass('active');
});
I recommend adding an id or other class to your navbar so you can be sure you have selected the correct one.
Simplest thing to do is send a ViewBag parameter from your controllers like following;
public ActionResult About()
{
ViewBag.Current = "About";
return View();
}
public ActionResult Contact()
{
ViewBag.Current = "Contact";
return View();
}
In the cshtml page do the following;
<ul class="nav navbar-nav">
<li class="#(ViewBag.Current == "About" ? "active" : "")">#Html.ActionLink("About", "About", "Home")</li>
<li class="#(ViewBag.Current == "Contact" ? "active" : "")">#Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
Courtesy to #Damith in here
Simply you can do this in any view
<ul class="nav navbar-nav">
#Html.NavigationLink("Link1", "Index", "Home")
#Html.NavigationLink("Link2", "Index", "Home")
#Html.NavigationLink("Link3", "Index", "Home")
#Html.NavigationLink("Links with Parameter", "myAction", "MyController", new{ id=999}, new { #class= " icon-next" })
</ul>
After you add this method to a new class or existing HtmlExtensions class
public static class HtmlExtensions
{
public static MvcHtmlString NavigationLink(this HtmlHelper html, string linkText, string action, string controller, object routeValues=null, object css=null)
{
TagBuilder aTag = new TagBuilder("a");
TagBuilder liTag = new TagBuilder("li");
var htmlAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(css);
string url = (routeValues == null)?
(new UrlHelper(html.ViewContext.RequestContext)).Action(action, controller)
:(new UrlHelper(html.ViewContext.RequestContext)).Action(action, controller, routeValues);
aTag.MergeAttribute("href", url);
aTag.InnerHtml = linkText;
aTag.MergeAttributes(htmlAttributes);
if (action.ToLower() == html.ViewContext.RouteData.Values["action"].ToString().ToLower() && controller.ToLower() == html.ViewContext.RouteData.Values["controller"].ToString().ToLower())
liTag.MergeAttribute("class","active");
liTag.InnerHtml = aTag.ToString(TagRenderMode.Normal);
return new MvcHtmlString(liTag.ToString(TagRenderMode.Normal));
}
}
I believe you have the selection backward. You're adding the class, then removing it from the siblings, and I think doing the remove second is causing the issue. Can you try reversing this to be:
<script type="text/javascript">
$(function () {
$('.navbar-nav li').click(function () {
$(this).siblings().removeClass('active');
$(this).addClass('active');
});
});
</script>
Your javascript function should work fine... The issue is that your links route to a controller and reload the entire page. In order to avoid this behavior you could render your body content as a partial view, that way the navbar elements do not reload. You shouldn't have to write a function to handle dom events - that is what javascript is for.
To see what I mean, change your code:
<li>About</li>
<li>Students Sample</li>
to:
<li>About</li>
<li>Students Sample</li>
Just add this JQuery coded :
<script>
$(document).ready(function () {
$('body').find('a[href="' + location.pathname + '"]')
.addClass('active');
});
</script>
With this simplified version of the code from here, you can use a tag helper to mark the anchor 'active' if the controller & action match (or just the controller if no action is supplied).
The nav:
<li class="nav-item px-2">
<a class="nav-link" asp-active-route asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item px-2">
<a class="nav-link" asp-active-route asp-controller="Car" asp-action="Add">Add Car</a>
</li>
The tag helper class
[HtmlTargetElement(Attributes = "asp-active-route")]
public class ActiveRouteTagHelper : TagHelper
{
[HtmlAttributeName("asp-controller")] public string Controller { get; set; }
[HtmlAttributeName("asp-action")] public string Action { get; set; }
[HtmlAttributeNotBound] [ViewContext] public ViewContext ViewContext { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (IsActive())
{
output.AddClass("active", HtmlEncoder.Default);
}
output.Attributes.RemoveAll("asp-active-route");
base.Process(context, output);
}
private bool IsActive()
{
var currentController = ViewContext.RouteData.Values["Controller"].ToString();
var currentAction = ViewContext.RouteData.Values["Action"].ToString();
var active = false;
if (!string.IsNullOrWhiteSpace(Controller) && !string.IsNullOrWhiteSpace(currentController))
{
active = Controller.Equals(currentController, StringComparison.CurrentCultureIgnoreCase);
if (active && !string.IsNullOrWhiteSpace(Action) && !string.IsNullOrWhiteSpace(currentAction))
{
active = Action.Equals(currentAction, StringComparison.CurrentCultureIgnoreCase);
}
}
return active;
}
}
Don't forget to add the tag helper to your _ViewImports.cshtml
#addTagHelper *, My.Awesome.Web
Future improvement would be to also check areas and possibly wrap the entire nav in a control/helper so you only have to compare the routes once and flag the item
You can use Bootstrap's nav-pills to change the color of active menu item.
Bootstrap's nav-pills to change the color of active menu item
Here is a simple solution that works - store class name in TempData:
In each action of the controller add one line:
// add "active" class to nav-item TempData["Home"] = "active";
In Layout view nav item:
<a class="nav-link #TempData["Home"]">Home</a>
Thus only one nav-item will get class "active"
I am trying to build a version of WordPress in MVC4. Currently I am working on the page view.
I've decided that in this view I should also include a menu of all the other pages that have been created.
This is my method for the showPage view:
public ActionResult showPage(string myTitle)
{
var query = from a in db.Pages
where a.title == myTitle
select a;
PageModels item = new PageModels();
item = query.FirstOrDefault<PageModels>();
if (item != null)
{
return View(item);
}
else
{
item.content = "No page with title :" + myTitle + " found.";
return View(item);
}
}
This is my method for the partial I am trying to render:
public ActionResult List()
{
return View(db.Pages.ToList());
}
This is how my view looks like:
#model Dynamic_Web_Pages.Models.PageModels
#{
ViewBag.Title = "Page Preview";
}
#Html.Partial("_ListPartial", new IEnumerable<Dynamic_Web_Pages.Models.PageModels>)
<div class="content">#Html.Raw(Model.content)</div>
Finally this is my partial:
#model IEnumerable<Dynamic_Web_Pages.Models.PageModels>
<div class="dropdown">
#foreach (var page in Model)
{
if(page.parent == 0)
{
<div class="btn-group">
<a class="btn dropdown-toggle" id="#Html.DisplayFor(modelItem => page.id)" role="button" data-toggle="dropdown" data-target="#" href="/#Html.DisplayFor(modelItem => page.title)" >#Html.DisplayFor(modelItem => page.title)
<b class="caret"></b>
</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="#Html.DisplayFor(modelItem => page.id)">
#foreach (var child in Model)
{
if (child.parent == page.id)
{
<li><a class="child" href="/#Html.DisplayFor(modelItem => child.title)" >#Html.DisplayFor(modelItem => child.title)</a></li>
}
}
</ul>
</div>
}
}
</div>
I get the following error in my view:
Cannot create an instance of the abstract class or interface 'System.Collections.Generic.IEnumerable<Dynamic_Web_Pages.Models.PageModels>'
What should be the second argument of the #Html.Partial be?
Your view accepted a single instance of PageModels
#model Dynamic_Web_Pages.Models.PageModels
#{
ViewBag.Title = "Page Preview";
}
and yet you are passing that in your partial that accepts an IEnumerable and so you got that exception.
Now based on our conversation in your comments, you can load the partial using jquery:
<script>
$("#navmenu").load('#Url.Action("List")');
</script>
where you replace this code:
#Html.Partial("_ListPartial", new IEnumerable<Dynamic_Web_Pages.Models.PageModels>)
// with this
<div id="navmenu"></div>
remove new IEnumerable
and just keep it as
Html.Partial("_ListPartial") in the view
So you should return PartialView instead of view see below code
public ActionResult List()
{
return PartialView (db.Pages.ToList());
}
I have a couple of list items in a shared _layout.cshtm file (master page) in my MVC application.
something like:
<ul>
<li>Home</li>
<li>about</li>
<li>contact</li>
<li>blog</li>
</ul>
when the user is in a homepage, I want home li item to have class selected, like so:
<li class="selected">Home</li>
and so on. What is the best way to do this?
In regular asp.net website, I used to have a method in master page and call that method from child page but in MVC I am not sure what to do.
thanks.
You could write a custom helper method:
public static MvcHtmlString MenuItem(
this HtmlHelper htmlHelper,
string text,
string action,
string controller
)
{
var li = new TagBuilder("li");
var routeData = htmlHelper.ViewContext.RouteData;
var currentAction = routeData.GetRequiredString("action");
var currentController = routeData.GetRequiredString("controller");
if (string.Equals(currentAction, action, StringComparison.OrdinalIgnoreCase) &&
string.Equals(currentController, controller, StringComparison.OrdinalIgnoreCase))
{
li.AddCssClass("selected");
}
li.SetInnerText(text);
return MvcHtmlString.Create(li.ToString());
}
and then:
<ul>
#Html.MenuItem("Home", "home", "home")
#Html.MenuItem("About", "about", "home")
#Html.MenuItem("Contact", "contact", "home")
#Html.MenuItem("Blog", "blog", "home")
</ul>
The helper check the current action and controller and if they match the one passed as arguments to the helper it appends the selected CSS class to the li.
Just wanted to share what i do:
I create folder App_Code and add CustomHelpers.cshtml. In it i create something like this:
#helper MainMenu(string SelectedItem) {
<ul class="MainMenu">
<li><a href="/home" #if (SelectedItem == "Home") { <text>class="Active"</text> }>Home</a></li>
<li><a href="/about" #if (SelectedItem == "About") { <text>class="Active"</text> }>About</a></li>
<li><a href="/foo" #if (SelectedItem == "Foo") { <text>class="Active"</text> }>Foo</a></li>
</ul>
}
Than in my MasterPage (_Layout.cshtml) i add this where i want my menu to apear:
#CustomHelpers.MainMenu(ViewBag.SelectedMenu)
And than in my view, just like i change my page title, i change my selected menu:
#{
ViewBag.Title = "Welcome to my homepage";
ViewBag.SelectedMenu = "Home";
}
Got my idea from this tutorial: www.asp.net/mvc/videos/mvc-3/mvc-3-razor-helpers