Move 'ul' element into the previous adjacent 'li'` element - javascript

I have a poorly formatted sidebar aside which I have no control over the structure. It automatically generates a ul from the body of the document, but incorrectly is placing the sub-lists outside of the respective parent element.
Valid html code should be along the lines of:
<ul>
<li>
<a>Text</a>
<ul>
<li>
<a>Text</a>
</li>
</ul>
</li>
</ul>
Where the child ul elements are nested inside the previous li element.
In the system I am using there is also the ability to have chapter headings, which should be the top level li elements in the main list
<ul>
<li><a>Text</a></li> <-- chapter title
<li><a>Text</a></li> <-- chapter title
<li><a>Text</a> <-- chapter title
<ul>
<li>
<a>Text</a> <-- sub-chapter title
</li>
</ul>
</li>
</ul>
Below is a test html structure and some of the javascript I've been trying to get to work.
At the moment it is currently iterating through the loop of all the found li elements and adding the next ul element into it. However, because it is doing it top down its running into a continual loop of adding in the element.
I tried using the .previousElementSibling but that was returning null for a lot of the elements in the list when trying to insert it to the element above.
This is where I'm up to, and hopefully someone can guide me to the correct way of working this data.
// -- get the sidebar list
const sidebarList = document.querySelector('div.sidebar-nav > ul');
const reorder = ((list) => {
const listItems = list.querySelectorAll(':scope li');
listItems.forEach((element) => {
// -- last h6 wont have a next sibling
if (element.nextElementSibling === null) return;
// -- if it is a second sub heading skip
if (element.nextElementSibling.tagName !== 'UL') return;
// -- add into the next element
element.innerHTML = element.innerHTML + element.nextElementSibling.innerHTML
});
})(sidebarList);
/*
------
HOW IT SHOULD RENDER
------
<aside class="sidebar">
<div class="sidebar-nav">
<ul>
<li>
<a>Heading level 1</a>
<ul>
<li>
<a>Heading level 2</a>
<ul>
<li>
<a>Heading level 3</a>
<ul>
<li>
<a>Heading Level 4 - 1</a>
</li>
<li>
<a>Heading Level 4 - 2</a>
<ul>
<li>
<a>Heading Level 5</a>
<ul>
<li>
<a>Heading level 6</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<a>Heading level 1</a>
<ul>
<li>
<a>Heading level 2</a>
<ul>
<li>
<a>Heading level 3</a>
<ul>
<li>
<a>Heading Level 4 - 1</a>
</li>
<li>
<a>Heading Level 4 - 2</a>
<ul>
<li>
<a>Heading Level 5</a>
<ul>
<li>
<a>Heading level 6</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</aside>
*/
<aside class="sidebar">
<div class="sidebar-nav">
<ul>
<li><a>Heading level 1</a></li>
<ul>
<li><a>Heading level 2</a></li>
<ul>
<li><a>Heading level 3</a></li>
<ul>
<li><a>Heading Level 4 - 1</a></li>
<li><a>Heading Level 4 - 2</a></li>
<ul>
<li><a>Heading Level 5</a></li>
<ul>
<li><a>Heading level 6</a></li>
</ul>
</ul>
</ul>
</ul>
</ul>
<li><a>Heading level 1</a></li>
<ul>
<li><a>Heading level 2</a></li>
<ul>
<li><a>Heading level 3</a></li>
<ul>
<li><a>Heading Level 4 - 1</a></li>
<li><a>Heading Level 4 - 2</a></li>
<ul>
<li><a>Heading Level 5</a></li>
<ul>
<li><a>Heading level 6</a></li>
</ul>
</ul>
</ul>
</ul>
</ul>
</ul>
</div>
</aside>

This seems to work:
document.querySelectorAll('ul').forEach(function(list) {
let prev = list.previousElementSibling;
if (!prev) return;
if (list.parentNode.nodeName == "UL" && prev.nodeName == "LI") {
prev.appendChild(list);
}
});
It goes through every ul, checking whether the ul has a direct ul parent and preceding li. If it does, it appends the ul to the preceding li.
https://jsfiddle.net/ago8by90/

Related

Codepen Returning Empty Array When Expecting Few Items

I'm trying to console log a variable that should contain an array. However, in Codepen, it returns an empty array. I'm expecting it to return contents of the list item.
Here's the Codepen along w/ some snippet code:
This is a React project, btw, which is why I'm using className instead of class.
HTML
<ul className="nav">
<li>
<a>Products</a>
<div className="subnav-block">
<ul>
<li>
<a>Product A</a>
</li>
<li>
<a>Product B</a>
</li>
</ul>
</div>
</li>
<li>
<a>About</a>
<div className="subnav-block">
<ul>
<li>
<a>About Us</a>
</li>
<li>
<a>Press</a>
</li>
</ul>
</div>
</li>
</ul>
JS
const nav = document.querySelectorAll('.nav > li');
console.log(nav)
Codepen is returning:
[Object NodeList] {Length: 0}
Because className isn't how you add a HTML class - it's simply class.
const nav = document.querySelectorAll(".nav > li");
console.log(nav);
<ul class="nav">
<li>
<a>Products</a>
<div class="subnav-block">
<ul>
<li>
<a>Product A</a>
</li>
<li>
<a>Product B</a>
</li>
</ul>
</div>
</li>
<li>
<a>About</a>
<div class="subnav-block">
<ul>
<li>
<a>About Us</a>
</li>
<li>
<a>Press</a>
</li>
</ul>
</div>
</li>
</ul>
To keep using className, use an attribute selector:
const nav = document.querySelectorAll("[className='nav'] > li");
console.log(nav);
<ul className="nav">
<li>
<a>Products</a>
<div className="subnav-block">
<ul>
<li>
<a>Product A</a>
</li>
<li>
<a>Product B</a>
</li>
</ul>
</div>
</li>
<li>
<a>About</a>
<div className="subnav-block">
<ul>
<li>
<a>About Us</a>
</li>
<li>
<a>Press</a>
</li>
</ul>
</div>
</li>
</ul>
If this is in a react app make sure you are calling the code in the correct place so that the dom has actually been updated with your markup before the query is being called. If you are using a class component move your code inside componentDidMount
componentDidMount() {
const nav = document.querySelectorAll(".nav > li");
console.log(nav);
}
If you are using hooks place it inside useEffect
useEffect(() => {
const nav = document.querySelectorAll('.nav > li')
console.log(nav)
}, [])
here is a working example using hooks: https://codesandbox.io/s/currying-monad-t6104?fontsize=14

Removing items by its contained text from html

I'm looking for a solution to remove items from my html when the page is loaded.
<ul class="nav navbar-nav">
<li class="dropdown">
Модули <span class="caret"></span>
<ul class="dropdown-menu" role="menu">
<li>
<strong>blabla</strong>
</li>
<li>
blabla6
</li>
<li role="presentation" class="divider"></li>
<li>
<strong>blabla5</strong>
</li>
<li>
blabla4
</li>
<li>
blabla3
</li>
<li>
blabla2
</li>
<li>
blabl1
</li>
</ul>
</li>
So I want to remove for example blabla4, blabla2, blabla1 including their <li> tags
This is what I've tried so far
<script type="text/javascript">
// select relevant elements
var elements = $('content-main');
// go through the elements and find the one with the value
elements.each(function(index, domElement) {
var $element = $(domElement);
// does the element have the text we're looking for?
if ($element.text() === "blabla2") {
$element.hide(); // hide the element with jQuery return false;
// jump out of the each
}
});
</script>
Just collect the list items, cast the resulting NodeList to an Array, and iterate over it, removing items whose innerHTML contains 'blabla'.
const listitems = Array.from(document.querySelectorAll('.dropdown-menu li'));
for (const listitem of listitems) {
if (listitem.innerHTML.indexOf('blabla') != -1) {
listitem.parentNode.removeChild(listitem);
}
}
<ul class="dropdown-menu" role="menu">
<li>
<strong>blabla</strong>
</li>
<li>
blabla6
</li>
<li role="presentation" class="divider"></li>
<li>
<strong>blabla5</strong>
</li>
<li>
blabla4
</li>
<li>
blabla3
</li>
<li>
blabla2
</li>
<li>
blabl1
</li>
</ul>
Use JQuery .remove() to delete HTML tags, But you should have a selector on the tags that you want to remove:
$( document ).ready(function() {
$('.dropdown-menu').find("li").slice(4, 5).remove();
$('.dropdown-menu').find("li").slice(5, 6).remove();
$('.dropdown-menu').find("li").slice(5, 6).remove();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="nav navbar-nav">
<li class="dropdown">
Модули <span class="caret"></span>
<ul class="dropdown-menu" role="menu">
<li>
<strong>blabla</strong>
</li>
<li>
blabla6
</li>
<li role="presentation" class="divider"></li>
<li>
<strong>blabla5</strong>
</li>
<li>
blabla4
</li>
<li>
blabla3
</li>
<li>
blabla2
</li>
<li>
blabl1
</li>
</ul>
</li>
</ul>
You can create a array data structure that will hold the values for which <li> should be removed then loop over to all the <li> tags inside the class dropdown-menu and remove them based on their content match with the array value:
$(document).ready(function(){
var removeText = ['blabla4', 'blabla2', 'blabla1'];
$('.dropdown-menu li').each(function(){
var text = $(this).find('a').text().trim();
if(removeText.indexOf(text) !== -1){
$(this).remove();
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="nav navbar-nav">
<li class="dropdown">
Модули <span class="caret"></span>
<ul class="dropdown-menu" role="menu">
<li>
<strong>blabla</strong>
</li>
<li>
blabla6
</li>
<li role="presentation" class="divider"></li>
<li>
<strong>blabla5</strong>
</li>
<li>
blabla4
</li>
<li>
blabla3
</li>
<li>
blabla2
</li>
<li>
blabl1
</li>
</ul>
</li>
You can do it using JQuery:
$(".dropdown-menu > li > a").each(function () {
var $this = $(this);
if ($this.html() == 'bla bla bla') {
$this.parent().remove();
}
});
An alternative is using the selector :contains and the function closest to select the parent element li.
This approach removes the element li with text blabla4
$('#remove').on('click', function() {
$("li a:contains(blabla4)").closest('li').remove();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="nav navbar-nav">
<li class="dropdown">
Модули <span class="caret"></span>
<ul class="dropdown-menu" role="menu">
<li>
<strong>blabla</strong>
</li>
<li>
blabla6
</li>
<li role="presentation" class="divider"></li>
<li>
<strong>blabla5</strong>
</li>
<li>
blabla4
</li>
<li>
blabla3
</li>
<li>
blabla2
</li>
<li>
blabl1
</li>
</ul>
</li>
</ul>
<button id='remove'>remove blabla4</button>
You can select the items using querySelectorAll, convert the NodeList to Array with Array.from, then iterate over each item and remove it.
const wordsToRemove = ['blabla3', 'blabla2'];
const items = Array.from(document.querySelectorAll('li > a'));
const result = items
.filter(item => wordsToRemove.includes(item.textContent))
.forEach(item => item.parentNode.parentNode.removeChild(item.parentNode));
<ul class="nav navbar-nav">
<li class="dropdown">
Модули <span class="caret"></span>
<ul class="dropdown-menu" role="menu">
<li>
<strong>blabla</strong>
</li>
<li>
blabla6
</li>
<li role="presentation" class="divider"></li>
<li>
<strong>blabla5</strong>
</li>
<li>
blabla4
</li>
<li>
blabla3
</li>
<li>
blabla2
</li>
<li>
blabl1
</li>
</ul>
</li>

How to get value of parent of li in submenu with jquery?

I create submenu and I need to get value of direct parent for Submenu"li"
this is my code
<div class="box">
<div class="col-sm-6">
<!-- main menu-->
<ul>
<li class="m-sub">
Cars
<!-- sub menu for cars-->
<ul class="sub">
<li> Audi</li>
<li> KiA</li>
</ul>
</li>
<li class="m-sub">
Tablets
<!--start sub menu for tablet -->
<ul class="sub">
<li> Phone</li>
<li> Sony</li>
</ul>
</li>
<li> Laptop</li>
<li> Glass </li>
</ul>
</div>
</div>
I need to get direct parent of sony for example, when I use ($(this).parent()).text() the result was phone not tablet.
I need to get value of parent "tablet" ,when select Audi i need to get cars
thanks in advance.
Tablets isn't inside the direct parent of your list item; you'll need a bit more jQuery:
var li = $('.sub > li')
console.log(
li.closest('.m-sub').contents().eq(2).text().trim() //=> 'Tablets'
)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li class="m-sub">
<span><i class="icon-tablet"></i></span> Tablets <span class="leftarr"><i class="icon-chevron-right"></i></span>
<ul class="sub">
<li> Phone
</li>
<li> Sony
</li>
</ul>
</li>
<li> Laptop
</li>
<li> Glass
</li>
</ul>

jQuery - has children without ANY class (not specific class)

So, guys, basically what I need to find:
----> All <li> which has <ul> without ANY class.
In this example I would ONLY want the <li> parent of the commented <ul>:
HTML
<ul id="myList">
<li>
<a>Item 1</a>
<ul class="someClass">
</ul>
</li>
<li> <!--------- Just Need This One ---------->
<a>Item 2</a>
<ul>
</ul>
</li>
<li>
<a>Item 3</a>
<ul class="justAnotherClass">
</ul>
</li>
</ul>
I'm aware of .not("[class]"), and so I tried something like:
$('#myList li').has('ul').not("[class]")
which obviously doesn't work because jQuery is looking for <li>'s which have a nested <ul> but doesn't have any class.
So, guys, can anyone please help? :)
Instead of using
$('#myList li').has('ul').not("[class]")
you can use
$('#myList > li').has('ul:not([class])')
you can also use this selector
$('#myList > li:has(ul:not([class]))')
$('#myList > li:has(ul:not([class]))').css('background' , 'red');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="myList">
<li>
<a>Item 1</a>
<ul class="someClass">
</ul>
</li>
<li>
<a>Item 2</a>
<ul> <!--------- Just Need This One ---------->
</ul>
</li>
<li>
<a>Item 3</a>
<ul class="justAnotherClass">
</ul>
</li>
</ul>
Use filter() over li and then check for children ul without any class with .length,
$('#myList li').filter(function(){
return $('ul:not([class])',this).length > 0 //$('child','context') selector
//or $('> ul:not([class])',this) in case of direct <ul>
});
var $li = $('#myList li').filter(function(){
return $('ul:not([class])',this).length > 0 //$('child','context') selector
});
console.log($li)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<ul id="myList">
<li>
<a>Item 1</a>
<ul class="someClass">
</ul>
</li>
<li>
<a>Item 2</a>
<ul> <!--------- Just Need This One ---------->
</ul>
</li>
<li>
<a>Item 3</a>
<ul class="justAnotherClass">
</ul>
</li>
</ul>
Try $('#myList li ul').filter(':not([class])'):
console.log($('#myList li ul').filter(':not([class])').html());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
<ul id="myList">
<li>
<a>Item 1</a>
<ul class="someClass">
</ul>
</li>
<li>
<a>Item 2</a>
<ul> <!--------- Just Need This One ---------->
</ul>
</li>
<li>
<a>Item 3</a>
<ul class="justAnotherClass">
</ul>
</li>
</ul>
I really hate that 'too short to be an answer' ....
$('#myList li > ul:not([class])')
Seems to be the simplest one that does the job ?

Zurb Foundation dropdown position to very first parent

In Foundation 6 by default, dropdown menus appear on the same level as the parent like so:
Is there a way to make it so that the menu appears on the same level as the first menu? This is the desired outcome:
I have looked through the documentation and cannot seem to find a way to do it. Thanks in advance!
This is a snippet of the default code from Foundation site
<ul class="dropdown menu" data-dropdown-menu>
<li>
<a>Item 1</a>
<ul class="menu">
<li>Item 1A Loooong
</li>
<li>
<a href='#'> Item 1 sub</a>
<ul class='menu'>
<li><a href='#'>Item 1 subA</a>
</li>
<li><a href='#'>Item 1 subB</a>
</li>
<li>
<a href='#'> Item 1 sub</a>
<ul class='menu'>
<li><a href='#'>Item 1 subA</a>
</li>
<li><a href='#'>Item 1 subB</a>
</li>
</ul>
</li>
<li>
<a href='#'> Item 1 sub</a>
<ul class='menu'>
<li><a href='#'>Item 1 subA</a>
</li>
</ul>
</li>
</ul>
</li>
<li>Item 1B
</li>
</ul>
</li>
<li>
Item 2
<ul class="menu">
<li>Item 2A
</li>
<li>Item 2B
</li>
</ul>
</li>
<li>Item 3
</li>
<li><a href='#'>Item 4</a>
</li>
</ul>
Here is a fiddle of the default: http://jsfiddle.net/65017wc2/
Here is a possible fix to do this :
.dropdown.menu {
top: 0!important;
}
.dropdown.menu {
position: relative;
}
.dropdown.menu li {
position: static !important;
}
.dropdown.menu ul {
margin-top: -1px;
}
I overwrite the relative position of li elements to the native static position and made the main ul relative, so all the dropdown menus are relative to the main ul menu.
See this fiddle

Categories

Resources