Open/Close submenu when clicking the item name - javascript

I am having problems with the menu part of a wordpress site (salient theme), when i am on mobile, i open the menu with the hamburger button and have several options, some with sub menus, so the items with sub menus only open when clicking the little arrow icon to the right of the item, i am trying to get it to open also when you click on the item itself by making it so when you click the item it triggers a click on the arrow
here is the html of the menu
and here is the javascript i am doing to get it to work(only doing it for the first item with submenu here), i am new to javascript but for what i've seen i think this should work (i am using the Code Snippets
plugin for wordpress)
<?php
add_action( 'wp_footer', function () { ?>
<script>
var el = (document.querySelector('.menu-item.menu-item-type-custom.menu-
item-object-custom.menu-item-has-children.menu-item-5812 a'));
console.log(el);
var el2 =(document.querySelector('.menu-item.menu-item-type-custom.menu-
item-object-custom.menu-item-has-children.menu-item-5812 span'));
console.log(el2);
el.onclick = function()
{
$el2.click();
};
</script>
<?php } );
?>
SOLUTION:
aside from the answear by Alvaro Montoro i needed to encapsulate everything inside an eventListener with DomLoaded, here is the final code
add_action( 'wp_footer', function () { ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
var elSupervivencia = document.querySelector('#slide-out-widget-area > div >
div.inner > div > ul:nth-child(1) > li.menu-item.menu-item-type-custom.menu-
item-object-custom.menu-item-has-children.menu-item-5812 > a');
console.log(elSupervivencia);
var elSupervivenciaFlecha = document.querySelector('#slide-out-widget-area >
div > div.inner > div > ul:nth-child(1) > li.menu-item.menu-item-type-
custom.menu-item-object-custom.menu-item-has-children.menu-item-5812 .ocm-
dropdown-arrow i');
console.log(elSupervivenciaFlecha);
elSupervivenciaFlecha.onclick = function() {
console.log("Clicked on the span");
}
elSupervivencia.onclick = function(e)
{
e.preventDefault();
elSupervivenciaFlecha.click();
};
});
</script>
<?php } );

For what you seem to want, you almost have it. The only thing that seems to be missing is to prevent the default behavior when you click on the link (which could be a potential problem as pointed on the comments above, because the linked page may be innaccessible through the menu now).
With .preventDefault() you will prevent the default action for that element for that event, so you would just need to add that:
var el = (document.querySelector('.menu-item.menu-item-type-custom.menu-item-object-custom.menu-item-has-children.menu-item-5812 a'));
console.log(el);
var el2 = (document.querySelector('.menu-item.menu-item-type-custom.menu-item-object-custom.menu-item-has-children.menu-item-5812 span'));
console.log(el2);
el2.onclick = function() {
console.log("Clicked on the span");
}
el.onclick = function(e) {
e.preventDefault();
el2.click(); // removed the $
};
<ul class="menu">
<li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children menu-item-5812">
Supervivencia
<ul class="sub-menu">
<li>Supervivencia 1</li>
<li>Supervivencia 2</li>
<li>Supervivencia 3</li>
</ul>
<span class="ocm-drowndown-arrow" style="top: 17.5px">
<i class="fa-angle-down"></i>
</span>
</li>
</ul>
As they pointed in the comments, that may not be too usable, as the linked page is no longer accessible on the menu, you may want to add some conditions (checking for window size or a variable/class that indicates that the mobile menu is active) to perform that preventDefault().
Apart from that, you may want to consider changing the selector for el and el2, as they are not specific and could match more than one element. I know you are using querySelector so only the first element that matches the selector will be returned, which should not be a problem for el but could be problematic with el2 (because the a could be a child span that would be selected over the sibling one that is the one you want.)

Related

document.querySelector() always selects the element with the class "Open" even after it has been removed and replaced with the class "Close" | Vue3 js

So I was trying to find a Vue Solution of the jQuery SlideUp() and slideDown() functionality. And I have run into a problem while creating the same for my Side Bar Menu Open and Close funtion. See my code below:
UPDATE: Here's the stackblitz link to the problem: https://stackblitz.com/edit/vue-lvjxmz
I'd really appreciate if someone helps me to solve this problem. Thanks.
export default {
mounted() {
let hasSubmenu = Array.from(document.querySelectorAll(".hasSubmenu.close"));//Select all hasSubmenu
let self = this;
hasSubmenu.forEach(function (el) {//Show Hide Submenu Based On Clicks
el.addEventListener("click", self.menuOpen.bind(this, el));
});
},
methods:{
//Menu Opener Function
menuOpen(el){
let ul = el.querySelector("ul");
ul.style.height = `${ul.scrollHeight}px`;
el.classList.remove("close");//Remove The Class "close"
el.classList.add("open");// add the class "open"
this.menuClose();//Call this function to close the menu
},
//Menu Closer Function
menuClose(){
console.log(document.querySelector(".hasSubmenu.open"));//Log the Selected Element
document.querySelector(".hasSubmenu.open").addEventListener("click", function (e) {
e.currentTarget.querySelector("ul").style.height = "0px";
e.currentTarget.classList.remove("open");
e.currentTarget.classList.add("close");
});
}
},
}
<ul id="main-nav">
<li class="hasSubmenu close">
<div> Main Menu One</div>
<ul style="height: 0px">
<li>
Sub Menu One
</li>
<li>
Sub Menu Three
</li>
</ul>
</li>
</ul>
So what we are doing here is: we are first selecting all the hasSubmenu items with the class close has in it. Then firing the click event where the menu opens and replace the class close with "open". Then we are calling the menuClose function where we are selecting the element which has the .open class. then firing another click event when the menu closes and removes the class .open and adds .close again.
The problem is somehow the menuClose function keeps selecting the element as if it was pointing to the hasSubmenu element and caching it in, it doesn't matter whether the class has been changed or not. I mean, even after the .open class has been removed on the click event it stills selecting it and thus clicking on the menuItem after the first 2 clicks the menuOpen function first opens the menu then calls the menuClose function and immediately closes the menu again although menuClose functionality only added to document.querySelector(".hasSubmenu.open"). As if, it first selected the element itself not the class and cached it in.
Okay, as per James Reply I have managed to solve the problem in another way. So the solution goes as below:
// Simply write one function and check if the submenu's
// height is 0px or not. Based on that write your
// open and close logics
menuOpenClose(el){
let ul = el.querySelector("ul");
let getHeight = ul.style.height;
console.dir(getHeight);
if (getHeight == '0px' || !getHeight.length) {
ul.style.height = `${ul.scrollHeight}px`;
el.classList.remove("close");
el.classList.add("open");
} else{
ul.style.height = `0px`;
el.classList.remove("open");
el.classList.add("close");
}
}

Hide siblings when another dropdown is opened

I'm fairly new to .js and have been working on a dropdown nav menu. I've got most of it functioning, but I was asked to include a specific snippet for the menu activation.
I'd like to figure out how to make the other subnav items hide or scroll up when a different subnav is opened.
What am I doing wrong here?
<div id="nav_mob">
<div id="nav-toggle"><span></span></div>
<div class="dropdown_mob">
<ul>
<a class="dropdown_btn">
<li>Overview</li>
</a>
<div class="subnav_mob">
<ul>
<li>Introduction</li>
<li>Research</li>
<li class="padded">Planning & Preparation</li>
<li>International</li>
</ul>
</div>
<a class="dropdown_btn"><li>Profile</li></a>
<div class="subnav_mob">
<ul>
<li>My Account</li>
<li>My Cart</li>
<li>Check Out</li>
<li>Log Out</li>
</ul>
</div>
<a class="dropdown_btn"><li>Search</li></a>
<div class="subnav_mob">
<ul>
<li><div id="smallsearch"><input type="text"></div></li>
</ul>
</div>
</div>
</ul>
</div>
</div>
the snippet I was given:
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';
}
});
}
and the fix I tried to implement:
$(document).ready(function() {
$('.dropdown_btn').on('click', function() {
var state = $('.dropdown_btn').is('.active');
if(state) {
$('.dropdown_btn').removeClass('active').next('.subnav_mob')
.slideUp();
} else {
$('.dropdown_btn').addClass('active').next('.subnav_mob').slideDown();
$.closest('.dropdown_btn').siblings('.dropdown_btn')
.find('.dropdown_mob').slideUp().end();
$.find('.dropdown_btn').not(this).removeClass('active');
}
})
})
Your first problem is that $('dropdown_button') selects every element with that same class, not just the one you clicked on. Operating on it will thus operate on every dropdown at once. You may have noticed that clicking one button causes every dropdown to open, and clicking another button causes them all to close again. This is why.
Your second problem is that $.closest is not a thing. If you press F12 and check out the console, you'll notice an error being thrown from that line, saying that '$.closest' is not a function. It's actually 'undefined', and attempting to invoke it as a function with () causes this error. This prevents any code after this point from being run, though even if you fix this that code still won't work for similar reasons. $.find is not a function, either, for example. closest and find, like next and slideup, are methods on jQuery instances, not on the global jQuery object itself.
This should work. Note that $(this) refers to the clicked element wrapped in a JQuery instance:
$(document).ready(function() {
$('.dropdown_btn').on('click', function() {
var state = $(this).is('active');
if(state) {
$(this).removeClass('active')
.next('.subnav_mob').slideUp();
} else {
$(this).addClass('active')
.next('.subnav_mob').slideDown();
$(this).siblings('.dropdown_btn').removeClass('active')
.next('.subnav_mob').slideUp();
}
})
})
I would recommend stepping through each call in this, compare it with the jQuery documentation, to really make sure you understand it. I'd also might recommend trying to do it without jQuery-- using the native DOM API like the original snippet was doing. Such an exercise might be frustrating, but valuable.

Final adjustment needed for collapsible menu (jquery)

function collapseAll() {
$(".cmenu > li > a ").each(function() {
$(this).addClass("collapsed");
$(this).find("+ ul").each(function() {
$(this).css("display","none");
})
})
}
$(document).ready(function () {
var handleCollapsibleMenu = function (e, menu) {
var expanded = e.parent().parent().find('> li > a.expanded').not(e);
expanded.removeClass('expanded').addClass('collapsed').find('+ ul').slideUp('medium');
e.find('+ ul').slideToggle('medium');
};
collapseAll();
$('.cmenu').on('click', '> li > a', function () {
var self = $(this);
var menu = self.parent().parent();
var el = self.find('+ ul');
self.toggleClass('expanded').toggleClass('collapsed');
handleCollapsibleMenu(self, el);
});
})
The collapsible menu shown here (jsfiddle) is far from perfect but it does what I need it to do ... except that I'd like to be able to collapse any previous levels when I expand a new level.
To illustrate (at the fiddle):
Click Level 1[1] to expand it.
Click Level 2[2] to expand it.
Click Level 2[3] to expand it. Notice that Level 2[2] now collapses. That is the behaviour that I want when....
Click Level 1[2]. You see that Level 1[1] remains expanded. What I'd like is for Level 1[1] to collapse when that happens (but obviously Level 1[2] remains expanded).
Note: I borrowed this js and css from a source and modified it to allow level 3. But then couldn't find the original source. If anyone recognizes this code please let me know the source. Thanks for any help.
The problem is in your html: you have two <ul>s at the root level, when you only want one of these.
So remove the </ul><ul class='cmenu'> section in the middle. ie.
...
</li>
</ul>
<ul class='cmenu'>
<li><a>Level 1[2]</a>
...
is now just
...
</li>
<li><a>Level 1[2]</a>
...
This gives the structure at the root level of
<ul>
<li>...</li>
<li>...</li>
</ul>
rather than
<ul>
<li>...</li>
</ul>
<ul>
<li>...</li>
</ul>
which causes the problem.

Sort <li> with jQuery

Currently I have a language select which is a simple <ul> with <li>s and <a>s inside.
Every <li> has a class - lang-inactive or lang-active. It depends on what language the user is using right now.
The <li>s with .lang-inactive are hidden by default. When you .hover() the ul the other options are showed.
Here is a simple example.
But as you can see the first <li> is French, and when I'm using English and I hover the language bar the French appears over the english.
Is there a way I can sort the <li> depending on whether they are lang-active or lang-inactive. The inactive ones should appear below the active one.
My current code is:
var ul = $('#languages-iv');
ul.css('position', 'absolute');
ul.css('top', 5);
li = ul.children('li');
li.detach().sort(function(a,b) {
//how do I sort
});
ul.append(li);
$("#languages-iv").hover(function(){
$('.lang-inactive').slideToggle();
}, function() {
$('.lang-inactive').stop().slideToggle();
});
This executed on page-load (or whenever your language selector gets created) should push the active language up to first child.
$('.lang-active').prependTo('#languages-iv');
Demo:
http://jsfiddle.net/gqfPV/
<ul>
<li>English</li>
<li>French</li>
<li>German</li>
</ul>
<script>
$(document).ready(function(){
$('ul li').live('click',function(){
$('li a').removeClass('lang-active');
var elem = $(this);
$(this).remove();
$('ul').prepend(elem);
$(this).children('a').addClass('lang-active');
});
});
<script>
Here's your sort:
var listItems = myList.children('li').get();
listItems.sort(function(a,b){
return $(a).hasClass('lang-inactive') ? -1 : $(b).hasClass('lang-inactive') ? 1 : 0;
});

JQuery: combine click / trigger elements for more efficiently written code?

I am trying to figure out a more efficient way to write my code which works but it seems inefficient. Essentially I have a series of Id elements in a nav that trigger a click function on various ids elsewhere on the page. I tried combining my elements but that does not seem to work:
$("#red, #green, #blue").bind("click", (function () {
$("#section-red, #section-green, #section-blue").trigger("click");
alert("#section-red (Red heading) has been triggered!");
alert("#section-green (Green heading) has been triggered!");
alert("#section-blue (Blue heading) has been triggered!");
}));
... but this just seems to trigger everything.
I can do this below but for lots of ids, it will be a monster to maintain and update. Not sure if there is a better way.
$("#red").bind("click", (function () {
$("#section-red").trigger("click");
alert("#section-red (Red heading) has been triggered!");
}));
$("#green").bind("click", (function () {
$("#section-green").trigger("click");
alert("#section-green (Green heading) has been triggered!");
}));
// etc...
I have a fiddle here that I have been playing with but still no joy. Essentially a click on the top nav trigger a simulated click on an H2 heading which works but it's just the code efficiency at this point.
I would add data attributes to your nav elements like:
<ul>
<li id="red" data-trigger-id="#section-red">Section Red</li>
<li id="green" data-trigger-id="#section-green">Section Green</li>
<li id="blue" data-trigger-id="#section-blue">Section Blue</li>
</ul>
then in jQuery:
$("#red, #green, #blue").bind("click", (function () {
var triggerID = $(this).data("trigger-id");
$(triggerID).trigger("click");
}
Using event delegation you only need to register two event handlers.
$("ul").delegate("li", "click", function() {
var id = $(this).attr("id");
$("#section-"+id).trigger("click");
});
$(document).delegate("h2", "click", function() {
console.log($(this).attr("id"));
});
EDIT
You could make it more efficient by caching the element lookups
var h2 = [];
h2['red'] = $("#section-red");
h2['blue'] = $("#section-blue");
h2['green'] = $("#section-green");
Inside the ul delegate click handler
h2[id].trigger('click');
Fiddle
First, I would create a class for the ul - in this example, I called it "sections":
<ul class="sections">
<li id="red">Section Red</li>
<li id="green">Section Green</li>
<li id="blue">Section Blue</li>
</ul>
Next, bind a click even to $('.sections>li'), get the index, and apply it to the relative div.
$(".sections>li").click(function () {
var index=$(this).index();
$('.faqfield-question.accordion').eq(index).click();
});
That's all there is to it!
DEMO:
http://jsfiddle.net/6aT64/43/
Hope this helps and let me know if you have any questions!
How about doing something like this. This works for all browsers(including IE ;-) )
document.onclick = function(event){
event = event || window.event; //IE does not pass Object of event.
var target = event.target || event.srcElement;
switch(target.id){
case "header-red":
case "red-section":
redClicked();
break;
case "header-green":
case "green-section":
greenClicked();
break;
}
};

Categories

Resources