@@ -15,133 +15,121 @@ export default Base.extend({
1515
1616 init ( ) {
1717 this . options = parser . parse ( this . el , this . options ) ;
18+
19+ this . init_listeners ( ) ;
20+
21+ this . mark_current ( ) ;
22+ } ,
23+
24+ /**
25+ * Initialize listeners for the navigation.
26+ */
27+ init_listeners ( ) {
1828 const current = this . options . currentClass ;
1929
20- // Automatically load the ``.current`` item.
30+ // Mark the navigation items after pat-inject triggered within this navigation menu.
31+ this . $el . on ( "patterns-inject-triggered" , "a" , ( ev ) => {
32+ // Remove all set current classes
33+ this . clear_items ( ) ;
34+
35+ // Mark the current item
36+ this . mark_current ( ev . target ) ;
37+ } ) ;
38+
39+ // Automatically and recursively load the ``.current`` item.
2140 if ( this . el . classList . contains ( "navigation-load-current" ) ) {
41+ // Check for current elements injected here.
42+ this . $el . on ( "patterns-injected-scanned" , ( ev ) => {
43+ const target = ev . target ;
44+ if ( target . matches ( `a.${ current } ` ) ) target . click ( ) ;
45+ else if ( target . matches ( `.${ current } ` ) ) target . querySelector ( "a" ) ?. click ( ) ; // prettier-ignore
46+ } ) ;
2247 this . el . querySelector ( `a.${ current } , .${ current } a` ) ?. click ( ) ;
23- // check for current elements injected here
24- this . $el . on (
25- "patterns-injected-scanned" ,
26- function ( ev ) {
27- const target = ev . target ;
28- if ( target . matches ( `a.${ current } ` ) ) target . click ( ) ;
29- if ( target . matches ( `.${ current } ` ) )
30- target . querySelector ( "a" ) ?. click ( ) ;
31- this . _updatenavpath ( ) ;
32- } . bind ( this )
33- ) ;
3448 }
3549
36- // Mark the navigation items after pat-inject triggered within this navigation menu.
37- this . $el . on (
38- "patterns-inject-triggered" ,
39- "a" ,
40- function ( ev ) {
41- const target = ev . target ;
42- // Remove all set current classes
43- this . el . querySelectorAll ( `.${ current } ` ) . forEach ( ( it ) => {
44- it . classList . remove ( current ) ;
45- } ) ;
46- // Set current class on target
47- target . classList . add ( current ) ;
48- // Also set the current class on the item wrapper.
49- target . closest ( this . options . itemWrapper ) ?. classList . add ( current ) ;
50- this . _updatenavpath ( ) ;
51- } . bind ( this )
52- ) ;
53-
5450 // Re-init when navigation changes.
55- const observer = new MutationObserver ( this . _initialSet . bind ( this ) ) ;
51+ const observer = new MutationObserver ( ( ) => {
52+ this . init_listeners ( ) ;
53+ this . mark_current ( ) ;
54+ } ) ;
5655 observer . observe ( this . el , {
5756 childList : true ,
5857 subtree : true ,
5958 attributes : false ,
6059 characterData : false ,
6160 } ) ;
62-
63- // Initialize.
64- this . _initialSet ( ) ;
6561 } ,
6662
67- _initialSet ( ) {
68- const current = this . options . currentClass ;
69-
70- // Set current class if it is not set
71- if ( this . el . querySelectorAll ( `.${ current } ` ) . length === 0 ) {
72- const a_els = this . el . querySelectorAll ( "a" ) ;
73- for ( const a_el of a_els ) {
74- const li = a_el . closest ( this . options . itemWrapper ) ;
75- const url = a_el . getAttribute ( "href" ) ;
76- if ( typeof url === "undefined" ) {
77- return ;
78- }
79- const path = this . _pathfromurl ( url ) ;
80- log . debug ( `checking url: ${ url } , extracted path: ${ path } ` ) ;
81- if ( this . _match ( window . location . pathname , path ) ) {
82- log . debug ( "found match" , li ) ;
83- a_el . classList . add ( current ) ;
84- li . classList . add ( current ) ;
85- }
63+ /**
64+ * Get a matching parent or stop at stop_el.
65+ *
66+ * @param {Node } item - The item to start with.
67+ * @param {String } selector - The CSS selector to search parent elements for.
68+ * @param {Node } stop_el - The element to stop at.
69+ *
70+ * @returns {Node } - The matching parent or null.
71+ */
72+ get_parent ( item , selector , stop_el ) {
73+ let matching_parent = item . parentNode ;
74+ while ( matching_parent ) {
75+ if ( matching_parent === stop_el || matching_parent === document ) {
76+ return null ;
8677 }
78+ if ( matching_parent . matches ( selector ) ) {
79+ return matching_parent ;
80+ }
81+ matching_parent = matching_parent . parentNode ;
8782 }
88-
89- // Set current class on item-wrapper, if not set.
90- if (
91- this . options . itemWrapper &&
92- this . el . querySelectorAll ( `.${ current } ` ) . length > 0 &&
93- this . el . querySelectorAll ( `${ this . options . itemWrapper } .${ current } ` ) . length ===
94- 0
95- ) {
96- this . el
97- . querySelector ( `a.${ current } ` )
98- . closest ( this . options . itemWrapper )
99- ?. classList . add ( current ) ;
100- }
101-
102- this . _updatenavpath ( ) ;
10383 } ,
10484
105- _updatenavpath ( ) {
106- const in_path = this . options . inPathClass ;
107- if ( ! in_path ) {
108- return ;
85+ /**
86+ * Mark an item and it's wrapper as current.
87+ *
88+ * @param {Node } [current_el] - The item to mark as current.
89+ * If not given, the element's tree will be searched for an existing current item.
90+ * This is to also mark the wrapper and it's path appropriately.
91+ */
92+ mark_current ( current_el ) {
93+ const current_els = current_el
94+ ? [ current_el ]
95+ : document . querySelectorAll ( `.current > a, a.current` ) ;
96+
97+ for ( const item of current_els ) {
98+ item . classList . add ( this . options . currentClass ) ;
99+ const wrapper = item . closest ( this . options . itemWrapper ) ;
100+ wrapper ?. classList . add ( this . options . currentClass ) ;
101+ this . mark_in_path ( wrapper || item ) ;
102+ log . debug ( "Statically set current item marked as current" , item ) ;
109103 }
110- this . el . querySelectorAll ( `.${ in_path } ` ) . forEach ( ( it ) => {
111- it . classList . remove ( in_path ) ;
112- } ) ;
113- this . el
114- . querySelectorAll (
115- `${ this . options . itemWrapper } :not(.${ this . options . currentClass } )`
116- )
117- . forEach ( ( it ) => {
118- if ( it . querySelector ( `.${ this . options . currentClass } ` ) ) {
119- it . classList . add ( in_path ) ;
120- }
121- } ) ;
122104 } ,
123105
124- _match ( curpath , path ) {
125- if ( ! path ) {
126- log . debug ( "path empty" ) ;
127- return false ;
128- }
129- // current path needs to end in the anchor's path
130- if ( path !== curpath . slice ( - path . length ) ) {
131- log . debug ( `Current path ${ curpath } does not end in ${ path } ` ) ;
132- return false ;
106+ /**
107+ * Mark all parent navigation elements as in path.
108+ *
109+ * @param {Node } start_el - The element to start with.
110+ *
111+ */
112+ mark_in_path ( start_el ) {
113+ let path_el = this . get_parent ( start_el , this . options . itemWrapper , this . el ) ;
114+ while ( path_el ) {
115+ if ( ! path_el . matches ( `.${ this . options . currentClass } ` ) ) {
116+ path_el . classList . add ( this . options . inPathClass ) ;
117+ log . debug ( "Marked item as in-path" , path_el ) ;
118+ }
119+ path_el = this . get_parent ( path_el , this . options . itemWrapper , this . el ) ;
133120 }
134- // XXX: we might need more exclusion tests
135- return true ;
136121 } ,
137122
138- _pathfromurl ( url ) {
139- const path = url . split ( "#" ) [ 0 ] . split ( "://" ) ;
140- if ( path . length > 2 ) {
141- log . error ( "weird url" , url ) ;
142- return "" ;
123+ /**
124+ * Clear all navigation items from the inPath and current classes
125+ */
126+ clear_items ( ) {
127+ const items = this . el . querySelectorAll (
128+ `.${ this . options . inPathClass } , .${ this . options . currentClass } `
129+ ) ;
130+ for ( const item of items ) {
131+ item . classList . remove ( this . options . inPathClass ) ;
132+ item . classList . remove ( this . options . currentClass ) ;
143133 }
144- if ( path . length === 1 ) return path [ 0 ] ;
145- return path [ 1 ] . split ( "/" ) . slice ( 1 ) . join ( "/" ) ;
146134 } ,
147135} ) ;
0 commit comments