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
-
11
1
function copyAttrs ( src , target ) {
12
2
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 ) ;
14
7
}
15
8
}
16
9
17
10
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
+ }
20
28
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" ) ;
24
30
25
- new_menu = menu ;
31
+ this . element . querySelectorAll ( "ul" ) . forEach ( ( ul ) => ul . classList . add ( "nav" , "flex-column" ) ) ; // add bs5 classes to lists
26
32
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
28
36
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 ) ;
35
44
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 ;
38
53
39
54
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" ) ;
47
56
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 } ) ;
53
63
64
+ collapsable . addEventListener ( 'show.bs.collapse' , ( e ) => {
65
+ e . target . parentElement . querySelector ( '.tree-icon i' ) . classList . replace ( 'fa-chevron-right' , 'fa-chevron-down' ) ;
66
+ } ) ;
54
67
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
+ } ) ;
56
73
57
- a . setAttribute ( "href" , "#menuTreeCollapse_" + ul . getAttribute ( "id" ) ) ;
58
- a . setAttribute ( "aria-controls" , "menuTreeCollapse_" + ul . getAttribute ( "id" ) ) ;
74
+ } else {
59
75
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" ) ;
65
77
66
- } else {
78
+ icon_parent . innerHTML = this . linkIcon ;
67
79
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
+ } ) ;
70
84
71
- }
72
- } ) ;
85
+ a . classList . add ( 'active' ) ;
86
+ } ) ;
73
87
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 ) ;
84
90
} ) ;
85
- } ) ;
86
-
87
- menu . querySelectorAll ( 'a.tree-group-link' ) . forEach ( ( group ) => {
88
- handleChevron ( group ) ;
89
- handleCollapse ( group ) ;
90
91
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
+ }
108
117
109
- function handleCollapse ( target ) {
110
- const group = ( target . tagName === "A" ) ? target : target . parentElement ;
118
+ } ) ;
111
119
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' ) ;
116
120
}
117
121
}
118
- }
119
122
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 ) ;
133
133
} ) ;
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 ) ;
147
134
}
148
135
149
- } ) ;
136
+ this . init ( ) ;
137
+ }
0 commit comments