Kfir

Mavigator - Marking Your Navigators with Ease

Marking our website's navigators can sometimes be a non-trivial task. Such a simple thing to do can sometimes take a while to implement robustly. But what if we could make this task a no-brainer?

The Issue 🤔

If your backend language of choice is PHP, you may have written the following code over and over for pretty much every project:

<a href="/" class="<?php echo $_SERVER['REQUEST_URI'] == '/' ? 'active' : '' ?>">Home</a>
<a href="/about" class="<?php echo $_SERVER['REQUEST_URI'] == '/about' ? 'active' : '' ?>">About</a>
<!-- ... -->

You may have written a function and made it slightly less verbose:

function activeClass($uri, $activeClass = 'active')
{
    echo $_SERVER['REQUEST_URI'] == $uri ? $activeClass : '';
}

Then, you may use it like so:

<a href="/" class="<?php activeClass('/') ?>">Home</a>
<a href="/about" class="<?php activeClass('/about') ?>">About</a>

However, our code will break if the current route has a query parameter as the $_SERVER['REQUEST_URI'] will also match that query parameter, resulting in the following check:

echo '/about?name=john' == '/about' ? active : '';

Sure, we can easily fix it by using PHP's parse_url() method, but we are still required to repeat this code for every single link on our page that we want to mark.

Offloading the task 🏋️‍♂️

I would like to propose a different approach. Why won't we offload the task of marking our navigators to the client-side?

Let's take a quick look at how we might go about implementing that.

Given we have the following navigator markup which we want to mark:

<nav>
    <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/about">About</a></li>
        <li><a href="/contact">Contact</a></li>
    </ul>
</nav>

We can use Javascript to scan the links in our <nav> element and mark the correct link:

const links = document.querySelectorAll('nav a');

links.forEach(link => link.pathname === window.location.pathname && link.classList.add('active'));

Believe it or not, that's the essence of it. We can, of course, keep working on that script and make it more sophisticated, taking into account cases where we want to apply the active class to the parent of the link, or cases where the page URI is a "resource" link and we need to mark some other link on the page; for example, if the page URI is example.com/forum/some-thread-slug and we want to mark a link with the pathname of /forum.

Just so you won't need to write this logic yourself, I've written a small package to do just that. I call it Mavigator.

Mavigator Got your Back 👀

Mavigator is a lightweight, intelligent script that will mark your navigators.

Mavigator looks for any anchor tag (<a>) inside a given selector (which incidentally defaults to html), scans each node and figures out whether it needs to be marked or not.

If needed, you can scope the <a> tag scanning to a specific element type such as <nav>, a class name or an ID.

The Mavigator.mark() method's blueprint is:

Mavigator.mark(selector, options);

Where selector is a string compatible with document.querySelectorAll(). The default selector is html.

If you don't wish to override the selector but supply the options object, you can also pass the options as the first argument and the selector will default to html:

// This will set the `selector` to "html" and use the given options.
Mavigator.mark(options);

The options are as follow:

Property Default value Description
className "active" The class which will be added to the element once found.
uri window.location.pathname The uri Mavigator will try to find on anchor tags. The default value is okay in most cases. When you want to mark a link that its uri is '/discuss' and the uri of the page is something like '/discuss/general/how-we-do-x' we can use this option to tell Mavigator to look for the '/discuss' uri.
classToParent false Whether to add the className to the anchor tag or its parent.
markTreeDepth 0 Tells Mavigator whether it should "expand" the URI to match. The possible values are:
  1. 0 (default) - Mark the given URI only.
  2. -1 - Mark every URI segment. For example, given a URI like /settings/access/auth, Mavigator will mark the following URIs:
    1. /settings/access/auth
    2. /settings/access
    3. /settings
  3. 1,2,3,...,n - Mark every URI segment up to the given value (starting from the "bottom of the tree"). For example, given a URI like /settings/access/auth and the value 1, Mavigator will mark the following URIs:
    1. /settings/access/auth
    2. /settings/access - This URI is 1 level "above" the given URI
warn false Whether to warn in the console if no link to mark was found.

You can read more about Mavigator on Github.

I hope you find this package useful. Happy coding!