I have this dropdown menu solution. http://jsfiddle.net/ftymhs8s/
I need to hide the one dropdown menu that is displayed when I click on a different line and show dropdown menu of it. I also need the first line dropdown menu to be displayed always when people go to the website.
I hope I correctly described my problem, can you please help me?
// Dropdown Menu
var dropdown = document.querySelectorAll('.dropdown');
var dropdownArray = Array.prototype.slice.call(dropdown, 0);
dropdownArray.forEach(function(el) {
var button = el.querySelector('a[data-toggle="dropdown"]'),
menu = el.querySelector('.dropdown-menu'),
arrow = button.querySelector('i.icon-arrow');
button.onclick = function(event) {
if (!menu.hasClass('show')) {
menu.classList.add('show');
menu.classList.remove('hide');
arrow.classList.add('open');
arrow.classList.remove('close');
event.preventDefault();
} else {
menu.classList.remove('show');
menu.classList.add('hide');
arrow.classList.remove('open');
arrow.classList.add('close');
event.preventDefault();
}
};
})
Element.prototype.hasClass = function(className) {
return this.className && new RegExp("(^|\\s)" + className + "(\\s|$)").test(this.className);
};
ul {
list-style: none
}
.dropdown a {
text-decoration: none;
}
.dropdown [data-toggle="dropdown"] {
position: relative;
display: block;
color: black;
padding: 10px;
}
.dropdown .dropdown-menu {
max-height: 0;
overflow: hidden;
}
.dropdown .dropdown-menu li {
padding: 0;
}
.dropdown .dropdown-menu li a {
display: block;
padding: 10px 10px;
}
.dropdown .show {
display: block;
max-height: 9999px;
margin-left: 50px;
}
.dropdown .hide {
max-height: 0;
}
<div class="container">
<ul>
<li class="dropdown">
First Menu
<ul class="dropdown-menu">
<li>Home</li>
<li>About Us</li>
<li>Services</li>
<li>Contact</li>
</ul>
</li>
<li class="dropdown">
Second Menu
<ul class="dropdown-menu">
<li>Home</li>
<li>About Us</li>
<li>Services</li>
<li>Contact</li>
</ul>
</li>
<li class="dropdown">
Third Menu
<ul class="dropdown-menu">
<li>Home</li>
<li>About Us</li>
<li>Services</li>
<li>Contact</li>
</ul>
</li>
</ul>
</div>
The behavior of multiple collapsible elements where only one of them can be open at one time is that of an accordion. The general idea is to first close all collapsibles then open the one that was selected by user. The following demo exhibits that behavior by Event Delegation.
BTW I noticed that you made a hasClass... er Class. That's not necessary, just use: node.classList.contains('class')
Details are commented in the demo
Demo
/* Added .main class to parent <ul>
|| By adding the eventListener to the
|| parent of multiple clickable nodes
|| and using e.target property to find
|| the exact node actually clicked, we
|| have just needed the <ul> to listen
|| rather than 3 separate <li>
|| This is part of Event Delagation
*/
var main = document.querySelector('.main');
main.addEventListener('click', accordion, false);
function accordion(e) {
/* Gather all .dropdown-menu to a NodeList
|| then covert it to an array
*/
var dropArray = Array.from(document.querySelectorAll('.dropdown-menu'));
/* Gather all links in the .dropdown-menus to
|| a NodeList then convert it to an array
*/
var linxArray = Array.from(document.querySelectorAll('a + .dropdown-menu a'));
/* if the clicked node (e.target) is NOT the
|| node listening for event (e.currentTarget
|| ul.main) then...
*/
if (e.target !== e.currentTarget) {
// Assign e.target to var tgr
var tgr = e.target;
/* if tgr has data-toggle attribute...
|| Find tgr next sibling (.dropdown-menu)
|| Iterate through dropArray wth a
|| for...of loop
|| Remove .show and add .hide on
|| each .dropdown-menu in dropArray
|| Then add .show and remove .hide
|| on tgt
|| Finally stop the click event from
|| bubbling, thereby preventing anything
|| else from being triggered.
*/
if (tgr.hasAttribute('data-toggle')) {
// Stop <a> from jumping
e.preventDefault();
var tgt = tgr.nextElementSibling;
for (let drop of dropArray) {
drop.classList.remove('show');
drop.classList.add('hide');
}
tgt.classList.add('show');
tgt.classList.remove('hide');
} else {
return;
}
e.stopPropagation();
}
}
html,
body,
.contain {
height: 100%;
width: 100%;
}
.main,
section,
article {
margin-bottom: 100vh;
}
ul {
list-style: none
}
.dropdown a {
text-decoration: none;
}
.dropdown [data-toggle="dropdown"] {
position: relative;
display: block;
color: black;
padding: 10px;
}
.dropdown .dropdown-menu {
max-height: 0;
overflow: hidden;
}
.dropdown .dropdown-menu li {
padding: 0;
}
.dropdown .dropdown-menu li a {
display: block;
padding: 10px 10px;
}
.dropdown .show {
display: block;
max-height: 9999px;
margin-left: 50px;
}
.dropdown .hide {
max-height: 0;
}
<div id='home' class="container">
<ul class='main'>
<li class="dropdown">
First Menu
<ul class="dropdown-menu">
<li>Home</li>
<li>About Us</li>
<li>Services</li>
<li>Contact</li>
</ul>
</li>
<li class="dropdown">
Second Menu
<ul class="dropdown-menu">
<li>Section I</li>
<li>Section II</li>
<li>Section III</li>
<li>Section IV</li>
</ul>
</li>
<li class="dropdown">
Third Menu
<ul class="dropdown-menu">
<li>Example</li>
<li>Example</li>
<li>Example</li>
<li>Example</li>
</ul>
</li>
</ul>
<article id='about'>
<h2>About</h2>
</article>
<article id='services'>
<h2>Services</h2>
</article>
<article id='contact'>
<h2>Contact</h2>
</article>
<hr>
<section id='1'>
<h2>Section I</h2>
</section>
<section id='2'>
<h2>Section II</h2>
</section>
<section id='3'>
<h2>Section III</h2>
</section>
<section id='4'>
<h2>Section IV</h2>
</section>
</div>
Related
I have a nav menu bar.
This nav menu contain a sub menu.
When i click on the li the sub menu appear, but when i click again on the li to hide the sub menu this sub menu doesn't hide but if i click on another li the first sub menu disappear.
What i want to make is when i click on the li the sub menu apear and when i click again the sub menu disappear and if i click on another li that contain the class sub it should close any other sub menu and appear the sub menu of this li
i try toggle, classList.contains, if condition but this not work for me
below an example
let menuLi = document.querySelectorAll('.sub'),
subMenu = document.querySelectorAll('.sub-menu');
menuLi.forEach((li) => {
li.addEventListener('click', () => {
menuLi.forEach((li) => {
li.classList.remove('active');
});
if (li.classList.contains('active')) {
li.classList.remove('active');
} else {
li.classList.add('active');
}
})
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
list-style: none;
text-decoration: none;
}
.navbar {
display: flex;
align-items: center;
gap: 20px;
height: 60px;
padding: 10px;
background: red;
}
.sub {
position: relative;
}
.sub .sub-menu {
position: absolute;
top: 39px;
left: 0;
width: 100px;
display: none;
}
.sub .sub-menu li {
margin: 10px 0;
}
.sub.active .sub-menu {
display: block;
}
<ul class="navbar">
<li>
Home
</li>
<li class="sub">
Info
<ul class="sub-menu">
<li>Info 1</li>
<li>Info 2</li>
</ul>
</li>
<li class="sub">
image
<ul class="sub-menu">
<li>img 1</li>
<li>img 2</li>
</ul>
</li>
</ul>
I'd probably just keep previous state, reset state of all and set state accordingly.
menuLi.forEach((li) => {
li.addEventListener('click', () => {
const isOpened = li.classList.contains('active')
menuLi.forEach((li) => {
li.classList.remove('active');
});
if (isOpened) {
li.classList.remove('active');
} else {
li.classList.add('active');
}
})
});
I'm trying to make a search bar. No matter what I type in search box it shows the "Dropdown" as result and it doesn't show the items inside that list when I search. What am I doing wrong?
https://jsfiddle.net/5xh86fkn/
var dropdown = document.getElementsByClassName("dropdown-btn");
var i;
for (i = 0; i < dropdown.length; i++) {
dropdown[i].addEventListener("click", function() {
this.classList.toggle("active");
var dropdownContent = this.nextElementSibling;
if (dropdownContent.style.display === "block") {
dropdownContent.style.display = "none";
} else {
dropdownContent.style.display = "block";
}
});
}
function myFunction() {
var input, filter, ul, li, a, i;
input = document.getElementById("mySearch");
filter = input.value.toUpperCase();
ul = document.getElementById("myMenu");
li = ul.getElementsByTagName("li");
for (i = 0; i < li.length; i++) {
a = li[i].getElementsByTagName("a")[0];
if (a.innerHTML.toUpperCase().indexOf(filter) > -1) {
li[i].style.display = "";
} else {
li[i].style.display = "none";
}
}
}
<div class="sidenav">
<input type="text" id="mySearch" onkeyup="myFunction()" placeholder="Search.." title="Type in a category">
<ul id="myMenu">
<li>About</li>
<li>Services</li>
<li>Clients</li>
<li>Contact</li>
<button class="dropdown-btn">Dropdown
<i class="fa fa-caret-down"></i>
</button>
<div class="dropdown-container">
<li>Link 1</li>
<li>Link 2</li>
<li>Link 3</li>
</div>
<li>Search</li>
</ul>
</div>
This is a working demo of your attempt of filtering the menu items in real time by typing suggestions on the input text.
The main problem was using the correct strategy to fetch the menu items from dom. The element you wanted to partecipate in the filtering, wasn't a <LI> element.
Plus the list you embedded inside the Dropdown button wasn't included in a ol container and was breaking the correct behaviour.
I slightly refactored your html and focused on the single js function in charge of filtering the list according to the input typed:
function f(filter){
menuItems = document.querySelectorAll('#myMenu > li');
for(const menuItem of menuItems){
const textContent = menuItem.textContent.trim().toUpperCase();
//console.log(`"${textContent}" "${filter}" "${textContent.indexOf(filter)}"`);
if (textContent.indexOf(filter.toUpperCase()) > -1) {
menuItem.classList.remove('hidden');
}else{
menuItem.classList.add('hidden');
}
}
}
.hidden{
display: none;
}
body {
font-family: "Lato", sans-serif;
}
/* Fixed sidenav, full height */
.sidenav {
height: 100%;
width: 200px;
position: fixed;
z-index: 1;
top: 0;
left: 0;
background-color: #111;
overflow-x: hidden;
padding-top: 20px;
}
/* Style the sidenav links and the dropdown button */
.sidenav a, .dropdown-btn {
padding: 6px 8px 6px 16px;
text-decoration: none;
font-size: 20px;
color: #818181;
display: block;
border: none;
background: none;
width: 100%;
text-align: left;
cursor: pointer;
outline: none;
}
/* On mouse-over */
.sidenav a:hover, .dropdown-btn:hover {
color: #f1f1f1;
}
/* Main content */
.main {
margin-left: 200px; /* Same as the width of the sidenav */
font-size: 20px; /* Increased text to enable scrolling */
padding: 0px 10px;
}
/* Add an active class to the active dropdown button */
.active {
background-color: green;
color: white;
}
/* Dropdown container (hidden by default). Optional: add a lighter background color and some left padding to change the design of the dropdown content */
.dropdown-container {
display: none;
background-color: #262626;
padding-left: 8px;
}
/* Optional: Style the caret down icon */
.fa-caret-down {
float: right;
padding-right: 8px;
}
/* Some media queries for responsiveness */
#media screen and (max-height: 450px) {
.sidenav {padding-top: 15px;}
.sidenav a {font-size: 18px;}
}
<div class="sidenav">
<input type="text" id="mySearch" onkeyup="f(this.value)" placeholder="Search.." title="Type in a category">
<ul id="myMenu">
<li>About</li>
<li>Services</li>
<li>Clients</li>
<li>Contact</li>
<li>
<button class="dropdown-btn">
Dropdown
<i class="fa fa-caret-down"></i>
</button>
<div class="dropdown-container">
<ol>
<li>Link 1</li>
<li>Link 2</li>
<li>Link 3</li>
</ol>
</div>
</li>
<li>
Search
</li>
</ul>
</div>
For simplicity I removed code not related to the Search question.
I would suggest a slightly different approach here.
Use the textContent of the nodes so that you would not match the HTML other than that.
Leverage the data attribute and some CSS to make the code smaller by setting them to a true/false string and show/hide based upon that.
Use a descender selector to find only a that is directly inside an li by using li>a - I would strongly suggest classes instead of element selectors however for the "searchable" elements.
Note I hide the parent li by a toggle of the dataset value
I removed the JavaScript from the HTML as a best practice
Note this code can be further reduced but I left it verbose for clarity.
document.getElementById("mySearch").addEventListener('keyup', (event) => {
const findMe = event.target.value.toUpperCase();
const searchable = document.querySelectorAll('li>a');
searchable.forEach(function(searchItem) {
searchItem.parentElement.dataset.isfound = searchItem.textContent.toUpperCase().includes(findMe) ? "true" : "false";
});
});
li[data-isfound="true"] {
background-color: yellow;
}
li[data-isfound="false"] {
display: none;
}
<div class="sidenav">
<input type="text" id="mySearch" placeholder="Search.." title="Type in a category">
<ul id="myMenu">
<li>About</li>
<li>Services</li>
<li>Clients</li>
<li>Contact</li>
<li><button class="dropdown-btn">Dropdown
<i class="fa fa-caret-down"></i>
</button></li>
<ul class="dropdown-container">
<li>Link 1</li>
<li>Link 2</li>
<li>Link 3</li>
</ul>
<li>Search</li>
</ul>
</div>
how to prevent opening link if parent li hasClass: has-menu ? it must work for all nested level
in example below only Sub Sub cat 1/1 and Sub cat 1/2 should open link but if i make it work then collapse with + and - is broken , how to combinate both collapse and link opening only if li dont have class: has-menu
$('li').click(function(e){
if ($(this).hasClass('has-menu')) {
e.preventDefault();
e.stopPropagation();
$(this).children("div").children("ul").toggleClass("menu-close");
$(this).toggleClass("menu-open");
}
})
ul{
padding: 0px;
list-style: none;
}
ul li{
padding: 5px 0;
}
ul li a{
width: 100%;
display: inline-block;
}
.menu-close{
display: none;
/*background-color:red;*/
}
/* Main menu */
.menu-main{
width: 250px;
}
.menu-main li{
position: relative;
}
.has-menu:before{
content: '+';
position: absolute;
right: 0;
}
.has-menu.menu-open:before{
content: '-' !important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul class="menu-main">
<li class="has-menu">
Test 1
<div class="menu-wrapper">
<ul class="menu-child menu-close">
<li class="has-menu">
Sub cat 1/1
<div class="menu-wrapper">
<ul class="menu-subchild menu-close">
<li>
Sub Sub cat 1/1
</li>
</ul>
</div>
</li>
</ul>
<ul class="menu-child menu-close">
<li>
Sub cat 1/2
</li>
</ul>
</div>
</li>
</ul>
.has-menu>a{
pointer-events: none;
}
Try this.
just disable pointer-events on anchor tags that have parent with class .has-menu
I have gotten it to open and close when hovering over the nav link but how do I keep it open so I can access the content on the menu? I need it to work exactly how a dropdown menu works.
This is what I have done so far I also need the html layout to stay the same.
$('.nav-link--dropdown').mouseover(function() {
$('.dropdown-menu').css('display', 'block');
});
$('.nav-link--dropdown').mouseleave(function() {
$('.dropdown-menu').css('display', 'none');
});
ul {
list-style: none;
padding: 0px;
display: flex;
}
li {
margin-right: 10px;
}
.dropdown-menu {
display: none;
}
.dropdown-menu ul {
display: block;
background-color: grey;
color: white;
padding: 10px;
text-align: center;
width: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="nav">
<ul>
<li class="nav-link">home</li>
<li class="nav-link--dropdown">dropdown</li>
</ul>
</div>
<div class="dropdown-menu">
<ul>
<li class="dropdown-menu__link">random text</li>
<li class="dropdown-menu__link">random text</li>
<li class="dropdown-menu__link">random text</li>
</ul>
</div>
View on jsFiddle
Should remove margin use padding for that, if not when we enter that margin area mouse leave event will trigger.
$('.nav-link--dropdown').mouseover(function () {
$('.dropdown-menu').css('display', 'block');
});
$('.dropdown-menu').mouseover(function () {
$('.dropdown-menu').css('display', 'block');
});
$('.nav-link--dropdown').mouseleave(function () {
$('.dropdown-menu').css('display', 'none');
});
$('.dropdown-menu').mouseleave(function () {
$('.dropdown-menu').css('display', 'none');
});
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
ul {
list-style: none;
padding: 0px;
display: flex;
}
li {
padding: 10px;
}
.dropdown-menu {
display: none;
}
.dropdown-menu ul {
display: block;
background-color: grey;
color: white;
padding: 10px;
text-align: center;
width: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<div class="nav">
<ul>
<li class="nav-link">home</li>
<li class="nav-link--dropdown">dropdown</li>
</ul>
</div>
<div class="dropdown-menu">
<ul>
<li class="dropdown-menu__link">random text</li>
<li class="dropdown-menu__link">random text</li>
<li class="dropdown-menu__link">random text</li>
</ul>
</div>
The problem you have here is that you leave the nav-link--dropdown when you move to your dropdown-menu. The simple solution: Include your dropdown-menu in your nav item.
$('.nav-link--dropdown').mouseover(function() {
$('.dropdown-menu').css('display', 'block');
});
$('.nav-link--dropdown').mouseleave(function() {
$('.dropdown-menu').css('display', 'none');
});
ul {
list-style: none;
padding: 0px;
display: flex;
}
li {
margin-right: 10px;
}
.dropdown-menu {
display: none;
}
.dropdown-menu ul {
display: block;
background-color: grey;
color: white;
padding: 10px;
text-align: center;
width: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="nav">
<ul>
<li class="nav-link">home</li>
<li class="nav-link--dropdown">
dropdown
<div class="dropdown-menu">
<ul>
<li class="dropdown-menu__link">random text</li>
<li class="dropdown-menu__link">random text</li>
<li class="dropdown-menu__link">random text</li>
</ul>
</div>
</li>
</ul>
</div>
You can achieve this by bundling the menu item you want to hover over and it's dropdown in the same div.
<div class="nav">
<ul>
<li class="nav-link">home</li>
<div class="innerNav">
<li class="nav-link--dropdown">dropdown</li>
<div class="dropdown-menu">
<ul>
<li class="dropdown-menu__link">random text</li>
<li class="dropdown-menu__link">random text</li>
<li class="dropdown-menu__link">random text</li>
</ul>
</div>
</div>
</ul>
</div>
And:
$('.innerNav').mouseover(function () {
$('.dropdown-menu').css('display', 'block');
});
$('.innerNav').mouseleave(function () {
$('.dropdown-menu').css('display', 'none');
});
See this JSFiddle: https://jsfiddle.net/rmdfqh6c/
Since your problem is the mouseleave event will trigger when you try to move the mouse to the bellow dropdown menu, if you really must keep the elements separated, add the dropdown-menu to the class that keeps the display:block
$('.nav-link--dropdown, .dropdown-menu').mouseover(function () {
$('.dropdown-menu').css('display', 'block');
});
$('.nav-link--dropdown, .dropdown-menu').mouseleave(function () {
$('.dropdown-menu').css('display', 'none');
});
And remove margin/padding from menu items. Use line-height instead if you want some spacing:
.nav ul li {
line-height: 40px;
}
ul {
list-style: none;
margin: 0; // <--- I changed this from padding: 0px;
display: flex;
}
Otherwise the other suggested solutions to include the dropdown within you parent <ul> element is the best.
JSFiddle
I dont know how to approach this. I would like to be able to do the following: I have a icon for a folder that links to another page. When the user clicks on the icon, instead of going to the page, a subfolder(s) appears below the folder icon and when the user clicks on one of these folders, then it directs the user to the page.
Below is what I did orignally:
<h4>
<a href="CalMediConnect_DMgmt.cfm">
<img src="images/folder-documents-icon32.jpg" alt="Custodial" class="float-left">
Disease Management
</a>
</h4>
<br /><br />
UPDATE
I have tried the following and it appears to not be working:
The following is the script:
var tree = document.querySelectorAll('ul.tree a:not(:last-child)');
for(var i = 0; i < tree.length; i++) {
tree[i].addEventListener('click', function(e) {
var parent = e.target.parentElement;
var classList = parent.classList;
if(classList.contains("open")) {
classList.remove('open');
var opensubs = parent.querySelectorAll(':scope .open');
for(var i = 0; i < opensubs.length; i++) {
opensubs[i].classList.remove('open');
}
} else {
classList.add('open');
}
});
}
The following is the CSS:
ul.tree li {
list-style-type: none;
position: relative;
}
ul.tree li ul {
display: none;
}
ul.tree li.open > ul {
display: block;
}
ul.tree li a {
color: #4284B0;
text-decoration: none;
}
ul.tree li a:before {
height: 1em;
padding: .1em;
font-size: .8em;
display: block;
position: absolute;
left: -1.3em;
top: .2em;
}
.margin-left {
margin-left: -15px;
}
And the HTML:
<ul class="tree margin-left">
<li>
<h4>
<a href="#">
<img src="images/folder-documents-icon32.jpg" alt="Custodial" class="float-left">
Disease Management
</a>
</h4>
<ul>
<li>
<h5>
<a href="CalMediConnect_DMgmt.cfm">
<img src="images/folder-documents-icon32.jpg" alt="Custodial" class="float-left">
Disease Management
</a>
</h5>
</li>
</ul>
</li>
</ul>
Any help would be appreciated.
Here is a very basic example built with jQuery...
https://jsfiddle.net/kennethcss/8b4e6o42/
$('.folder').on('click', function(e) {
var folder = $(this).find('.sub-folder');
if (e.target !== this) return;
if(folder.hasClass('hidden')) {
folder.removeClass('hidden');
} else {
folder.addClass('hidden');
}
});
.folder {
cursor: pointer;
}
.hidden {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
<ul class="container">
<li class="folder">Primary
<ul class="sub-folder hidden">
<li>Secondary</li>
<li>Secondary</li>
<li>Secondary</li>
</ul>
</li>
<li class="folder">Primary
<ul class="sub-folder hidden">
<li>Secondary</li>
<li>Secondary</li>
<li>Secondary</li>
</ul>
</li>
<li class="folder">Primary
<ul class="sub-folder hidden">
<li>Secondary</li>
<li>Secondary</li>
<li>Secondary</li>
</ul>
</li>
</ul>
Of course, you can style however you'd like; this example merely demonstrates how you might structure your HTML, CSS and JS to create a simple, folder like structure.
Gist
https://gist.github.com/kennethcss/8db1dc3326917c77846e84d263beb67d