Toggle class of parent element onclick without jQuery - javascript

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
};
}
}

Related

Open only one submenu when clicked on the parent in Vanilla JS

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');
}
}
}

Remove class based on elements but not others

I have these sections on this side scrolling site. And want to add a class which will change styling depending if you're on a certain section.
I'm working on this function. The top is what determines the section of the side scroller you are viewing.
The let variables and below is where it stops working. I'm trying to have it so if a nonHome ID section is clicked, for example "slide-1", then add the class 'nav-visibilty'. If they are a match "slide-2" and "slide-2" then remove said class. Am I close?
https://codepen.io/mikayp-the-styleful/pen/NWPxoXR?editors=1111
setTimeout(function(){
for (i=0; i < nonHome.length; i++ ){
if (nonHome[i].id != nonHomeID){
nonHome[i].classList.add("nav-visibility");
console.log('add')
} else{
nonHomeID.classList.remove("nav-visibility");
console.log('rem')
}
}
I am still not totally clear on the behavior that you want, but there are two errors in the code that can be fixed:
It seems like you are always using 'slide-2' instead of the slideId in your event handler.
As mentioned in a comment, nonHomeID is being used incorrectly in your comparison (it is either a string or an element, but you are using it as if it was a string in the if condition, and as the element in the else branch.) Here I have kept it as an element and renamed it for clarity.
Fixing these errors results in code that applies the nav-visibility class to all slides except the one selected by the button. Is that the desired behavior?
let nonHome = document.querySelectorAll(".slide-container section");
let nonHomeSelected = document.getElementById(slideId);
var i;
setTimeout(function() {
for (i = 0; i < nonHome.length; i++) {
if (nonHome[i] != nonHomeSelected) {
nonHome[i].classList.add("nav-visibility");
console.log("add");
} else {
nonHome[i].classList.remove("nav-visibility");
console.log("rem");
}
}
}, 1000);
Edit to add: If the goal is to add nav-visibility to all only the specific slideId, you should not be adding in a loop, i.e. you need to pull your check for whether the slide is Home outside the loop. There are conceptually two steps here: remove the class from all elements that are no longer to have it, then add the class to the element that needs it.
let slideToAddVisibilityTo = document.getElementById(slideId)
let homeSlide = document.getElementById('slide-2')
let allSlides = document.querySelectorAll(".slide-container section")
for (let i = 0; i < allSlides.length; ++i)
allSlides[i].classList.remove('nav-visiblity')
if (slideToAddVisibilityTo != homeSlide)
slideToAddVisibilityTo.classList.add('nav-visibility')
Just hide them all, then show the clicked one:
function showSection(id) {
var sections = document.getElementsByTagName("section");
for(var i=0; i<sections.length; i++) sections[i].classList.remove("nav-visibility");
var current = document.getElementById(id);
current.classList.add("nav-visibility");
}
Example: showSection("foo") will remove nav-visibility from all sections, then add it to the section with id foo.

Javascript - Toggle Multiple Classes onclick

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');
}
}

How do I filter an unorderded list to display only selected items using Javascript?

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.

When using this .js 2 times on a single page it only works in the one instance

This script creates menu tabs above a text area. The script works if use only once on a page, I however need to use it twice on a single page, to create 2 text areas, each with a menu above them. As soon as I use it twice only one instance works. Any suggestions.
window.onload=function() {
// get tab container
var container = document.getElementById("tabContainer");
// set current tab
var navitem = container.querySelector(".tabs ul li");
//store which tab we are on
var ident = navitem.id.split("_")[1];
navitem.parentNode.setAttribute("data-current",ident);
//set current tab with class of activetabheader
navitem.setAttribute("class","tabActiveHeader");
//hide two tab contents we don't need
var pages = container.querySelectorAll(".tabpage");
for (var i = 1; i < pages.length; i++) {
pages[i].style.display="none";
}
//this adds click event to tabs
var tabs = container.querySelectorAll(".tabs ul li");
for (var i = 0; i < tabs.length; i++) {
tabs[i].onclick=displayPage;
}
}
// on click of one of tabs
function displayPage() {
var current = this.parentNode.getAttribute("data-current");
//remove class of activetabheader and hide old contents
document.getElementById("tabHeader_" + current).removeAttribute("class");
document.getElementById("tabpage_" + current).style.display="none";
var ident = this.id.split("_")[1];
//add class of activetabheader to new active tab and show contents
this.setAttribute("class","tabActiveHeader");
document.getElementById("tabpage_" + ident).style.display="block";
this.parentNode.setAttribute("data-current",ident);
}
Havn't found solution yet, but FYI, you originally marked this as jQuery, if it had been jquery, you could easily break a few lines of that code and write it as simple as: (depending on version)
function displayPage(e) {
var current = $(this).parent().attr("data-current");
$("#tabHeader_" + current).removeClass("tabActiveHeader")
$("#tabpage_" + current).hide();
var ident = this.id.split("_")[1];
$(this).addClass("tabActiveHeader");
$("#tabpage_" + ident).show();
$(this).parent().attr({ 'data-current': ident })
}
$(function() {
var container = $("#tabContainer"),
navitem = container.find((".tabs ul li")).first(),
ident = navitem[0].id.split("_")[1];
navitem.addClass("tabActiveHeader").parent().attr({ 'data-current': ident });
$(".tabpage").filter(function(i) { return i>0; }).hide();
// OR
// $(".tabpage:not(:first-child)").hide();
$(".tabs ul li").on("click", displayPage)
});​
See WORKING Example of the previous jQUERY in this jsFiddle
ALSO, Have you look at jQueryUI.Tabs?
Instead of hard-setting window.onload—which replaces the last-set handler with the new one—use the following code that registers an arbitrary number of event handlers for the same event on the same object:
window.addEventListener('load',function(){
// Your code here
},false);
More can be read about element.addEventListener and specifically IE Support
This will not work for older versions of IE; if you need this support, I strongly recommend using a cross-browser library like jQuery. You originally tagged your question as relating to jQuery, but there is no jQuery used in your code.

Categories

Resources