Javascript remove all empty innerHTML elements children - javascript

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.

Related

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

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>

Using jQuery .nextUntil() on split lists

Is it possible to get the .nextUntil() to work on split lists, or get the same functionality?
So I am trying to implement the ever so popular shift select for my items, and since they are ordered in a list in my application I want to be able to select across <ul> borders.
I have the following set of DOM elements:
<ul class="current">
<li class="item">first</li>
<li class="item clicked">second</li>
<li class="item">third</li>
<li class="item">fourth</li>
</ul>
<ul class="later">
<li class="item">fifth</li>
<li class="item selected">sixth</li>
<li class="item">seventh</li>
</ul>
And using something like this:
$('li.clicked').nextUntil('li.selected');
I'd like a list containing the following elements
[ <li class="item">third</li>,
<li class="item">fourth</li>,
<li class="item">fifth</li> ]
However all I get is the elements leading up to the split </ul>. Is there any way of doing this? I have also tried to first selecting all items with $('.item')and then using .nextUntil() on them without any luck.
Is this what you are looking for?
$('li').slice($('li').index($('.clicked'))+1,$('li').index($('.selected')));
For reference
Jquery.Index
Jquery.Slice
Edit
So if you do
$('li')
you will get an array of all elements 'li' getting:
[<li class=​"item">​first​</li>​,
<li class=​"item clicked">​second​</li>​,
<li class=​"item">​third​</li>​,
<li class=​"item">​fourth​</li>​,
<li class=​"item">​fifth​</li>​,
<li class=​"item selected">​sixth​</li>​,
<li class=​"item">​seventh​</li>​]
Since it is an array you can slice him to get an sub array you just need two positions, where to start and here to finish.
//start
$('li').index($('.clicked'))+1 // +1 because you dont want to select him self
//end
$('li').index($('.selected'))
For better preformance you should before create an array with all li so it will not search all dom 3 times for the array of 'li'
var array = $('li');
var subarray = array.slice(array.index($('.clicked'))+1,array.index($('.selected')));
Assuming these lists cannot be merged into one, it is impossible using the nextUntil method. This is because of how jQuery performs traversing. According to the documentation,
Get all following siblings of each element up to but not including the element matched by the selector, DOM node, or jQuery object passed.
fifth is not a sibling of the clicked element, but rather it is a child of the sibling of the element's parents.
I came up with two possible solutions.
Solution 1: Combine NEXT and PREV traversals
Assuming that .clicked is always in the first list and .selected is always in the second list, combining prevAll() with nextAll() should do the trick. This assumes that the order is the same.
var siblings = $("li.clicked").nextAll()
Get all siblings of the current element AFTER the element itself.
var distantSiblings = $("li.selected").prevAll();
Get all distant siblings after the first element, but before the second one.
siblings.push(distantSiblings);
Combine them into two and then iterate over each element.
var siblings = $("li.clicked").nextAll()
var distantSiblings = $("li.selected").prevAll();
siblings.push(distantSiblings);
siblings.each(function() {
$(this).addClass("blue");
});
.blue { color: blue; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="current">
<li class="item">first</li>
<li class="item clicked">second</li>
<li class="item">third</li>
<li class="item">fourth</li>
</ul>
<ul class="later">
<li class="item">fifth</li>
<li class="item selected">sixth</li>
<li class="item">seventh</li>
</ul>
http://jsfiddle.net/r15z10o4/
Note:
You will notice that the above code works, however it might not be the optimal solution. This is only confirmed to work for your example above. There may also be a less verbose solution.
Solution 2 (Find index of all list items)
Another idea is to find the index of all items, and collect the elements that are sandwiched between those two indices. You will then want to use the 'slice' selector to get the range in between.
var items = $(".item");
var clicked = $(".clicked");
var selected = $(".selected");
var clickIndex = items.index(clicked);
var selectIndex = items.index(selected);
$("li").slice(clickIndex + 1, selectIndex).addClass("blue");
var clicked = $(".clicked");
var selected = $(".selected");
var clickIndex = $("li").index(clicked);
var selectIndex = $("li").index(selected);
$("li").slice(clickIndex+1, selectIndex).addClass("blue");
.blue { color: blue; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="current">
<li class="item">first</li>
<li class="item clicked">second</li>
<li class="item">third</li>
<li class="item">fourth</li>
</ul>
<ul class="later">
<li class="item">fifth</li>
<li class="item selected">sixth</li>
<li class="item">seventh</li>
</ul>
You can do it manually by selecting all these items at once, and using loops.
Consider the parent element, let's say "container":
<div id="container">
<ul class="current">
<li class="item">first</li>
<li class="item clicked">second</li>
<li class="item">third</li>
<li class="item">fourth</li>
</ul>
<ul class="later">
<li class="item">fifth</li>
<li class="item selected">sixth</li>
<li class="item">seventh</li>
</ul>
</div>
Now, you can select all these items:
var $items = $("#container > ul > li.item"); // or simply $("#container .item");
And iterate through them:
var $items = $(".item"), $result = $(), found = false;
for (var i = 0; i < $items.length; i++)
{
$currentItem = $items.eq(i);
if ($currentItem.is('.clicked')) {
found = true;
continue;
}
if ($currentItem.is('.selected'))
break;
if (found)
$result = $result.add($currentItem);
}
console.log($result);
Here is the working JSFiddle demo.
In any case it feels like you will need to define groups of li.
I think the easiest is to create a function getting a list of lis that you can request any way you want then to filter the el you are interested in.
function elRange(elList, start, end){
// we do not use indexOf directly as elList is likely to be a node list
// and not an array.
var startI = [].indexOf.call(elList, start);
var endI = [].indexOf.call(elList, end);
return [].slice.call(elList, startI, endI + 1);
}
// request the group of *ordered* elements that can be selected
var liList = document.querySelectorAll('ul.current > li, ul.later > li');
var selectionEnd = document.querySelector('.selected');
[].forEach.call(liList, function(li){
li.addEventListener('click', function(){
var selected = elRange(liList, li, selectionEnd);
selected.forEach(function(el){
el.classList.add('red');
})
});
});
.selected {
color: blue;
}
.red {
color: red;
}
<ul class="current">
<li class="item">first</li>
<li class="item">second</li>
<li class="item">third</li>
<li class="item">fourth</li>
</ul>
<ul class="later">
<li class="item">fifth</li>
<li class="item selected">sixth</li>
<li class="item">seventh</li>
</ul>

How to get specific element javascript based on style

I need to acces an element that has a certain style.
This is my structure
<ul>
<li> Hi </li>
<li> bye </li>
<li> third one </li>
</ul>
The list items are placed on top of each other (last one first) and I can dislike something or like something. Once I do that, it gets a style display:none like following:
<ul>
<li> Hi </li>
<li> bye </li>
<li style:"display:none;"> third one </li>
</ul>
Now after I did that I want to be able to acces the last element that does not have display:none, (the bye) how can I do this?
I was thinking of something in the form of:
var myId = $("#slider > ul li").last().attr("id");
But obviously I always get the ID of the item that is hidden since its still there.
Can I do something like select last where !display:hidden ?
Can I do something like select last where !display:hidden ?
Yes, with jQuery's :visible pseudo-class:
var myId = $("#slider > ul li:visible").last().attr("id");
(Note: Your li elements don't actually have id values, but that's a tweak.)
Live Example:
var listItem = $("#slider > ul li:visible").last();
$("<p>")
.text("Text of last visible item: " + listItem.text())
.appendTo(document.body);
<div id="slider">
<ul>
<li>Hi</li>
<li>bye</li>
<li style="display:none;">third one</li>
</ul>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Can use ':visible' selector
var myId = $("#slider > ul li:visible").last().attr("id");
It should work using:
$("#slider > ul li:visible").last().attr("id");
https://api.jquery.com/visible-selector/
so your inline styling is a bit off it should be
<ul>
<li> Hi </li>
<li> bye </li>
<li style="display:none;"> third one </li>
</ul>
You could do a few different things, best is probably just iterate through and check for where display = none, then go to the previous element:
$('ul').children().each(function(e) {
if($(this)[0].style.display == 'none') {
console.log($(this).prev());
}
})

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

jquery next siblings

I've been trying to get this problem solved, but I can't seem to figure it out without some serious workarounds.
if I have the following HTML:
<ul>
<li class="parent"> headertext </li>
<li> text </li>
<li> text </li>
<li> text </li>
<li class="parent"> headertext </li>
<li> text </li>
<li> text </li>
</ul>
Now, how do I now just select the <li> tags following the first parent (or second, for that matter)? Basically selecting an <li> with class="parent" and the following siblings until it reaches another <li> with the parent class.
I could restructure the list with nested lists, but I don't want to do that. Any suggestions?
actually, you can easily do this using nextUntil().
no need to write your own "nextUntil" since it already exists.
ex. -
$(".a").nextUntil(".b");
or as suggested by Vincent -
$(".parent:first").nextUntil(".parent");
The root of your problem is that the <li>s you have classed as parent really are NOT parents of the <li>s "below" them. They are siblings. jQuery has many, many functions that work with actual parents. I'd suggest fixing your markup, really. It'd be quicker, cleaner, easier to maintain, and more semantically correct than using jQuery to cobble something together.
I don't think there is a way to do this without using each since any of the other selectors will also select the second parent and it's next siblings.
function getSibs( elem ) {
var sibs = [];
$(elem).nextAll().each( function() {
if (!$(this).hasClass('parent')) {
sibs.push(this);
}
else {
return false;
}
});
return $(sibs);
}
You will have to run the loop yourself since jQuery does not know how to stop on a specific condition.
jQuery.fn.nextUntil = function(selector)
{
var query = jQuery([]);
while( true )
{
var next = this.next();
if( next.length == 0 || next.is(selector) )
{
return query;
}
query.add(next);
}
return query;
}
// To retrieve all LIs avec a parent
$(".parent:first").nextUntil(".parent");
But you may be better using a really structured list for your parent/children relationship
<ul>
<li class="parent"> <span>headertext</span>
<ul>
<li> text </li>
<li> text </li>
<li> text </li>
</ul>
</li>
<li class="parent"> <span>headertext</span>
<ul>
<li> text </li>
<li> text </li>
</ul>
</li>
</ul>
$("li.parent ~ li");
I know this is a very old thread, but Jquery 1.4 has a method called nextUntil, which could be useful for this purpose:
http://api.jquery.com/nextUntil/
<html>
<head>
<script type="text/javascript">
$(document).ready(function() {
$("a").click(function() {
var fred = $("li").not('.parent').text();
$('#result').text(fred);
});
});
</script>
</head>
<body>
Click me
<ul>
<li class="parent"> headertextA </li>
<li> text1 </li>
<li> text2 </li>
<li> text3 </li>
<li class="parent"> headertextB </li>
<li> text4 </li>
<li> text5 </li>
</ul>
<div id="result"></div>
</body>
</html>

Categories