Remove class from nav when sibling is clicked - javascript

I've created a navigation that highlights whatever menu item is selected by adding an 'active' class. If you click the body it removes the active class from the menu item and starts over. It works as expected except when you click a sibling on the menu. The active class is added to the newly clicked menu item, but it also remains on the old one. I wrote code that is suppose to loop through all menu items to see if any of them have the 'active' class, remove the class and then add the 'active' class to the new selection. It's not working.
What am I doing wrong? Also is there any easier way to do this? I need to solve this with vanilla Javascript. I can't use jQuery.
// html
<ul class="nav-items">
<li class="nav-item"></li>
<li class="nav-item"></li>
<li class="nav-item"></li>
<li class="nav-item"></li>
<li class="nav-item"></li>
<li class="nav-item"></li>
<li class="nav-item"></li>
</ul>
// js
if (navItems) {
navItems.addEventListener("click", function(e) {
var background = document.querySelector('.background');
var callout = document.querySelectorAll('.background, .nav-close')
console.log(e.target.closest('.nav-item'));
if (background.style.display !== "block") {
background.style.display = "block";
for( let i = 0; i < e.target.closest('.nav-items').children.length; i++ ) {
console.log(e.target.closest('.nav-items'));
e.target.closest('.nav-item').classList.remove('active');
}
e.target.closest('.nav-item').classList.add('active');
if (background.style.display === "block") {
callout.forEach(function(elem) {
elem.addEventListener("click", function(event) {
background.style.display = "none";
console.log('target', e.target);
e.target.closest('.nav-item').classList.remove('active');
});
});
}
} else {
background.style.display = "none";
e.target.closest('.nav-item').classList.remove('active');
}
})
}

closest() is for ancestors not siblings. So e.target.closest('.nav-item') is only ever going to find itself as closest() finds either itself or an ancestor that matches that selector. Not any of it's sibling nav elements.
Meaning
e.target.closest('.nav-item').classList.remove('active')
Only ever tries to remove a class active from the currently clicked li.
You probably meant to use e.target.closest('.nav-items').children in your loop as a way of accessing the child li's, eg
var li = e.target.closest('.nav-items').children[i];
li.classList.remove('active');
But you don't really need a loop, unless you think you might end up with multiple active elements. You could just find the current active element by finding your closest ul element, and then from that find the nav-item that has class active, ie css class selector .nav-item.active
var parentUL = e.target.closest('.nav-items');
var current = parentUL.querySelector('.nav-item.active');
current.classList.remove('.active');

Related

How to tell Javascript getElementByClassName to get all the element of a ul, instead of repeating the class name in each li?

I'm trying to clean my html/css. How can I get this Javascript to still work, without inserting the class name in each li element from the ul?
How to make the html more pretty and readable?
const ProfileForm = document.getElementsByClassName('profile_container');
const dash = document.getElementsByClassName('dashboard_buttons');
var index;
var array = [];
for (var i = 0; i < dash.length; i++) {
array.push(dash[i]);
dash[i].onclick = function() {
index = array.indexOf(this);
ProfileForm[index].style.display = "block";
var check = ProfileForm[index];
for (var i = 0; i < ProfileForm.length; i++) {
if (ProfileForm[i].style.display == "block" && ProfileForm[i] != check) {
ProfileForm[i].style.display = "none";
}
}
}
}
<ul>
<li class="dashboard_buttons"><i class="far fa-user"></i>Profile</li>
<li class="dashboard_buttons"><i class="far fa-list-alt"></i>My Properties</li>
<li class="dashboard_buttons"><i class="far fa-money-bill-alt"></i>My Offers</li>
<li class="dashboard_buttons"><i class="fas fa-file-contract"></i>My Utilities & Ejari</li>
<li class="dashboard_buttons"><i class="far fa-heart"></i>Favourite Properties</li>
<li class="dashboard_buttons"><i class="far fa-envelope"></i>Messages</li>
<li class="dashboard_buttons"><i class="fas fa-cogs"></i>Settings</li>
</ul>
A simple document.querySelectorAll("ul li") should work. Be careful however, as this will select all the <li> in your whole document. If you want to select a specific <ul>, give it a class or an ID, and select only this one :
<ul id="myList"><li>...<li></ul>
document.querySelectorAll("ul#myList li")
You can use event delegation to notice all events within some "ancestor" element.
(Using event listeners instead of inline event handlers allows this and also promotes separation of concerns between HTML and JavaScript.)
Since I wasn't sure what you wanted to do in your loop, the demo below just "un-highlights" all the list items (and then highlights whichever one was clicked). See the in-code comments for further explanation.
Does this show you how to accomplish what you're trying to do?
// Identifies the `ul` element, and calls `hightlightItem` when user clicks inside it
const list = document.getElementsByClassName("list")[0];
list.addEventListener("click", highlightItem);
// Defines the listener function
function highlightItem(event){ //Listener can access the triggering event
const clickedThing = event.target; // Event's `target` property is useful
// Makes sure the click was on an `li` element before proceeding
if(clickedThing.tagName.toLowerCase() == "li"){
// Collects the list items
const items = list.querySelectorAll("li");
// Loops through the collection
for(let item of items){
// Changes the classList for the current item in the loop
item.classList.remove("highlight");
// Maybe changes it again before continuing to next item in loop
if(item == event.target){ item.classList.add("highlight"); }
}
}
}
.highlight{ background-color: yellow; }
<ul class="list">
<li>Profile</li>
<li>My Properties</li>
<li>My Offers</li>
</ul>
you can do it by query selector
// Get all 'li' elements in the document with class="example"
var x = document.querySelectorAll("li.example");
Instead of using document.getElementByClassName, use document.querySelectorAll

Closing one element when opening another within the same loop using Javascript

first time on here so i'll try my best to explain what I'm asking.
So I have 3 list items with the same class name. I've put them in a looping function so that when you click on one it will display a sub set of list items for that specific list item. I also have them inside an if statement that adds a new class name to the specific list item that was clicked. It allows opening and closing of the sub list items when you click the corresponding parent element.
My question is; how can I use this same principle of checking for the additional class name, when the user clicks any of the list items. In other words, I am trying to code it in a way that will allow me to close any of the open sub list items when the user clicks a new list item.
This is what I came up with but it doesn't know what button[i] is when I include it within the "click" function. What I was trying to do with this code is to take whatever list item was clicked, and then check the previous and next iterations of the class name "button" to see if any of the contain also contain the class name "clicked.
HTML
<div class="main">
<ul>
<li>One
<ul>
<li>One-1</li>
<li>One-2</li>
</ul>
</li>
<li>Two
<ul>
<li>Two-1</li>
<li>Two-2</li>
</ul>
</li>
<li>Three
<ul>
<li>Three-1</li>
<li>Three-2</li>
</ul>
</li>
</ul>
</div>
CSS
.main ul ul {
display: none;
}
.main ul ul li {
display: block;
}
Javascript
var button = document.getElementsByClassName("button");
for (i = 0; i < button.length; i++) {
button[i].addEventListener("click", function() {
var prevItem = button[i - 1];
var nextItem = button[i + 1];
if (prevItem.className !== "button") {
prevItem.className = "button";
prevItem.nextElementSibling.style.display = "none";
}
if (nextItem.className !== "button") {
nextItem.className = "button";
nextItem.nextElementSibling.style.display = "none";
}
if (this.className === "button") {
this.className += " clicked";
this.nextElementSibling.style.display = "block";
}
});
}
I am wanting to make this code usable no matter how many list items you add. So checking exactly button[0] button[1] and button[2] wasn't really an option, but I can see how button[i + 1] might not check every list item after it but rather just the next one. I tried adding another loop but ran into similar issues. anyway that's why I'm here. Thanks for any help in advance.
Since I am not sure whether I understood your question correctly, I quickly rephrase it in my own words.
Question: "I have an arbitrary number of list elements, of which each contains a button and a nested list. The button is always visible, the nested list is hidden by default. When the user clicks on a button, the corresponding nested list should be shown. At the same time, all other shown nested lists should be hidden again. How can I achieve this?"
The original HTML looks fine:
<div class="main">
<ul>
<li>One
<ul>
<li>One-1</li>
<li>One-2</li>
</ul>
</li>
<li>Two
<ul>
<li>Two-1</li>
<li>Two-2</li>
</ul>
</li>
<li>Three
<ul>
<li>Three-1</li>
<li>Three-2</li>
</ul>
</li>
</ul>
</div>
The CSS I did not fully understand, but I suggest the following:
.main ul ul {
display: none;
}
.main li.is-active ul {
display: block;
}
.main ul ul li {
display: block;
}
By adding the "is-active" class to an LI element, it is shown. This way, the CSS controls the visibility.
For the JavaScript part, I suggest this:
const buttonElements = Array.from(document.querySelectorAll('.button'));
buttonElements.forEach(buttonElement => {
buttonElement.addEventListener('click', () => {
const activeElements = Array.from(document.querySelectorAll('.is-active'));
activeElements.forEach(activeElement => {
activeElement.classList.remove('is-active');
});
buttonElement.parentElement.classList.add('is-active');
});
});
This solution assumes you can use newer versions of JavaScript/ECMAScript. Overall, it makes use of const and arrow functions.
First, we get all elements with the class "button" by using document.querySelectorAll(). Since the result is a NodeList and no array, we convert it using Array.from(). Afterwards, we loop through the array by using Array.prototpye.forEach(). We add an event listener for the "click" event. When a button is clicked, we search for all elements with the "is-active" class and for each one remove it. Finally, we add the "is-active" class to the parent element of the clicked button using Node.prototype.parentElement().
Here is another solution that works in older browsers:
var buttonElements = document.getElementsByClassName('button');
for (var i = 0; i < buttonElements.length; i++) {
buttonElements[i].addEventListener('click', function(event) {
var activeListElements = document.getElementsByClassName('is-active');
for (var i = 0; i < activeListElements.length; i++) {
activeListElements[i].setAttribute('class', '');
}
event.target.parentNode.setAttribute('class', 'is-active');
});
}
This is pretty much the same as the other approach but works with older versions of JavaScript.
Generally, the idea is to focus on an arbitrary sum of elements instead of an array with a specific length. In natural language something like: "Give me all buttons. For every button, add an event listener. When a button is clicked, give me all active list elements and remove their active status. Then, mark the list item above the button as active".
Hope this helps

do something when an instance becomes true in an each() function

I am running jQuery's .each function on a button click that runs through three line items. When another object on the page is clicked one of the line items will receive a class of 'selected', it starts on the first line item as default.
I want to execute something when the data-index of the li is greater than 0 and that li has the class name of selected.
const $orderStatusButton = $('form#oar-widget-orders-and-returns-form button');
$orderStatusButton.on('click', function() {
$(".selectric-scroll ul li").each(function() {
var DataIndex = $(this).data('index');
console.log(DataIndex);
if ((DataIndex > 0) && ($(this).hasClass('selected'))) {
$('div#oar_select-error').css('display', 'none');
} else {
$('div#oar_select-error').css('display', 'block');
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li data-index="0" class="selected"></li>
<li data-index="1" class=""></li>
<li data-index="2" class=""></li>
</ul>
Essentially, I want to hide a div when the line item with the data-index of 1 or 2 has the class name of selected and show it when the line item with the data-index of 0 has the class name selected and I'm not sure if I'm going about this correctly as it isn't working.
Since you are looping through all elements you'll always end up with the display value of the last iteration of the each function.
What you should probably be doing is using the selected class on your jquery selector:
$(".selectric-scroll ul li.selected")
then you only have to process one element on the each function.
Why not just use toggleClass("someclass") and add CSS
li.someclass {
display:none;
OR
visibility:hidden;
};

How to find the class of element in a each loop

I am trying to create a menu system where I can change the style of the active page item in the menu. I am using a separate body class on each page, then I want to cycle through the li in the menu and find a match to the body class. At that match I will add the new styling to that menu item.
Here is my code so far.
HTML
<body class="home-state">
...
<div class="menu-left">
<ul>
<li class="home-state">
Home
</li>
<li class="work-state">
Work
</li>
<li class="services-state">
Services
</li>
<li class="about-state">
About
</li>
<li class="blog-state">
Blog
</li>
<li class="shop-state">
Shop
</li>
<li class="contact-state">
<a data-toggle="modal" href="#modal-coworking">Contact</a>
</li>
<li class="project-state">
Project brief
</li>
</ul>
</div>
...
</body>
JS
var bodyClass = $("body").attr('class');
$('.menu-left ul li').each(function(){
First: I want to find the element's class here I have used $(this).attr("class"); which didn't work
var element = $(this);
Second: I want to use a if statement to check to see if the class matches the bodyClass
console.log(element);
Last: If there is a match I want to add the class .active to the element li.
});
Given that elements can have multiple classes, I'd suggesting changing your body element to use a data- attribute rather than a class to specify what the current page is:
<body data-current="home-state">
Then the JS needed to add the active class to the relevant menu item is simple:
$("li." + $("body").attr("data-current")).addClass("active")
You don't need to loop over the menu items comparing classes as mentioned in the question, because you can just directly select the required li element based on its class.
In the event that the body element doesn't have a data-current attribute then $("body").attr("data-current") would return undefined, which would mean the code above tries to select an element with $("li.undefined") and add a class to it. Probably you have no elements with such a class so that would be harmless, but if you wanted to explicitly test that the data-current attribute exists:
var current = $("body").attr("data-current")
if (current) {
$("li." + current).addClass("active")
}
You can do this in couple ways, here is the simple way to do this;
var bodyClass = $("body").attr('class');
$("li." + bodyClass).addClass("active")
You can also use a loop for this one;
var bodyClass = $("body").attr('class');
$(".menu-left li").each(function(i, classes) {
if (bodyClass === $(this).attr("class")) {
$(this).addClass("active")
}
})
both will do the job.
enter image description here
enter image description here
as the comment said,the element can have more than one class ,so you should check it one by one
You missed to bind the click event for the menu item. Follow like below
var bodyClass = $("body").attr('class');
$('.menu-left ul li').on( "click", function() {
var myClass = $(this).attr("class");
alert(myClass);
});
Tested: https://codepen.io/anon/pen/XzYjGY

Jquery to copy first link to second ul and then change class of first link

Short question:
http://jsfiddle.net/wF4FH/2/
What I want is for Page1 to be right above Page2 and Page10 above Page 20 before I change the classes. This should work for any number of elements.
The code provided gives an "Uncaught TypeError: Object # has no method 'append' ".
Long question:
I'm having problem finding the correct way to insert an li element based on the first link. The problem is I cant use id's on my markup so I have to "walk through" each class and check for names. I might just make this a lot more complicated than it is because my first two solutions didn't work the way I thought they would.
html
<ul class="nav">
<li class="active">
Start
</li>
<li class="has-child">
page1
<ul class="">
<li>
page2
</li>
</ul>
</li>
<li class="has-child">
page10
<ul class="">
<li>
page20
</li>
<li>
page30
</li>
</ul>
</li>
javascript
//Copy first link to child ul li
var pageLinks = $("li.has-child > a:first-child");
if (pageLinks != null) {
//var dropdownMenus = $("li.dropdown > a:first-child");
for (var i = 0; i < pageLinks.length; i++) {
for (var x = 0; x < pageLinks.length; x++) {
if (pageLinks[i].innerHTML === pageLinks[x].innerHTML) {
pageLinks[x].childNodes.append(pageLinks[i]);
}
}
}
}
//Change css classes
$("li.has-child").attr('class', 'dropdown');
$(".dropdown ul").addClass("dropdown-menu");
$(".dropdown a").attr("href", "#").addClass("dropdown-toggle").attr('data-toggle', 'dropdown');
strong text
What I want is for Page1 to be right above Page2 and Page10 above Page 20 before I change the classes. This should work for any number of elements.
When they are copied to the inner ul I change the top level menu item to a different class to work as a clickable dropdown men item.
The code provided gives an "Uncaught TypeError: Object # has no method 'append' ".
It is the navigation of a cms I cant change the markup on.
try this:
$links = $('li.has-child').children('a:first-child');
if($links.length > 0){
$links.each(function(i,link){
$(link).next().prepend($('<li></li>').append($(link)))
})
}
http://jsfiddle.net/wF4FH/6/
You need .clone() method to copy elements..
UPDATED
$links = $('li.has-child').children('a:first-child');
if($links.length > 0){
$links.each(function(i,link){
$(link).next().prepend($('<li></li>').append($(link).clone()))
})
}
http://jsfiddle.net/wF4FH/7/
When you have a jQuery object and you access it by numeric index, you're left with an HTML element. So $('body')[0] == document.body. This means that when you access pageLinks[x], you're really getting a raw element. This means that you want pageLinks[x].appendChild(pageLinks[i]);, not pageLinks[x].childNodes.append(pageLinks[i]);

Categories

Resources