keep only one li node open in tree view display - javascript

I want to hide other siblings li when i click one li to diplay tree view.
Only one node should open and its children elements have to expand. check the fiddle links below .
Html code :
Part 1
Item A
Sub-item 1
Sub-item 2
Sub-item 3
Item B
Sub-item 1
Sub-item 2
Sub-item 3
Item C
Sub-item 1
Sub-item 2
Sub-item 3
Item D
Sub-item 1
Sub-item 2
Sub-item 3
Item E
Sub-item 1
Sub-item 2
Sub-item 3
<li>Part 2
<ul>
<li>Item A
<ul>
<li>Sub-item 1</li>
<li>Sub-item 2</li>
<li>Sub-item 3</li>
</ul>
</li>
<li>Item B
<ul>
<li>Sub-item 1</li>
<li>Sub-item 2</li>
<li>Sub-item 3</li>
</ul>
</li>
<li>Item C
<ul>
<li>Sub-item 1</li>
<li>Sub-item 2</li>
<li>Sub-item 3</li>
</ul>
</li>
<li>Item D
<ul>
<li>Sub-item 1</li>
<li>Sub-item 2</li>
<li>Sub-item 3</li>
</ul>
</li>
<li>Item E
<ul>
<li>Sub-item 1</li>
<li>Sub-item 2</li>
<li>Sub-item 3</li>
</ul>
</li>
</ul>
</li>
<li>Part 3
<ul>
<li>Item A
<ul>
<li>Sub-item 1</li>
<li>Sub-item 2</li>
<li>Sub-item 3</li>
</ul>
</li>
<li>Item B
<ul>
<li>Sub-item 1</li>
<li>Sub-item 2</li>
<li>Sub-item 3</li>
</ul>
</li>
<li>Item C
<ul>
<li>Sub-item 1</li>
<li>Sub-item 2</li>
<li>Sub-item 3</li>
</ul>
</li>
<li>Item D
<ul>
<li>Sub-item 1</li>
<li>Sub-item 2</li>
<li>Sub-item 3</li>
</ul>
</li>
<li>Item E
<ul>
<li>Sub-item 1</li>
<li>Sub-item 2</li>
<li>Sub-item 3</li>
</ul>
</li>
</ul>
</li>
</ul>
css :
ul.tree li {
list-style-type: none;
position: relative;
}
ul.tree li ul {
display: none;
}
ul.tree li.open > ul {
display: block;
}
ul.tree li a {
color: black;
text-decoration: none;
}
ul.tree li a:before {
height: 1em;
padding:0 .1em;
font-size: .8em;
display: block;
position: absolute;
left: -1.3em;
top: .2em;
}
ul.tree li > a:not(:last-child):before {
content: '+';
}
ul.tree li.open > a:not(:last-child):before {
content: '-';
}
js code :
var tree = document.querySelectorAll('ul.tree a:not(:last-child)');
for(var i = 0; i < tree.length; i++){
tree[i].addEventListener('click', function(e) {
var parent = e.target.parentElement;
var classList = parent.classList;
if(classList.contains("open")) {
classList.remove('open');
var opensubs = parent.querySelectorAll(':scope .open');
for(var i = 0; i < opensubs.length; i++){
opensubs[i].classList.remove('open');
}
} else {
classList.add('open'); // Here only i want that condition to check li is already opened or not//
}
});
}
For more info : https://jsfiddle.net/te366hu2/2/

This should work!
All info in comments:
https://jsfiddle.net/xog7hxLs/
var tree = document.querySelectorAll('ul.tree a:not(:last-child)');
for(var i = 0; i < tree.length; i++){
tree[i].addEventListener('click', function(e) {
var element = e.target.parentElement; //actually this is just the elem itself
var parent = element.parentElement //real parent
var opensubs = parent.querySelectorAll(':scope .open'); //check which are opened on parent
if(opensubs.length !=0) {
for(var i = 0; i < opensubs.length; i++){
opensubs[i].classList.remove('open'); //closing opened previously
}
}
element.classList.add('open'); //opening current
});
}

I have modified you js code a bit to collapse the sibling nodes. Full working fiddle here
var tree = document.querySelectorAll('ul.tree a:not(:last-child)');
for(var i = 0; i < tree.length; i++){
tree[i].addEventListener('click', function(e) {
var parent = e.target.parentElement;
var classList = parent.classList;
var closeAllOpenSiblings = function(){
var opensubs = parent.parentElement.querySelectorAll(':scope .open');
for(var i = 0; i < opensubs.length; i++){
opensubs[i].classList.remove('open');
}
}
if(classList.contains("open")) {
classList.remove('open');
} else {
closeAllOpenSiblings();
classList.add('open');
}
});
}

Use the node open event to look through the tree and close other nodes.
$('#tree').on('open_node.jstree', function (e, data) {
var nodesToKeepOpen = [];
// get all parent nodes to keep open
$('#'+data.node.id).parents('.jstree-node').each(function() {
nodesToKeepOpen.push(this.id);
});
// add current node to keep open
nodesToKeepOpen.push( data.node.id );
// close all other nodes
$('.jstree-node').each( function() {
if( nodesToKeepOpen.indexOf(this.id) === -1 ) {
$("#tree").jstree().close_node(this.id);
}
})
});
JSFiddle Demo

Related

Can you dynamically add an event listener to variables in a for loop using Javascript

I am trying to dynamically add an eventlistener to a group of li tags that will toggle a CSS class
const item = $('#dynamic-list').getElementsByTagName('li');
const strikeOut = () => this.classList = this.classList.toggle('strike-out');
const addClass = function() {
for (let i = 0; i < item.length; i++) {
let link = item[i];
link.onclick = strikeOut;
}
}
addClass();
.strike-item {
text-decoration: line-through;
}
<ul id="dynamic-list" style="list-style: none;">
<li class="dynamic-item">Item 1</li>
<li class="dynamic-item">Item 2</li>
<li class="dynamic-item">Item 3</li>
<li class="dynamic-item">Item 4</li>
<li class="dynamic-item">Item 5</li>
</ul>
I have a feeling that I'm not assigning the strikeout function correctly to each link however I'm open to all suggestions
Some problems to fix:
jQuery collections don't have a getElementsByTagName function. No need for jQuery, just use a plain querySelectorAll
Arrow functions do not capture their calling context - rather, they inherit the calling context of their surrounding block. Use a standard function instead so that the this inside strikeOut refers to the clicked element
Your CSS refers to strike-item, but your JS toggles a class name of strike-out.
After fixing:
const item = document.querySelectorAll('#dynamic-list li');
const strikeOut = function() {
this.classList.toggle('strike-item');
}
const addClass = function() {
for (let i = 0; i < item.length; i++) {
let link = item[i];
link.onclick = strikeOut;
}
}
addClass();
.strike-item {
text-decoration: line-through;
}
<ul id="dynamic-list" style="list-style: none;">
<li class="dynamic-item">Item 1</li>
<li class="dynamic-item">Item 2</li>
<li class="dynamic-item">Item 3</li>
<li class="dynamic-item">Item 4</li>
<li class="dynamic-item">Item 5</li>
</ul>
But you might consider using event delegation instead, that way you only add one listener rather than a listener for every li:
document.querySelector('#dynamic-list')
.addEventListener('click', (e) => {
const { target } = e;
if (!target.matches('li.dynamic-item')) return;
target.classList.toggle('strike-item');
});
.strike-item {
text-decoration: line-through;
}
<ul id="dynamic-list" style="list-style: none;">
<li class="dynamic-item">Item 1</li>
<li class="dynamic-item">Item 2</li>
<li class="dynamic-item">Item 3</li>
<li class="dynamic-item">Item 4</li>
<li class="dynamic-item">Item 5</li>
</ul>
You have many things going wrong here, getElementsByTagName is not a jquery selector its a javascript selector. Here is an example how to toggle :
const item = $('#dynamic-list li');
const strikeOut = (e) =>{
$(e.target).toggleClass('strike-item')
}
const addClass = function() {
for( let i=0; i<item.length; i++) {
let link = item[i];
link.onclick = strikeOut;
}
}
addClass();
.strike-item {
text-decoration: line-through;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="dynamic-list" style="list-style: none;">
<li class="dynamic-item">Item 1</li>
<li class="dynamic-item">Item 2</li>
<li class="dynamic-item">Item 3</li>
<li class="dynamic-item">Item 4</li>
<li class="dynamic-item">Item 5</li>
</ul>
You Can Try Simple Code here
function myAction(me){
me.classList.toggle('strike-out');
}
.dynamic-item.strike-out {
text-decoration: line-through;
}
<ul id="dynamic-list" style="list-style: none;">
<li class="dynamic-item" onClick="myAction(this)">Item 1</li>
<li class="dynamic-item" onClick="myAction(this)">Item 2</li>
<li class="dynamic-item" onClick="myAction(this)">Item 3</li>
<li class="dynamic-item" onClick="myAction(this)">Item 4</li>
<li class="dynamic-item" onClick="myAction(this)">Item 5</li>
</ul>

Jquery Nested Accordion to Show Only one Level

Suppose I have a following structure:
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3
<ul>
<li>Item11</li>
<li>Item12</li>
<li>Item13</li>
</ul>
</li>
This tree structure can have many levels.
Initially, I want to show following list:
Item 1
Item 2
Item 3 +
When I click on + list becomes
Item 3 -
Item 11
Item 12
Item 13
So, parent list disappears and sublist is shown.
If I click -, then everything is returned to the previous list.
Is there some jquery plugin for doing this?
Perhaps, some options in accordion?
Thanks in advance.
Used Dkouk his version to create what you need.
EDIT 1: Hide other menu items
EDIT 2: Second level menu
$(function () {
$('ul li').each(function () {
if ( $(this).find('ul').length > 0 ){
$(this).addClass('child');
}
});
$('ul li.child span').click(function() {
$(this).parent().toggleClass('open').find('ul').first().slideToggle();
$(this).parent().siblings().slideToggle();
});
});
ul {
list-style:none;
}
ul li.child span:after {
content:"+";
}
ul li.child.open span:after {
content:"-";
}
li ul { display:none; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<ul class="main">
<li>Item 1</li>
<li>Item 2</li>
<li>
<span>Item 3</span>
<ul>
<li>Item 1</li>
<li>
<span>Item 2</span>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</li>
<li>Item 3</li>
</ul>
</li>
</ul>
You can add a class to your list when it's have a sublist, and toggle the list and toggle another class for parent of list so can change the '+' to '-'.
You can as many levels, and the code will work,
I've add a span to the list have child, but so can trigger the click only at text, if you trigger click for LI list and have sublist open then will close again.
You can style the content.
Here a simple example :
$(function () {
$('ul li').each(function () {
if ( $(this).find('ul').length > 0 ){
$(this).addClass('child');
}
});
$('ul li.child span').click(function() {
$(this).toggleClass('open').parent().find('ul:first').slideToggle();
$(this).parent().siblings().slideToggle();
});
});
ul {
list-style:none;
}
ul li.child span:after {
content:"+";
}
ul li.child span.open:after {
content:"-";
}
ul li ul { display:none; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>
<span>Item 3</span>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>
<span>Item 3</span>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</li>
</ul>
</li>
</ul>

How can I add class on active li with JavaScript code

<ul class="nav nav-tabs">
<li onclick="this.className='active'">Home</li>
<li onclick="this.className='active'">Menu 1</li>
<li onclick="this.className='active'">Menu 2</li>
<li onclick="this.className='active'">Menu 3</li>
</ul>
How can I add active class on li tag with JavaScript. Here I am try to do it. It is working but not properly. I am doing for tabs here.
As per the comments, I guess you are expecting this:
var a = document.querySelectorAll(".nav li a");
for (var i = 0, length = a.length; i < length; i++) {
a[i].onclick = function() {
var b = document.querySelector(".nav li.active");
if (b) b.classList.remove("active");
this.parentNode.classList.add('active');
};
}
.active {
background-color: #0f9;
}
<ul class="nav nav-tabs">
<li>Home
</li>
<li>Menu 1
</li>
<li>Menu 2
</li>
<li>Menu 3
</li>
</ul>
if you have jquery
<ul class="nav nav-tabs">
<li>Home</li>
<li>Menu 1</li>
<li>Menu 2</li>
<li>Menu 3</li>
</ul>
<style>
.active {background-color: #0f9;}
</style>
<script type="text/javascript">
$("ul.nav.nav-tabs").on('click', 'li', function(){
$(this).siblings().removeClass('active');
$(this).addClass('active');
});
</script>
Vanilla JavaScript (ES6 where I tested, might work on ES5)
const elm = document.querySelector('ul');
elm.addEventListener('click', (el) => {
const elActive = elm.querySelector('.active');
if (elActive) {
elActive.removeAttribute('class');
}
el.target.setAttribute('class', 'active');
});

See more and see less button

Here's a script that shows 4 items each time a button is clicked. What i need is to change the text of the button after a click to "show even more" and then change to "show less" at the end when all items shown. I tried to add this:
if (nowShowing >= numInList) {
$('.partners__button__a').toggle(function() {
$(this).text('Show More');
}, function() {
$(this).text('Show Less');
button.show();
});
}
but it's not working the way I need.
And also how to add a reverse function to hide items?
Thank you.
$(document).ready(function() {
var list = $(".partners__ul li");
var numToShow = 4;
var button = $(".partners__button__a");
var numInList = list.length;
list.hide();
if (numInList > numToShow) {
button.show();
}
list.slice(0, numToShow).show();
button.click(function() {
var showing = list.filter(':visible').length;
list.slice(showing - 1, showing + numToShow).fadeIn();
var nowShowing = list.filter(':visible').length;
});
});
.partners__ul {
list-style: none;
padding: 0;
margin: 0;
}
.partners__ul li {
position: relative;
margin-bottom: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<ul class="partners__ul">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
<li>Item 7</li>
<li>Item 8</li>
<li>Item 9</li>
<li>Item 10</li>
<li>Item 11</li>
<li>Item 12</li>
</ul>
<button class="partners__button__a">Show More</button>
</div>
I did something different. Used two buttons but instead of one just to hide and show at the same time when half content is visible. Not sure whether you need it this way, just thought about making it more functional.
$(document).ready(function() {
var list = $(".partners__ul li");
var numToShow = 4;
var button = $(".partners__button__a");
var buttonLess = $(".partners__button__a_less");
var numInList = list.length;
var nowShowing = 4;
list.hide();
if (numInList > numToShow) {
button.show();
}
list.slice(0, numToShow).show();
button.click(function() {
var showing = list.filter(':visible').length;
list.slice(showing - 1, showing + numToShow).fadeIn();
nowShowing = list.filter(':visible').length;
if(numInList === nowShowing) {
$(this).hide();
buttonLess.text('Show Less')
} else if(nowShowing > numToShow) {
$(this).text('Show Even More');
buttonLess.show();
}
});
buttonLess.click(function() {
var showing = list.filter(':visible').length;
list.slice(showing - numToShow, showing).fadeOut();
nowShowing = nowShowing - numToShow;
if(numToShow === nowShowing) {
$(this).hide();
button.text('Show More');
} else if(nowShowing < numInList) {
$(this).text('Show Less');
button.show();
}
});
});
.partners__ul {
list-style: none;
padding: 0;
margin: 0;
}
.partners__ul li {
position: relative;
margin-bottom: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<ul class="partners__ul">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
<li>Item 7</li>
<li>Item 8</li>
<li>Item 9</li>
<li>Item 10</li>
<li>Item 11</li>
<li>Item 12</li>
</ul>
<button class="partners__button__a">Show More</button>
<button class="partners__button__a_less" style="display:none">Show Less</button>
</div>
Getting the text to change is easy enough, the simplest way would be to add this to the bottom of your button's click handler:
if(nowShowing == numInList){
$(this).text("Show less");
}
else{
$(this).text("Show even more");
}
As for the second item of showing less, you could add this (agin in the click handler)
if(showing < numInList){
list.slice(showing - 1, showing + numToShow).fadeIn();
}
else{
list.slice(showing - numToShow, numInList).fadeOut();
}
From here, you need to handle the fact that once you've shown everything and you start being able to show less, you need some form of boolean to indicate if we're currently in the state of "showing" or "hiding".
This then presents another problem! As you're fading in and out the :visible state will not be correct until after the fade has completed. Therefore you should defer the functionality using an overload of fadeIn / fadeOut which takes a callback.
The finished code can be seen below.
$(document).ready(function() {
var list = $(".partners__ul li");
var numToShow = 4;
var button = $(".partners__button__a");
var numInList = list.length;
var isShowing = true;
list.hide();
if (numInList > numToShow) {
button.show();
}
list.slice(0, numToShow).show();
button.click(function() {
var showing = list.filter(':visible').length;
if(isShowing){
list.slice(showing - 1, showing + numToShow).fadeIn(100,onFadeComplete);
}
else{
list.slice(showing - numToShow, numInList).fadeOut(100,onFadeComplete);
}
});
function onFadeComplete(){
var nowShowing = list.filter(':visible').length;
if(nowShowing == numInList && isShowing){
isShowing = false;
button.text("Show less");
}
else if(isShowing){
button.text("Show even more");
}
if(nowShowing == numToShow){
button.text("Show more");
isShowing = true;
}
}
});
.partners__ul {
list-style: none;
padding: 0;
margin: 0;
}
.partners__ul li {
position: relative;
margin-bottom: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<ul class="partners__ul">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
<li>Item 7</li>
<li>Item 8</li>
<li>Item 9</li>
<li>Item 10</li>
<li>Item 11</li>
<li>Item 12</li>
</ul>
<button class="partners__button__a">Show More</button>
</div>

JavaScript: Slide a UL down to last element

Can't think how best to do this. Thought it would be a simple show/hide but it dosn't seem as simple as that.
There is a UL with an indeterminable amount of items in it. It needs to be able to show the first 10 but no more unless a 'show more' button is clicked. When the 'show more' button is clicked it will expand the list open to show the complete list.
http://jsfiddle.net/kbUhW/
Interested to see how this is achieved.
Here is an example: http://jsfiddle.net/WqxGf/
JS:
count = 0;
$('ul li').hide();
$('ul').children().each(function(){
if(count >= 10) return;
$(this).show();
count++;
})
$('.slide').click(function(){$('ul li').show('blind');})
HTML:
<ul>
<li>Item One</li>
<li>Item Two</li>
<li>Item Three</li>
<li>Item Four</li>
<li>Item Five</li>
<li>Item Six</li>
<li>Item One</li>
<li>Item Two</li>
<li>Item Three</li>
<li>Item Four</li>
<li>Item Five</li>
<li>Item Six</li>
<li>Item One</li>
<li>Item Two</li>
<li>Item Three</li>
<li>Item Four</li>
<li>Item Five</li>
<li>Item Six</li>
<li>Item One</li>
<li>Item Two</li>
<li>Item Three</li>
<li>Item Four</li>
<li>Item Five</li>
<li>Item Six</li>
<li>Item One</li>
<li>Item Two</li>
<li>Item Three</li>
<li>Item Four</li>
<li>Item Five</li>
<li>Item Six</li>
</ul>
<a href="#" class='slide'>Slide Down</a>
All the other answers use jQuery, but your question didn't actually specify it. So here's one way to do it with plain JavaScript. Let's assume your <ul> has the ID foo, your "reveal" link has the ID reveal, and that there's a class hide with display: none. Then we have:
(function getChildNodes(id, num) { // ID of element, number to show
var obj = document.getElementById(id),
children = obj.childNodes,
elemcounter = 0;
for (var i = 0; i < children.length; i++) { // loop all children
if (children[i].nodeType === 1) { // examine elements only
elemcounter++;
if (elemcounter > num) { // element number in range to hide?
children[i].className = 'hide';
}
}
}
}('foo', 3)); // id foo, show 3
document.getElementById('reveal').onclick = function() { // handle click
var items = document.getElementsByTagName('li');
for( var i = 0; i < items.length; i++ ){ // for all list elements...
var tempclass = items[i].className;
// if the class is "hide", unhide
items[i].className = tempclass === 'hide' ? '' : tempclass;
}
}
Of course there are many other ways to do this more thoroughly -- and this one doesn't even slide. jQuery does make life a bit easier.
Here's the working example: http://jsfiddle.net/redler/jsQ47/
Here's with the slide down effect:
http://jsfiddle.net/deNzh/
That's what you're looking for, right?
you could assign the first ten < li >s a class like < li class="always_show">Stuff goes here< /li > and then make a script that hides all, shows the "always_show" class and waits for a button click to show the whole thing.
might look something like:
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
$(function(){
$("#listorama").hide();
});
$(function(){
$(".always_show").show();
});
$(function(){
$("#show_all").click(function(){
$("#listorama").show();
});
});
</script>
<ul id="listorama">
<li class="always_show"></li>
<li class="always_show"></li>
<li class="always_show"></li>
<li class="always_show"></li>
<li class="always_show"></li>
<li class="always_show"></li>
<li class="always_show"></li>
<li class="always_show"></li>
<li class="always_show"></li>
<li class="always_show"></li>
<li>stuff to hide first</li>
<li>stuff to hide first</li>
<li>stuff to hide first</li>
<li>stuff to hide first</li>
<li>stuff to hide first</li>
</ul>
<button id="show_all">Show All</button>
Hope this helps!
Andy
function toggleListDisplay (list, cap) {
cap = parseInt(cap);
if (cap == null || cap < 0) { return; }
var elements = $(list).children();
if ($(elements[cap]).css('display') == 'none') {
// means we need to expand the list
elements.each(function(ind, ele) {
if (ind >= cap) { $(ele).slideDown(); }
});
$('.slide').html('Slide Up');
} else {
// means we need to shorten the list
elements.each(function(ind, ele) {
if (ind >= cap) { $(ele).slideUp(); }
});
$('.slide').html('Slide Down');
}
}
$('.slide').click(function(){
toggleListDisplay('#tester', 10);
})
toggleListDisplay('#tester', 10);
JSFiddle: http://jsfiddle.net/WqxGf/7/
I don't know why the others feel like making such a simple task more complicated than it is, but here is a much easier, shorter, and simpler way of achieving this:
$("a").click(function() {
var ul = $("#myid");
ul.animate({"height": ul[0].scrollHeight}, 1000);
});
Example: http://jsfiddle.net/kbUhW/13/

Categories