toggle menu with JavaScript. Target multiple variables with 1 click function - javascript

I am trying to make a menu that collapses on click.
I also want to add some more changes on that same function.
For instance I want to change the background of another object.
In this snippet you can see it works on only the first link. The other toggleable link is not targeted.
var pill = document.querySelector(".navpill");
var sub = document.querySelector(".submenu");
pill.onclick = () => {
sub.classList.toggle("collapse");
pill.classList.toggle("active");
}
.mainmenu {
background-color: #1f1f1f;
}
.navpill {
padding: 15px;
}
.navpill.active {
background: red;
}
.navpill a {
text-decoration: none;
color: white;
}
.submenu {
display: none;
}
.submenu.collapse {
display: block;
}
<div>
<ul class="mainmenu">
<li class="navpill">Link collapse 1
<ul class="submenu">
<li class="navpill">sub Link 1</li>
<li class="navpill">sub Link 1</li>
<li class="navpill">sub Link 1</li>
<li class="navpill">sub Link 1</li>
</ul>
</li>
<li class="navpill">Link collapse 2
<ul class="submenu">
<li class="navpill">sub Link 1</li>
<li class="navpill">sub Link 1</li>
<li class="navpill">sub Link 1</li>
</ul>
</li>
<li class="navpill">no link</li>
<li class="navpill">no link</li>
</ul>
</div>
From a previous answer I got this piece of code which makes it work on all the links, but I have no idea how to add more var and toggles to the function.
var pills = document.querySelectorAll(".expand");
pills.forEach(function(pill) {
pill.onclick = () => {
var sub = pill.querySelector(".submenu");
sub.classList.toggle("collapse");
}
});
I tried adding this to the code but it does not work.
var navpill = pill.querySelector(".navpill");
navpill.classList.toggle("active");
If possible I would also like a way of clearing what has been done when clicked on the next submenu.
If I use the code above. It stays open when I click on the second link and then they are both open. I want the first one to close if the second is clicked.

I think this is probably closer to what you want.
(It's unclear if you wanted the submenu items to be highlighted when they're clicked - currently, clicking them just collapses the menu anyway so you wouldn't see. Also I removed the hrefs because they aren't adding anything useful.)
var pills = document.querySelectorAll(".expand");
var subs = document.querySelectorAll(".submenu");
pills.forEach(function(pill) {
pill.addEventListener("click", function() {
var sub = pill.querySelector(".submenu");
var alreadyOpen = false;
if (sub.classList.contains("collapse")) alreadyOpen = true;
pills.forEach(function(pill2) {
pill2.classList.remove("active");
});
subs.forEach(function(sub2) {
sub2.classList.remove("collapse");
});
if (!alreadyOpen) {
sub.classList.toggle("collapse");
this.classList.add("active");
}
});
});
.expand.active {
background-color: red;
}
.expand.active > .submenu
{
background-color: #1f1f1f;
}
.mainmenu {
background-color: #1f1f1f;
}
.navpill {
padding: 15px;
color: white;
}
.submenu {
display: none;
}
.submenu.collapse {
display: block;
}
<div>
<ul class="mainmenu">
<li class="navpill expand">Link collapse 1
<ul class="submenu">
<li class="navpill">sub Link 1</li>
<li class="navpill">sub Link 1</li>
<li class="navpill">sub Link 1</li>
<li class="navpill">sub Link 1</li>
</ul>
</li>
<li class="navpill expand">Link collapse 2
<ul class="submenu">
<li class="navpill">sub Link 1</li>
<li class="navpill">sub Link 1</li>
<li class="navpill">sub Link 1</li>
<li class="navpill">sub Link 1</li>
</ul>
</li>
<li class="navpill">no link</li>
<li class="navpill">no link</li>
</ul>
</div>

Related

Up and down slide dropdown in vue js

.main ul {
height: 100px;
width: 200px;
background: gray;
overflow:hidden
}
.main a {
margin: 10px 0;
}
<div class="main">
Up
<ul>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
</ul>
Down
</div>
Now i want if i click up button then list item should be slide up and if i click down button the list item should going down. How can i do this by vue js please anyone help me.
Is this the effect that you were hoping to achieve? Liberal use of querySelector, querySelectorAll and various sibling selectors make this a relatively straight forward task.
// assign event handler to each hyperlink which themselves have a dataset attribute to aid identification
document.querySelectorAll('a[data-dir]').forEach( a=>a.addEventListener('click',function(e){
// the css class that will be assigned to indicate selected item
const cn='active';
let ul=this.parentNode.querySelector('ul');
let col=Array.from( ul.querySelectorAll('li') );
// prevent highlist disappearing
if( ( ul.querySelector('li.active')==ul.lastElementChild && this.dataset.dir=='down' ) || ( ul.querySelector('li.active')==ul.firstElementChild && this.dataset.dir=='up' ) )return false;
// If no active element is found, make the first LI active.
if( !ul.querySelector('li.active') )ul.firstElementChild.classList.add( cn );
else{
// otherwise process all LI elements until we find the active element
col.some( li=>{
if( li.classList.contains(cn) ){
// clear an other instances of the active class
col.forEach(n=>n.classList.remove(cn));
// highlight appropriate previous/next LI element
switch( this.dataset.dir ){
case 'up':
if( li.previousElementSibling!=null && li.previousElementSibling.tagName=='LI' )li.previousElementSibling.classList.add(cn)
break;
case 'down':
if( li.nextElementSibling!=null && li.nextElementSibling.tagName=='LI' )li.nextElementSibling.classList.add(cn);
break;
}
return true;
}
});
}
}));
.main ul {
min-height: 100px;
width: 200px;
background: gray;
overflow:hidden;
list-style:none inside;
}
.main a {
margin: 10px 0;
}
li{
padding:0.25rem;background:whitesmoke;
}
.active{
background:pink;
}
<div class="main">
<a href="#" data-dir='up'>Up</a>
<ul>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
<li>dropdown 1</li>
</ul>
<a href="#" data-dir='down'>Down</a>
</div>
#Professor Abronsius has a good solution using pure Javascript but if you want to do this in Vue you'll need to have your times in your data object and a scoped variable to keep track of which item is selected.
To expand on this you'd probably want to dynamically update your times array as the user cycled through them.
new Vue({
el: "#app",
data: {
selected:2,
times: [
"11:00am",
"10:00am",
"9:00am",
"8:00am",
"7:00am",
"6:00am"
]
},
methods: {
up: function(){
this.selected--;
},
down: function(){
this.selected++;
}
}
})
ul {
width:100px;
}
li.active {
background-color:lightgrey;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>Times:</h2>
<span v-on:click="up">Up</span>
<ul>
<li v-for="(time, index) in times" :class="{ active : selected == index}">
{{time}}
</li>
</ul>
<span v-on:click="down">Down</span>
</div>

How to add event listener with same class?

I have two li with class="tree" in a ul, I want to set a height" to ul.sub-menu when someone clicks on its parent li. But It not working for the second li. Run snippet
document.querySelector(".tree").addEventListener("click", function () {
var height = document.querySelector(".sub-menu").scrollHeight;
document.querySelector(".sub-menu").style.height = height + "px";
});
.sub-menu {
position: relative;
background-color: aqua;
width: 100px;
height: 0;
overflow: hidden;
transition: height 0.3s;
}
<ul>
<li class="tree">
Item
<ul class="sub-menu">
<li>Sub Item1</li>
<li>Sub Item1</li>
<li>Sub Item1</li>
</ul>
</li>
<li class="tree">
Item
<ul class="sub-menu">
<li>Sub Item2</li>
<li>Sub Item2</li>
<li>Sub Item2</li>
</ul>
</li>
</ul>
querySelector will return only the first element.
However it is recommended to delegate from the closest common static container - this is also much more elegant than looping over all elements to add eventListeners to each
document.getElementById("topUl").addEventListener("click", function (e) {
const tgt = e.target.closest("li");
if (tgt.classList.contains("tree")) {
const sm = tgt.querySelector(".sub-menu");
const height = sm.scrollHeight;
sm.style.height = height + "px";
}
});
.sub-menu {
position: relative;
background-color: aqua;
width: 100px;
height: 0;
overflow: hidden;
transition: height 0.3s;
}
<ul id="topUl">
<li class="tree">
Item
<ul class="sub-menu">
<li>Sub Item1</li>
<li>Sub Item1</li>
<li>Sub Item1</li>
</ul>
</li>
<li class="tree">
Item
<ul class="sub-menu">
<li>Sub Item2</li>
<li>Sub Item2</li>
<li>Sub Item2</li>
</ul>
</li>
</ul>
All you need to do is use querySelectorAll to select all the classes, and add eventListener inside loop
document.querySelectorAll(".tree").forEach(i => i.addEventListener(
"click",/*your code here*/));
Solution using forEach() method.
let tree = document.querySelectorAll(".tree");
let height = document.querySelectorAll(".sub-menu");
tree.forEach(function(tree_current, index) {
tree_current.addEventListener("click", function () {
height[index].style.height = height[index].scrollHeight + "px";
});
})
.sub-menu {
position: relative;
background-color: aqua;
width: 100px;
height: 0;
overflow: hidden;
transition: height 0.3s;
}
<ul>
<li class="tree">
Item
<ul class="sub-menu">
<li>Sub Item1</li>
<li>Sub Item1</li>
<li>Sub Item1</li>
</ul>
</li>
<li class="tree">
Item
<ul class="sub-menu">
<li>Sub Item2</li>
<li>Sub Item2</li>
<li>Sub Item2</li>
</ul>
</li>
</ul>
document.querySelector() returns the first object matching the query, not all of them. You can check them by pasting console.log(document.querySelector('.tree')); at the top of your JS file, and you'll see that it only logs your first element. If you want to add the listener to all of your elements matching the query, you need to use document.querySelectorAll() and iterate over that array using forEach:
document.querySelectorAll('.tree').forEach(el => el.addEventListener('click', () => {
/* Code Here */
}));
Another issue you'll face with your code is that your other query selector also returns the first occurrence of .sub-menu. To fix it, you can change
document.querySelectorAll('.tree').forEach(el => el.addEventListener('click', () => {
/* Code Here */
}));
to
document.querySelectorAll('.tree').forEach(el => {
const submenu = el.querySelector('.sub-menu');
el.addEventListener('click', () => submenu.style.height = submenu.scrollHeight + "px");
});
Since .sub-menu is a child of .tree in your hierarchy, this will only search your .tree for instances of .sub-menu and hence return the correct element you need. Your final code would then look like this:
document.querySelectorAll('.tree').forEach(el => {
const submenu = el.querySelector('.sub-menu');
el.addEventListener('click', () => submenu.style.height = submenu.scrollHeight + "px");
})
.sub-menu {
position: relative;
background-color: aqua;
width: 100px;
height: 0;
overflow: hidden;
transition: height 0.3s;
}
<ul>
<li class="tree">
Item
<ul class="sub-menu">
<li>Sub Item1</li>
<li>Sub Item1</li>
<li>Sub Item1</li>
</ul>
</li>
<li class="tree">
Item
<ul class="sub-menu">
<li>Sub Item2</li>
<li>Sub Item2</li>
<li>Sub Item2</li>
</ul>
</li>
</ul>
EDIT: Don't repeat yourself. To avoid having to lookup the element every time the button is clicked we cache it as soon as we initialise the listener and the refer to that stored variable in the click handler.
Just use querySelectorAll it is gonna select all classes, and add click event inside it.
document.querySelectorAll(".tree").forEach(
index => index.addEventListener("click",
/*your code here*/
));

jquery slideToggle() not working with toggleClass

I wanted to slideToggle menu items with toggleclass, .opened class should be added and removed for menu items. This is working for me when I toggle different menu item but for same menu item when I click this, .opened class won't get removed here is my code
Html menu tag
<ul id="menu-main-menu">
<li class="menu-item"><a href="link_url">text<a>
<ul class="sub-menu">
<li class="menu-item">
<ul class="sub-menu">
<li class="menu-item"><a href="link_url">second sub item<a></li>
</ul>
</li>
<li class="menu-item"><a href="link_url">first sub item<a></li>
<li class="menu-item"><a href="link_url">first sub item<a></li>
</ul>
</li>
<li class="menu-item"><a href="link_url">text<a></li>
</ul>
jquery code
$('.menu-item').on('click', function(e) {
$('.menu-item').removeClass('opened')
$(this).toggleClass('opened');
if ($('.sub-menu', this).length >=1) {
e.preventDefault();
}
$(this).children('ul').slideToggle('fast');
$(this).siblings('li').find('ul').hide('slow')
e.stopPropagation();
});
I am not sure what I am doing wrong. Can you please help me for this?
Thanks
There is a basic mistake in your code.
Close Anchor tags, you have an opening anchor tag on both the ends.
then use the logic to get your result, see the example, If need anything else, please let me know
Add sub items Achor or li text, that depends on your requirement, but for UX you should add some text so users can get that there is still some more content to see.
$('.menu-item').click(function(e){
$(this).siblings().find('> .sub-menu').slideUp();
$(this).find('> .sub-menu').slideToggle();
$(this).siblings().removeClass('opened');
$(this).toggleClass('opened');
e.stopPropagation();
});
.sub-menu {
display: none;
}
.menu-item a{
display: inline-block;
margin-bottom: 10px;
}
.menu-item {
margin-bottom: 10px;
}
.menu-item.hasSubmenu {
border-bottom: 1px solid;
}
.menu-item a {
background-color: red;
color: white;
}
.hasSubmenu {
position: relative;
}
.hasSubmenu:after {
position: absolute;
right: 10px;
top: 0px;
content: "+";
display: block;
font-size: 20px;
font-weight: bold;
}
.hasSubmenu.opened:after {
content: "-";
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul id="menu-main-menu">
<li class="menu-item hasSubmenu">
text
<ul class="sub-menu">
<li class="menu-item hasSubmenu">
First level
<ul class="sub-menu">
<li class="menu-item">second sub item</li>
<li class="menu-item">second sub item</li>
</ul>
</li>
<li class="menu-item">first sub item</li>
<li class="menu-item">first sub item</li>
</ul>
</li>
<li class="menu-item hasSubmenu">
text
<ul class="sub-menu">
<li class="menu-item hasSubmenu">
First level
<ul class="sub-menu">
<li class="menu-item">second sub item</li>
</ul>
</li>
<li class="menu-item">first sub item</li>
<li class="menu-item">first sub item</li>
</ul>
</li>
</ul>
$('.menu-item').on('click', function(e) {
$(this).toggleClass('opened');
$('.menu-item').not($(this)).removeClass('opened');
if ($('.sub-menu', this).length >= 1) {
e.preventDefault();
}
$(this).children('ul').slideToggle('fast');
$(this).siblings('li').find('ul').hide('slow')
e.stopPropagation();
});
Change the order of removing classes, then skip the current element.

How to show each nested list in a seperate column, preferrably while using CSS Grid?

I have some nested lists. Some go as deep as 4-5 level deep. For example:
<ul>
<li>
<span class="is-parent">parent</span>
<ul class="children">
<li class="no-child">child</li>
<li class="no-child">child</li>
<li class="no-child">child</li>
</ul>
</li>
<li>some child</li>
<li>another child</li>
<ul>
This list is displayed in a css grid column, the first one to be specific. I have set the columns using
grid-template-columns: repeat(5, 1fr);
When I click on parent, I want the nested <ul> to display on the next column.
What is the best way to do that using vanila JavaScript?
Something like the example here: https://mynameistechno.github.io/finderjs/#examples
But without the use of a library or plugin.
HTML
<ul>
<li>
<span class="is-parent">parent</span>
<ul class="children hide">
<li class="no-child">child</li>
<li class="no-child">child</li>
<li class="no-child">child</li>
</ul>
</li>
<li>some child</li>
<li>another child</li>
<ul>
CSS
ul {
grid-template-columns: repeat(auto-fill, 1fr);
}
.hide {
display: none;
}
JS
document.querySelectorAll('is-parent').forEach(node =>
node.parentNode.addEventListener('click', (ev) => {
let cl = ev.currentTarget.querySelector('ul').classList
cl.contains('hide') ? cl.remove('hide') : cl.add('hide')
}
}
It's really hard to do this without the jQuery library.
But here I wrote an example using only javascript.
The idea is when you click on is-parent you clone the Children and append to container.
You could build on this example.
I hop this help you somehow
var x = document.getElementsByClassName("is-parent");
var container = document.getElementsByClassName("container")[0];
function bind(items){
new Array(items).forEach(function(item, index){
item = item[0]
item.addEventListener("click", function(){
var children= [].filter.call(item.parentElement.childNodes, function(node) {
return node.className == "children"
})[0];
var i= children.cloneNode(true);
container.appendChild(i);
i.style.display = "block";
i.className = index;
});
});
}
bind(x);
.children{
display:none;
}
ul{
pointer:default;
}
div{
display: grid;
grid-template-columns: auto auto auto;
background-color: #2196F3;
padding: 10px;
}
<div class="container">
<ul>
<li>
<span class="is-parent">parent</span>
<ul class="children">
<li class="no-child">child</li>
<li class="no-child">child</li>
<li class="no-child">child</li>
</ul>
</li>
<li>some child</li>
<li>another child</li>
</ul>
</div>

Disable mouseenter when over element on page load

I'm trying to find a way to disable mouseenter when the top-level navigation item is clicked & on pageload and re-enable again when the mouse leaves and enters the element again.
User hovers over element = show submenu
User clicks menu = hide submenu and only show submenu when user leaves menu elements and enters again.
If user is over the element onLoad then only show submenu when user leaves element and enters again.
$('.navmenu li').on('mouseenter', function(e) {
$(e.target).next().addClass('js-hover')
}).on('mouseleave', function(e) {
$(e.target).next().removeClass('js-hover')
});
$('.navmenu').on('click', function(e) {
$(e.target).next().removeClass('js-hover')
location.reload(true);
})
.navmenu .submenu {
display:none;
}
.navmenu li {
display: inline;
}
.navmenu .submenu {
position:absolute;
top:40px;
left:0
}
.navmenu li:hover .js-hover {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<nav class="navmenu">
<ul>
<li>
Menu
<nav class="submenu">
<ul>
<li>Submenu 1</li>
<li>Submenu 2</li>
<li>Submenu 3</li>
</ul>
</nav>
</li>
<li>
Menu 2
<nav class="submenu">
<ul>
<li>Submenu 4</li>
<li>Submenu 5</li>
<li>Submenu 6</li>
</ul>
</nav>
</li>
</ul>
</nav>
this could be done using variables and storing a state of element (if it should be hidden or not). But since you tried to do this through class attributes, I did the same. Here is simle example of one menu item, everything should be clear.
<nav class="navmenu">
<ul>
<li>
Menu
<nav class="submenu" hidden>
<ul>
<li>Submenu 1</li>
<li>Submenu 2</li>
<li>Submenu 3</li>
</ul>
</nav>
</li>
</ul>
</nav>
and javascript:
$menuLink = $("nav.navmenu li > a");
$menuLink.click(function () {
$(this).addClass("dontHide");
});
$menuLink.mouseenter(function () {
$(this).next("nav.submenu").removeAttr("hidden");
$(this).removeClass("dontHide");
});
$menuLink.mouseleave(function () {
if(!$(this).hasClass("dontHide")) {
$(this).next("nav.submenu").attr("hidden", true);
}
});
Live demo: https://jsfiddle.net/g3fua461/24/

Categories