Render aria-current attribute with blazor's NavLink component - asp.net-core

Blazor's NavLink component detects whether the link refers to the current page, and sets the active class.
It is customary to also set the aria-current="page" attribute, when it is part of a menu.
Can the component do that somehow? Or could I wrap it in a custom component that does this?
I can't find an extension point that easily allows for this: docs, source.

Extending the existing NavLink
public class NavLinkExtended : NavLink
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "a");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", CssClass);
if(CssClass.Contains("active"))
{
builder.AddAttribute(3, "aria-current", "page");
builder.AddContent(4, ChildContent);
builder.CloseElement();
}
else
{
builder.AddContent(3, ChildContent);
builder.CloseElement();
}
}
}

Here is my solution.
AriaNavLink.razor:
<NavLink class=#CssClass #attributes=Attributes>
#ChildContent
</NavLink>
#inherits NavLink
#code {
private const string _defaultActiveClass = "active";
private Dictionary<string, object>? _attributes;
private IReadOnlyDictionary<string, object> Attributes
{
get
{
if (_attributes != null) return _attributes;
_attributes = AdditionalAttributes != null
? new Dictionary<string, object>(AdditionalAttributes)
: new Dictionary<string, object>();
// the trick to inferring "active" indirectly:
var isActive = CssClass?.Split(' ', StringSplitOptions.TrimEntries).Contains(ActiveClass ?? _defaultActiveClass) ?? false;
if (isActive)
_attributes.Add("aria-current", "page");
return _attributes;
}
}
}
Some page using bootstrap:
<li class="nav-item">
<AriaNavLink href="/foo" class="nav-link" foo="bar">
<i class="some-icon"></i>
Foo
</AriaNavLink>
</li>

Pardon me for not being exact, but... I would approach this with something like...
aria-current="#(route == nav route) ? "page" : "" ")
and then figure out how to detect the route of the current page and the configured nav route for the thing you're modifying.
Then, if you have a LOT of these nav things and you don't want to duplicate the logic all over the place.... write a method that does the above and add that to your code like...

The accepted answer is a good solution for now. When v8 is released (probably November 2023), the functionality will be built-in. The merged PR is here.

Related

Could not set or bind model property with Bootstrap Datepicker in Blazor

I am using bootstrap datepicker and the problem is that when I pick a date, it does not fire a change or input event and noting is binding with the model property Course.StartDate or Course.EndDate.
The default datepicker works but does not support Afghanistan datetime. That is why I use boostrap datepicker.
Blazor code:
#using Microsoft.AspNetCore.Mvc.Rendering
#using myproject.Data
#using Microsoft.JSInterop;
#inject myproject.Repository.CoursesRepository _coursesRepository
#inject IJSRuntime JS
<EditForm Model="#Course" OnValidSubmit="e=> { if(selectedId == 0) { addCourse(); } else { updateCourse(Course.CourseId); } }">
<div class="mb-2">
<div>#Course.StartDate</div>
<label class="col-form-label" for="StartDate">#Loc["Start Date"]<span class="text-danger fs--1">*</span>:</label>
<InputDate class="form-control" #bind-Value="Course.StartDate" #bind-Value:format="yyyy-MM-dd" id="StartDate" />
<ValidationMessage class="text-danger" For="(() => Course.StartDate)"/>
</div>
<div class="mb-2">
<label class="col-form-label" for="EndDate">#Loc["End Date"]<span class="text-danger fs--1">*</span>:</label>
<InputDate class="form-control" #bind-Value="Course.EndDate" #bind-Value:format="yyyy-MM-dd" id="EndDate"/>
<ValidationMessage class="text-danger" For="(() => Course.EndDate)"/>
</div>
</EditForm>
#code {
public CourseModel Course = new();
public string[] dates = new string[] { "#StartDate", "#EndDate" };
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
loadScripts();
}
void addCourse()
{
_coursesRepository.AddCourse(Course);
FillData();
Course = new();
var title = "Course";
Swal.Success(title : Loc[$"{title} added successfully"],toast : true);
}
// initializes the datepicker
public async Task loadScripts()
{
await JS.InvokeVoidAsync("initializeDatepicker", (object) dates);
}
}
This is script for initializing the datepickers
<script>
function initializeDatepicker(dates) {
dates.forEach((element) => {
$(element).datepicker({
onSelect: function(dateText) {
// this is not working
element.value = this.value;
/*
tried this and still not working
$(element).trigger("change");
also tried this and still not working
$(element).change();
*/
// this is working
console.log("Selected date: " + dateText + "; input's current value: " + this.value);
},
dateFormat: 'yy-mm-dd',
changeMonth: true,
changeYear: true
});
});
}
</script>
The reason for this is that the changes are made with JavaScript and so the page state does not change for Blazor, in other words, Blazor does not notice the value change at all.
To solve this problem, you must inform the Blazor component of the changes by calling a C# method inside the JavaScript function. For this, you can use the DotNet.invokeMethodAsync built-in dotnet method. As follows:
DotNet.invokeMethodAsync('ProjectAssemblyName', 'ComponentMethod', this.value.toString())
Its first argument is the assembly name of your project. The second argument is the name of the C# function that you will write in the component, and finally, the third argument is the selected date value.
The method called in C# should be as follows:
static string selectedDate;
[JSInvokable]
public static void ComponentMethod(string pdate)
{
selectedDate = pdate;
}
This method must be decorated with [JSInvokable] and must be static.
I have done the same thing for another javascript calendar in Persian language. Its codes are available in the JavaScriptPersianDatePickerBlazor repository.
You can also create a custom calendar in the form of a component so that you can use it more easily in all components in any formats that you want such as DateTime or DateTimeOffset or string and so on. There is an example of this in the AmibDatePickerBlazorComponent repository.

How can I pass Dictionary<string, dynamic> to a blazor component?

I am trying to pass a dictionary as a parameter to a blazor component. The dictionary needs to store <string, dynamic>, <string, List>, and <string, Dictionary<string, dynamic>> key-value pairs.
I tried to do this, but get the error, "'EventCallbackFactory' has no applicable method named 'CreateBinder' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched."
Is what I am trying to do valid, and if not, why? Is there another way I should approach this?
Here is the code for my blazor component, for reference:
#page "/dictitemcomponent"
#using System.Collections.Generic;
<ul>
#foreach (KeyValuePair<string, dynamic> item in thisDict)
{
#if(item.Value.GetType() == typeof(Dictionary<string, dynamic>))
{
<li>#item.Key.ToString() : </li>
#foreach (var dict in item.Value)
{
<DictItemComponent thisDict=dict/>
}
}
#if(item.Value.GetType() == typeof(List<dynamic>))
{
<li>#item.Key.ToString() : </li>
#foreach (var value in item.Value)
{
<li>#value</li>
}
}
#if(item.Value.GetType() != typeof(List<dynamic>) && item.Value.GetType() != typeof(Dictionary<dynamic, dynamic>))
{
<li>#item.Key.ToString() : <input #bind="item.Value"/></li>
}
}
</ul>
#code
{
public KeyValuePair<string, dynamic> newProperty = new KeyValuePair<string, dynamic>();
[Parameter] public Dictionary<string,dynamic> thisDict {get; set;}= new Dictionary<string, dynamic>();
//convert the value of a KVP to a dictionary
public void ValueToProperty(KeyValuePair<string,dynamic> property)
{
string key = property.Key;
property = new KeyValuePair<string, dynamic>(key, new Dictionary<string, dynamic>());
}
public void ValueToList(KeyValuePair<string,dynamic> property)
{
string key = property.Key;
property = new KeyValuePair<string, dynamic>(key, new List<dynamic>());
}
}
May I know what you are trying to achieve?
Based on your first if statement, why would you have a dictionary inside a dictionary?
By reading your exception and your 3rd conditional statement, it seems that you are trying to bind the input to your dictionary value. However, this is not how you should use a dictionary. Unless you are binding to the usual "type" property (e.g. string), you may use #bind=someVariable. Otherwise, in a dictionary, each value should tie to their respective key and therefore, #bind=_dict[key] ("shorthand" syntax for #bind-value and #bind-value:event) instead of binding input to the item.value. For easier understanding, I've scoped out the aforementioned conditional statement and simulated a solution to the problem in the following lines. It should render value in the <label> next to the input during onchange:
#page "/dict"
#foreach (var kvp in _dict)
{
<div>
<label>#kvp.Key</label>
<input #bind=_dict[kvp.Key] />
<label>Key:#kvp.Key|Value:#kvp.Value</label>
}
#code {
private Dictionary<string, string> _dict = new()
{
["1"] = "One",
["2"] = "Two",
["3"] = "Three",
["4"] = "Four",
["5"] = "Five",
};
}
Meanwhile, you should simplify your if statements as follow:
#if(item.Value.GetType() == typeof(Dictionary<string, dynamic>))
{
//dowork
}
else if(item.Value.GetType() == typeof(List<dynamic>))
{
//do more work
}
else
{
//do other work
}
Screenshot for my Input fields rendered based on Key Value Pair and value entered

Spinner does not show b/c the bound variable is not updated

I'm working on a Blazor server side app. The page has a table with a list of cars and some filter elements on top. When I select a filter, a spinner should be visible until the new data is fetched and rendered.
The spinner with its variable:
<div class="spinner-border #spinner" role="status">
<span class="visually-hidden">Loading...</span>
</div>
#code{
string spinner = "invisible";
public string vehicleTypeFilter
{
set
{
_vehicleTypeFilter = value;
ApplyFilters();
}
get { return _vehicleTypeFilter; }
}
}
The select for the Baumuster (vehicleType) is bound to the vehicleTypeFilter variable:
<div class="col-md-2 form-floating">
<select class="form-control" #bind="vehicleTypeFilter">
<option value="" selected>Alle</option>
#foreach (var vehicleType in vehicleTypes.OrderBy(x => x.Description))
{
<option value="#vehicleType.Description">#vehicleType.Description</option>
}
</select>
<label>Baumuster</label>
</div>
Then a value is selected, the ApplyFilter method is triggered through the setter of the vehicleTypeFilter variable:
public void ApplyFilters()
{
ToggleSpinner();
// I also tried a StateHasChanged(); right here
// 1. Get all cars
cars = model.CreateIndexViewModel();
// 2. Filter for Baumuster / vehicle type
if (!string.IsNullOrEmpty(vehicleTypeFilter))
{
cars.viewModels = cars.viewModels.Where(x => x.VehicleDescription == vehicleTypeFilter).ToList();
}
ToggleSpinner();
}
The ToggleSpinner method:
public void ToggleSpinner()
{
if (spinner == "invisible" )
spinner = "";
else
spinner = "invisible";
}
Unfortunately, I don't see the spinner. When I inspect the html page right after the breakpoint hits the Baumuster-filter, the value of spinner is still set to "invisible". I even tried to call StateHasChanged(); after the first ToggleSpinner() but that didn't help.
You've shown a lot of code, but I don't see ToggleSpinner
However, you call it twice in your ApplyFilters method, with no blocking calls, so I'd assume that it's turning the spinner on and off so fast that it doesn't render (or at least that you can't notice it).
If the methods you call in ApplyFilters actually take any time, then Henk's got the right idea-- except you should use async Task I think.
Your problem is that you want async behaviour from a synchronous property. The standard advice is against async void but if you want to stay with the property, the minimal change would be:
public async void ApplyFilters()
{
ToggleSpinner();
// I also tried a StateHasChanged(); right here
StateHasChanged(); // this _requests_ an update
await Task.Delay(1); // this is why you need async void
... as before
ToggleSpinner();
StateHasChanged();
}

Blazor. NavLink is not working in a foreach block when clicking multiply times

I created list of NavLink, this is only one difference between them, dynamic parameter Id
<div>
#foreach (var service in pageGlobal.Person.Services)
{
var link = $"service_description/{service.Identifier}";
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="#link">
#service.Name
</NavLink>
</li>
</ul>
}
</div>
And it works only if I click first time(no matter what NavLink item it works properly).
Click to the next link - url is changed but nothing happens.
Code of the ServiceDescription.razor
#page "/service_description/{Id:int}"
#using Site.Data
#using Site.ViewModels
#inject StartUpService page
#if (service != null)
{
<div> <h3>Service Name - #service.Name</h3> - <h4>#service.Description</h4> </div>
}
#code
{
[Parameter]
public int Id { get; set; }
PageGlobal pageGlobal;
Service service;
protected override async Task OnInitializedAsync()
{
pageGlobal = await page.GetPageGlobalAsync();
service = pageGlobal.Person.Services.Where(e => e.Identifier == Id).FirstOrDefault();
}
}
How can I force client reload ServiceDescription.razor with a new parameter using NavLink functionality?
This issue is caused by OnInitializedAsync. For OnInitializedAsync, it will be called only when the component is invoked when the component is ready to start.
If you want to change service based on parameter, you should use OnParametersSetAsync like below:
protected override async Task OnParametersSetAsync()
{
pageGlobal = new PageGlobal
{
Person = new Person()
{
Services = new List<Service>(){
new Service{ Identifier = 1, Name = "Test1", Description = "D1" },
new Service{ Identifier = 2, Name = "Test2" , Description = "D2" },
new Service{ Identifier = 3, Name = "Test3", Description = "D3" }
}
}
};
service = pageGlobal.Person.Services.Where(e => e.Identifier == Id).FirstOrDefault();
}
You could check ComponentBase

Is there a simple MVC4 html helper for collapsible (multi-level) Bootstrap 3 menus?

I have seen several examples on creating a HTML helper method for active menu items.
**Summary:** Simply put, in an MVC project, using the Twitter Bootstrap, I am trying to preserve the open state of a collapsible menu when a child is selected.
I am using a collapsible menu, where the parent's css (the selected item) needs to include active open if a child is selected. This will ensure that the menu is open at the right location. With the use of another HTML helper, the active item is already set to active.
HTML for the menu:
<div id="sidebar">
<ul>
<li class="active"><i class="icon-home"></i> <span>Dashboard</span></li>
<li class="submenu">
<i class="icon-beaker"></i> <span>UI Lab</span> <i class="arrow icon-chevron-right"></i>
<ul>
<li>Interface Elements</li>
<li>jQuery UI</li>
<li>Buttons & icons</li>
</ul>
</li>
<li class="submenu">
<i class="icon-th-list"></i> <span>Form elements</span> <i class="arrow icon-chevron-right"></i>
<ul>
<li>Common elements</li>
<li>Validation</li>
<li>Wizard</li>
</ul>
</li>
<li><i class="icon-th"></i> <span>Tables</span></li>
<li><i class="icon-th-list"></i> <span>Grid Layout</span></li>
<li class="submenu">
<i class="icon-file"></i> <span>Sample pages</span> <i class="arrow icon-chevron-right"></i>
<ul>
<li>Invoice</li>
<li>Support chat</li>
<li>Calendar</li>
<li>Gallery</li>
<li>Messages</li>
</ul>
</li>
<li>
<i class="icon-signal"></i> <span>Charts & graphs</span>
</li>
<li>
<i class="icon-inbox"></i> <span>Widgets</span>
</li>
</ul>
</div>
Here is the helper method for items:
public static MvcHtmlString MenuItem(this HtmlHelper htmlHelper,
string text,
string action,
string controller,
string iconClass)
{
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("active");
}
var i = new TagBuilder("i");
i.AddCssClass(iconClass);
var basePath = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority);
//li.InnerHtml = htmlHelper.ActionLink("<i>something</i>" + text, action, controller).ToHtmlString();
li.InnerHtml = htmlHelper.Raw(string.Format("<i class=\"{4}\"></i>{3}", basePath, controller, action, text, iconClass)).ToString();
return MvcHtmlString.Create(li.ToString());
}
And implemented like this:
<div id="sidebar">
<ul>
#Html.MenuItem("Dashboard", "Index", "Dashboard", "icon-home")
#* <li class="active"><i class="icon-home"></i> <span>Dashboard</span></li>*#
<li class="submenu">
<i class="icon-beaker"></i> <span>UI Lab</span> <i class="arrow icon-chevron-right"></i>
<ul>
<li>#Html.MenuItem("Websites", "Index", "Websites", null)</li>
<li>jQuery UI</li>
<li>Buttons & icons</li>
</ul>
</li>
<li class="submenu">
<i class="icon-th-list"></i> <span>Form elements</span> <i class="arrow icon-chevron-right"></i>
<ul>
<li>Common elements</li>
<li>Validation</li>
<li>Wizard</li>
</ul>
</li>
So what I don't have is something for the submenu items.
Is there a simpler way of trying to accomplish this?
--UPDATE--
I'm guessing that putting this into a partial view may be best. I need to find some way to preserve the selected item on click to reference it on every menu item, rather than check if the controller/action matches in order to set the current item to "active". A controller method that activates on click, checks if the currently selected item is a parent or child, and if the parent matches a child, then format differently? I'm sure there has to be an easier way.
Alright, so here is one solution I came up with.
To recap, this isn't as simple as adding an "active" CSS class to an item if it is selected (as per the default Bootstrap MVC. In this solution we need to identify the parent of and a child and identify both.
Default page is Dashboard. The user then clicks on "Configuration" to expand the menu, then selects the "Websites" link which opens the page.
Here is the solution:
Model:
public class NavigationMenu
{
public string Text { get; set; }
public string Action { get; set; }
public string Controller { get; set; }
public string Icon { get; set; }
public bool Selected { get; set; }
public List<NavigationMenu> MenuChildren;
}
Controller:
public class NavigationController : Controller
{
[ChildActionOnly]
public ActionResult GenerateMenu()
{
List<NavigationMenu> menuItems = new List<NavigationMenu>();
// build the menu
menuItems.Add(new NavigationMenu
{
Text = "Dashboard",
Action = "",
Controller = "Dashboard",
Icon = "icon-home",
Selected = true, // default selected menu item
MenuChildren = null
});
menuItems.Add(new NavigationMenu
{
Text = "Configuration",
Action = null,
Controller = null,
Icon = "icon-cog",
MenuChildren = new List<NavigationMenu>{
new NavigationMenu{
Text = "Websites",
Action = "",
Controller = "Websites",
Icon = null,
MenuChildren = null
},
new NavigationMenu{
Text = "Child 2",
Action = null,
Controller = null,
Icon = null,
MenuChildren = null
}
}
});
menuItems.Add(new NavigationMenu
{
Text = "Item 2",
Action = "",
Controller = "Item2",
Icon = "icon-random",
MenuChildren = null
});
menuItems.Add(new NavigationMenu
{
Text = "Item 3",
Action = "",
Controller = "Item3",
Icon = "icon-certificate",
MenuChildren = null
});
string action = ControllerContext.ParentActionViewContext.RouteData.Values["action"].ToString() == "Index" ? "" : ControllerContext.ParentActionViewContext.RouteData.Values["action"].ToString();
string controller = ControllerContext.ParentActionViewContext.RouteData.Values["controller"].ToString();
foreach (var item in menuItems)
{
if (item.MenuChildren != null)
{
foreach (var cItem in item.MenuChildren)
{
if (cItem.Controller == controller && cItem.Action == action)
{
cItem.Selected = true;
break;
}
else
{
cItem.Selected = false;
}
}
}
if (item.Controller == controller && item.Action == action)
{
item.Selected = true;
break;
}
else
{
item.Selected = false;
}
}
return PartialView("~/Views/Shared/_Navigation.cshtml", menuItems);
}
}
Shared View:
#model IEnumerable<AdminWebsite.Models.NavigationMenu>
#{
var basePath = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority);
}
<div id="sidebar">
#Html.Raw("<ul>")
#foreach (var item in Model)
{
// if the menu item does not have children then it should be clickable
if (item.MenuChildren == null & item.Selected)
{
<li class="active"><i class="#item.Icon"></i> <span>#item.Text</span></li>
}
else if (item.MenuChildren == null & !item.Selected)
{
<li><i class="#item.Icon"></i> <span>#item.Text</span></li>
}
// has children and one of its children is selected
if (item.MenuChildren != null)
{
if (item.MenuChildren.Any(c => c.Selected) == true)
{
<text><li class="submenu active open"></text>
}
else
{
<text><li class="submenu"></text>
}
// sub-menu parent
if (item.MenuChildren != null & item.Selected)
{
<i class="#item.Icon"></i> <span>#item.Text</span>
}
else if (item.MenuChildren != null & !item.Selected)
{
<i class="#item.Icon"></i> <span>#item.Text</span>
}
// children
<text><ul></text>
// iterate through children
foreach(var cItem in item.MenuChildren)
{
if (cItem.MenuChildren == null & cItem.Selected)
{
<li class="active"><i class="#cItem.Icon"></i> <span>#cItem.Text</span></li>
}
else if (cItem.MenuChildren == null & !cItem.Selected)
{
<li><i class="#cItem.Icon"></i> <span>#cItem.Text</span></li>
}
}
#Html.Raw("</ul>");
#Html.Raw("</li>");
}
}
#Html.Raw("</ul>")
</div>
Implementation in the view:
#{Html.RenderAction("GenerateMenu", "Navigation");}
The controller checks if the current action/controller names match one on the menu and if so, set selected = true. In the partial view, there is some logic to determine the display structure, based on the parent/child relationships, and if a child is selected, so is the parent.
In brief, that's it. I'd like to hear some comments/other examples.
Here is a solution using most of the code from the accepted answer, refactored to use HtmlHelpers and TagBuilders with a little renaming to fit my project.
Model:
public class MenuViewModel
{
public IList<MenuItemDto> MenuItems;
}
public class MenuItemDto
{
public string Text { get; set; }
public string Action { get; set; }
public string Controller { get; set; }
public string IconCssClass { get; set; }
public bool Active { get; set; }
public List<MenuItemDto> MenuChildren;
}
Controller:
public ActionResult GenerateMenu()
{
var viewModel = new MenuViewModel();
viewModel.MenuItems = //code to build menu model like ElHaix provided in his Controller;
return PartialView("~/Views/Shared/_Menu.cshtml", viewModel);
}
Shared View:
#using Extensions
<div id="sidebar">
#Html.Raw("<ul>")
#foreach (var item in Model.MenuItems)
{
// if the menu item does not have children then it should be clickable
if (item.MenuChildren == null)
{
#Html.LiForMenuItem(item)
}
// has children and one of its children is selected
if (item.MenuChildren != null)
{
if (item.MenuChildren.Any(c => c. Active) == true)
{
<text><li class="submenu active open">
</text>
}
else
{
<text>
<li class="submenu">
</text>
}
// sub-menu parent
if (item.MenuChildren != null)
{
#Html.HrefForSubMenuItemRoot( item)
}
// children
<text><ul>
</text>
// iterate through children
foreach (var cItem in item. MenuChildren)
{
if (cItem.MenuChildren == null)
{
#Html.LiForMenuItem(cItem)
}
}
#Html.Raw("</ul>");
#Html.Raw("</li>");
}
}
#Html.Raw("</ul>")
</div>
Html Helpers:
namespace Extensions
{
public static class MenuExtensions
{
public static MvcHtmlString LiForMenuItem(this HtmlHelper htmlHelper, MenuItemDto menuItem)
{
var li = new TagBuilder("li");
AddActiveCssClassToTag(menuItem, li);
var contentUrl = GenerateContentUrlFromHttpContext(htmlHelper);
li.InnerHtml = GenerateLinkForMenuItem(menuItem, contentUrl);
return MvcHtmlString.Create(li.ToString());
}
public static MvcHtmlString HrefForSubMenuItemRoot(this HtmlHelper htmlHelper, MenuItemDto menuItem)
{
var a = new TagBuilder("a");
AddActiveCssClassToTag(menuItem, a);
var contentUrl = GenerateContentUrlFromHttpContext(htmlHelper);
a.Attributes.Add("href", GenerateUrlForMenuItem(menuItem, contentUrl));
a.InnerHtml = GenerateInnerHtmlForMenuItem(menuItem);
return MvcHtmlString.Create(a.ToString());
}
private static void AddActiveCssClassToTag(MenuItemDto menuItem, TagBuilder tag)
{
if (menuItem.Active)
{
tag.AddCssClass("active");
}
}
private static string GenerateContentUrlFromHttpContext(HtmlHelper htmlHelper)
{
return UrlHelper.GenerateContentUrl("~/", htmlHelper.ViewContext.HttpContext);
}
private static string GenerateLinkForMenuItem(MenuItemDto menuItem, string contentUrl)
{
var a = new TagBuilder("a");
a.Attributes.Add("href", GenerateUrlForMenuItem(menuItem, contentUrl));
a.InnerHtml = GenerateInnerHtmlForMenuItem(menuItem);
return a.ToString();
}
private static string GenerateInnerHtmlForMenuItem(MenuItemDto menuItem)
{
var html = string.Empty;
//Add <i></i> if there is an IconCssClass present
var i = new TagBuilder("i");
if (!String.IsNullOrEmpty(menuItem.IconCssClass))
{
i.AddCssClass(menuItem.IconCssClass);
html += i.ToString();
}
//add a span for the text of the menuItem
var span = new TagBuilder("span");
span.InnerHtml = menuItem.Text;
html += span.ToString();
return html;
}
private static string GenerateUrlForMenuItem(MenuItemDto menuItem, string contentUrl)
{
var url = contentUrl + menuItem.Controller;
if (!String.IsNullOrEmpty(menuItem.Action)) url += "/" + menuItem.Action;
return url;
}
}
}