Child element effecting open/close toggle - javascript

I have created code for an open/close toggle filter by targeting the first li of the filter. For some reason, the children li's that are nested inside this are also closing the toggle when selected to filter the feed.
Please would someone be able to advise how to only target the parent li without the children li affecting the toggle from opening and closing?
Here is a link to the page with the filter. Please test the toggle and select a filter to see the issue that I am currently facing.
https://snapstaging.co.uk/coolkitnew/vans/
It may be worth noting that I don't want to target by class because I have multiple of these elements all with different classes.
let filterBlock = document.querySelectorAll('.searchandfilter li ul')
let filterLi = document.querySelectorAll('.searchandfilter ul li')
let filterLiOpen = [];
filterLi.forEach((tag, index) => {
tag.addEventListener('click', () => {
filterLi[index].classList.toggle('active')
if (!filterLiOpen[index]) {
filterLiOpen[index] = true;
} else if (filterLiOpen[index]) {
filterLiOpen[index] = false;
}
console.log(filterLiOpen);
})
})
.active { color: #C00; }
<div class="searchandfilter">
<ul>
<li class="sf-field-post-meta-vehicle_size">
<h4>Size</h4>
<ul data-operator="and" class="">
<li class="sf-level-0"><label class="sf-label-checkbox">Large Van<span class="sf-count">(20)</span></label></li>
</ul>
</li>
</ul>
</div>

The issue is because you select all the li elements in the DOM. To target only those which are children of the top level ul, not its descendants, use the child operator in your selector: >.
Also note that you can simplify the logic which toggles the boolean value you store in your array. Here's a working example:
let filterLi = document.querySelectorAll('.searchandfilter > ul > li')
let filterLiOpen = [];
filterLi.forEach((tag, index) => {
tag.addEventListener('click', e => {
e.currentTarget.classList.toggle('active');
filterLiOpen[index] = !filterLiOpen[index];
console.log(filterLiOpen);
})
})
.active { color: #C00; }
<div class="searchandfilter">
<ul>
<li class="sf-field-post-meta-vehicle_size">
<h4>Size</h4>
<ul data-operator="and" class="">
<li class="sf-level-0">
<label class="sf-label-checkbox">
Large Van
<span class="sf-count">(20)</span>
</label>
</li>
</ul>
</li>
<li class="sf-field-post-meta-vehicle_size">
<h4>Size</h4>
<ul data-operator="and" class="">
<li class="sf-level-0">
<label class="sf-label-checkbox">
Medium Van
<span class="sf-count">(10)</span>
</label>
</li>
</ul>
</li>
<li class="sf-field-post-meta-vehicle_size">
<h4>Size</h4>
<ul data-operator="and" class="">
<li class="sf-level-0">
<label class="sf-label-checkbox">
Small Van
<span class="sf-count">(5)</span>
</label>
</li>
</ul>
</li>
</ul>
</div>

Related

How To Add Active Class To a Current Navbar Element?

hope you all doing great.
I've been trying to add the (active class )to each of the navbar links when the user is on that specific section of the page with this
Tutorial (i'm stuck at 2:45:05) and no success so far can anyone tell me what i did wrong .thank you.
const menu = document.querySelector(' nav .container ul');
const navItems = menu.querySelectorAll('li');
navItems.forEach(item => {
const link = item.querySelector('a');
ink.addEventListener('click', () => {
link.classList.add(".active");
});
});
nav .container ul li a.active {
background: var(--color-primary);
color: var(--color-white);
}
<nav>
<div class="container">
<a href="#">
<h3> AMANI DEV </h3>
</a>
<ul>
<li>Home</li>
<li>About</li>
<li>Skills</li>
<li>Services </li>
<li>Portfolio </li>
<li>Contact Me </li>
</ul>
</div>
</nav>
Typo with ink not link.
When you assign a class with classList you don't include the .: classList.add('active').
In your CSS background should probably be background-color.
If you want to remove the other active links before applying the new one you can use forEach to iterate over the links and use classList.remove('active') on each one.
You may find event delegation easier to manage. Rather than attaching multiple listeners to multiple elements attach one listener to the list element that watches out for events from its child elements as they "bubble up the DOM. You can then check that the clicked element is a link, remove the active classes from the previous link(s), and then apply the active class to the clicked link.
Here's an example using event delegation.
// Cache the list, and the items
const list = document.querySelector(' nav .container ul');
const links = list.querySelectorAll('a');
// Add one listener to the list element
list.addEventListener('click', handleClick);
// If the clicked element is a link remove all
// the active classes from the other links, and then
// add the active class to the link that was clicked on
function handleClick(e) {
if (e.target.matches('a')) {
links.forEach(link => link.classList.remove('active'));
e.target.classList.add('active');
}
}
:root { --color-white: white; --color-primary: red; }
.active {
background-color: var(--color-primary);
color: var(--color-white);
}
<nav>
<div class="container">
<a href="#">
<h3> AMANI DEV </h3>
</a>
<ul>
<li><a class="active" href="#home">Home</a></li>
<li>About</li>
<li>Skills</li>
<li>Services </li>
<li>Portfolio </li>
<li>Contact Me </li>
</ul>
</div>
</nav>
You need to do querySelectorAll in a tag not on the li tag. Just do this and do let me know.
Modify the code in the following line :
ink.addEventListener('click',() => {
to
link.addEventListener('click',() => {
to be like this
const menu = document.querySelector(' nav .container ul');
const navItems = menu.querySelectorAll('li');
navItems.forEach(item => {
const link = item.querySelector('a');
link.addEventListener('click',() => {
link.classList.add(".active");
});
});
document.querySelectorAll('ul li').forEach(el => {
el.onclick = () => {
document.querySelectorAll('ul li').forEach(el => el.classList.remove('active'));
el.classList.add('active');
}
})
here a demo code:
document.querySelectorAll('#myNav li').forEach(el => {
el.onclick = () => {
document.querySelectorAll('#myNav li').forEach(el => el.classList.remove('active'));
el.classList.add('active');
}
})
.active {
font-size: 70px;
}
<nav>
<div class="container">
<a href="#">
<h3> AMANI DEV </h3>
</a>
<ul id="myNav">
<li><a class="active" href="#home">Home</a></li>
<li>About</li>
<li>Skills</li>
<li>Services </li>
<li>Portfolio </li>
<li>Contact Me </li>
</ul>
</div>
</nav>
// selecting all a element on the page
const links = document.querySelectorAll('a');
// adding a click event on all elements
links.forEach((link) => {
link.addEventListener('click', (e) => {
// if we click first thing is deleting the active class from all link
links.forEach((link) => {
link.classList.remove('active')
})
// then in the end add the active class only in the correct one
e.target.classList.add('active')
})
})

How to get immediate child of <ul> using cheerio

I have html file html content like this :
<ul>
<li class="class_1">111</li>
<li class="class_2">
<ul>
<li class="class_3">222</li>
<li class="class_4">333</li>
</ul>
</li>
<li class="class_5">444</li>
</ul>
After Loading html content in cheerio module and while searching for immediate li childs it's getting all items from child ul as well like this :
this._$$=cheerio.load(<htmlContent>, {xmlMode : true});
const liElements = this._$$(`ul > *`);
When i print liElements in after converting to html content i am getting output like this :
<li class="class_1">111</li>
<li class="class_2">
<ol>
<li class="class_3">222</li>
<li class="class_4">333</li>
</ol>
</li>
<li class="class_5">444</li>
<li class="class_3">222</li>
<li class="class_4">333</li>
You can see content from child ul is repeating here. I tried a lots of options from cheerio documentation but no luck. Can any help me to get immediate li child of ul.
Many Thanks in Advance.
the issue is that ul > *is too generic and it will return all the ul child even ones inside ul under li tag
maybe you have two solutions to fix this situation
1) put a class name on top ul
<ul class="main-ul">
<li class="class_1">111</li>
<li class="class_2">
<ul>
<li class="class_3">222</li>
<li class="class_4">333</li>
</ul>
</li>
<li class="class_5">444</li>
</ul>
selector became const liElements = this._$$(.main-ul > li);
2) get child of ul inside li tag and remove them from list of all child
const liWithLiParent= this._$$(`li > ul > *`);
const liElements = this._$$(`ul > *`).filter(li => !liWithLiParent.some(liWithParent => liWithParent === li));
Simplest is just to remove the child ul's
$('ul ul').remove()

Change background list item on click in dynamic nested List

I have nested lists draw in run time dynamic from database in away like this :
<div class="list"><ul>
<li>
listA
<ul>
<li>Alist1</li>
<li>Alist2</li>
<li>Alist3</li>
</ul>
</li>
<li>
listB
<ul>
<li>BList1</li>
<li>BList2</li>
<li>BList3</li>
</ul>
</li>
i want to change the back ground of list item when clicked but it change style of the all nested list by the following method :
var $li = $('#list li').click(function () {
$li.removeClass('selected');
$(this).addClass('selected');
});
using this style :
li.selected {
background-color: aqua;}
I know that i should use the direct descendant operator (>) to force change to parent only but my problem that list is drawn dynamically and I can't limit its levels and nested list.
is there away to always force only clicked item to be changed only ?
1- You can't use #list while your list have a class list not id list with classes you need to use dot not #
2- You need to use > like $('.list > ul > li')
$(document).ready(function(){
$('ul > li').on('click' , function(e){
e.stopPropagation();
//$('li > ul').hide();
$(this).find(' > ul').slideDown();
$(this).parent('ul').find('li').removeClass('selected');
$(this).addClass('selected');
});
});
ul{
background : #fff;
}
li > ul{
display : none;
}
li.selected{
background : red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="list">
<ul>
<li>
listA
<ul>
<li>Alist1
<ul>
<li>Alist1-1</li>
<li>Alist1-2</li>
<li>Alist1-3</li>
</ul>
</li>
<li>Alist2</li>
<li>Alist3</li>
</ul>
</li>
<li>
listB
<ul>
<li>BList1</li>
<li>BList2</li>
<li>BList3</li>
</ul>
</li>
</ul>
</div>
-- It'll be better to work with <a> see the next example
$(document).ready(function(){
$('a').on('click' , function(e){
e.preventDefault();
var GetLi = $(this).closest('li');
var GetBigUL = $(this).closest('ul');
var GetNextUL = $(this).next('ul');
GetBigUL.find('a').next('ul').not(GetNextUL).slideUp();
GetNextUL.slideDown();
GetBigUL.find('li').removeClass('selected');
GetLi.addClass('selected');
});
});
ul{
background : #fff;
}
li > ul{
display : none;
}
li.selected{
background : red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="list">
<ul>
<li>
<a>listA</a>
<ul>
<li>
<a>Alist1</a>
<ul>
<li><a>Alist1-1</a></li>
<li><a>Alist1-2</a></li>
<li><a>Alist1-3</a></li>
</ul>
</li>
<li><a>Alist2</a></li>
<li><a>Alist3</a></li>
</ul>
</li>
<li>
<a>listB</a>
<ul>
<li><a>BList1</a></li>
<li><a>BList2</a></li>
<li><a>BList3</a></li>
</ul>
</li>
</ul>
</div>

Getting/changing class of an element with other elements in it. JavaScript/JQuery

I made a blog archive in the format of this:
+Year
+Month
Title
Sample code:
<ul>
<span class="toggle">+</span>
<li class="year">$year
<ul>
<span class="toggle">+</span>
<li class="month active">$month
<ul>
<li class="title active">$title</li>
</ul>
</li>
</ul>
</li>
</ul>
I used $(this).next().toggle(), which works fine toggling the lists, but the entire list is expanded in the beginning when the page loads, and I don't want that.
So I changed to changing class names (active/inactive). I want to change the class of the month/title lists to inactive and back when the + span is clicked. The problem is using $(this).next() doesn't work.
If I try $(this).next().hasClass("active");
It will return a false. Or console.log($(this).next().attr("class"));, which gives undefined.
$(this).next().html(); gives:
<li class="month active"><span class="toggle">+</span><ul><li class="title active">...</li></ul></li></ul></li></ul>
The very next thing that follows the + span is the list with class of active, but it doesn't recognize the class? I don't understand why .toggle() works, but this doesn't.
What option do I have to make this work?
The idea is to capture the click event on the span class and toggle active/inactive on the year so that it shows correctly. Here's some psuedo code:
$('.toggle').on('click', function(){
$(this).next().toggleClass('active').toggleClass('inactive');
});
This will only work if the element has a class of inactive on page load, like this:
<ul>
<span class="toggle">+</span>
<li class="year inactive">$year
<ul>
<span class="toggle">+</span>
<li class="month active">$month
<ul>
<li class="title active">$title</li>
</ul>
</li>
</ul>
</li>
</ul>
When you had your initial toggle working but it displayed the items on load, you could have set the next element (the unordered list) to
style="display: none"
As for
console.log($(this).next().attr("class");
You are missing a parenthesis:
console.log( $(this).next().attr("class") );
Hope this helps.
By using little bit of CSS and toggling the class of ul to active only on click will fix your issue. Below is a working example.
$('.toggle').on('click', function() {
$(this).parent().toggleClass('active');
});
ul {
list-style-type: none;
}
ul:not(#MainNode) {
display: none;
}
ul.active > li > ul {
display: block !important;
}
.toggle {
cursor: pointer
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="MainNode">
<span class="toggle">+</span>
<li class="year">Year
<ul>
<span class="toggle">+</span>
<li class="month active">Month
<ul>
<li class="title active">Title</li>
</ul>
</li>
</ul>
</li>
</ul>

Sizzle returning only one element while selector matches two

Sizzle does not return all the elements which match the selector. Here is JSBin Demo showing the problem.
HTML
<h4> Playing with Sizzle JS </h4>
<ul class="list">
<li> Item 1 </li>
<li class="row"> Item 2 </li>
<li class="row"> <span>Item 3</span> </li>
<li class="divider">List item with unique class name </li>
</ul>
<ul class="list">
<li> Item 1 </li>
<li class="row"> Item 2 </li>
<li class="row"> <span>Item 3</span> </li>
<li class="divider">List item with unique class name </li>
</ul>
JS
var selector = 'UL.list > LI:eq(1)';
var elements = Sizzle(selector);
console.log(elements.length); //Says 1
My Question is:
Why it returns only 1 element while there are 2 elements which match the selector?
How can I make sizzle to return all matching elements ?
UL.list > LI:eq(1) will only ever return one element: the 2nd element that matches UL.list > LI, as indicated by :eq(1).
If you're looking for all li elements, remove the :eq().
If you're looking for every li that is the second child, use :nth-child():
var elements = Sizzle('UL.list > LI:nth-child(2)');

Categories