2

This is my menu

<ul class="sidebar-menu" id="navigation-menu">
    <li class="active"><a class="nav-link" href="/"><i class="fas fa-home"></i><span>Home Page</span></a></li>
    <li class="nav-item dropdown">
        <a href="#" class="nav-link has-dropdown"><i class="fas fa-cog"></i><span>Configuration</span></a>
        <ul class="dropdown-menu">
            <li><a class="nav-link" href="/Configuration/AccountCodes">Account Codes</a></li>
            <li><a class="nav-link" href="/Configuration/Branches">Branches</a></li>
        </ul>
     </li>
     <li class="nav-item dropdown">
         <a href="#" class="nav-link has-dropdown"><i class="fas fa-person-booth"></i><span>Directory</span></a>
         <ul class="dropdown-menu">
             <li><a class="nav-link" href="/Directory/Users?Role=Administrator">Administrators</a></li>
             <li><a class="nav-link" href="/Directory/Users?Role=Manager">Managers</a></li>
             <li><a class="nav-link" href="/Directory/Users?Role=Agent">Agents</a></li>
         </ul>
     </li>
</ul>

This is my Jquery, when I select AccountCodes which is under the Configuration dropdown, it should only set the parent list item active. However, the Directory parent is set to active as well. I'm not sure how to select it directly.

Also, due to the structure of my URL, I am unable to continue to use endWith. Is there an alternate method of setting an active class based on the url? Currently, if I select AccountCodes or Branches, it correctly sets the Configuration dropdown item as active. However, If I select agent, nothing is selected at all due to its url ending with a ?Agent instead of Users

<script>
    $(document).ready(function () {
        var current = location.pathname;
        console.log(current);
        $("#navigation-menu a").each(function () {
            var $this = $(this);
            console.log($this.attr('href'));
            if ($this.attr('href').endsWith(current)) {
                console.log($this.parent());
                $this.parent().addClass('active');
                console.log("matched");
            }
        });
        if ($(".dropdown-menu li").hasClass("active")) {
            console.log("Yes");
            var $this = $(this);
            $(".dropdown-menu li").prev().parent().parent().addClass('active');
            console.log($(".dropdown-menu li").closest(".nav-item dropdown"));
        }
    });
</script>
Siavas
  • 4,992
  • 2
  • 23
  • 33
JianYA
  • 2,750
  • 8
  • 60
  • 136
  • When you debug, what is the value of `current`? What is the value of `$this.attr('href')`? What is the result of `$this.attr('href').indexOf(current)`? – David Mar 02 '19 at 13:12
  • When I am on the AB page the URL is localhost.com:5888/Configuration/AC and console shows Configuration/AC. Thats the value of current. – JianYA Mar 02 '19 at 13:15
  • Are you familiar with how to use your browser's debugging tools? Because now is the time to use them. You can place a breakpoint in your code to pause execution on that line and then use the console to observe values and the results of operations while paused. When you do that... What is the value of `current`? What is the value of `$this.attr('href')`? What is the result of `$this.attr('href').indexOf(current)`? – David Mar 02 '19 at 13:18
  • I got these results current = /Configuration/AccountCodes thisvalue = w.fn.init [a.nav-link] indexOfResult = -1 – JianYA Mar 02 '19 at 13:22
  • When I log $this.attr('href'), I get the last entry of the menu. That's why Im always getting a false. – JianYA Mar 02 '19 at 13:24
  • `thisvalue = w.fn.init [a.nav-link]` - It's not really clear what that means, but the string `"w.fn.init [a.nav-link]"` definitely doesn't contain the string `"/Configuration/AccountCodes"`. As an aside, you should really also be examining your HTML and not your server-side code here. JavaScript doesn't interact with the server-side code. Perhaps it's possible this code is generating different HTML than you expect. – David Mar 02 '19 at 13:26
  • The JavaScript itself, unchanged, seems to work just fine: https://jsfiddle.net/6egvywj1/1/ (Note that you may have to click "Run" after loading the page, as the window's URL changes in jsFiddle between the initial load and clicking "Run".) – David Mar 02 '19 at 13:33
  • Are you using ASP.NET and can generate the links from the back end? – Siavas Mar 06 '19 at 01:04
  • 1
    Yeah. I'll change it to html though so its easier to read. – JianYA Mar 06 '19 at 01:20
  • @JianYA without `CSS` it's difficult to test this; else it should be pretty simple. – Martin Zeitler Mar 06 '19 at 02:11

1 Answers1

0

There would be two ways to mark only the parent link as active.

1. With jQuery

The advantage of this is that it will be compatible with any back end platform.

<script>
    $(document).ready(function () {
        var current = location.pathname;
        console.log(current);
        $("#navigation-menu a").each(function () {
            var $this = $(this);
            var link = $this.attr('href');
            console.log(link);

            // Remove the query string parameters by finding their starting position
            var indexOfQueryString = link.lastIndexOf("?");
            link = link.substring(0, indexOfQueryString);

            if (link.endsWith(current)) {
                console.log("matched");
                if ($this.parent().parent().hasClass('.dropdown-menu')) {
                    var parentLink = $this.closest('.dropdown');
                    console.log("Setting parent link as active:", parentLink);
                    // Find the closest parent link and add active class
                    parentLink.addClass('active');
                } else {
                    console.log("Setting link as active: ", $this.parent());
                    $this.parent().addClass('active');
                }
            }
        });
    });
</script>

In this case, as you have mentioned you encountered problems with ?Agent being present in link – this is what we refer to as query string parameters, which we can safely remove by searching for the unencoded question mark. This is already considered in the solution.

2. With ASP.NET MVC

Doing this in the back end would be more robust and should JavaScript be disabled by the user, this functionality will still work in your website. The disadvantage though, as you might have guessed, is that it is platform-dependent.

There are many ways of going about this. For your specific use-case, as your dropdown links are related to the parent by area, I have adapted another SO answer to cover your specific use-case:

public static string IsSelected(this HtmlHelper html, string area = "", string cssClass = "active")
{
    ViewContext viewContext = html.ViewContext;
    RouteValueDictionary routeValues = viewContext.RouteData.Values;
    string currentArea = routeValues["area"] as string;

    return area.Equals(currentArea) ? cssClass : String.Empty;
}

Then use it this way:

<ul class="sidebar-menu" id="navigation-menu">
    <li><a asp-area="" asp-controller="Home" asp-action="Index" class="nav-link"><i class="fas fa-home"></i><span>Home
                Page</span></a></li>
    <li class="nav-item dropdown @Html.IsSelected(area: "Configuration")">
        <a href="#" class="nav-link has-dropdown"><i class="fas fa-cog"></i><span>Configuration</span></a>
        <ul class="dropdown-menu">
            <li><a asp-area="Configuration" asp-controller="AccountCodes" asp-action="Index" class="nav-link">Account
                    Codes</a></li>
            <li><a asp-area="Configuration" asp-controller="Branches" asp-action="Index" class="nav-link">Branches</a>
            </li>
        </ul>
    </li>
    <li class="nav-item dropdown  @Html.IsSelected(area: "Directory")">
        <a href="#" class="nav-link has-dropdown"><i class="fas fa-person-booth"></i><span>Directory</span></a>
        <ul class="dropdown-menu">
            <li><a asp-area="Directory" asp-controller="Users" asp-action="Index" asp-route-Role="Administrator"
                    class="nav-link">Administrators</a></li>
            <li><a asp-area="Directory" asp-controller="Users" asp-action="Index" asp-route-Role="Manager"
                    class="nav-link">Managers</a></li>
            <li><a asp-area="Directory" asp-controller="Users" asp-action="Index" asp-route-Role="Agent"
                    class="nav-link">Agents</a></li>
        </ul>
    </li>
</ul>

Would this benefit you, please make sure to upvote the other linked answer as well.

Siavas
  • 4,992
  • 2
  • 23
  • 33
  • I like your second suggestion more. However, it looks like it sets the list item directly. Does it work with the parent list item as well? – JianYA Mar 06 '19 at 01:39
  • Yes it should, @JianYA. As your dropdown links are grouped by area, I have added code that will use that instead. Let us know how this goes. – Siavas Mar 06 '19 at 02:31
  • @JianYA you may also consider inserting the previous version of HTML as well in your post so others will be able to follow this answer from there. – Siavas Mar 07 '19 at 12:40