Add classes to parent nodes (<a>) in a nested list - javascript

The <a> inside the last <li> of the innermost <ul> has a class which is named current. It highlights the active link.
I want to highlight all links that are parents from this link.
HTML
<ul>
<li>
Samsung
<ul>
<li>
Galaxy
<ul>
<li>
Galaxy Note 4
</li>
<li>
Galaxy S 5
</li>
</ul>
</li>
</ul>
</li>
</ul>
Galaxy S 5 has the class current, but I also want to add the class current to the parent links (Samsung, Galaxy).

Define an outermost parent element by giving it an id.
Get the element with the current class and assign it to a temp var.
Loop over the elements starting with your current element and change the value of the temp var to temp.parentNode. If the elements parent node is a list item add your current class to the first child element of the list element.
var until = document.getElementById("list"),
crnt = document.getElementsByClassName('current'),
tmp = crnt.item(0);
while(tmp !== until) {
if(tmp.tagName === "LI") {
tmp.children.item(0).classList.add("current");
}
tmp = tmp.parentNode;
}
.current {
color: tomato;
}
<ul id="list">
<li>
Samsung
<ul>
<li>
Galaxy
<ul>
<li>
Galaxy Note 4
</li>
<li>
Galaxy S 5
</li>
</ul>
</li>
</ul>
</li>
</ul>

var els = document.getElementsByClassName('current')
for(var i = 0; i < els.length; i++) {
if(els[i].parentNode != null)
els[i].parentNode.className += ' current';
}

HTML :
<ul>
<li>
Samsung
<ul>
<li>
Galaxy
<ul>
<li>
Galaxy Note 4
</li>
<li>
Galaxy S 5
</li>
</ul>
</li>
</ul>
</li>
</ul>
JavaScript :
<script>
ele = document.getElementById("s5");
par1 = ele.parentNode.parentNode.parentNode;
par1.children[0].className+="current";
par2 = par1.parentNode.parentNode;
par2.children[0].className+="current";
</script>
I've used parentNode to select parent element.starting from the last children.Hope this will work for you. :)

There are six parents from the inside class="current". Suppose there are no other classes that are named with current.
Take the inside node and go from there to the outside node. Add the parentNode to the current node.
node = node.parentNode
As been said there are six nodes from there.
var count = 0;
var max = 6;
while(count < max) { ...; count += 1 }
Check if the node name is a <li> and not the inside <li>. Add to the first child, which is the <a> the class name.
if(node.nodeName === "LI") {
if(count > 1) {
node.children[0].className = "myClass";
}
}
// If there are no other classes that are named with "current"
// There are six parents from inside node
var node = document.getElementsByClassName("current")[0];
var count = 0;
var max = 6;
var cN = "current";
while (count < max) { // 6 times (ul li ul li ul li)
node = node.parentNode;
if (node.nodeName === "LI") {
if (count > 1) { // Not <li> inside
node.children[0].className = cN; // <a>
}
}
count += 1;
}
.current {
background: pink;
}
<ul>
<li>
Samsung
<ul>
<li>
Galaxy
<ul>
<li>
Galaxy Note 4
</li>
<li>
<a href="c.html" class='current'>Galaxy S 5</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>

Related

How do I fix adding multiple items based on their name to an array list using the same function?

I have created a system where if you click a button it adds the buttons textContent to an array list. The problem is I made sure you could not click the same button multiple times to add it to the list. If you click a different button though, it does not add it to the list but instead the console outputs 'item already in list'. Along with adding the text content of the first item of the li list.
Code:
const cartItems = [];
function cartAmount() {
var itemsInCart;
itemsInCart = document.getElementById('cartamount');
if(cartItems.length >= 1) {
itemsInCart.style.visibility = "visible";
}
itemsInCart.innerHTML = cartItems.length;
}
function addCart() {
var itemVal, aVal, a;
a = document.getElementById('item');
aVal = a.value;
itemVal = a.textContent;
if(cartItems.includes(a.textContent)) {
console.log('item already in cart');
} else {
cartItems.push(itemVal);
// cartAmount();
console.log('added Item to cart');
}
}
<ul id="wrapper">
<li>
Drum
</li>
<li>
Stand Off
</li>
<li>
Seat
</li>
<li>
Drum Sticks
</li>
<li>
Symbols
</li>
<li>
Keyboard
</li>
<li>
High Hat
</li>
<li>
Bass Drum
</li>
<li>
Snare Drum
</li>
<li>
Jake Inator
</li>
</ul>
You need to use different ids for the items so you can distinguishes between items:
Run the next snippet which show a solution to your problem.
const cartItems = [];
function addCart(id) {
var itemVal, aVal, a;
a = document.getElementById(id);
aVal = a.value;
itemVal = a.textContent;
console.log(itemVal);
if(cartItems.includes(a.textContent)) {
console.log('item already in cart');
} else {
cartItems.push(itemVal);
console.log('added Item to cart');
}
}
<ul id="wrapper">
<li>
Drum
</li>
<li>
Stand Off
</li>
<li>
Seat
</li>
<li>
Drum Sticks
</li>
<li>
Symbols
</li>
<li>
Keyboard
</li>
<li>
High Hat
</li>
<li>
Bass Drum
</li>
<li>
Snare Drum
</li>
<li>
Jake Inator
</li>
</ul>
Its because all of your "a" tags have the same id. An id is supposed to be unique so you want to give a different id to each of the items you want users to be able to add to the cart.
Then inside your js function you need to update it so you get the event object as a parameter and get the element that was clicked on through it
function addCart(e) {
var itemVal, aVal, a;
aVal = e.target.value;
itemVal = e.target.textContent;
if(cartItems.includes(itemVal)) {
console.log('item already in cart');
} else {
cartItems.push(itemVal);
cartAmount();
console.log('added Item to cart');
}
}```

Javascript remove all empty innerHTML elements children

ul element that has dynamically loaded li a children, sometimes the li a populate empty innerHTML. How do I remove all of the li elements that have an empty a child?
Current (errors Uncaught TypeError: Cannot read property 'innerHTML' of null)
var stepList = document.querySelector(".parent"),
listItems = stepList.children;
for (var i = 0; i < listItems.length; i++) {
if (listItems[i].firstElementChild.innerHTML === "") {
listItems[i].remove();
}
}
Starting
<ul class="parent">
<li>
One
</li>
<li>
</li>
<li>
</li>
<li>
Two
</li>
<li>
Three
</li>
</ul>
Goal
<ul class="parent">
<li>
One
</li>
<li>
Two
</li>
<li>
Three
</li>
</ul>
Problem with your code is when you remove items, children will actually reduce, so you are shifting everything down one index. People typically loop backwards to stop this error.
Perosnally I would just use a empty pseudo class selector with querySelectorAll and a forEach loop.
var emptyAnchors = document.querySelectorAll("li > a:empty")
emptyAnchors.forEach(function (a) {
a.parentNode.remove()
})
<ul class="parent">
<li>
One
</li>
<li>
</li>
<li>
</li>
<li>
Two
</li>
<li>
Three
</li>
</ul>
When you're looping through a list and removing things, it throws the index off. Loop through it backwards instead:
let lis = document.querySelector(".parent").children
for (let i = lis.length - 1; i >= 0; i--) {
if (lis[i].firstElementChild.innerHTML === "") {
lis[i].remove()
}
}
<ul class="parent">
<li>One</li>
<li></li>
<li></li>
<li>Two</li>
<li>Three</li>
</ul>
<script>
//list of all anchor elements in your HTML DOM
onload = function() {
var anchors = document.getElementsByTagName("a");
for(var i = 0; i < anchors.length; i++) {
var anchor = anchors[i];
if(anchor.innerHTML == "") {
anchor.parentNode.removeChild(anchor); //removes the anchor
}
}
}
</script>
Something
Something
Something
VERY CHEEKY pure-css solution: cause the a tags, and not the li tags, to be the ones displaying the list-item bullet-points. Then simply apply display: none to all empty a tags! (Their containing li elements will naturally flatten to height: 0 since they will be display: block, and entirely without content.)
li {
position: relative;
display: block;
}
a {
position: relative;
display: list-item;
}
a:empty {
display: none;
}
<ul class="parent">
<li>
One
</li>
<li>
</li>
<li>
</li>
<li>
Two
</li>
<li>
Three
</li>
</ul>
Note that css solutions aren't always ideal, because even if things look fine visually, the semantic markup of the page may still be in an undesirable state.

Add classes to two level navigation with javascript

I have a simple navigation with two levels. The li-elements of the first level shall get class="n11", that of the second class="n12".
This will write class="n11" to every li-element.
var firstNavi = document.getElementsByClassName("nav1-1");
for(var i = 0; i < firstNavi.length; i++) {
var firstLi = firstNavi[i].querySelectorAll("li");
for(var i = 0; i < firstLi.length; i++) {
firstLi[i].classList.add("n11");
}
}
<ul class="nav1-1">
<li >1.1.</li>
<li >1.2
<ul class="nav1-2">
<li>1.2.1</li>
<li>1.2.2</li>
</ul>
</li>
<li>1.3
<ul class="nav1-2">
<li>1.3.1</li>
<li>1.3.2</li>
</ul>
<li>1.4</li>
</ul>
How to achieve that is written class="n12" to the second li-elements and class="n11" only to the first level entries? Thanks for any help.
You can just retrieve the first level <li> elements as well as the second level <li> elements by using the child combinator > on the first level parent <ul> element and the second level <ul> parent element and now you can just loop through each list element and add the class name accordingly like this:
const firstLevel = document.querySelectorAll('.nav1-1 > li');
const secondLevel = document.querySelectorAll('.nav1-2 > li');
for (var i = 0; i < firstLevel.length; i++) {
firstLevel[i].classList.add('n11');
}
for (var i = 0; i < secondLevel.length; i++) {
secondLevel[i].classList.add('n12');
}
<ul class="nav1-1">
<li >1.1.</li>
<li >1.2
<ul class="nav1-2">
<li>1.2.1</li>
<li>1.2.2</li>
</ul>
</li>
<li>1.3
<ul class="nav1-2">
<li>1.3.1</li>
<li>1.3.2</li>
</ul>
<li>1.4</li>
</ul>
If you do not care about IE 11 compatibility or you are using a JavaScript compiler like Babel, you can further shorten and simplify the above JavaScript by using the forEach() method and arrow functions like this:
const firstLevel = document.querySelectorAll('.nav1-1 > li');
const secondLevel = document.querySelectorAll('.nav1-2 > li');
firstLevel.forEach(e => e.classList.add('n11'));
secondLevel.forEach(e => e.classList.add('n12'));
<ul class="nav1-1">
<li >1.1.</li>
<li >1.2
<ul class="nav1-2">
<li>1.2.1</li>
<li>1.2.2</li>
</ul>
</li>
<li>1.3
<ul class="nav1-2">
<li>1.3.1</li>
<li>1.3.2</li>
</ul>
<li>1.4</li>
</ul>
You can use the ">" query selector to find li elements that have a parent of .nav1-1, then add class n11 to each. Rinse and repeat for the li elements that have a parent of .nav1-2.
Here it is in code.
'use strict';
const li1s = document.querySelectorAll('.nav1-1 > li');
for (const li of li1s)
li.classList.add('n11');
const li2s = document.querySelectorAll('.nav1-2 > li');
for (const li of li2s)
li.classList.add('n12');
<ul class="nav1-1">
<li >1.1.</li>
<li >1.2
<ul class="nav1-2">
<li>1.2.1</li>
<li>1.2.2</li>
</ul>
</li>
<li>1.3
<ul class="nav1-2">
<li>1.3.1</li>
<li>1.3.2</li>
</ul>
<li>1.4</li>
</ul>

Next child LI that is wrapped in a different UL

I have the following list:
<ul>
<li class="item">One</li>
<li class="item">Two</li>
<li class="item">Three
<ul>
<li class="item">Something Original</li>
<li class="item selected">Something</li>
</ul>
</li>
<li>Four
<ul>
<li class="item">I want this selected next</li>
<li class="item">Good</li>
</ul>
</li>
</ul>
Using jQuery, how do I find the next li with the class="item" since it is wrapped in a different container. Obviously I cannot do $(".selected").next(".item") so how else can I do it?
JSFiddle: http://jsfiddle.net/q3f6v7zz/
Since the li elements are nested and you know that you want the next appearing li with a particular class, you can use .index() and do something like this
var $li = $('.item'); // <--- get the list of all lis with class .item
var index = $li.index($('.selected')); // <--- find the index of the one with .selected amongst all the lis
console.log($li.eq(index+1).html()); // <--- index+1 because you need the next appearing li after selected
If you want to move the selected class on keydown something like this should do
var $li = $('.item');
$(document).on('keydown', function(e) {
if (e.keyCode == 40) {
var index = $li.index($('.selected'));
$li.eq(index).removeClass('selected');
index = (index+1) % $li.length; // <--- to rotate the values from 0 to count of li.item elements
$li.eq(index).addClass('selected');
}
});
var $li = $('.item');
$(document).on('keydown', function(e) {
if (e.keyCode == 40) {
var index = $li.index($('.selected'));
$li.eq(index).removeClass('selected');
index = (index+1) % $li.length;
$li.eq(index).addClass('selected');
}
});
.selected {
background: green;
color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<ul>
<li class="item">One</li>
<li class="item">Two</li>
<li>Three
<ul>
<li class="item">Something</li>
<li class="item selected">Something Else</li>
</ul>
</li>
<li>Four
<ul>
<li class="item">I want this selected next</li>
<li class="item">Good</li>
</ul>
</li>
</ul>
You can get the index of the selected element within all lis, and then increment that index to get the next one.
$("ul").on("click", "li.item.selected", function() {
var all_li = $("li.item");
var selected_index = all_li.index(this);
var next_li = all_li.eq((selected_index + 1) % all_li.length);
$(this).removeClass("selected");
next_li.addClass("selected");
});
.item.selected {
background-color: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li class="item">One</li>
<li class="item">Two</li>
<li class="item">Three
<ul>
<li class="item selected">Something</li>
</ul>
</li>
<li>Four
<ul>
<li class="item">I want this selected next</li>
<li class="item">Good</li>
</ul>
</li>
</ul>
I used the modulus so it will wrap around at the end.
Not sure what you are exactly looking for but you can use $(Element").parent().parent().find("li");
So in other words .parent() may be what you are looking for there is also .sibling() to find or you may want $('li').closest('ul').find('li')
which will go up the tree to find the nearest ul to the one you are looking for
https://api.jquery.com/closest/
You may also use:
Vanilla JS to do something similar to what was discussed by others with $index if it makes more sense to you:
Again this isn't as efficient but that is basically what JQuery is doing:
var myLis = document.getElementsByTagName('li');
var wantedIndex;
for(var i = 0;i<myLis.length; i++){
if(myLis[i].className === "active"){
wantedIndex = i+1; //gets the li which is next when selecting all lis
}
}

Keyboard navigation on nested list using only javascript

I have a ul list as follows. I am new to JS and trying to do a keyboard navigation, just the arrow keys using only javascript.
<ul id= nav>
<li class =subnav id =sub1> Companies
<ul id = hidden>
<li> item 1 </li>
<li> item 2 </li>
<li> item 3 </li>
</ul>
</li>
<li class =subnav id =sub2> LINKS
<ul id = hidden>
<li> item 4 </li>
<li> item 5 </li>
<li> item 6 </li>
</ul>
</li>
</ul>
my JS:
ul = document.getElementById("nav");
li = ul.getElementsByClassName("subnav");
ul2 = document.getElementById("hidden");
li = ul.getElementsByTagName("li");
function keyPress(e)
{
var e = e||window.event;
e =e.which||e.keyCode;
for( var i=0; i<li.length; i++)
{
var f = li[i].childNodes[0];
if(li[i].children.length > 0)
{
for(var j=0; j<li2.length; j++)
{
var x = li2[j].childNodes[0];
}
}
else
{
alert("no child nodes");
}
}
}
I am trying to set focus on the first item and then moving to each nodes using keys.
I suggest using jwerty, awesome keyboard events library.
I used jQuery and jWerty plugin.
Here is a quick JSFiddle: (Click the preview window and start hitting the down key)
http://jsfiddle.net/8QZrV/
As a basic idea, you should create an object with all the elements and then iterate through them, my basic example was like this:
var i = 0;
j = jQuery('.navigator li').length;
And then you hook it up in jwerty, I guess you want to make some actions there, so I guess you should also .focus() the current element.
Enjoy!

Categories