0
  1. I am using a bootstrap navbar in my _Layout.cshtml.
  2. On my Index page, the list item "Home" shows as color: rgba(255, 140, 0, 0.781) and the "Features" list item shows as color: green

Desired Result: When I click the "Features" link and navigate to the Features.cshtml page, I want the "Features" list item to change color to color: rgba(255, 140, 0, 0.781) and the Home item to change to color: green.

This is easy to do if I put the navbar markup into every cshtml page. But I would like to just have my bootstrap navbar once in my _Layout.cshtml page.

_Layout.cshtml

<nav class="navbar navbar-expand-lg navbar-light">
    <div class="container">
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item active">
                    <a class="nav-link" style="color: rgba(255, 140, 0, 0.781)" asp-page="Index")>@_loc["Home"]</a>
                </li>
                <li class="nav-item active">
                    <a class="nav-link" style="color: green" asp-page="Features" localize-content>@_loc["Features"]</a>
                </li>
                <partial name="_LoginPartial" />
            </ul>
        </div>
    </div>
</nav>

I have this working fine by putting the HTML for the menu bar in every page, but that is not a good solution as I will have to separately maintain a number of instances of my menu bar.

I tried a number of stackoverflow items but didn't find one that worked for this case. Such as set Different colors in asp:DropDownList Items

I tried following the MSDocs for the ForeColor property but couldn't achieve this either.

I have also tried using [ViewData] set in my Index.cshtml.cs but still couldn't figure out how to change the color on page load or when navigating to the Features page.

I have also tried adding @ code directly to my _Layout page, such as @if(this.Page = "Index") and @if(System.IO.Directory.GetCurrentDirectory = "Index") but no joy.

DMur
  • 625
  • 13
  • 26

2 Answers2

3

Change colour of the navbar element corresponding to the active page

I've had success using a custom tag helper for this, using some code inspirited from here.

enter image description here

The Microsoft doc on creating tag helpers is available here. Third party tutorial available here.

The tag helper will let us specify the action that corresponds to each navbar item, then when the page is being built on the server we will add the active class to the corresponding item.

<ul class="navbar-nav flex-grow-1">
    <li navigation-active-for="/Index" class="nav-item">
        <a class="nav-link" asp-area="" asp-page="/Index">Home</a>
    </li>
    <li navigation-active-for="/Privacy" class="nav-item">
        <a class="nav-link" asp-area="" asp-page="/Privacy">Privacy</a>
    </li>
    <li navigation-active-for="/Movies/Index" class="nav-item">
        <a class="nav-link" asp-area="" asp-page="/Movies/Index">Movies</a>
    </li>
</ul>

In this case, the navigation-active-for attribute will be used.

The tag helper will be as follows (in my case, the project is called Emotify)

using System;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.AspNetCore.Routing;

namespace Emotify.TagHelpers
{
    [HtmlTargetElement("li", Attributes = _for)]
    public class ActiveItemTagHelper : TagHelper
    {
        private readonly IUrlHelper _urlHelper;
        private readonly IHttpContextAccessor _httpAccess;
        private readonly LinkGenerator _linkGenerator;
        private const string _for = "navigation-active-for";

        public ActiveItemTagHelper(
            IActionContextAccessor actionAccess,
            IUrlHelperFactory factory,
            IHttpContextAccessor httpAccess,
            LinkGenerator generator
        )
        {
            _urlHelper = factory.GetUrlHelper(actionAccess.ActionContext);
            _httpAccess = httpAccess;
            _linkGenerator = generator;
        }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            // grab attribute value
            var targetPage = output.Attributes[_for].Value.ToString();
            // remove from html so user doesn't see it
            output.Attributes.Remove(output.Attributes[_for]);
            
            // get the URI that corresponds to the attribute value
            var targetUri = _linkGenerator.GetUriByPage(_httpAccess.HttpContext, page: targetPage);
            // get the URI that corresponds to the current page's action
            var currentUri = _urlHelper.ActionLink();

            // if they match, then add the "active" CSS class
            if (targetUri == currentUri) {
                output.AddClass("active", HtmlEncoder.Default);
            }
        }
    }
}

The services used in the constructor from dependency injection must be registered in Startup.cs::ConfigureServices. ref ref

services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();

Important: in order to work the tag helper must be registered, like in _ViewImports.cshtml.

@using Emotify
@addTagHelper *, Emotify

Note that this ISNT @addTagHelper *, Emotify.TagHelpers since that's wrong apparently, doesn't work. I'm not sure how namespace nesting works in C#.

Additional notes

TeamDman
  • 649
  • 6
  • 26
1

Desired Result: When I click the "Features" link and navigate to the Features.cshtml page, I want the "Features" list item to change color to color: rgba(255, 140, 0, 0.781) and the Home item to change to color: green.

If you'd like to dynamically set color for active item, you can store active page name in localStorage, then you can retrieve the stored data and dynamically set custom class for specific element to apply expected color to it in _Layout page, like below.

Html

<div class="collapse navbar-collapse" id="navbarSupportedContent">
    <ul class="navbar-nav mr-auto">
        <li class="nav-item active" onclick="changeactive('Home')">
            <a class="nav-link" asp-page="Index" )>@_loc["Home"]</a>
        </li>
        <li class="nav-item active" onclick="changeactive('Features')">
            <a class="nav-link" asp-page="Features" localize-content>@_loc["Features"]</a>
        </li>
        <partial name="_LoginPartial" />
    </ul>
</div>

JS code

<script>
    function changeactive(pname) {

        //console.log(pname);

        localStorage.setItem("activepage", pname);
    }

    $(function () {
        var pname = localStorage.getItem("activepage");

        if (pname == "Home" || pname == "" || pname == null) {
            $("ul.navbar-nav li.nav-item:nth-child(1) a").addClass("active-item");
            $("ul.navbar-nav li.nav-item:nth-child(2) a").addClass("normal-item");
        } else {
            $("ul.navbar-nav li.nav-item:nth-child(1) a").addClass("normal-item");
            $("ul.navbar-nav li.nav-item:nth-child(2) a").addClass("active-item");
        }
    })
</script>

CSS class

<style>
    .active-item {
        color: green !important;
    }

    .normal-item {
        color: rgba(255, 140, 0, 0.781) !important;
    }
</style>

Test Result

enter image description here

Fei Han
  • 26,415
  • 1
  • 30
  • 41
  • Brilliant! A few things clicked for me when implementing this and working around a few changes. Thanks. – DMur May 18 '20 at 13:41
  • NOTE: This needs to use sessionStorage() instead of localStorage(). – DMur May 19 '20 at 13:54