I have created the N'th level Dynamic Menu recursive method which creates a menu.
As seen in above picture the content is returned to ParialView named "_Menu.cshtml" and this Partial View file is empty.
and then there is a _LayoutPage.Cshtml
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
</head>
<body>
<div>
<div>
<input type="text" id="txtSearch" />
<button id="btnSearch">Search</button>
</div>
<div id="Menu">
@{Html.RenderAction("_Menu", "Home"); }
@Html.Partial("_Menu")
</div>
<div>
@RenderBody()
</div>
</div>
</body>
</html>
it successfully puts the result into the browser but as a plain text as i mentioned above and can be seen below.
How can i make these act like links, not like a plaintext? Help will be appreciated. Thanks :)
I took a slightly more involved view on this one.
In the layout I put ...
@if (Model != null && Model is Core.Objects.Entities.CMS.Page)
{
@Html.Action("Menu", new MenuOptions { IsRootMenu = true, ForPageId = Model.Id, Depth = 3 })
}
I first check if the user is logged in and thus "on a managed page" which only happens if the model is of a specific type "Core.Objects.Entities.CMS.Page".
I then call @Html.Action which calls the following child action on my controller, and I construct my menu options ...
[HttpGet]
[ChildActionOnly]
[Route("~/Menu")]
public ActionResult Menu(MenuOptions options)
{
//TODO: consider other complex menuing scenarios like a popup / tree based structures
// New api calls may be needed to support more complex menuing scenarios
// TODO: figure out how to build these, perhaps with a string builder
var expand = string.Empty;
if (options.Depth > 0)
{
switch (options.Depth)
{
case 1: expand = "?$expand=PageInfo,Pages($expand=PageInfo)"; break;
case 2: expand = "?$expand=PageInfo,Pages($expand=PageInfo,Pages($expand=PageInfo))"; break;
case 3: expand = "?$expand=PageInfo,Pages($expand=PageInfo,Pages($expand=PageInfo,Pages($expand=PageInfo)))"; break;
case 4: expand = "?$expand=PageInfo,Pages($expand=PageInfo,Pages($expand=PageInfo,Pages($expand=Pages($expand=PageInfo))))"; break;
case 5: expand = "?$expand=PageInfo,Pages($expand=PageInfo,Pages($expand=PageInfo,Pages($expand=PageInfo,Pages($expand=PageInfo,Pages($expand=PageInfo)))))"; break;
}
}
var query = options.IsRootMenu
? "Core/Page/MainMenuFor(id=" + options.ForPageId + ")" + expand
: "Core/Page/ChildrenOf(id=" + options.ForPageId + ")" + expand;
var items = Task.Run(() => Api.GetODataCollection<Page>(query)).Result;
return PartialView(new MenuViewModel { Origin = options.ForPageId, Pages = items });
}
In my case this area needs a little work and I should probably replace that case statement with some smarter looping code that builds the query.
In short though all this does is pull a hierarchy of Page objects off my OData API to the depth determined by my options passed in as menus tend to vary, some are single level options others go multiple levels but I wanted a reusable menuing system.
Having done that I can take the OData result and build out a model for a menu view which looks like this ...
@model MenuViewModel
@if (Model.Pages != null)
{
<ul class="menu">
@foreach (var p in Model.Pages)
{
Html.RenderPartial("MenuItem", new MenuItemViewModel { Page = p, Origin = Model.Origin });
}
</ul>
}
Menu views setup a list for the root and render a child for each child page, I plan to expand on this view at some point for things like spitting out the parent page which could be in the OData result I pulled in the controller.
And the final piece of the puzzle is the menu item view that deals with rendering each individual menu item ...
@model MenuItemViewModel
<li>
<a href="@Html.Raw("/" + Model.Page.Path)" @CurrentItem(Model.Page)>@Html.PageInfo(Model.Page).Title</a>
@if (Model.Page.Pages != null && Model.Page.Pages.Any())
{
<ul class="submenu">
@foreach (var p in Model.Page.Pages)
{
Html.RenderPartial("MenuItem", new MenuItemViewModel { Page = p, Origin = Model.Origin });
}
</ul>
}
</li>
@functions{
MvcHtmlString CurrentItem(Core.Objects.Entities.CMS.Page p)
{
if (Model.Origin == p.Id) return new MvcHtmlString("class='selected'");
return null;
}
}
Having done all this I end up with essentially a nested set of lists I then use CSS to handle things like popups or dynamic expands ect.
This functionality is not quite complete, I need to make sure I handle "marking the current item" with a class or something so I can style it differently and there are a few other menuing scenarios I would like to add, but also I have considered reusing this html structure in some way for tree views but trees may be a bit more involved in the longer term but doing things like putting an image next to menu items seems like a nice logical improvement if its configurable :)
One last thing I forgot to put in before is the menu options model used on the Menu action in the controller ...
public class MenuOptions
{
public bool IsRootMenu { get; set; }
public int ForPageId { get; set; }
public int Depth { get; set; }
}