admin管理员组文章数量:1291568
My brain has checked out for weekend...
I am looking for a solution in plain Javascript where if one dropdown menu box is opened on click of another main menu item, the previous opened dropdown would close and then display the newly clicked main menu item's dropdown. I know this is probably so simple, but I cannot e up with a solution that is not convoluted.
Also if you click outside of the menu items (anywhere on the document that is not a menu item or dropdown box) should close any open dropdowns.
Thank you for any help.
function testFunc(el) {
var parent = el.parentElement;
var dd = parent.lastChild.previousSibling;
dd.classList.toggle('show');
}
ul { list-style: none; margin: 0; padding: 0; }
ul li {
width: 100px;
float: left;
background: #dbdbdb;
line-height: 2em;
text-align: center;
margin: 0 5px;
cursor: pointer;
}
ul li span {
display: block;
}
ul li ul {
display: none;
}
.show {
display: block;
}
<ul>
<li>
<span onclick="testFunc(this)">Item 1</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
<li>
<span onclick="testFunc(this)">Item 2</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
<li>
<span onclick="testFunc(this)">Item 3</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
<li>
<span onclick="testFunc(this)">Item 4</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
</ul>
My brain has checked out for weekend...
I am looking for a solution in plain Javascript where if one dropdown menu box is opened on click of another main menu item, the previous opened dropdown would close and then display the newly clicked main menu item's dropdown. I know this is probably so simple, but I cannot e up with a solution that is not convoluted.
Also if you click outside of the menu items (anywhere on the document that is not a menu item or dropdown box) should close any open dropdowns.
Thank you for any help.
function testFunc(el) {
var parent = el.parentElement;
var dd = parent.lastChild.previousSibling;
dd.classList.toggle('show');
}
ul { list-style: none; margin: 0; padding: 0; }
ul li {
width: 100px;
float: left;
background: #dbdbdb;
line-height: 2em;
text-align: center;
margin: 0 5px;
cursor: pointer;
}
ul li span {
display: block;
}
ul li ul {
display: none;
}
.show {
display: block;
}
<ul>
<li>
<span onclick="testFunc(this)">Item 1</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
<li>
<span onclick="testFunc(this)">Item 2</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
<li>
<span onclick="testFunc(this)">Item 3</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
<li>
<span onclick="testFunc(this)">Item 4</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
</ul>
Share
Improve this question
asked Jun 9, 2018 at 13:47
SergioSergio
8223 gold badges13 silver badges42 bronze badges
2 Answers
Reset to default 7Toggling menu visibility
You can save the last opened menu in a variable opened
outside the function. Then when a menu is clicked if opened
is not null
it will toggle the opened
(i.e. hide the last opened menu) and toggle the clicked item.
let opened = null
function testFunc(el) {
// gets the <ul> element of the clicked menu item
const menu = el.parentElement.lastChild.previousSibling;
if (!opened) {
// no menu item is shown
opened = menu
opened.classList.toggle('show');
} else if (menu == opened) {
// the clicked item is already showing
menu.classList.toggle('show')
opened = null
} else {
// the clicked item is hiddden but another one is showing
opened.classList.toggle('show')
opened = menu
opened.classList.toggle('show')
}
}
Here is the code:
let opened = null
function testFunc(el) {
const menu = el.parentElement.lastChild.previousSibling;
if(!opened) {
opened = menu
opened.classList.toggle('show');
} else if(menu == opened) {
menu.classList.toggle('show')
opened = null
} else {
opened.classList.toggle('show')
opened = menu
opened.classList.toggle('show')
}
}
ul {
list-style: none;
margin: 0;
padding: 0;
}
ul li {
width: 100px;
float: left;
background: #dbdbdb;
line-height: 2em;
text-align: center;
margin: 0 5px;
cursor: pointer;
}
ul li span {
display: block;
}
ul li ul {
display: none;
}
.show {
display: block;
}
<ul>
<li>
<span onclick="testFunc(this)">Item 1</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
<li>
<span onclick="testFunc(this)">Item 2</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
<li>
<span onclick="testFunc(this)">Item 3</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
<li>
<span onclick="testFunc(this)">Item 4</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
</ul>
A variant with ES6 syntax
Here is a variant with some ES6 syntax, note I have changed the HTML naming structure to better maintain the code, calling the elements by class name allows
to not have to use of inline event listeners
call all the menu items in one line
Here is the JavaScript code:
let opened = null
const toggleVisibility = e => e.classList.toggle('show')
const toggleDropDown = e => {
const clickedItem = e.target.parentElement.lastChild.previousSibling
toggleVisibility(clickedItem);
if (!opened) {
opened = clickedItem
} else if (opened == clickedItem) {
opened = null
} else {
toggleVisibility(opened);
opened = clickedItem
}
}
[...document.querySelectorAll('.dropDown')].forEach(dropDown => dropDown.addEventListener('click', toggleDropDown))
let opened = null
const toggleVisibility = e => e.classList.toggle('show')
const toggleDropDown = e => {
const clickedItem = e.target.parentElement.lastChild.previousSibling
toggleVisibility(clickedItem);
if (!opened) {
opened = clickedItem
} else if (opened == clickedItem) {
opened = null
} else {
toggleVisibility(opened);
opened = clickedItem
}
}
[...document.querySelectorAll('.dropDown')].forEach(dropDown => dropDown.addEventListener('click', toggleDropDown))
ul {
list-style: none;
margin: 0;
padding: 0;
}
ul li {
width: 100px;
float: left;
background: #dbdbdb;
line-height: 2em;
text-align: center;
margin: 0 5px;
cursor: pointer;
}
ul li span {
display: block;
}
ul li ul {
display: none;
}
.show {
display: block;
}
<ul>
<li>
<span class="dropDown">Item 1</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
<li>
<span class="dropDown">Item 2</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
<li>
<span class="dropDown">Item 3</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
<li>
<span class="dropDown">Item 4</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
</ul>
Toggling menu visibility + closing when clicking elsewhere
If you want to close any opened menu if the user clicks outside of the menu you'll need to have an event listener on the document itself. So instead of having one event listener per menu button, you will have a single one watching for any click happening in the document.
The event listener will determine if the clicked item is a menu button, in this case, it will run the menu handler. Else it will close the last opened menu item.
JavaScript code:
let opened = null
const toggleVisibility = e => e.classList.toggle('show')
const handleDropdown = e => {
const clickedItem = e.parentElement.lastChild.previousSibling
toggleVisibility(clickedItem)
if (!opened) {
opened = clickedItem
} else if (opened == clickedItem) {
opened = null
} else {
toggleVisibility(opened)
opened = clickedItem
}
}
const handleClick = e => {
if (e.target.className.includes('dropDown')) {
handleDropdown(e.target)
} else if (opened) {
toggleVisibility(opened)
opened = null
}
}
document.addEventListener('click', handleClick)
Here is the full code:
let opened = null
const toggleVisibility = e => e.classList.toggle('show')
const handleDropdown = e => {
const clickedItem = e.parentElement.lastChild.previousSibling
toggleVisibility(clickedItem)
if (!opened) {
opened = clickedItem
} else if (opened == clickedItem) {
opened = null
} else {
toggleVisibility(opened)
opened = clickedItem
}
}
const handleClick = e => {
if (e.target.className.includes('dropDown')) {
handleDropdown(e.target)
} else if (opened) {
toggleVisibility(opened)
opened = null
}
}
document.addEventListener('click', handleClick)
ul {
list-style: none;
margin: 0;
padding: 0;
}
ul li {
width: 100px;
float: left;
background: #dbdbdb;
line-height: 2em;
text-align: center;
margin: 0 5px;
cursor: pointer;
}
ul li span {
display: block;
}
ul li ul {
display: none;
}
.show {
display: block;
}
<ul>
<li>
<span class="dropDown">Item 1</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
<li>
<span class="dropDown">Item 2</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
<li>
<span class="dropDown">Item 3</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
<li>
<span class="dropDown">Item 4</span>
<ul>
<li>Sub Item 1</li>
<li>Sub Item 2</li>
</ul>
</li>
</ul>
It's hard to pete with Ivan's answer but this would be my solution:
function Dropdown() {
// Listen to ALL (!) click events to also catch clicks OUTSIDE the dropdowns
document.addEventListener('click', function(e) {
if (e.target.closest('.dropdown')) {
closeOthers(e.target);
handleClick(e.target);
} else {
closeOthers(null);
}
});
// Add or remove 'expanded' CSS class, depending on the current situation
function handleClick(dropdown) {
if (dropdown.classList.contains('expanded')) {
dropdown.classList.remove('expanded');
} else {
dropdown.classList.add('expanded');
}
}
// Close all dropdowns except the one that gets passed as the element parameter
// Note that we may also pass null in order to close ALL dropdowns
function closeOthers(element) {
document.querySelectorAll('.dropdown > a').forEach(link => {
if (element != link) {
link.classList.remove('expanded');
}
});
}
}
document.addEventListener('DOMContentLoaded', Dropdown);
<div class="dropdown">
<a aria-label="Settings"></a>
<ul>
<li><a href="/account">Account</a></li>
<li><a href="/profile">Profile</a></li>
<li><a href="/tutorial">Tutorial</a></li>
</ul>
</div>
It works for me. Not sure if it can work for someone else. Feedback appreciated.
本文标签: navigationVanilla Javascript Toggle Drop Down MenuStack Overflow
版权声明:本文标题:navigation - Vanilla Javascript Toggle Drop Down Menu - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741531778a2383797.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论