I want an active state in my navbar. Have found only solutions in jQuery, but want it in pure javascript. Long time ago that i have used a for loop. So I do not know, how it is working. Have an old example with foreach in jQuery, but can not translated in pure javascript.
Here is a snippet:
"use strict";
window.onload = function() {
/* Navbar active state */
const pathname = window.location.pathname;
const pages = ["logos", "sketches", "fotos", "tutorials", "kontakt"];
for (let key in pages) {
if (pathname.includes(pages[key])) {
console.log(key, pages[key]);
this.classList.add("active");
} else if (this.classList.includes("active")) {
this.classList.remove("active");
}
}
}
* {
text-decoration: none;
list-style-type: none;
float: left;
padding: 10px;
}
.active {
border-bottom: 2px solid blue;
}
<nav>
<ul class="topnav" id="myTopnav">
<li class="nav-item">
<a class="nav-link" href="#">Logos</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Sketches</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Fotos</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Tutorials</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Kontakt</a>
</li>
</ul>
</nav>
The literal answer to your question - how do you replicate
var pathname = window.location.pathname,
pages = ['home', 'library', 'uploads','about'];
$('.nav-item').each(function(i) {
if (pathname.includes(pages[i])) this.addClass('active');
else if (this.className.includes('active')) this.removeClass('active');
});
...in vanilla JS is:
var pathname = window.location.pathname,
pages = ['home', 'library', 'uploads','about'];
let currentlyActive = document.querySelector('.active')
if(currentlyActive) currentlyActive.classList.remove('active');
let pageMatch = pages.indexOf(pathname);
if(pageMatch !== -1) [...document.querySelectorAll('.nav-item')][pageMatch].classList.add('active');
...a far more detailed explanation is below, in my original answer.
Sure thing! There's a few issues with the code you provided, but they're easy enough to resolve.
[...]
// Here's problem #1: "this" refers to the window scope, in this case.
// It does NOT refer to the nav item you wish to actually append the class to.
this.classList.add("active");
...instead, you need to target the specific nav item you wish to modify. This can be done any number of ways, but the simplest is to just add an id or an additional class to each nav item that corresponds to those in your pages collection (see "Changes to the HTML", below).
// Problem #2, here, is multi-pronged:
// 2a. Strictly speaking, you don't need to test to see if the `classList`
// actually includes the `"active"` class. You're iterating all of them
// anyway, so you may as well just rip off `active` wherever it appears
// that is NOT the match in your above condition.
// 2b. You're still using the `this` keyword, which still applies to the
// `window` object.
} else if (this.classList.includes("active")) {
this.classList.remove("active");
}
}
This can be done a few ways. The simplest is to just add an id
to each link:
<li class="nav-item">
<a id="logos" class="nav-link" href="#">Logos</a>
</li>
<li class="nav-item">
<a id="sketches" class="nav-link" href="#">Sketches</a>
</li>
[...]
...alternatively, you could add a second class to each:
[...]
<li class="nav-item">
<a class="nav-link fotos" href="#">Fotos</a>
</li>
<li class="nav-item">
<a class="nav-link tutorials" href="#">Tutorials</a>
</li>
[...]
Assuming you've elected to go with the id
approach, your simplest means of updating the code would read something like this:
// Get ALL the link objects into an array.
const allMyLinks = [...document.querySelectorAll(".nav-link")];
// Loop through all your links...
for(let linkObj of allMyLinks){
// If the pathname contains their id, they're a match!
if(window.location.pathname.contains(linkObj.id)){
linkObj.classList.add("active");
// Otherwise, they are not!
}else{
linkObj.classList.remove("active");
}
}
All of this presupposes that you're doing this within a SPA (single-page app), in which you NEED to manage the "off" state of the links. If you're building a standard, multi-page site, then you don't even need to worry about .remove
's, as their respective highlights will reset with each page load. In that case, it's as simple as...
// You should, as a general rule, avoid using window.onload (likewise button.onclick; really ANY of the "on*" properties, unless you have a specific reason to do so), favoring the addEventListener method instead. The reason for this is when you directly set .onload is you rip any existing/other load event listeners that are already set. This can cause all kinds of headaches in a LOT of situations.
window.addEventListener('DOMContentLoaded', ()=> {
// document.getElementById, document.querySelector both target specific elements on screen (the former by its ID, the latter by its selector (just like CSS uses). document.querySelectorAll gathers EVERY element that matches your selector (id's are supposed to be unique, so there is no getElementsById).
const allMyLinks = [...document.querySelectorAll(".nav-link")];
for(let linkObj of allMyLinks){
// Since the id's we used are the same as the page names, we can target
// JUST the one that corresponds to the location.
if(window.location.pathname.contains(linkObj.id)){
linkObj.classList.add("active");
}
}
});
...although this, too, can be simplified still farther: if you have a flat site structure (that is, domain.com/page
or domain.com/page.html
) we need even fewer changes. In that case, you can skip the id
or class
changes entirely, and just roll with:
window.addEventListener('DOMContentLoaded', ()=> {
// Grab the .nav-link whose href is the pathname
let activeLink = document.querySelector(".nav-link[href='" + window.location.pathname + "']");
// Set it to active. You want the if condition on the off chance there's
// not a viable match.
if(activeLink) activeLink.classList.add("active");
}