Active Page Navigation

I want to write about a faily common design pattern across the web: Active Page Navigation. Picture a website's navigation bar, which lists a set of available pages on the site, such as "Home", "About", and "Contact". As the user navigates the website, the current or "active" page is marked or styled in the navigation. When the user is on the Home page, "Home" has styles applied to it to indicate that that's the page the user is on. When the user navigates to the About page, "About" now has those styles. It's simple and intuitive, but how is it done?

This week I have been coding (what I hope are) usability improvements on the KLRU Schedule page. We have four different channels, KLRU (which broadcasts PBS national content), KLRU Create (which features cooking and home improvement shows), KLRU Q (which is locally programmed -- lots of weird stuff there), and KLRU PBS Kids (24/7 PBS Kids programming).

The basic structure of the schedule is that each channel gets its own page, and alongside the channel's listings for the date that's been selected, there is a navigation bar that lists all four channels, with the currently selected channel highlighted, indicating which channel the user is looking at. It's very similar to the "Home", "About", and "Contact" example mentioned above.

The Setup

Okay so how did I accomplish this?

Like almost everything code-related, there's no one right way to execute this. My own current setup uses Handlebars as the templating language, and I'll be using ES6 arrow functions and array methods to target the generated navigation list elements, and apply a class name to the correct navigation item.

First, the page needs a list of navigation items. I gave it an array that looks something like this:

const klruChannels = [
  {
    "full_name": "KLRU",
    "slug": "klru",
    "channel": "18.1",
  },
  {
    "full_name": "KLRU Create",
    "slug": "klrucreate",
    "channel": "18.2",
  },
  {
    "full_name": "KLRU Q",
    "slug": "klruq",
    "channel": "18.3",
  },
  {
    "full_name": "KLRU PBS Kids",
    "slug": "klrupbskids",
    "channel": "18.4",
  }
];

I then used the {{#each}} loop in Handlebars to display a list of links, which looks like this:

<ul class="channels">
  {{#each klruChannels}}
    <li><a href="/schedule/{{slug}}/" data-channel="{{channel}}" class="channel">{{full_name}}</a></li>
  {{/each}}
</ul>

The above produces this code:

<ul class="channels">
  <li><a href="/schedule/klru/" data-channel="18.1" class="channel">KLRU</a></li>
  <li><a href="/schedule/klrucreate/" data-channel="18.2" class="channel">KLRU Create</a></li>
  <li><a href="/schedule/klruq/" data-channel="18.3" class="channel">KLRU Q</a></li>
  <li><a href="/schedule/klrupbskids/" data-channel="18.4" class="channel">KLRU PBS Kids</a></li>
</ul>

Selecting the Active Page

With my navigation list showing up in the DOM, it's time to determine which page the user is on, then assign the current page's anchor tag a class that I can use for styling.

Two things of note:

  1. The urls in the links above have the channel name in the endpoint. Express allows a developer to get this piece of data using the req.params API endpoints.
  2. The urls also have a "data-channel" attribute, meaning that "channel" is an available field in the dataset.

I'm pretty new to datasets in HTML, but coding this seemed like the perfect opportunity to get my feet wet.

So first, I will grab an array of these navigation items.

const channelList = Array.from(document.querySelectorAll('.channel-list'));

The above code gives me an array that I can apply methods to. I'll use the filter method to isolate the item where the data-channel value matches the req.params.channel item:

const thisChannel = channelList.filter(channel => channel.dataset.channel === '{{channel}}');

...and with this new array of one item, we simply map over it and add a class of "current-channel" to the single item in that array. The following is chained onto the previous code snippet.

.map(channel => channel.classList.add('current-channel'));

It's that simple. Three easy lines of code, all together, and the active page has a class of "current-channel". Here is the final code:

const channelList = Array.from(document.querySelectorAll('.channel-list'));
const thisChannel = channelList
  .filter(channel => channel.dataset.channel === '{{channel}}')
  .map(channel => channel.classList.add('current-channel'));

On the KLRU Create page, the HTML that results is:

<ul class="channels">
  <li><a href="/schedule/klru/" data-channel="18.1" class="channel">KLRU</a></li>
  <li><a href="/schedule/klrucreate/" data-channel="18.2" class="channel current-channel">KLRU Create</a></li>
  <li><a href="/schedule/klruq/" data-channel="18.3" class="channel-list">KLRU Q</a></li>
  <li><a href="/schedule/klrupbskids/" data-channel="18.4" class="channel-list">KLRU PBS Kids</a></li>
</ul>

With the "current-channel" class applied to only the correct item, it's just a matter of using CSS to apply the styles that I want. In this case, I made the background color a shade of orange that adheres to our branding guidelines.

I know for my part I'll be looking at implementing this solution across other navigation areas. Again, this is just one solution to a common problem. I'm always on the lookout for ways to improve and optimize the code, and should I figure out a better way, you can count on me to post it on this blog.

That said, we're already hitting record high temperatures this summer here in Austin, and there's a pool outside that's calling my name.