How to make JS code accept multiple similar IDs - javascript

I have the JS code below that I use with the form after it. However, it only calls the first dropdown and ignores the rest. The dropdown are generated by Hugo (SSG) and I all share the same ID/class in the menu. I need help to make the code work for multiple dropdown menus.
document.getElementById("dropbtn").addEventListener('click', function() {
document.getElementById("sub-menu").classList.toggle("show");
var x = document.getElementById("dropbtn").getAttribute("aria-expanded");
if (x == "true")
{
x = "false"
} else {
x = "true"
}
document.getElementById("dropbtn").setAttribute("aria-expanded", x);
});
// Close the dropdown menu if the user clicks outside of it
window.onclick = function(event) {
if (!event.target.matches('.dropbtn')) {
var dropdowns = document.getElementsByClassName("sub-menu");
var i;
for (i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show');
}
}
}
}
<nav>
<ul>
<li class="dropdown" id="dropdown">
<a id="dropbtn" class="dropbtn" href="#" aria-expanded="false">Toggle<span class="drop-icon" for="toggle">▾</span></a>
<ul id="sub-menu" class="sub-menu">
<li>
A
</li>
<li>
B
</li>
<li>
<a href="/c/" >C</a>
</li>
</ul>
</li>
<li class="dropdown" id="dropdown">
<a id="dropbtn" class="dropbtn" href="#" aria-expanded="false">Dropdown <span class="drop-icon" for="dropdown">▾</span></a>
<ul id="sub-menu" class="sub-menu">
<li>
D
</li>
<li>
E
</li>
<li>
F
</li>
</ul>
</li>
</ul>
</nav>

It's not reccomended to use multiple id's with the same name.
Here's my solution for your case.
<nav>
<ul>
<li class="dropdown">
<a class="dropbtn" aria-expanded="false" href="#">Toggle<span class="drop-icon">▾</span></a>
<ul class="sub-menu">
<li>
A
</li>
<li>
B
</li>
<li>
C
</li>
</ul>
</li>
<li class="dropdown">
<a class="dropbtn" aria-expanded="false" href="#">Dropdown <span class="drop-icon">▾</span></a>
<ul class="sub-menu">
<li>
D
</li>
<li>
E
</li>
<li>
F
</li>
</ul>
</li>
</ul>
</nav>
<script>
let dropbtns = document.getElementsByClassName('dropbtn');
for (let dropbtn of dropbtns) {
dropbtn.onclick = () => {
dropbtn.nextElementSibling.classList.toggle('show');
let expanded = dropbtn.getAttribute('aria-expanded');
dropbtn.setAttribute('aria-expanded', expanded == 'true' ? 'false' : 'true');
};
}
// Close the dropdown menu if the user clicks outside of it
window.onclick = function (event) {
if (!event.target.matches('.dropbtn')) {
let dropbtns = document.getElementsByClassName('dropbtn');
for (let dropbtn of dropbtns) {
dropbtn.nextElementSibling.classList.remove('show');
dropbtn.setAttribute('aria-expanded', 'false');
}
}
};
</script>

So, first of all. There can only be one unique ID per page, so your HTML is now invalid. You will have to have different IDs on your HTML elements (if you still need them at all). Below JS hopefully should work.
const dropdownTriggers = document.querySelectorAll('.dropbtn');
[...dropdownTriggers].forEach((trigger) => {
trigger.addEventListener('click', (e) => {
const triggerClicked = e.target;
const dropdownMenu = triggerClicked.nextElementSibling;
const isDropdownShown = Boolean(triggerClicked.getAttribute('aria-expanded'));
dropdownMenu.classList.toggle('show', !isDropdownShown);
triggerClicked.setAttribute('aria-expanded', isDropdownShown);
})
})
document.addEventListener('click', (e) => {
if (!event.target.matches('.dropbtn')) {
[...dropdownTriggers].forEach((trigger) => {
const dropdownMenu = trigger.nextElementSibling;
dropdownMenu.classList.toggle('show', false);
trigger.setAttribute('aria-expanded', false);
})
}
});

Related

How to change the font color of a list in Javascript?

So, I'm trying to make these nav-links of mine to change its color whenever I click a button, but it does not seem to work.
<ul class="nav-links">
<li id = link>Home</li>
<li id = link>Projects</li>
<li id = link>About</li>
<li id = link>Contacts</li>
</ul>
<label class="toggle">
<input type="checkbox" class="check">
<span class="slider" onclick="dark()"></span>
</label>
I tried doing this script but it still doesn't work
const text = document.querySelector(".logo");
const body = document.querySelector("body");
const banner = document.querySelector(".L-banner");
const navLinks = document.getElementById("#link");
function dark(){
text.style.color = "#fff";
body.style.background = "#000";
banner.style.color = "#fff";
for(var i = 0; i < navLinks.length; i++){
navLinks[i].style.color = "#fffff";
}
}
You can't give multiple elements the same ID, and querySelector only selects a single element.
To select all the links, you can use querySelectorAll
document.getElementById('button').addEventListener('click', changeColor);
function changeColor() {
const links = [...document.querySelectorAll('.nav-links li a')];
links.forEach(link => link.style.color = "cornflowerblue");
}
<ul class="nav-links">
<li>
Home
</li>
<li>
Projects
</li>
<li>
About
</li>
<li>
Contacts
</li>
</ul>
<button id="button">Change link color</button>
you should change it to this
const navLinks = document.querySelectorAll("#link a");
also don't forget to fix html
<ul class="nav-links">
<li id="link">Home</li>
<li id="link">Projects</li>
<li id="link" ink>About</li>
<li id="link" ink>Contacts</li>
</ul>

Anchor href doesn't work when i add dynamically hr tag after user click

<div class="nav-center">
<ul class="center">
<a class="nav-item" href="">
<li>Random facts</li>
<hr class="line">
</a>
<a class="nav-item" href="/blog/technology/">
<li>Technology</li>
</a>
<a class="nav-item" href="/blog/sport/">
<li>Sports</li>
</a>
<a class="nav-item" href="">
<li>About poeters</li>
</a>
</ul>
</div>
//JavaScript Code
let navItems = document.querySelectorAll(".nav-item");
let addchild = document.createElement("hr");
addchild.classList.add("line");
let Handler = (e) => {
e.preventDefault();
navItems.forEach((node) => {
if (node.contains(removechild)){
node.removeChild(removechild);
}
});
e.currentTarget.appendChild(addchild);
};
navItems.forEach((node) => {
node.addEventListener("click", Handler);
});
See the first anchor tag i added hr tag through html it is showing line below Random Facts and also works fine for my expected link.. But when i add dynamically after user click, line shows but anchor link does not work after user click why??
NOTE: Your HTML is invalid
Here is a version that tests the URL. This script needs to be in all pages
LIVE DEMO
window.addEventListener("load",function() {
// const url = new URL(location.href); // uncomment on your server
const url = new URL("https://www.yourserver.com/blog/technology/");
const path = url.pathname;
let where = path.split("/")
if (!where[where.length-1]) where.pop() // last part of url ending with /
where = where.pop(); // now we have the folder or filename
console.log("w",where,"path",path)
const link = document.querySelector(".nav-center a[href*='"+where+"']");
if (link) link.parentNode.insertBefore(document.createElement("hr"), link.nextSibling);
else console.log("from path",path,"where not found",where)
})
<script src="/blog/scripts/nav.js"></script>
<div class="nav-center">
<ul class="center">
<li><a class="nav-item" href="/blog/index.html">Home</a></li>
<li><a class="nav-item" href="/blog/random/">Random facts</a></li>
<li><a class="nav-item" href="/blog/technology/">Technology</a></li>
<li><a class="nav-item" href="/blog/sport/">Sports</a><br/></li>
<li><a class="nav-item" href="/blog/about/">About</a></li>
</ul>
</div>
<h1>Home</h1>
Here is a version that expect you to fetch/ajax your pages into the existing page. Also it is using delegation
let nav = document.querySelector(".nav-center ul");
let addchild = document.createElement("hr");
addchild.classList.add("line");
const handler = e => {
e.preventDefault();
const tgt = e.target.closest("li");
const url = tgt.querySelector("a")?.href ?? "";
if (url) {
nav.querySelector(".line")?.remove();
tgt.querySelector("a").appendChild(addchild);
// here you would ajax the href into a div elsewhere or target an iFrame
console.log(url)
}
};
nav.addEventListener("click", handler);
<div class="nav-center">
<ul class="center">
<li><a class="nav-item" href="">Random facts</a></li>
<li><a class="nav-item" href="/blog/technology/">Technology</a></li>
<li><a class="nav-item" href="/blog/sport/">Sports</a><br/></li>
<li><a class="nav-item" href="">About poeters</a></li>
</ul>
</div>

Dismiss dropdown on outside click using vanilla JavaScript and ARIA roles

I've been trying to create a very basic dropdown menu but am unable to get the hide on outside click part right. I've gone through several iterations yet always end up unable to dismiss it correctly or close other opened dropdowns when another is activated. Here is my latest variation.
function Menu(trigger) {
let expanded = trigger.getAttribute("aria-expanded") === "true" || false;
let menu = trigger.nextElementSibling;
trigger.setAttribute("aria-expanded", !expanded);
menu.hidden = !menu.hidden;
}
const menuTriggers = document.querySelectorAll(".menu-trigger");
menuTriggers.forEach((menuTrigger) => {
menuTrigger.addEventListener("click", () => {
Menu(menuTrigger);
});
});
<button class="menu-trigger" aria-expanded="false">Menu</button>
<ul class="menu" hidden>
<li class="menu-item">
<a class="menu-link" href="">Books</a>
</li>
<li class="menu-item">
<a class="menu-link" href="">Authors</a>
</li>
<li class="menu-item">
<a class="menu-link menu-link-separator" href="">Genres</a>
</li>
<li class="menu-item">
<a class="menu-link" href="">Themes</a>
</li>
</ul>
You'll have to listen for a mousedown event on the window object to detect the off-click and only close the drop down menu if the event.target is not contained by the drop down menu itself.
function Menu(trigger)
{
let expanded = trigger.getAttribute("aria-expanded") === "true" || false;
let menu = trigger.nextElementSibling;
trigger.setAttribute("aria-expanded", !expanded);
menu.hidden = !menu.hidden;
if(!menu.hidden)
{
setTimeout(() =>
{
let eventType = "mousedown";
let handler = (e) =>
{
if (trigger !== e.target && !menu.contains(e.target))
{
Menu(trigger);
}
window.removeEventListener(eventType, handler, false);
};
window.addEventListener(eventType, handler, false);
}, 0);
}
}
const menuTriggers = document.querySelectorAll(".menu-trigger");
menuTriggers.forEach((menuTrigger) =>
{
menuTrigger.addEventListener("click", () =>
{
Menu(menuTrigger);
});
});
<div style="display:inline-block">
<button class="menu-trigger" aria-expanded="false">Menu</button>
<ul class="menu" hidden>
<li class="menu-item">
<a class="menu-link" href="https://amazon.com">Books</a>
</li>
<li class="menu-item">
<a class="menu-link" href="">Authors</a>
</li>
<li class="menu-item">
<a class="menu-link menu-link-separator" href="">Genres</a>
</li>
<li class="menu-item">
<a class="menu-link" href="">Themes</a>
</li>
</ul>
</div>

Detecting selected element

I have the following Javascript to operate my accordion menu.
<script src="http://thecodeplayer.com/uploads/js/jquery-1.7.1.min.js"></script>
<script src="http://thecodeplayer.com/uploads/js/prefixfree-1.0.7.js"></script>
<script>
$(document).ready(function() {
$("#accordian a").click(function() {
var link = $(this);
var closest_ul = link.closest("ul");
var parallel_active_links = closest_ul.find(".active")
var closest_li = link.closest("li");
var link_status = closest_li.hasClass("active");
var count = 0;
closest_ul.find("ul").slideUp(function() {
if (++count == closest_ul.find("ul").length)
parallel_active_links.removeClass("active");
});
if (!link_status) {
closest_li.children("ul").slideDown();
closest_li.addClass("active");
}
})
$(".selected").parent().slideDown();
})
</script>
How do I modify the code to detect the 'selected' class and open the corresponding panel from the following html script.
<div id="accordian">
<ul>
<li>
<h3 class="mtop"> </h3>
</li>
<li>
<h3>Dashboard</h3>
<ul>
<li class="litop">Reports</li>
<li class="limid">Search</li>
<li class="limid">Graphs</li>
<li class="libot">Settings</li>
</ul>
</li>
<li>
<h3>Calendar</h3>
<ul>
<li class="litop">Current Month</li>
<li class="limid">Current Week</li>
<li class="limid">Previous Month</li>
<li class="limid">Previous Week</li>
<li class="limid">Next Month</li>
<li class="limid">Next Week</li>
<li class="limid">Team Calendar</li>
<li class="limid">Private Calendar</li>
<li class="libot">Settings</li>
</ul>
</li>
<li>
<h3>Favourites</h3>
<ul>
<li class="litop">Global favs</li>
<li class="limid selected">My favs</li>
<li class="limid">Team favs</li>
<li class="libot">Settings</li>
</ul>
</li>
<li>
<h3 class="mbot"> </h3>
</li>
</ul>
</div>
Here IS Fiddle Link - https://jsfiddle.net/p7wm4tL2/
You have old version of jquery,
Try updating your jquery version.
$(document).ready(function() {
$("#accordian a").click(function() {
var link = $(this);
var closest_ul = link.closest("ul");
var parallel_active_links = closest_ul.find(".active")
var closest_li = link.closest("li");
var link_status = closest_li.hasClass("active");
var count = 0;
closest_ul.find("ul").slideUp(function() {
if (++count == closest_ul.find("ul").length)
parallel_active_links.removeClass("active");
});
if (!link_status) {
closest_li.children("ul").slideDown();
closest_li.addClass("active");
}
})
$(".selected").parent().slideDown();
});
Once check working code here.

Lists using CSS and JavaScript

I am learning JavaScript and CSS and got stuck somewhere-
I have three different lists top_menu, submenu3 and submenu4, when I click on item no 3 I want submenu 3 to show up and when I click on item no 4 I want submenu4 to show up and submenu3 should vanish.
My HTML Code is-
<ul id="top_menu">
<li onclick="arrow(1)">item no 1
</li>
<li onclick="arrow(2)">item no 2
</li>
<li onclick="arrow(3)">item no 3
</li>
<li onclick="arrow(4)">item no 4
</li>
</ul>
<ul id="submenu1" style="display:none;"></ul>
<ul id="submenu2" style="display:none;"></ul>
<ul id="submenu3" style="display:none;">
<li>A3
</li>
<li>B3
</li>
<li>C3
</li>
<li>D3
</li>
<li>E3
</li>
</ul>
<ul id="submenu4" style="display:none;">
<li>A4
</li>
<li>B4
</li>
<li>C4
</li>
<li>D4
</li>
<li>E4
</li>
</ul>
and JavaScript implementation for function arrow() is
function arrow(x) {
for (i = 1; i <5; i++) {
if (i==x) {
document.getElementById("submenu"+i).style.display = "block";
}
else {
document.getElementById("submenu"+i).style.display = "none";
}
}
}
Could someone tell where the mistake is?
Thanks!
The for statement should be
for (i = 1; i <5; i++)
Apart from that it seems to be working for me.
A non elegant solution would be to declare your function at the global scope :
window.arrow=function(x){
for (i = 1; i <5; i++) {
if (i==x) {
document.getElementById("submenu"+i).style.display = "block";
}
else {
document.getElementById("submenu"+i).style.display = "none";
}
}
}

Categories