$.fn.extend({
treed: function (o) {
var openedClass = 'fa-minus-circle';
var closedClass = 'fa-plus-circle';
if (typeof o != 'undefined'){
if (typeof o.openedClass != 'undefined'){
openedClass = o.openedClass;
}
if (typeof o.closedClass != 'undefined'){
closedClass = o.closedClass;
}
}
//initialize each of the top levels
var tree = $(this);
tree.addClass("tree");
tree.find('li').has("ul").each(function () {
var branch = $(this); //li with children ul
branch.prepend("<i class='indicator fas " + closedClass + "'></i>");
branch.addClass('branch');
// declare a flag to toggle
let flag = false;
branch.on('click', function (e) {
if (this == e.target) {
// toggle on click
flag = !flag;
var icon = $(this).children('i:first');
icon.toggleClass(openedClass + " " + closedClass);
// show loader only when its opened, and collpase once toggled
if(flag) {
$(this).append("<div class='loading'><span class='fa fa-spinner fa-spin'</span></div>");
setTimeout(()=>{
$(this).find('.loading').remove();
$(this).children().children().slideToggle(400);
}, 400)
} else {
$(this).children().children().slideToggle(400);
}
}
});
branch.children().children().toggle();
});
//fire event from the dynamically added icon
tree.find('.branch .indicator').each(function(){
$(this).on('click', function () {
$(this).closest('li').click();
});
});
//fire event to open branch if the li contains an anchor instead of text
tree.find('.branch>a').each(function () {
$(this).on('click', function (e) {
$(this).closest('li').click();
e.preventDefault();
});
});
//fire event to open branch if the li contains a button instead of text
tree.find('.branch>button').each(function () {
$(this).on('click', function (e) {
$(this).closest('li').click();
e.preventDefault();
});
});
//adding "openAll" and "closeAll" functions to the tree
tree.openAll = function(){
tree.find('li').has("ul").each(function () {
//finding if opened or closed from icon
//TODO: find a simpler way, like a property set on "branch"?
var branch = $(this); //li with children ul
var icon = branch.children('i:first');
//opening all closed ones
if(icon.hasClass(closedClass)){
branch.click();
}
});
};
tree.closeAll = function(){
tree.find('li').has("ul").each(function () {
//finding if opened or closed from icon
//TODO: find a simpler way, like a property set on "branch"?
var branch = $(this); //li with children ul
var icon = branch.children('i:first');
//opening all closed ones
if(icon.hasClass(openedClass)){
branch.click();
}
});
};
//return the tree so we can store it in a var and call the functions
return tree;
}
});
//Initialization of treeviews
//$('#tree1').treed();
var tree2 = $('#tree2').treed({openedClass:'fa-folder-open', closedClass:'fa-folder'});
//code for the buttons
$('#openall').on('click', function(){
tree2.openAll();
});
$('#closeall').on('click', function(){
tree2.closeAll();
});
$('#searchInput').on('keyup', function() {
var searchText = $(this).val().toLowerCase();
$('#tree2 li').each(function() {
var name = $(this).text().toLowerCase();
if (name.indexOf(searchText) !== -1) {
$(this).show();
}
});
});
.intro {
background-color: yellow;
}
.tree {
overflow-y: auto;
}
.branch {
white-space: nowrap;
}
.tree, .tree ul {
margin:0;
padding:0;
list-style:none;
margin-left:10px;
}
.tree ul {
margin-left:1em;
position:relative
}
.tree ul ul {
margin-left:.5em
}
.tree ul:before {
content:"";
display:block;
width:0;
position:absolute;
top:0;
bottom:0;
left:0;
border-left:1px solid
}
.tree li {
margin:0;
padding:0 1em;
line-height:2em;
color:#369;
font-weight:700;
position:relative;
}
.tree ul li:before {
content:"";
display:block;
width:10px;
height:0;
border-top:1px solid;
margin-top:-1px;
position:absolute;
top:1em;
left:0
}
.tree ul li:last-child:before {
background:#fff;
height:auto;
top:1em;
bottom:0
}
.indicator {
margin-right:5px;
}
.tree li a {
text-decoration: none;
color:#369;
}
.tree li button, .tree li button:active, .tree li button:focus {
text-decoration: none;
color:#369;
border:none;
background:transparent;
margin:0px 0px 0px 0px;
padding:0px 0px 0px 0px;
outline: 0;
}
.loading {
font-style: italic;
}
.timeline {
border-left: 1px solid hsl(0, 0%, 90%);
position: relative;
list-style: none;
}
.timeline .timeline-item {
position: relative;
}
.timeline .timeline-item:after {
position: absolute;
display: block;
top: 0;
}
.timeline .timeline-item:after {
background-color: hsl(0, 0%, 90%);
left: -38px;
border-radius: 50%;
height: 11px;
width: 11px;
content: "";
}
<section class="content">
<div class="container-fluid">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-md-4">
<div class="input-group mb-3">
<span class="input-group-text" id="basic-addon1"> <i class="fa fa-search"></i></span>
<input type="text" class="form-control" id="searchInput" aria-label="Username" aria-describedby="basic-addon1">
</div>
<br>
<button type="button" id ="openall"class="btn btn-warning" id ">open All</button>
<button type="button" id="closeall" class="btn btn-info">closeAll</button>
<br>
<ul id="tree2">
<li>
<a href="#">TECH</a>
<ul>
<li>Company Maintenance</li>
<li>
Employees
<ul>
<li>
Reports
<ul>
<li>Report1
<ul>
<li>Report1
<ul>
<li>Report1
<ul>
<li>Report1</li>
<li>Report2</li>
<li>Report3</li>
</ul>
</li>
<li>Report2</li>
<li>Report3</li>
</ul>
</li>
<li>Report2</li>
<li>Report3</li>
</ul>
</li>
<li>Report2</li>
<li>Report3</li>
</ul>
</li>
<li>Employee Maint.</li>
</ul>
</li>
<li>Human Resources</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
<div class="card-footer text-body-secondary">
</div>
</div> <!-- /.container-fluid -->
</section>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.rtl.min.css">
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.0/css/all.css" integrity="sha384-lZN37f5QGtY3VHgisS14W3ExzMWZxybE1SJSEsQp9S+oqd12jhcu+A56Ebc1zFSJ" crossorigin="anonymous">
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
I have this TreeView and I want to filter its items and display the item whose name is entered in the input above this TreeView. I want the searched item to be displayed in a different format (background – color = yellow ) and all the items in the TreeView to be expanded. If the item is not found, all the menu items should be collapsed and a notification message should be displayed indicating that the desired item is not found.
$('#searchInput').on('keyup', function() { var searchText = $(this).val().toLowerCase(); $('#tree2 li').each(function() { var name = $(this).text().toLowerCase(); if (name.indexOf(searchText) !== -1) { $(this).addClass('intro'); tree2.openAll(); }
This is the function that I am using, but no item appears in the TreeView even if the searched item’s name matches an item in the TreeView. Also, several errors occur in the TreeView and the function enters a continuous loop of opening and closing the TreeView. Additionally, the function applies the yellow color to all items in the TreeView. Please review the errors in the function and correct them. I would be grateful for your assistance
2
Answers
I checked your code (not thoroughly) and some obvious issues came to my sight at first.
Problem:
The
tree.openAll
function is generic and currently responsible for opening up all the nodes in the tree and not just the ancestors of the searched value.As you are calling the
openAll
function inside thekeyup
event (when the searched string matches with any of the node texts), it will trigger the generictree.openAll
function where it will not just try to open the ancestors of the searched node but all the nodes (this is happening through theclick
function). This is the reason you are seeing the tree opening and closing multiple times (infinite loop as you say).Solution:
The approach you can take to fix the above problem:
Get all the ancestors of the searched node and trigger
click
fromtop to bottom. You can also add the
background-color
one by one(to show it more intuitive) as they keep on opening up (Maybe, some
fadeIn
or other animation that goes with your UX design).To trigger the
click
from top to bottom, I suggest you createanother function that just takes the searched node as an argument
and inside that function, create a logic to find all the ancestors
and trigger the click using your already created
click
function.I hope this somehow helps you. I see you have the same issue with the
closeAll
as well. For closing everything, this would work but for closing a few or some of the nodes, this is not the right function.