I'm trying to build a menu with a submenu. I used forEach to loop over the menu items and inside of it I used a for loop to show the submenu for that specific menu. However when I click on the parent menu all the submenus appear (Example 1 below)
I'm fairly new to Javascript. Is there a way to fix this? or maybe a better way to do it?.
Thanks!
Example 1
Here is the JS code:
const menuLink = document.querySelectorAll(".nav-link-mobile");
const subMenu = document.querySelectorAll(".mobile-submenu");
menuLink.forEach(function (element) {
element.addEventListener("click", () => {
for (let i = 0; i < subMenu.length; i++) {
subMenu[i].classList.toggle("mobile-submenu-visible");
console.log(subMenu.length);
}
});
});
Without your HTML, or an reproductible exemple it is difficult to says.
But try adding the index.
menuLink.forEach(function (element, index) {
element[index].addEventListener("click", () => {
for (let i = 0; i < subMenu.length; i++) {
subMenu[i].classList.toggle("mobile-submenu-visible");
console.log(subMenu.length);
}
});
If it is not that I will need more from your code.
You have a click Event on the menuLink, that when clicked (called) loops over every subMenu item. You want to put the click Event directly on the subMenu. I would do something like:
let sub;
for(let m of menuLink){
sub = m.querySelectorAll('.mobile-submenu');
for(let s of sub){
s.onclick = function(){
this.classList.toggle('mobile-submenu-visible');
}
}
}
Related
I'm trying to toggle the class of a parent li element when the sub ul element is active. I know how to do it in jQuery, but the goal with this project is to not rely on libraries like Bootstrap or jQuery.
I have a demo on CodePen: https://codepen.io/mikejandreau/pen/eRvOBQ
There's also a dev site using the same menu here: http://losaidos.com/dev/baseinstall/.
This is the JavaScript currently controlling the sub-menu toggles:
// Add toggles to menu items that have submenus and bind to click event
var subMenuItems = document.body.querySelectorAll('.page_item_has_children > a');
var index = 0;
for (index = 0; index < subMenuItems.length; index++) {
var dropdownArrow = document.createElement('span');
dropdownArrow.className = 'sub-nav-toggle';
dropdownArrow.innerHTML = 'More';
subMenuItems[index].parentNode.insertBefore(dropdownArrow, subMenuItems[index].nextSibling);
}
// Enables toggling all submenus individually
var subMenuToggle = document.querySelectorAll('.sub-nav-toggle');
for(var i in subMenuToggle) {
if(subMenuToggle.hasOwnProperty(i)) {
subMenuToggle[i].onclick = function() {
this.parentElement.querySelector('.children').classList.toggle("active");
this.parentElement.querySelector('.sub-nav-toggle').classList.toggle("active");
};
}
}
I tried duplicating the logic by adding this to the subMenuToggle[i].onclick function:
this.parentElement.querySelector('.page_item_has_children a').classList.toggle("active");
But no luck so far in getting it working. I get the feeling I'm close but missing something obvious.
Any pointers would be greatly appreciated - thanks in advance!
The answer was staring me in the face.
There was no need to do use .querySelector('.class-of-parent) because the target elements are already within the parent. I added this.parentElement.classList.toggle("active"); and it worked like a charm.
// Enables toggling all submenus individually
var subMenuToggle = document.querySelectorAll('.sub-nav-toggle');
for(var i in subMenuToggle) {
if(subMenuToggle.hasOwnProperty(i)) {
subMenuToggle[i].onclick = function() {
this.parentElement.querySelector('.children').classList.toggle("active");
this.parentElement.querySelector('.sub-nav-toggle').classList.toggle("active");
this.parentElement.classList.toggle("active"); // facepalm of obviousness
};
}
}
I am trying to toggle multiple classes onclick using vanilla Javascript. What i am trying to do is when a btn is clicked two classes to toggle with another two classes. I have 5 classes in total which are: .menu_btn , .main_nav, .btn_active, .container, .container_active. When i press the .menu_btn i would like the classes .main_nav to toggle with .btn_active and at the same time i would like to have the .container to toggle with .container_active. The class .container is the only one that has 5 elements of that class, the others are single. I have done this using jQuery but i would like to know the way using vanilla Javascript. Hopefully someone can help.
One thing to point out is when i console.log the .btn_active and .container_active i get back [ ] an empty array. Those 2 css classes are not assigned to any element of my project. They are existing only in the css and their purpose is for toggle.
Thanks
jQuery Code:
$(function(){
$(".menu_btn").on("click", function(){
$(".main_nav").toggleClass("btn_active");
$(".container").toggleClass("container_active");
});
});
Vanilla Javascript Code:
var menuBtn = document.getElementsByClassName("menu_btn");
var mainNav = document.getElementsByClassName("main_nav");
var btnActive = document.getElementsByClassName("btn_active");
var container = document.getElementsByClassName("container");
var containerActive = document.getElementsByClassName("container_active");
menuBtn.onclick = function(){
mainNav.classList.toggle(btnActive);
for ( index = 0; index <= container.lenght -1; index++ ){
container[index].classList.toggle(containerActive);
}
};
I have modified your script and created a fiddle so you see how it works: https://jsfiddle.net/eyrpdsc2/
The toggle accepts a string as a parameter, not a Node. So you need to pass 'btn_active' instead of btnActive. Also keep in mind that querySelectorAll returns a NodeList (not an array) so you cannot use forEach.
var menuBtn = document.querySelectorAll(".menu_btn");
var mainNav = document.querySelectorAll(".main_nav");
var container = document.querySelectorAll(".container");
for (var i = 0; i < menuBtn.length; ++i) {
menuBtn[i].addEventListener('click', toggleClasses);
}
function toggleClasses() {
var i = 0;
for (i = 0; i < mainNav.length; ++i) {
mainNav[i].classList.toggle('btn_active');
}
for (i = 0; i < container.length; ++i) {
container[i].classList.toggle('container_active');
}
}
I have this JSFiddle where I am trying to make it so that the items in an unordered list are visible only if the option selected in a drop down matches their class. List items may have multiple classes, but so long as at least one class matches, the item should be made visible.
The Javascript looks like this:
function showListCategories() {
var selection = document.getElementById("listDisplayer").selectedIndex;
var unHidden = document.getElementsByClassName(selection);
for (var i = 0; i < unHidden.length; i++) {
unHidden[i].style.display = 'visible';
}
};
The idea is that it gets the current selection from the drop down, creates an array based on the matching classes, then cycles through each item and sets the CSS to be hidden on each one.
However, it's not working. Can anyone tell me where I'm going wroing?
Note that I haven't yet coded the "show all" option. I think I'll probably be able to figure that out once I have this first problem solved.
In your fiddle change load script No wrap - in <head>.
Just change your function like following
function showListCategories() {
var lis = document.getElementsByTagName('li');
for (var i = 0; i < lis.length; i++) {
lis[i].style.display = 'none';
}
//above code to reset all lis if they are already shown
var selection = document.getElementById("listDisplayer").value;
lis = document.getElementsByClassName(selection);
for (var i = 0; i < lis.length; i++) {
lis[i].style.display = 'block';
}
};
and in css it should be none not hidden
.cats, .rats, .bats {
display: none;
}
If you want to show all li when showAll is selected, add all classes to all lis.
You have a few things going on. First, your fiddle is not setup correctly, if you open the console you'll see:
Uncaught ReferenceError: showListCategories is not defined
This means that the function doesn't exist at the point you attach the event or that the function is out of scope, because by default jsFiddle will wrap your code in the onLoad event. To fix it you need to load the script as No wrap - in <body>.
Second, there's no such thing as a display:visible property in CSS. The property you want to toggle is display:none and display:list-item, as this is the default style of <li> elements.
Now, to make this work, it is easier if you add a common class to all items, let's say item, that way you can hide them all, and just show the one you want by checking if it has a certain class, as opposed to querying the DOM many times. You should cache your selectors, it is not necessary to query every time you call the function:
var select = document.getElementById('listDisplayer');
var items = document.getElementsByClassName('item');
function showListCategories() {
var selection = select.options[select.selectedIndex].value;
for (var i=0; i<items.length; i++) {
if (items[i].className.indexOf(selection) > -1) {
items[i].style.display = 'list-item';
} else {
items[i].style.display = 'none';
}
}
}
Demo: http://jsfiddle.net/E2DKh/28/
First there is no property in Css like display:hidden; it should be display: none;
here is the solution please not that i am doing it by targeting id finished
Js function
var selection = document.getElementById("listDisplayer");
var list = document.getElementsByTagName('li');
selection.onchange = function () {
var value = selection.options[selection.selectedIndex].value; // to get Value
for (var i = 0; i < list.length; i++) {
if (list[i].className.indexOf(value) > -1) {
list[i].style.display = "list-item";
} else {
list[i].style.display = "none"
}
}
}
css Code
.cats, .rats, .bats {
display: none;
}
JSFIDDLE
You have many things wrong in your code and a wrong setting in the jsFiddle. Here's a working version that also implements the "all" option:
Working demo: http://jsfiddle.net/jfriend00/5Efc5/
function applyToList(list, fn) {
for (var i = 0; i < list.length; i++) {
fn(list[i], list);
}
}
function hide(list) {
applyToList(list, function(item) {
item.style.display = "none";
});
}
function show(list) {
applyToList(list, function(item) {
item.style.display = "block";
});
}
function showListCategories() {
var value = document.getElementById("listDisplayer").value;
var itemList = document.getElementById("itemList");
var items = itemList.getElementsByTagName("li");
if (value === "all") {
show(items);
} else {
// hide all items by default
hide(items);
show(itemList.getElementsByClassName(value));
}
}
Changes made:
You have to fetch the .value of the select to see what the value was of the option that was picked. You were using the selectedIndex which is just a number.
A common technique for displaying only a set of objects is to hide all of them, then show just the ones you want. Since the browser only does one repaint for the entire operation, this is still visually seamless.
When finding items that match your class, you should be searching only the <ul>, not the entire document. I added an id to that <ul> tag so it can be found and then searched.
To save code, I added some utility functions for operating on an HTMLCollection or nodeList.
Tests for the "all" option and shows them all if that is selected
Changed the jsFiddle to the Head option so the code is available in the global scope so the HTML can find your change handler function.
Switched style settings to "block" and "none" since "visible" is not a valid setting for style.display.
I just finished creating two buttons with an onclick event,
You can view the demo of the buttons here.
However, I need help with two issues which I'm not sure how I can solve.
Issue 1
As you can see in the demo, if you first click a button, a dropdown menu appears, but if you click on the second button while the first dropdown menu is visible, the second dropdown overlaps the first dropdown menu.
How can I close the dropdown when you click somewhere else on the page or on another button using javascript?
Issue 2
I want the button to look like this even if you hover over the button
And this is how it's going to look when you click the button and when the dropdown menu becomes visible. It should stay like this even if you don't have your mouse on the button or the dropdown menu.
How can I accomplish this with JavaScript?
Thanks in advance!
You just need to keep an array of your elements, when you set one to display, set the others not to.
var dropdowns = ['language', 'delivery-country'];
var elements = [];
var hideElements = function (dropdown) {
for (var i = 0, l = dropdowns.length; i < l; i++) {
if (dropdown !== dropdowns[i]) {
var div = document.getElementById(dropdowns[i] + '-dropdown');
div.style.display = '';
elements[i].className = 'inactive';
}
}
}
for (var i = 0, l = dropdowns.length; i < l; i++) {
elements[i] = document.getElementById(dropdowns[i] + '-icon');
(function (index) {
elements[index].onclick = function() {
var div = document.getElementById(dropdowns[index] + '-dropdown');
if (div.style.display !== '') {
div.style.display = '';
elements[index].className = 'inactive';
} else {
div.style.display = 'block';
elements[index].className = 'active';
}
hideElements(dropdowns[index]);
};
})(i);
}
For the second part of your question, add the respective classes in your html (inactive). The code above will toggle the classes, you just need to figure out the CSS. Start by removing the hover classes on the icons, and also use classes for the icons rather than ids which you need to specify for every element.
I've got a page with bunch of drop downs and each drop down has a button next to it. When the page initially loads I want all the buttons to be disabled and if there is a change to a specific drop down then its corresponding button shall be enabled.
I've got the following code down for this but I need to know how to loop through all the drop downs and buttons so I can generalize it.
$(document).ready(function () {
//disable all buttons
function disableAllButtons () {
$(':input[type=button]').attr("disabled", "true");
}
disableAllButtons();
//enable button when drop down changes
$(':input[name=sNewPKvalue1]').focus(function() {
disableAllButtons();
$(':input[name=Update0]').removeAttr("disabled");
})
//enable button when drop down changes
$(':input[name=sNewPKvalue2]').focus(function() {
disableAllButtons();
$(':input[name=Update1]').removeAttr("disabled");
})
////.....question?
});
Question
If I have 12 dropdowns and 12 buttons
How do I loop through all the drop downs with name sNewPKvalue[1-12] and all the buttons with name Update[0-11]
I would not recommend a loop. Just use a selector that selects the elements you want and perform the appropriate action. My first thought is to assign a CSS class to the buttons and drop down lists you are talking about. Then you can simply do something like this:
$('.dropDown').focus(function(){
$(".ddlButton").attr("disabled", "true");
$(this).closest('.ddlButton').removeAttr("disabled");
});
I would do something like:
$.each([1, 12], function(index, value) {
var valmin = val - 1;
$(':input[name=sNewPKvalue'+value+']').focus(function() {
disableAllButtons();
$(':input[name=Update'+valmin+']').removeAttr("disabled");
})
});
I didn't test this one, but you should get the idea ;)
You can do it that way.
for (var i = 0; i < 12; i++)
{
$(':input[name=sNewPKvalue'+(i+1)+']').focus(function() {
disableAllButtons();
$(':input[name=Update'+i+']').removeAttr("disabled");
})
}
Or
$(':input[name^=sNewPKvalue]').focus(function() {
disableAllButtons();
$(':input[name=Update'+(Number(this.name.match(/[0-9]+/))-1)+']').removeAttr("disabled");
})
What you could do is to make a for loop.
for(var i = 1; i <= 12; i++) {
$("select[name='sNewPKvalue"+i+"']").doSomething();
}
for(var i = 1; i <= 11; i++) {
$(":button[name='Update"+i+"']").doSomething();
}