Skip to content

Commit d87eb28

Browse files
committed
v0.2
1 parent 60e213e commit d87eb28

File tree

3 files changed

+128
-134
lines changed

3 files changed

+128
-134
lines changed

index.html

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,44 +13,44 @@
1313

1414
<body>
1515
<div class="col-4 mx-auto">
16-
<ul id="menu-tree" searchable show-empty>
16+
<ul id="nav-tree" data-searchable data-show-empty-groups>
1717
<li id="li1">
18-
<a id="a1" href="#">
18+
<a href="#">
1919
Link 1
2020
</a>
2121
</li>
2222
<li id="li2">
23-
<a id="a2">
23+
<a>
2424
Collapse 1
2525
</a>
26-
<ul id="ul1">
26+
<ul>
2727
<li id="li4">
28-
<a id="a4">
28+
<a>
2929
Collapse 2
3030
</a>
31-
<ul id="ul2">
31+
<ul>
3232
<li id="li6">
33-
<a id="a6" href="#">
33+
<a href="#">
3434
Link 2
3535
</a>
3636
</li>
3737
<li id="li7">
38-
<a id="a7" href="#">
38+
<a href="#">
3939
Link 3
4040
</a>
4141
</li>
4242
<li id="li8">
43-
<a id="a8">
43+
<a>
4444
Collapse 3
4545
</a>
46-
<ul id="ul3">
46+
<ul>
4747
<li id="li9">
48-
<a id="a9" href="#">
48+
<a href="#">
4949
Link 4
5050
</a>
5151
</li>
5252
<li id="li10">
53-
<a id="a10" href="#">
53+
<a href="#">
5454
Link 5
5555
</a>
5656
</li>
@@ -59,14 +59,14 @@
5959
</ul>
6060
</li>
6161
<li id="li5">
62-
<a id="a5" href="#">
62+
<a href="#">
6363
Link 6
6464
</a>
6565
</li>
6666
</ul>
6767
</li>
6868
<li id="li3">
69-
<a id="a3" href="#">
69+
<a href="#">
7070
Link 7
7171
</a>
7272
</li>

static/css/tree.css

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
#menu-tree a.tree-link:focus{
1+
.tree-link:focus,
2+
.tree-link.active {
23
color: black;
4+
background-color: rgb(235, 235, 235);
35
}
46

5-
#menu-tree span.tree-icon {
6-
width: 25px;
7+
.tree-link:hover {
8+
background-color: rgb(245, 245, 245);
9+
}
10+
11+
.no-transition{
12+
transition: none !important;
713
}

static/js/tree.js

Lines changed: 105 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,149 +1,137 @@
1-
function htmlToElement(html) {
2-
var template = document.createElement('div');
3-
template.innerHTML = html.trim();
4-
return template.firstChild;
5-
}
6-
7-
const chevronIcon = `<span class="d-inline-block tree-icon"><i class="fas fa-chevron-down"></i></span>`;
8-
const linkIcon = `<span class="d-inline-block tree-icon"><i class="fas fa-link"></i></span>`;
9-
const searchInput = `<input type="text" name="menu-tree-search" id="menu-tree-search" class="form-control" placeholder="Search">`;
10-
111
function copyAttrs(src, target) {
122
for (let attr of src.attributes) {
13-
target.setAttribute(attr.name, attr.value);
3+
if (attr.name == "class")
4+
attr.value.split(" ").forEach((cls) => target.classList.add(cls))
5+
else
6+
target.setAttribute(attr.name, attr.value);
147
}
158
}
169

1710

18-
function update_tree(menu, menu_html) {
19-
let old_menu = menu.cloneNode(true);
11+
var NavTree = function (props) {
12+
this.props = props;
13+
this.element = document.querySelector(this.props.obj_id);
14+
this.searchable = this.element.dataset.hasOwnProperty('searchable');
15+
this.showEmptyGroups = this.element.dataset.hasOwnProperty('showEmptyGroups');
16+
this.chevronIcon = `<i class="fas fa-chevron-right"></i>`;
17+
this.linkIcon = `<i class="fas fa-link"></i>`;
18+
this.searchInput = `<input type="text" name="menu-tree-search" id="menu-tree-search" class="form-control" placeholder="Search">`;
19+
20+
this.init = function () {
21+
if (!this.parent) {
22+
var parent = document.createElement('div');
23+
parent.setAttribute('id', 'nav-tree-wrapper');
24+
this.element.parentElement.replaceChild(parent, this.element);
25+
parent.appendChild(this.element);
26+
this.parent = parent;
27+
}
2028

21-
let new_menu = htmlToElement(menu_html);
22-
menu.innerHTML = new_menu.innerHTML;
23-
init_tree(menu);
29+
this.element.classList.add("nav", "flex-column");
2430

25-
new_menu = menu;
31+
this.element.querySelectorAll("ul").forEach((ul) => ul.classList.add("nav", "flex-column")); // add bs5 classes to lists
2632

27-
const old_node_ids = Array.from(old_menu.querySelectorAll("[id]")).map((i) => i.getAttribute("id"));
33+
this.element.querySelectorAll("li").forEach((li) => {
34+
const li_id = li.getAttribute("id");
35+
li.classList.add("nav-item"); // add bs5 class to list item
2836

29-
old_node_ids.forEach((id) => {
30-
var new_node = new_menu.querySelector("#" + id);
31-
var old_node = old_menu.querySelector("#" + id);
32-
if (new_node && old_node)
33-
copyAttrs(old_node, new_node);
34-
});
37+
const a = li.querySelector("a");
38+
a.classList.add("nav-link");
39+
a.setAttribute("id", "tree-link-" + li_id);
40+
var icon_parent = document.createElement('span');
41+
icon_parent.classList.add("d-inline-block", "tree-icon");
42+
icon_parent.style.width = '25px';
43+
a.prepend(icon_parent);
3544

36-
handle_tree(menu);
37-
}
45+
if (li.querySelector("ul")) { // check list item has a ul object
46+
a.classList.add("tree-group-link");
47+
a.setAttribute("data-bs-toggle", "collapse");
48+
a.setAttribute("role", "button");
49+
a.setAttribute("aria-expanded", "false");
50+
a.setAttribute("href", "#nav-tree-list-wrapper-" + li_id);
51+
a.setAttribute("aria-controls", "nav-tree-list-wrapper-" + li_id);
52+
icon_parent.innerHTML = this.chevronIcon;
3853

3954

40-
function init_tree(menu) {
41-
menu.classList.add("nav", "flex-column");
42-
menu.querySelectorAll("ul").forEach((ul) => ul.classList.add("nav", "flex-column"));
43-
menu.querySelectorAll("li").forEach((li) => {
44-
li.classList.add("nav-item");
45-
const a = li.querySelector("a");
46-
if (li.querySelector("ul")) {
55+
const ul = li.querySelector("ul");
4756

48-
a.classList.add("nav-link", "tree-group-link");
49-
a.prepend(htmlToElement(chevronIcon));
50-
a.setAttribute("data-bs-toggle", "collapse");
51-
a.setAttribute("role", "button");
52-
a.setAttribute("aria-expanded", "false");
57+
var collapsable = document.createElement('div');
58+
collapsable.classList.add("collapse", "ms-4");
59+
collapsable.setAttribute("id", "nav-tree-list-wrapper-" + li_id);
60+
li.replaceChild(collapsable, ul);
61+
collapsable.appendChild(ul);
62+
new bootstrap.Collapse(collapsable, { toggle: false });
5363

64+
collapsable.addEventListener('show.bs.collapse', (e) => {
65+
e.target.parentElement.querySelector('.tree-icon i').classList.replace('fa-chevron-right', 'fa-chevron-down');
66+
});
5467

55-
const ul = li.querySelector("ul");
68+
collapsable.addEventListener('hide.bs.collapse', (e) => {
69+
e.target.parentElement.querySelector('.tree-icon i').classList.replace('fa-chevron-down', 'fa-chevron-right');
70+
if (e.target.querySelector('.collapse'))
71+
bootstrap.Collapse.getInstance(e.target.querySelector('.collapse')).hide();
72+
});
5673

57-
a.setAttribute("href", "#menuTreeCollapse_" + ul.getAttribute("id"));
58-
a.setAttribute("aria-controls", "menuTreeCollapse_" + ul.getAttribute("id"));
74+
} else {
5975

60-
var collapsable = document.createElement('div');
61-
collapsable.classList.add("collapse", "ms-4");
62-
collapsable.setAttribute("id", "menuTreeCollapse_" + ul.getAttribute("id"));
63-
li.replaceChild(collapsable, ul);
64-
collapsable.appendChild(ul);
76+
a.classList.add("tree-link");
6577

66-
} else {
78+
icon_parent.innerHTML = this.linkIcon;
6779

68-
a.classList.add("nav-link", "tree-link", "btn-outline-light");
69-
a.prepend(htmlToElement(linkIcon));
80+
a.addEventListener('click', () => {
81+
this.parent.querySelectorAll('a.tree-link').forEach((link) => {
82+
link.classList.remove('active');
83+
});
7084

71-
}
72-
});
85+
a.classList.add('active');
86+
});
7387

74-
handle_tree(menu);
75-
}
76-
77-
function handle_tree(menu) {
78-
menu.querySelectorAll('a.tree-link').forEach((item) => {
79-
item.addEventListener('click', () => {
80-
menu.querySelectorAll('a.tree-link').forEach((link) => {
81-
link.classList.remove('active', 'btn-outline-light');
82-
});
83-
item.classList.add('active', 'btn-outline-light');
88+
}
89+
icon_parent.firstElementChild.setAttribute('id', 'tree-icon-' + li_id);
8490
});
85-
});
86-
87-
menu.querySelectorAll('a.tree-group-link').forEach((group) => {
88-
handleChevron(group);
89-
handleCollapse(group);
9091

91-
group.addEventListener('click', (e) => {
92-
handleChevron(e.target);
93-
});
94-
});
95-
96-
function handleChevron(target) {
97-
const chevron = (target.tagName === "I") ? target : target.querySelector('i');
98-
const parent = (target.tagName === "I") ? target.closest('a') : target
99-
100-
if (parent.getAttribute('aria-expanded') === "false") {
101-
chevron.classList.remove("fa-chevron-down");
102-
chevron.classList.add("fa-chevron-right");
103-
} else {
104-
chevron.classList.remove("fa-chevron-right");
105-
chevron.classList.add("fa-chevron-down");
106-
}
107-
}
92+
if (this.searchable) {
93+
var search_parent = document.createElement('div');
94+
search_parent.setAttribute('id', 'nav-tree-search-wrapper');
95+
search_parent.classList.add('my-2');
96+
this.parent.prepend(search_parent);
97+
search_parent.innerHTML = this.searchInput;
98+
99+
search_parent.firstChild.addEventListener('keyup', (e) => {
100+
const nodes = Array.from(document.querySelectorAll("li"));
101+
nodes.forEach((i) => i.classList.remove("d-none"));
102+
103+
if (e.target.value !== '') {
104+
nodes.filter((i) => !i.querySelector('a').text.toLowerCase().includes(e.target.value)).forEach((i) => i.classList.add("d-none"));
105+
nodes.filter((node) => node.querySelector('a').classList.contains('tree-group-link')).forEach((node) => {
106+
if (Array.from(node.querySelectorAll("li")).filter((li) => !li.classList.contains('d-none')).length > 0) {
107+
const c = node.querySelector('.collapse');
108+
c.classList.add('no-transition');
109+
bootstrap.Collapse.getInstance(c).show();
110+
node.classList.remove('d-none');
111+
c.classList.remove('no-transition');
112+
} else if (!this.showEmptyGroups) {
113+
node.classList.add('d-none');
114+
}
115+
});
116+
}
108117

109-
function handleCollapse(target) {
110-
const group = (target.tagName === "A") ? target : target.parentElement;
118+
});
111119

112-
if (group.getAttribute('aria-expanded') === "true") {
113-
group.parentElement.querySelector(group.getAttribute('href')).classList.add('show');
114-
} else {
115-
group.parentElement.querySelector(group.getAttribute('href')).classList.remove('show');
116120
}
117121
}
118-
}
119122

120-
function init_search(show_empty) {
121-
document.querySelector("#menu-tree-search").addEventListener('keyup', (e) => {
122-
const nodes = Array.from(document.querySelectorAll(".tree-link, .tree-group-link"));
123-
nodes.forEach((i) => i.parentElement.classList.remove("d-none"));
124-
nodes.filter((i) => !i.text.toLowerCase().includes(e.target.value)).forEach((i) => i.parentElement.classList.add("d-none"));
125-
126-
document.querySelectorAll(".tree-group-link").forEach((i) => {
127-
if (Array.from(i.parentElement.querySelector("div ul").querySelectorAll("li")).filter((j) => !j.classList.contains("d-none")).length > 0) {
128-
i.parentElement.classList.remove("d-none"); // if link of ul does not contain text but ul has any element that contains text, then show the link of ul
129-
} else {
130-
if (!show_empty)
131-
i.parentElement.classList.add("d-none"); // if link of ul contains text but ul does not have any element that contains text, then hide the link of ul
132-
}
123+
this.update = function (menu_html) {
124+
const old = this.element.cloneNode(true);
125+
this.parent.innerHTML = menu_html;
126+
this.element = this.parent.querySelector('ul');
127+
this.init();
128+
Array.from(old.querySelectorAll("[id]")).map((i) => i.getAttribute("id")).forEach((id) => {
129+
var new_node = this.element.querySelector("#" + id);
130+
var old_node = old.querySelector("#" + id);
131+
if (new_node && old_node)
132+
copyAttrs(old_node, new_node);
133133
});
134-
135-
});
136-
}
137-
138-
document.addEventListener("DOMContentLoaded", function () {
139-
let menu = document.querySelector('#menu-tree');
140-
141-
init_tree(menu);
142-
143-
if (menu.getAttribute("searchable") !== null) {
144-
menu.parentElement.prepend(htmlToElement(searchInput));
145-
146-
init_search(menu.getAttribute("show-empty") !== null);
147134
}
148135

149-
});
136+
this.init();
137+
}

0 commit comments

Comments
 (0)