Closing one element when opening another within the same loop using Javascript - 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

Related

Remove class from nav when sibling is clicked

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

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

Hide <h3> when all ul>li of group does not contain filter term

I've created a small script that filters my ul with a nested ul inside of it. The only issue with my script is I want to hide the title of the nested ul if none of the li's contain the search term, but I am not sure how to go about checking the li's of each "group" as opposed to each li individually. The way it stands, it will display the title if it finds an li in the group matching the search term, but it will immediately turn around and hide the title if the same group contains an li that DOES NOT contain the search term. I know what I'm doing wrong, but I am not as skilled in jquery and cannot seem to visualize how to go about this.
Any help would be great. My code is below:
HTML:
<div id="sitemap">
<h3>Hospital Data Solutions Interactive Site Map</h3>
<hr/>
<p id="header"><input type="text" placeholder="Filter Site Map"> - Use this field filter our list of databases: Search by Topic or Topic Subgroup</p>
<ul id="toplist">
<li class="group">
<h3 class="sTitle">Available Beds - <a style="font-size:18px;">Go to Section</a></h3>
<ul class="sublist">
<li>General</li>
<li>ICU</li>
<li>CCU</li>
<li>BICU</li>
<li>SICU</li>
<li>Other</li>
<li>Hospice</li>
<li>Total</li>
</ul>
</li>
<hr/>
<li class="group">
<h3 class="sTitle">Discharges - <a style="font-size:18px;">Go to Section</a></h3>
<ul class="sublist">
<li>Medicare</li>
<li>Medicaid</li>
<li>Other</li>
<li>Total</li>
</ul>
</li>
</ul>
</div>
Jquery:
$(function(){
$('input[type="text"]').keyup(function(){
var searchText = $(this).val().toLowerCase();
$('.sublist>li').each(function(){
var currentLi = $(this).text().toLowerCase();
if(currentLi.search(searchText) != -1){
$(this).slideDown();
$(this).closest('.group').children('.sTitle').show();
} else { $(this).slideUp(); $(this).closest('.group').children('.sTitle').hide();}
});
});
});
First, select the .sublist elements instead of the lis.
Then iterate that collection using .each(), and use .children() to test each li like you currently are, except use .filter() instead of .each(). This will give you a collection as a result. If the collection is empty, there were no matches. If not, then there was a match.
$('input[type="text"]').keyup(function(){
var searchText = $(this).val().toLowerCase();
$('.sublist').each(function(i, sub){
var matches = $(sub).children().filter(function(i, li) {
return $(li).text().toLowerCase().search(searchText) != -1;
});
if (matches.length) {
$(sub).slideDown().prev().show();
} else {
$(sub).slideUp().prev().hide();
}
});
});
Now the slideDown/Up and show/hide are happening once per sublist instead of on every child li. And I just used .prev() to get back to the h3 element.
If you're going to be hiding those list items that don't match your search you're going to want to deal with them individually anyway, so I wouldn't abandon that approach. So you just need a way to check to see if the term was found somewhere in the search of the nested list. Here's what I might do to utilize what you already have.
After you capture the search term, loop through each of the sublists and set a flag to false; this will be where we capture whether there were any matches. Then loop through that sublist's items, and if you find a match set the flag to true, showing or hiding the item as necessary. Then, after you've checked all the items show or hide the heading based on that flag. It might look something like this:
$('.sublist').each(function(){
found = false;
$(this).children("li").each( function() {
var currentLi = $(this).text().toLowerCase();
if(currentLi.search(searchText) != -1){
$(this).slideDown();
found = true;
} else {
$(this).slideUp();
}
});
if(found) {
$(this).closest('.group').children('.sTitle').show();
} else {
$(this).closest('.group').css("list-style-type", "none");
$(this).closest('.group').children('.sTitle').hide();
}
});
I added a css line to show/hide the header's disc to avoid having that hanging there if everything else disappears. Hope this helps! Let me know if you have any questions.

Change style/class with javascript on keydown

I'm trying to change the style of an element using javascript on a keydown event. I've already done it with mouse clicks like this :
#mpc ul li:active {
background:#844;
And these are the original properties of the element im trying to change:
<div id="mpc">
<div id="wrapper">
<ul>
<li id="kickDrum_p1">1<p>Kick</p></li>
<li id="snareDrum_p2">2<p>Snare</p></li>
<li id="closedHiHat_p3">3<p>ClosHat</p></li>
<li id="openHiHat_p4">4<p>Open HiHat</p></li>
</ul>
</div>
</div>
This is the original css.
#mpc ul li {
background:#999;
}
I'm really new to css/html and javascript and haven't started learning jquery so I prefer doing it all by javascript and css.
The idea is to assign a different key to every list item and when that key is pressed on the page, the list item's background changes, just like I've done the click event. The difference with the click event is I've made it so it doesn't follow the id's since you manually select the list item you want and then it changes it's style.
With key input however I need to specify which list item exactly I'm targeting.
So I think I need to create a new css class with the properties of the background I want the element to change to and using javascript to tell that when I press the 'H' key for example it should change the element's style to that and when I let go of the key to reverse it back to normal. If that makes sense. I do not know how to ahcieve that.
EDIT:
This is how I tried to do it:
var keypress = document.getElementById("kickDrum_p1")[0]
document.addEventListener("keydown", function (e) {
if(e.keyCode == 69) {
var key = e.keyCode
var color = keypress.getElementByClassName("color")
if(color.length !=0 ){
cur = color[0]
cur.className = " "
}
cur.className="color"
}
});
And in the styles I have this:
#mpc ul li.color {
background:#844;
}
<P ID="MyID" CLASS="Testclass">Test</P>
<SCRIPT>
Add here jQuery
$("*").keypress(function(event){
if(event.which==107 || event.which==75){
$("#MyID").attr("class", "newclass");
}
}
</SCRIPT>
BTW: 107 and 57 are the K and the k in the ASCII table.

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