Javascript targeting the first element only with a double click issue - javascript

I'm learning JavaScript at the moment (started 3 weeks ago at college) and i'm struggling to create a mobile menu whereby you click and that reveals or hides a hidden menu.
The issue here is that JS only targets the first class and not all of them. And you need to click twice to reveal the element that does work.
I would be very grateful for some guidance, and hopefully an expiation to help me understand the problem.
Thank you
<script>
const btn = document.querySelector('.navigation-main-mobile .menu-item-has-children');
const box = document.querySelector('.navigation-main-mobile .menu-item-has-children .sub-menu');
btn.addEventListener('click', function handleClick() {
if (box.style.display === 'none') {
box.style.display = 'block';
} else {
box.style.display = 'none';
}
});
</script>
<ul id="menu-primary-menu" class="navigation-main-mobile"><li>Text</li>
<li class="menu-item-has-children">Text
<ul class="sub-menu">
<li>Text</li>
<li>Text</li>
<li>Text</li>
</ul>
</li>
<li class="current-menu-item">Text</a></li>
<li class="menu-item-has-children">Text</a></li>
<ul class="sub-menu">
<li>Text</li>
<li>Text</li>
<li>Text</li>
<li>Text</li>
</ul>
</li>
</ul>

You have to find all nodes which match the selector. querySelectorAll is used for that. Then you must loop through all the nodes and set the event.
Also, you have to remove the wrong closing <li> tag (which messes up the selector) after </a>Text</a> in the second menu-item-has-children
const btns = document.querySelectorAll('.navigation-main-mobile .menu-item-has-children');
const boxs = document.querySelectorAll('.navigation-main-mobile .menu-item-has-children .sub-menu');
btns.forEach((btn, index) => {
btn.addEventListener('click', function handleClick() {
if (boxs[index].style.display === 'none') {
boxs[index].style.display = 'block';
} else {
boxs[index].style.display = 'none';
}
})});

.querySelector() returns the first element that matches the values passed.
You are getting the double click issue because you have not set a value for the style.display attribute. When you click it for the first time the browser realized that and set the attribute to none and then the second click performs the changes that you coded.
Remember to be somewhat explicit in what you want to code and do not assume that the computer is going to complete anything for you. Good Luck!

Related

Open/Close submenu when clicking the item name

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.)

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.

Sometimes on click executed twice

I'm having a very strange problem. Basically I have created a menu in wordpress that has parent, childs, subchilds and inside some posts.
I wrote a jquery script that adds a highlight class on the <li> in order to expand the <ul> which is inside the <li>. the format of the list is the following
<li class"menu-item-has-children"><a>Parent</a>
<ul>
<li class="menu-item-has-children"><a>Child</a>
<ul>
<li class="menu-item-has-children"><a>Sub Child</a>
<ul>
<li><a> post</a></li>
<li><a> post 2 </a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
So I wrote the following script to add highlight class to expand the sub child of the clicked <li>. When I'm clicking on any <li> of parent,child, sub child is working fine but when I click on the posts it removes the highlight class from the parent <ul> instead of taking me to the page of the clicked post.
I did a console log of $(this) and when click on any parent, child,sub child it shows me <ul> that has, but when I click on any posts it shows me first the <a> tag that it has followed by the parent <ul>. Does anyone know why?
Here is my jquery code
$(document).unbind("click").on('click',".sidebar-right li",function(e){
var found = false;
var tmp = String($(this).val());
if (tmp.indexOf("ul") != -1){
found = true;
}
if (($(this).hasClass("menu-item-has-children")) && (!$(this).children('ul').hasClass("highlight")))
{
$(this).children("a").addClass("highlight_icon");
$(this).children('ul').addClass("highlight");
return false;
}
else if (($(this).has("a") && ($(this).has("ul"))) && (!$(this).children('ul').hasClass("highlight")))
{
return true;
}
else if ($(this).children('ul').hasClass("highlight"))
{
$(this).children('a').removeClass("highlight_icon");
$(this).children('ul').removeClass("highlight");
return false;
}
else
{
return true;
}
});
Here is the console log data
Child Parent etc
Post click
SOLUTION: event.stopPropagation() as #mohamed-yousef said
try
$(".sidebar-right").on('click',".sidebar-right li", function(){
....});

I need jQuery to perform a task if two conditions are true

I need some help to put an if statement together using jQuery. I want to change the logo on my site, if two conditions are true.
Here is some pseudo code, hopefully explaining what i want to archive:
if(li hasClass active and data-menuid is equal to 0033){
change logo...
}
Here is a simple example of the menu structure:
<div id="menu">
<ul id="menu-primary">
<li class="menuactive" data-menuid="0011">
Test1
<ul class="menu-dropdown">
<li data-menuid="0022">Test2</li>
<li class="active" data-menuid="0033">Test3</li>
</ul>
</li>
<li data-menuid="0044">Test4</li>
<li data-menuid="0055">Test5</li>
</ul>
</div>
You can check the combination of class and Attribute Equals Selector [name="value"]
if($('li.menuactive[data-menuid="0033"]').length){
//Your code to change the logo
}
You can use $.fn.filter()
Reduce the set of matched elements to those that match the selector or pass the function's test.
var listMeetingCondition = $('li').filter(function(){
return $(this).hasClass('menuactive') && $(this).attr('data-menuid') == "0033"
});
if(listMeetingCondition.length){
//Your code to change the logo
}
if($('li:has(.menuactive[data-menuid="0033"])').length){
change logo...
}
Another workaround:
var $target = $('li', '#menu-primary');
if( $target.hasclass('active') && $target.data('menuid') == '0033' ){
// change logo
}

Pass this value from a list with javascript

I have the following list:
<ul>
<li onclick="populateComment(..this.)">very good </li>
<li onclick="populateComment(..this.)">not so good</li>
<li onclick="populateComment(..this.)">no t.....</li>
</ul>
and javascript:
function populateComment() {
document.getElementById("COMMENT").value = document.getElementById("COMMENT").value + 'THE_STRING_VALUE_FROM_THE_LIST';
}
So the idea is that I have a textarea, which I fill by clicking on the list.
So if I click on the first list, the text "very good" should be appended to the textarea. But I dont want to retype the text "very good" in my function, can I take it directly with something as an argument like this.value.. so it automatically takes the string between the <li> ...</li>
Thank you for your time.
You're looking for:
this.textContent || this.innerText
this.textContent is supported in all decent browsers, this.innerText is needed for older versions of IE.
Instead of duplicating the onclick handler multiple times, you can also bind a listener to the <ul> element:
// Replace <ul> with <ul id="populatefrom">
document.getElementById("populatefrom").onclick = function(ev) {
ev = ev || window.event; //Backwards compatible with IE
var target = ev.target;
if (target.nodeName.toLowerCase() == "li") {
document.getElementById("COMMENT").value += target.textContent || target.innerText;
}
};
If you don't have anything other than a simple bit of text, you could also use:
this.firstChild.nodeValue;
However if there are any nodes at all (bold, span, or anything else other than text... even an HTML comment) then this won't work and you need the textContent/innerText combo Rob W gave.
Sure you could do this with jQuery like so (didn't test this yet):
<ul>
<li class="populateComment">very good </li>
<li class="populateComment">not so good</li>
<li class="populateComment">no t.....</li>
</ul>
and your jQuery:
$('.populateComment').click(function(){
$('#textboxID').val($(this).html());
});
Working jsfiddle: http://jsfiddle.net/zEaam/

Categories

Resources