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>
Related
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);
})
}
});
I am trying to add an EventListener to my window object and toggle an active class when scrolling. But the event is never fired, what could be the problem?
window.addEventListener('scroll', () => {
let current = '';
section.forEach(section => {
const sectionTop = section.offsetTop;
const sectionHeight = section.clientHeight;
if (pageYOffset >= sectionTop) {
current = section.getAttribute('id');
}
});
navli.forEach(li => {
li.classList.remove('active');
if (li.classList.contains(current)) {
li.classList.add('active');
}
});
});
Below is the HTML code
<nav>
<div class="navigation">
<ul>
<li class="home active">Home</li>
<li class="about">About</li>
<li class="service">Services</li>
<li class="work">Work</li>
<li class="contact">Contact</li>
</ul>
</div>
</nav>
I would like display combobox on IE 11 by referring this article https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.0pattern/combobox-autocomplete-none.html
but for some reason i can not display the listbox items on IE but for other browsers it works fine. Any idea?
If I try to remove this line
this.domNode = domNode;
it shows me the list but not clickable.
javascript class
/*
* This content is licensed according to the W3C Software License at
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
*/
var Option = function (domNode, listboxObj) {
this.domNode = domNode;
this.listbox = listboxObj;
this.textContent = domNode.textContent;
this.textComparison = domNode.textContent.toLowerCase();
};
Option.prototype.init = function () {
if (!this.domNode.getAttribute('role')) {
this.domNode.setAttribute('role', 'option');
}
this.domNode.addEventListener('click', this.handleClick.bind(this));
this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this));
this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this));
};
/* EVENT HANDLERS */
Option.prototype.handleClick = function (event) {
this.listbox.setOption(this);
this.listbox.close(true);
};
Option.prototype.handleMouseover = function (event) {
this.listbox.hasHover = true;
this.listbox.open();
};
Option.prototype.handleMouseout = function (event) {
this.listbox.hasHover = false;
setTimeout(this.listbox.close.bind(this.listbox, false), 300);
};
HTML
<section>
<h2 id="ex_label">Example</h2>
<div role="separator" id="ex_start_sep" aria-labelledby="ex_start_sep ex_label" aria-label="Start of"></div>
<div id="ex1">
<div class="combobox-list">
<label for="cb1-input">Search</label>
<div class="group">
<input id="cb1-input" class="cb_edit" type="text"
role="combobox"
aria-autocomplete="none"
aria-expanded="false"
aria-haspopup="true"
aria-owns="cb1-listbox"
readonly
/>
<button id="cb1-button" tabindex="-1" aria-label="Open">
▽
</button>
</div>
<ul id="cb1-listbox" role="listbox" aria-label="Previous Searches">
<li id="lb1-01" role="option">weather</li>
<li id="lb1-02" role="option">salsa recipes</li>
<li id="lb1-03" role="option">cheap flights to NY</li>
<li id="lb1-04" role="option">dictionary</li>
<li id="lb1-05" role="option">baseball scores</li>
<li id="lb1-06" role="option">hotels in NY</li>
<li id="lb1-07" role="option">mortgage calculator</li>
<li id="lb1-08" role="option">restaurants near me</li>
<li id="lb1-09" role="option">free games</li>
<li id="lb1-10" role="option">gas prices</li>
<li id="lb1-11" role="option">classical music</li>
</ul>
</div>
</div>
<div role="separator" id="ex_end_sep" aria-labelledby="ex_end_sep ex_label" aria-label="End of"></div>
</section>
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.
I have a vertical menu with some submenu items, and this submenu opens on click.
But the problem is that when I click on one of the submenu items, the submenu closes, whereas I want it to remain open, since when I browse on the submenu items
Here's the html:
<div class="ul_container">
<ul class="mainnav" id="nav" style="list-style:none;">
<li><a id="active" href="index.html"><strong>HOME</strong></a></li>
<li>COLLECTIONS
<ul class="subnav" id="sub1" style="display:none">
<li class="first">spring/summer 2013
<li class="first">autumn/winter 2013
<li class="first">autumn/winter 2012
<li class="first">autumn/winter 2011
</ul>
</li>
<li>PORTRAIT</li>
<li>HERITAGE</li>
<li>PRESS</li>
<li>CONTACTS</li>
</ul>
</div>
and the js
function toggleID(IDS) {
var sel = document.getElementById('nav').getElementsByTagName('ul');
for (var i=0; i<sel.length; i++) {
if (sel[i].id != IDS) { sel[i].style.display = 'none'; }
}
sel = document.getElementById(IDS);
sel.style.display = (sel.style.display != 'block') ? 'block' : 'none';
}
<div class="ul_container">
<ul class="mainnav" id="nav" style="list-style:none;">
<li><a id="active" href="#"><strong>HOME</strong></a></li>
<li>COLLECTIONS
<ul class="subnav" id="sub1" style="display:none">
<li class="first">spring/summer 2013</li>
<li class="first">autumn/winter 2013</li>
<li class="first">autumn/winter 2012</li>
<li class="first">autumn/winter 2011</li>
</ul>
</li>
<li>PORTRAIT</li>
<li>HERITAGE</li>
<li>PRESS</li>
<li>CONTACTS</li>
</ul>
$("#test").click(function() {
var sel = document.getElementById('nav').getElementsByTagName('ul');
for (var i = 0; i < sel.length; i++) {
if (sel[i].id != 'sub1') {
sel[i].style.display = 'none';
}
}
sel = document.getElementById('sub1');
sel.style.display = (sel.style.display != 'block') ? 'block' : 'none';
});
Working Example
Is your problem thay you need to remember the visibility state of the submenu once you klick on the subitems?
If so, you need to remember the state of the submenu. You can accomplish this using cookies or localstorage.
Html (just small changes, like removing onclick and fixing html)
<div class="ul_container">
<ul class="mainnav" id="nav" style="list-style:none;">
<li><a id="active" href="index.html"><strong>HOME</strong></a></li>
<li>COLLECTIONS
<ul class="subnav" id="sub1">
<li class="first">spring/summer 2013</li>
<li class="first">autumn/winter 2013</li>
<li class="first">autumn/winter 2012</li>
<li class="first">autumn/winter 2011</li>
</ul>
</li>
<li>PORTRAIT</li>
<li>HERITAGE</li>
<li>PRESS</li>
<li>CONTACTS</li>
</ul>
</div>
Javascript (using jquery with localstorage)
if(localStorage.getItem("state") == "closed") // <-- this checks closed/open state and sets subnav accordingly
$('.subnav').hide();
$('.subnav').parent().click(function () {
$(this).find('#sub1').toggle(); //<-- This toggles the menu open/close
// ** Remeber state ** //
if (localStorage.getItem("state") == "open") {
localStorage.setItem("state", "closed"); // <-- this remembers "open" after user navigates
} else {
localStorage.setItem("state", "open"); // <-- this remembers "open" after user navigates
}
// ** Remeber state ** //
});
My example uses jquery, if you have the possibility to start using it you should do so.
You can replace most of your javascript with a few lines using jquery
Check out working fiddle here
http://jsfiddle.net/urbanbjorkman/jGwnz/
Urban Bjorkman, I use your code but something is wrong...is that right?
<html>
<head>
<script type="text/javascript" >
if (localStorage.getItem("state") == "closed") // <-- this checks closed/open state and sets subnav accordingly
$('.subnav').hide();
$('.subnav').parent().click(function () {
$(this).find('#sub1').toggle(); //<-- This toggles the menu open/close
// ** Block to remeber state ** //
if (localStorage.getItem("state") == "open") {
localStorage.setItem("state", "closed"); // <-- this remembers "open" after user navigates
} else {
localStorage.setItem("state", "open"); // <-- this remembers "open" after user navigates
}
// ** Block to remeber state ** //
});
</script>
</head>
<body>
<div class="ul_container">
<ul class="mainnav" id="nav" style="list-style:none;">
<li><a id="active" href="index.html"><strong>HOME</strong></a>
</li>
<li>COLLECTIONS
<ul class="subnav" id="sub1">
<li class="first">spring/summer 2013
</li>
<li class="first">autumn/winter 2013
</li>
<li class="first">autumn/winter 2012
</li>
<li class="first">autumn/winter 2011
</li>
</ul>
</li>
<li>PORTRAIT
</li>
<li>HERITAGE
</li>
<li>PRESS
</li>
<li>CONTACTS
</li>
</ul>
</div>
</body>
</html>