Collapse and Expand Tree structure in Javascript - javascript

I need a help on collapse and Expand using Javascript.
Here is my running code (.html)
<h2>Test</h2>
<html lang="en">
<head>
<META http-equiv="Content-Type" content="text/html; charset=utf-16">
<title></title>
<script type="text/javascript">
function toggleDisplay(element)
{
element.style.display = element.style.display === 'none' ? '' : 'none';
};
function toggleDisplayAll(elements)
{
for(var i=0; i<elements.length; i++)
{
toggleDisplay(elements[i]);
}
}
</script>
</head>
<body>
<ul>
<a onclick="toggleDisplayAll(this.parentNode.getElementsByTagName('ul')); return false;" href="#">Name:</a>
<ul style="display:none;">
<a onclick="toggleDisplayAll(this.parentNode.getElementsByTagName('ul')); return false;" href="#">Address: </a>
<ul style="display:none;">
<a onclick="toggleDisplayAll(this.parentNode.getElementsByTagName('li')); return false;" href="#">Subject: </a>
<ul style="display:none;">
<li style="display:none;">Id
</li>
</ul>
</ul>
</ul>
</ul>
</body>
</html>
If you run this html, you will get out put as
Name
on click of Name, it is showing all the child elements
Name:
Address:
Subject:
On click of Subject it is showing Id
Name:
Address:
Subject:
. Id
What i want here is each child should open only on parent click.
When run the html, only Name will dispaly
Name:
On click of Name, only Address will be displayed as a child element.
Name:
Address:
Onclick of Address, only Subject will display
Name:
Address:
Subject:
Than finally on click of Subject, id will show up
Name:
Address:
Subject:
. Id
How to implement this tree structure. what i am doing wrong here. please suggest me.

Check this:
$('.expand').click(function() {
$('ul', $(this).parent()).eq(0).toggle();
});
ul li ul {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li>
<a class="expand">Root</a>
<ul>
<li>
<a class="expand">Child</a>
<ul>
<li>
<a class="expand">Super Child</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
Edit
If you don't want to use jQuery, you can try this:
var expander = document.querySelectorAll('.expand');
for (var i = 0; i < expander.length; ++i) {
expander[i].onclick = function() {
var ul = this.parentElement.querySelectorAll('ul')[0];
if (ul.offsetHeight > 0) {
ul.style.display = 'none';
} else {
ul.style.display = 'block';
}
}
}
ul li ul {
display: none;
}
<ul>
<li>
<a class="expand">Root</a>
<ul>
<li>
<a class="expand">Child</a>
<ul>
<li>
<a class="expand">Super Child</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>

You want to target just the first child element, instead of looping through all of them. You should also try to separate your logic from your markup. Give unobtrusive JavaScript a read.
function toggle() {
var ls = this.parentNode.getElementsByTagName('ul')[0],
styles, display;
if (ls) {
styles = window.getComputedStyle(ls);
display = styles.getPropertyValue('display');
ls.style.display = (display === 'none' ? 'block' : 'none');
}
}
var eles = document.querySelectorAll('.ele');
Array.prototype.slice.call(eles).forEach(function (e) {
e.addEventListener('click', toggle);
});
ul ul {
display: none;
}
.ele {
cursor: pointer;
}
.ele:hover {
color: red;
}
<ul>
<li><span class="ele">One</span>
<ul>
<li><span class="ele">Two</span>
<ul>
<li><span class="ele">Three</span>
<ul>
<li><span class="ele">Four</span></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>

In pure JavaScript
document.querySelector('.tree-structure').addEventListener('click', (e) => {
const el = e.target;
const sibling = el.nextSibling.nextSibling;
if (el && el.className == 'toggle' && sibling) {
sibling.classList.toggle('show');
}
});
ul ul {
display: none;
}
.show {
display: block;
}
<ul class="tree-structure">
<li>
<button class="toggle">1st Level</button>
<ul>
<li>
<button class="toggle">2nd Level</button>
<ul>
<li>
<button class="toggle">3rd Level</button>
<ul>
<li>
<button class="toggle">4th Level</button>
</li>
<li>
<button class="toggle">4th Level</button>
</li>
</ul>
</li>
<li>
<button class="toggle">3rd Level</button>
<ul>
<li>
<button class="toggle">4th Level</button>
</li>
<li>
<button class="toggle">4th Level</button>
</li>
</ul>
</li>
</ul>
</li>
<li>
<button class="toggle">2nd Level</button>
<ul>
<li>
<button class="toggle">3rd Level</button>
<ul>
<li>
<button class="toggle">4th Level</button>
</li>
<li>
<button class="toggle">4th Level</button>
</li>
</ul>
</li>
<li>
<button class="toggle">3rd Level</button>
<ul>
<li>
<button class="toggle">4th Level</button>
</li>
<li>
<button class="toggle">4th Level</button>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<button class="toggle">1st Level</button>
<ul>
<li>
<button class="toggle">2nd Level</button>
<ul>
<li>
<button class="toggle">3rd Level</button>
<ul>
<li>
<button class="toggle">4th Level</button>
</li>
<li>
<button class="toggle">4th Level</button>
</li>
</ul>
</li>
<li>
<button class="toggle">3rd Level</button>
<ul>
<li>
<button class="toggle">4th Level</button>
</li>
<li>
<button class="toggle">4th Level</button>
</li>
</ul>
</li>
</ul>
</li>
<li>
<button class="toggle">2nd Level</button>
<ul>
<li>
<button class="toggle">3rd Level</button>
<ul>
<li>
<button class="toggle">4th Level</button>
</li>
<li>
<button class="toggle">4th Level</button>
</li>
</ul>
</li>
<li>
<button class="toggle">3rd Level</button>
<ul>
<li>
<button class="toggle">4th Level</button>
</li>
<li>
<button class="toggle">4th Level</button>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>

Related

Filter nested elements using javascript

I have a nested list with groups and subgroups, and links contained therein.
function filtersearch() {
var input, filter, ul, tr, search, i, txtValue;
input = document.getElementById("myInput");
filter = input.value.toUpperCase();
ul = document.getElementById("list");
tr = ul.getElementsByTagName("li");
for (i = 0; i < tr.length; i++) {
search = tr[i].getElementsByTagName("a")[0];
txtValue = search.textContent || search.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
<input id="myInput" class="RxSearchbox" onkeyup="filtersearch()" placeholder="Filter" title="Filter list" type="text">
<div id="list">
<ul>
<li>
Group 1
<ul>
<li>
Subgroup1
<ul class="nav">
<li>
link1
</li>
<li>
link2
</li>
<li>
link3
</li>
<li>
link4
</li>
<li>
link5
</li>
</ul>
</li>
<li>
Subgroup2
<ul class="nav">
<li>
link6
</li>
<li>
link7
</li>
<li>
link8
</li>
<li>
link9
</li>
<li>
link10
</li>
</ul>
</li>
<li>
Subgroup3
<ul class="nav">
<li>
link11
</li>
<li>
link12
</li>
<li>
link13
</li>
<li>
link14
</li>
<li>
link15
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
I have an input box and am using Javascript to try to filter that list (e.g. if you type in "1", it would only show "1", "10", "11", "12", "13" etc., but it's only doing so on the parent element (i.e. will only show "Group 1") and not the children.
How can I get the script to filter all elements of li, parents and children, not just the parent?
Based on your HTML structure, the following JS code shall work fine.
P.S. Only unhide_parent() function was needed on top of your code.
function filtersearch() {
var input, filter, ul, tr, search, i, txtValue;
input = document.getElementById("myInput");
filter = input.value.toUpperCase();
ul = document.getElementById("list");
tr = ul.getElementsByTagName("li");
for (i = 0; i < tr.length; i++) {
search = tr[i].getElementsByTagName("a")[0];
txtValue = search.textContent || search.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
unhide_parent(tr[i])
} else {
tr[i].style.display = "none";
}
}
}
unhide_parent = (ele) => {
if(ele.parentNode.parentNode.style.display=="none") {
ele.parentNode.parentNode.style.display='';
unhide_parent(ele.parentNode.parentNode)
}
};
<input id="myInput" class="RxSearchbox" onkeyup="filtersearch()" placeholder="Filter" title="Filter list" type="text">
<div id="list">
<ul>
<li>
Group 1
<ul>
<li>
Subgroup1
<ul class="nav">
<li>
link1
</li>
<li>
link2
</li>
<li>
link3
</li>
<li>
link4
</li>
<li>
link5
</li>
</ul>
</li>
<li>
Subgroup2
<ul class="nav">
<li>
link6
</li>
<li>
link7
</li>
<li>
link8
</li>
<li>
link9
</li>
<li>
link10
</li>
</ul>
</li>
<li>
Subgroup3
<ul class="nav">
<li>
link11
</li>
<li>
link12
</li>
<li>
link13
</li>
<li>
link14
</li>
<li>
link15
</li>
</ul>
</li>
</ul>
</li>
<li>
Group 2
<ul>
<li>
Subgroup-dup-1
<ul class="nav">
<li>
link-dup-1
</li>
<li>
link-dup-2
</li>
<li>
link-dup-3
</li>
<li>
link-dup-4
</li>
<li>
link-dup-5
</li>
</ul>
</li>
<li>
Subgroup-dup-2
<ul class="nav">
<li>
link-dup-6
</li>
<li>
link-dup-7
</li>
<li>
link-dup-8
</li>
<li>
link-dup-9
</li>
<li>
link-dup-10
</li>
</ul>
</li>
<li>
Subgroup-dup-3
<ul class="nav">
<li>
link-dup-11
</li>
<li>
link-dup-12
</li>
<li>
link-dup-13
</li>
<li>
link-dup-14
</li>
<li>
link-dup-15
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
I would start by placing a data attribute data-id within each li that corresponds with the link number. Then get all the .nav li elements using querySelectorAll('.nav li') this will return everyone of the list elements. Then you can iterate over these using a forEach loop checking the el.dataset.id to see if it includes the input.value, if it does we have a match.
You can then reference these however you wish, hidden elements you turn visible or highlighting the list-items a tag...
function filtersearch() {
const input = document.getElementById("myInput");
const lists = document.querySelectorAll('.nav li')
lists.forEach((list, i) => {
const dataId = list.dataset.id
dataId.includes(input.value) && input.value !== '' ?
list.children[0].classList.add('green') :
list.children[0].classList.remove('green')
})
}
.green {
background-color: lightgreen;
color: darkgreen;
}
<input id="myInput" class="RxSearchbox" onkeyup="filtersearch()" placeholder="Filter" title="Filter list" type="text">
<div id="list">
<ul>
<li>
Group 1
<ul>
<li>
Subgroup1
<ul class="nav">
<li data-id="1">
link1
</li>
<li data-id="2">
link2
</li>
<li data-id="3">
link3
</li>
<li data-id="4">
link4
</li>
<li data-id="5">
link5
</li>
</ul>
</li>
<li>
Subgroup2
<ul class="nav">
<li data-id="6">
link6
</li>
<li data-id="7">
link7
</li>
<li data-id="8">
link8
</li>
<li data-id="9">
link9
</li>
<li data-id="10">
link10
</li>
</ul>
</li>
<li>
Subgroup3
<ul class="nav">
<li data-id="11">
link11
</li>
<li data-id="12">
link12
</li>
<li data-id="13">
link13
</li>
<li data-id="14">
link14
</li>
<li data-id="15">
link15
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>

How to toggle clicked menu link and untoggle all of toggled links

My website came with this javascript for submenu toggle that work, however, when I click a new menu link the rest toggled links does not close so all them are left open.
How to toggle only clicked link and untoggle or remove all "open" links? If you have a new format of javascript that's fine too.
jQuery(function($) {
$('li.has-submenu span.icon-caret').click(function(e){
var $me = $(this);
if($me.siblings('.wsite-menu-wrap').hasClass('open')) {
$me.siblings('.wsite-menu-wrap').toggleClass('open');
} else {
$me.siblings('.wsite-menu-wrap').addClass('open');
}
});
});
HTML:
<div id="navMobile" class="nav mobile-nav">
<ul class="wsite-menu-default">
<li class="wsite-menu-item-wrap has-submenu">
<a class="wsite-menu-item">Style</a>
<span class="icon-caret"></span>
<div class="wsite-menu-wrap" style="display:none">
<ul class="wsite-menu">
</ul>
</div>
</li>
<li class="wsite-menu-item-wrap has-submenu">
<a class="wsite-menu-item">Life</a>
<span class="icon-caret"></span>
<div class="wsite-menu-wrap" style="display:none">
<ul class="wsite-menu">
</ul>
</div>
</li>
<li class="wsite-menu-item-wrap has-submenu">
<a class="wsite-menu-item">Work</a>
<span class="icon-caret"></span>
<div class="wsite-menu-wrap" style="display:none">
<ul class="wsite-menu">
</ul>
</div>
</li>
</ul>
</div>
Remove the open class from each element first : $('.open').removeClass('open');
First I would remove the display: none if you have no animations with jQuery on the menues. Further I don't know your CSS but you can try it like the following example
$(document).ready(function() {
$('#navMobile .wsite-menu-item').on('click', toggleMenuLink);
function toggleMenuLink(event) {
var target = $(event.target).parent();
event.preventDefault();
target.siblings().removeClass('open');
if (target.hasClass('has-submenu')) {
target.toggleClass('open');
}
}
});
#navMobile .has-submenu > .wsite-menu-wrap,
#navMobile .has-submenu > .wsite-menu {
display: none;
}
#navMobile .has-submenu.open > .wsite-menu-wrap,
#navMobile .has-submenu.open > .wsite-menu {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="navMobile" class="nav mobile-nav">
<ul class="wsite-menu-default">
<li class="wsite-menu-item-wrap has-submenu">
<a class="wsite-menu-item">Style</a>
<span class="icon-caret"></span>
<div class="wsite-menu-wrap">
<ul class="wsite-menu">
<li class="wsite-menu-item-wrap">
<a class="wsite-menu-item">New 1</a>
<span class="icon-caret"></span>
</li>
</ul>
</div>
</li>
<li class="wsite-menu-item-wrap has-submenu">
<a class="wsite-menu-item">Life</a>
<span class="icon-caret"></span>
<div class="wsite-menu-wrap">
<ul class="wsite-menu">
<li class="wsite-menu-item-wrap">
<a class="wsite-menu-item">New 2</a>
<span class="icon-caret"></span>
</li>
</ul>
</div>
</li>
<li class="wsite-menu-item-wrap has-submenu">
<a class="wsite-menu-item">Work</a>
<span class="icon-caret"></span>
<div class="wsite-menu-wrap">
<ul class="wsite-menu">
</ul>
</div>
</li>
</ul>
</div>
I was able to crack the code...
$('li.has-submenu span.icon-caret').on('click', function(e) {
event.preventDefault();
var $me = $(this);
if ($me.parent().hasClass('open')) {
$me.parent().removeClass('open');
$me.find('.open').removeClass('open');
}
else {
$('.open').removeClass('open');
$me.parents('.has-submenu').children('.wsite-menu-wrap').addClass('open');
}
});

Get inner text to the before lastchild element

I have the following example, where I'm trying to extract the inner text for "a" element , which is a child of "i" element located before the last child of the list.
Example:
<div class="lms-pagination" id="lms-pagination">
<ul>
<li class="disabled">
<span class="current prev">Previous page</span>
</li>
<li class="active">
<span class="current en-lang">1</span>
</li>
<li>
<a data-page="1" class="page-link en-lang">2</a>
</li>
<li>
<a data-page="2" class="page-link en-lang">3</a>
</li>
<li class="disabled"><span class="ellipse">...</span></li>
<li>
<a data-page="17" class="en-lang">18</a> <!-- Try to extract 18 in this example -->
</li>
<li>
Next page
</li>
</ul>
</div>
I'm trying to use something like
lastPage = await newPage.$$eval('#lms-pagination>ul>li:nth-child(???)>a', el => el.innerText);
Using Javascript:=
function myFunction() {
var list = document.getElementById("myList").lastChild.innerHTML;
document.getElementById("demo").innerHTML = list;
var items = document.querySelectorAll("li");
var lastchild = items[items.length - 2].innerHTML;
document.getElementById("demo1").innerHTML = lastchild;
}
<p>Example list:</p>
<ul id="myList">
<li>Coffee</li>
<li>Tea</li>
<li>Coffee22</li>
<li>Tea22</li>
<li>Coffee33</li>
<li>Tea33</li>
</ul>
<p>Click the button to get the HTML content of the list's last child node.</p>
<button onclick="myFunction()">Try it</button>
<p id="demo"></p>
<p id="demo1"></p>
<script>
</script>
Using jQuery:=
alert($("li:nth-last-of-type(2)").text())
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="lms-pagination" id="lms-pagination">
<ul>
<li class="disabled">
<span class="current prev">Previous page</span>
</li>
<li class="active">
<span class="current en-lang">1</span>
</li>
<li>
<a data-page="1" class="page-link en-lang">2</a>
</li>
<li>
<a data-page="2" class="page-link en-lang">3</a>
</li>
<li class="disabled"><span class="ellipse">...</span></li>
<li>
<a data-page="17" class="en-lang">18</a>
<!-- Try to extract 18 in this example -->
</li>
<li>
Next page
</li>
</ul>
</div>
You can get second last child value like this.
$("li").eq(-2).text()

Can't select a child

I got this HTML:
<ul>
<li>Test</li>
<li>Test2</li>
<a href="#" class="with-sub"><li>Test3</li>
<ul class="sub">
<li>Sub1</li>
<li>Sub2</li>
<li>Sub3</li>
</ul>
</a>
</ul>
And jQuery:
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
<script>
$( document ).ready(function() {
$(".sub").hide();
});
$(".with-sub").click(function() {
$(this).find(".sub").slideDown( "slow", function() {
});
});
</script>
When I click on the link with "with-sub" class nothing happens.
You can change your html and jquery to following:
$(".with-sub").click(function() {
$(this).next("ul").slideDown();
});
.sub {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<a href="#">
<li>Test</li>
</a>
<a href="#">
<li>Test2</li>
</a>
<a href="#" class="with-sub">
<li>Test3
<ul class="sub">
<li>Sub1
</li>
<li>Sub2
</li>
<li>Sub3
</li>
</ul>
</li>
</a>
</ul>
As mention in comments your html is not correct. ul elements can contain zero or more li elements, eventually mixed with ol and ul elements.
$(".with-sub").click(function() {
$(this).find("ul").toggle();
});
.sub {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li>
Test
</li>
<li>
Test2
</li>
<li class="with-sub">Test3
<ul class="sub">
<li>
Sub1
</li>
<li>
Sub2
</li>
<li>
Sub3
</li>
</ul>
</li>
</ul>
Your current code does not work because you are attempting to bind an event to elements that do not exist in the DOM yet - you need to put the click() handler inside the DOMReady handler.
Also note that your HTML is invalid - only li elements can be direct descendants of the ul element. Try this:
<ul>
<li>
Test
</li>
<li>
Test2
</li>
<li>
Test3
<ul class="sub">
<li>
Sub1
</li>
<li>
Sub2
</li>
<li>
Sub3
</li>
</ul>
</li>
</ul>
Once you've fixed your HTML, the following jQuery will work:
$(document).ready(function() {
$(".sub").hide();
$(".with-sub").click(function() {
$(this).siblings(".sub").slideDown("slow");
});
});
Working example

Expand JAVASCRIPT MENU

can anyone tell me why this funcion drop's me to the top of the page ?
any ideas how to solve this problem?
Or any suggestion's of a better code to use?
P.S. this is for ebay template, quite a big menu section.
<script type="text/javascript">
startList = function() {
if (document.getElementById) {
navRoot = document.getElementById("nav");
for (i=0; i<navRoot.childNodes.length; i++) {
node = navRoot.childNodes[i];
if (node.nodeName=="LI") {
node.onclick=function() {
this.className = (this.className == "on") ? "off" : "on";
}
}
}
}
}
window.onload=startList;
</script>
I have a basic markup:
<ul id="nav">
<li>Home </li>
<li>About >
<ul>
<li>History </li>
<li>Team </li>
<li>Offices </li>
</ul>
</li>
<li>Services >
<ul>
<li>Web Design </li>
<li>Internet Marketing </li>
<li>Hosting </li>
<li>Domain Names </li>
<li>Broadband </li>
</ul>
</li>
<li>Contact Us >
<ul>
<li>United Kingdom</li>
<li>France</li>
<li>USA</li>
<li>Australia</li>
</ul>
</li>
</ul>
Why don't you just simplify it? You could just try:
<li>Home </li>
and change your JS to:
function List(element)
{
element.parentNode.className = (element.parentNode.className == "on") ? "off" : "on";
}
That's much simpler.
EDIT: Or you just replace href="#" with href="javascript:return(false);". As # is a reference to an anchor which does not exist, it will take you to the top of the page.
Here i have done bin using jQuery:
1) Include latest jquery.js file in html header tag first.
2) HTML:
<ul id="nav">
<li>
<a href="#">
Home
</a>
</li>
<li>
<a href="#">
About >
</a>
<ul>
<li>
<a href="#">
History
</a>
</li>
<li>
<a href="#">
Team
</a>
</li>
<li>
<a href="#">
Offices
</a>
</li>
</ul>
</li>
<li>
<a href="#">
Services >
</a>
<ul>
<li>
<a href="#">
Web Design
</a>
</li>
<li>
<a href="#">
Internet Marketing
</a>
</li>
<li>
<a href="#">
Hosting
</a>
</li>
<li>
<a href="#">
Domain Names
</a>
</li>
<li>
<a href="#">
Broadband
</a>
</li>
</ul>
</li>
<li>
<a href="#">
Contact Us >
</a>
<ul>
<li>
<a href="#">
United Kingdom
</a>
</li>
<li>
<a href="#">
France
</a>
</li>
<li>
<a href="#">
USA
</a>
</li>
<li>
<a href="#">
Australia
</a>
</li>
</ul>
</li>
</ul>
3) CSS Styles:
ul#nav{
background-color:#445566;
}
ul li{
list-style:none;
}
ul li ul{
background-color:#558888;
}
ul li a{
color:#fff;
text-decoration:none;
}
.off ul{
display:none;
}
.on ul{
display:block;
}
4) JQuery in Script Tag:
$(function() {
$("ul li ul").each(function() {
$(this).parent().addClass('off');
});
$("ul li").mouseenter(function() {
if ($(this).hasClass('off')) {
$(this).removeClass('off');
$(this).addClass('on');
}
});
$("ul li").mouseleave(function() {
if ($(this).hasClass('on')) {
$(this).removeClass('on');
$(this).addClass('off');
}
});
});
Try it on bin : http://codebins.com/bin/4ldqpal

Categories