1
1
import {
2
- NgModule ,
3
2
Component ,
4
- Compiler ,
5
3
ChangeDetectorRef ,
6
4
Injectable ,
7
5
Input ,
8
6
ElementRef ,
9
- ComponentFactoryResolver ,
10
7
OnInit ,
11
8
AfterViewInit ,
12
- ViewContainerRef ,
13
- ViewChild ,
14
9
OnDestroy ,
15
10
ChangeDetectionStrategy
16
11
} from '@angular/core' ;
17
- import { RouterModule } from '@angular/router' ;
18
12
19
13
import { ScrollSpyService } from '../index' ;
20
14
import { ScrollSpyIndexService } from './index.service' ;
@@ -28,21 +22,39 @@ export interface ScrollSpyIndexComponentOptions {
28
22
@Injectable ( )
29
23
@Component ( {
30
24
selector : 'scrollSpy-index-render' ,
31
- template : `<div #container></div>` ,
25
+ template : `
26
+ <div #container>
27
+ <ul class="nav menu">
28
+ <li *ngFor="let item of items" [class.active]="highlight(item.link)">
29
+ <a [routerLink]="" fragment="{{item.link}}" (click)="goTo(item.link)">{{item.text}}</a>
30
+ <ul *ngIf="item.children.length" class="nav menu">
31
+ <li *ngFor="let itemChild of item.children" [class.active]="highlight(itemChild.link)">
32
+ <a [routerLink]="" fragment="{{itemChild.link}}" (click)="goTo(itemChild.link)">{{itemChild.text}}</a>
33
+ <ul *ngIf="itemChild.children.length" class="nav menu">
34
+ <li *ngFor="let itemChild1 of itemChild.children" [class.active]="highlight(itemChild1.link)">
35
+ <a [routerLink]="" fragment="{{itemChild1.link}}" (click)="goTo(itemChild1.link)">{{itemChild1.text}}</a>
36
+ <ul *ngIf="itemChild1.children.length" class="nav menu">
37
+ <li *ngFor="let itemChild2 of itemChild1.children" [class.active]="highlight(itemChild2.link)">
38
+ <a [routerLink]="" fragment="{{itemChild2.link}}" (click)="goTo(itemChild2.link)">{{itemChild2.text}}</a>
39
+ </li>
40
+ </ul>
41
+ </li>
42
+ </ul>
43
+ </li>
44
+ </ul>
45
+ </li>
46
+ </ul>
47
+ </div>
48
+ ` ,
32
49
changeDetection : ChangeDetectionStrategy . OnPush
33
50
} )
34
51
export class ScrollSpyIndexRenderComponent implements OnInit , AfterViewInit , OnDestroy {
35
52
@Input ( ) public scrollSpyIndexRenderOptions : ScrollSpyIndexComponentOptions ;
36
53
37
- public stack : Array < any > = [ ] ;
38
- public parentStack : Array < any > = [ ] ;
39
- public lastItem : any ;
40
-
41
54
public currentScrollPosition : number ;
55
+ public items : any [ ] = [ ] ;
56
+ public itemsHash : any = { } ;
42
57
public itemsToHighlight : Array < string > = [ ] ;
43
-
44
- @ViewChild ( 'container' , { read : ViewContainerRef } )
45
- public viewContainerRef : ViewContainerRef ;
46
58
47
59
public defaultOptions : ScrollSpyIndexComponentOptions = {
48
60
spyId : 'window' ,
@@ -55,9 +67,7 @@ export class ScrollSpyIndexRenderComponent implements OnInit, AfterViewInit, OnD
55
67
public el : HTMLElement ;
56
68
57
69
constructor (
58
- private compiler : Compiler ,
59
70
private ref : ChangeDetectorRef ,
60
- private resolver : ComponentFactoryResolver ,
61
71
private elRef : ElementRef ,
62
72
private scrollSpy : ScrollSpyService ,
63
73
private scrollSpyIndex : ScrollSpyIndexService
@@ -103,90 +113,81 @@ export class ScrollSpyIndexRenderComponent implements OnInit, AfterViewInit, OnD
103
113
}
104
114
105
115
update ( ) {
106
- var items : Array < any > = this . scrollSpyIndex . getIndex ( this . scrollSpyIndexRenderOptions . id ) || [ ] ;
107
- var markup : string = '<ul class="nav menu">' ;
108
-
109
- for ( var i = 0 ; i < items . length ; i ++ ) {
110
- var item = this . itemConstruct ( items [ i ] ) ;
111
-
112
- if ( item . push ) {
113
- markup += '<ul class="nav menu">' ;
114
- } else if ( item . pop ) {
115
- for ( var j = 0 ; j < item . pop ; j ++ ) {
116
- markup += '</li></ul>' ;
116
+ const data : Array < any > = this . scrollSpyIndex . getIndex ( this . scrollSpyIndexRenderOptions . id ) || [ ] ;
117
+
118
+ let stack : Array < any > = [ ] ;
119
+ let parentStack : Array < any > = [ ] ;
120
+ let lastItem : any ;
121
+
122
+ this . items = [ ] ;
123
+ this . itemsHash = { } ;
124
+
125
+ for ( var i = 0 ; i < data . length ; ++ i ) {
126
+ // parse basic info from the dom item
127
+ var item : any = {
128
+ link : data [ i ] . id ,
129
+ text : data [ i ] . textContent || data [ i ] . innerText ,
130
+ parents : [ ] ,
131
+ children : [ ]
132
+ } ;
133
+
134
+ // build type identifier
135
+ var level : string = data [ i ] . tagName ;
136
+ for ( var n = 0 ; n < data [ i ] . classList . length ; n ++ ) {
137
+ level += ',' + data [ i ] . classList [ n ] ;
138
+ }
139
+
140
+ // here be dragons
141
+ var stacksize : number = stack . length ;
142
+ if ( stacksize === 0 ) {
143
+ // we are at the top level and will stay there
144
+ stack . push ( level ) ;
145
+ } else if ( level !== stack [ stacksize - 1 ] ) {
146
+ // traverse the ancestry, looking for a match
147
+ for ( var j = stacksize - 1 ; j >= 0 ; j -- ) {
148
+ if ( level === stack [ j ] ) {
149
+ break ; // found an ancestor
150
+ }
151
+ }
152
+ if ( j < 0 ) {
153
+ // this is a new submenu item, lets push the stack
154
+ stack . push ( level ) ;
155
+ parentStack . push ( lastItem ) ;
156
+ } else {
157
+ // we are either a sibling or higher up the tree,
158
+ // lets pop the stack if needed
159
+ while ( stack . length > j + 1 ) {
160
+ stack . pop ( ) ;
161
+ parentStack . pop ( ) ;
162
+ }
117
163
}
118
- } else if ( i !== 0 ) {
119
- markup += '</li>' ;
120
164
}
121
165
122
- markup += '<li [class.active]="highlight(\'' + item . link + '\')" pagemenuspy="' + item . link + '" parent="' + item . parent + '">' ;
123
-
124
- // HACK: remove click once https://github.com/angular/angular/issues/6595 is fixed
125
- markup += '<a [routerLink]="" fragment="' + item . link + '" (click)="goTo(\'' + item . link + '\')">' ;
126
- markup += item . text ;
127
- markup += '</a>' ;
128
- }
129
- markup += '</ul>' ;
130
-
131
- this . viewContainerRef . clear ( ) ;
132
- let componentFactory = this . compileToComponent ( markup , ( ) => this . getItemsToHighlight ( ) ) ;
133
- this . viewContainerRef . createComponent ( componentFactory ) ;
134
-
135
- setTimeout ( ( ) => {
136
- this . calculateHighlight ( ) ;
137
- } ) ;
138
- }
139
-
140
- itemConstruct ( data : any ) {
141
- // parse basic info from the dom item
142
- var item : any = {
143
- link : data . id ,
144
- text : data . textContent || data . innerText ,
145
- parent : ''
146
- } ;
147
-
148
- // build type identifier
149
- var level : string = data . tagName ;
150
- for ( var i = 0 ; i < data . classList . length ; i ++ ) {
151
- level += ',' + data . classList [ i ] ;
152
- }
153
-
154
- // here be dragons
155
- var stacksize : number = this . stack . length ;
156
- if ( stacksize === 0 ) {
157
- // we are at the top level and will stay there
158
- this . stack . push ( level ) ;
159
- } else if ( level !== this . stack [ stacksize - 1 ] ) {
160
- // traverse the ancestry, looking for a match
161
- for ( var j = stacksize - 1 ; j >= 0 ; j -- ) {
162
- if ( level === this . stack [ j ] ) {
163
- break ; // found an ancestor
166
+ // for next iteration
167
+ lastItem = item . link ;
168
+
169
+ // if we have a parent, lets record it
170
+ if ( parentStack . length > 0 ) {
171
+ item . parents = [ ...parentStack ] ;
172
+
173
+ let temp : any = this . items ;
174
+ for ( var t = 0 ; t < parentStack . length ; ++ t ) {
175
+ if ( t < parentStack . length - 1 ) {
176
+ temp = temp . filter ( ( e : any ) => { return e . link === parentStack [ t ] ; } ) [ 0 ] . children ;
177
+ } else {
178
+ temp . filter ( ( e : any ) => { return e . link === parentStack [ t ] ; } ) [ 0 ] . children . push ( item ) ;
179
+ }
164
180
}
165
- }
166
- if ( j < 0 ) {
167
- // this is a new submenu item, lets push the this.stack
168
- this . stack . push ( level ) ;
169
- item . push = true ;
170
- this . parentStack . push ( this . lastItem ) ;
171
181
} else {
172
- // we are either a sibling or higher up the tree,
173
- // lets pop the this.stack if needed
174
- item . pop = stacksize - 1 - j ;
175
- while ( this . stack . length > j + 1 ) {
176
- this . stack . pop ( ) ;
177
- this . parentStack . pop ( ) ;
178
- }
182
+ this . items . push ( item ) ;
179
183
}
180
- }
181
184
182
- // if we have a parent, lets record it
183
- if ( this . parentStack . length > 0 ) {
184
- item . parent = this . parentStack [ this . parentStack . length - 1 ] ;
185
+ this . itemsHash [ item . link ] = item ;
185
186
}
186
187
187
- // for next iteration
188
- this . lastItem = item . link ;
189
- return item ;
188
+ setTimeout ( ( ) => {
189
+ this . calculateHighlight ( ) ;
190
+ } ) ;
190
191
}
191
192
192
193
calculateHighlight ( ) {
@@ -208,56 +209,19 @@ export class ScrollSpyIndexRenderComponent implements OnInit, AfterViewInit, OnD
208
209
if ( ! highlightItem ) {
209
210
highlightItem = items [ 0 ] . id ;
210
211
}
211
- this . itemsToHighlight . push ( highlightItem ) ;
212
-
213
- while ( ! ! highlightItem ) {
214
- var item = this . el . querySelector ( '[pagemenuspy=' + highlightItem + ']' ) ;
215
- if ( ! ! item ) {
216
- var parent = item . getAttribute ( 'parent' ) ;
217
- if ( parent ) {
218
- highlightItem = parent ;
219
- this . itemsToHighlight . push ( highlightItem ) ;
220
- } else {
221
- highlightItem = null ;
222
- }
223
- } else {
224
- highlightItem = null ;
225
- }
226
- }
212
+ this . itemsToHighlight = [ highlightItem , ...this . itemsHash [ highlightItem ] . parents ] ;
227
213
228
214
this . ref . markForCheck ( ) ;
229
215
}
230
216
231
- getItemsToHighlight ( ) : Array < string > {
232
- return this . itemsToHighlight ;
217
+ highlight ( id : string ) : boolean {
218
+ return this . itemsToHighlight . indexOf ( id ) !== - 1 ;
233
219
}
234
220
235
- compileToComponent ( template : string , itemsToHighlight : any ) : any {
236
- @Injectable ( )
237
- @Component ( {
238
- selector : 'scrollSpyMenu' ,
239
- template
240
- } )
241
- class RenderComponent {
242
- highlight ( id : string ) : boolean {
243
- return itemsToHighlight ( ) . indexOf ( id ) !== - 1 ;
244
- }
245
-
246
- // HACK: remove click once https://github.com/angular/angular/issues/6595 is fixed
247
- goTo ( anchor : string ) {
248
- setTimeout ( ( ) => {
249
- document . querySelector ( '#' + anchor ) . scrollIntoView ( ) ;
250
- } ) ;
251
- }
252
- } ;
253
-
254
- @NgModule ( { imports : [ RouterModule ] , declarations : [ RenderComponent ] } )
255
- class RenderModule { }
256
-
257
- return this . compiler . compileModuleAndAllComponentsSync ( RenderModule )
258
- . componentFactories . find ( ( comp ) =>
259
- comp . componentType === RenderComponent
260
- ) ;
221
+ goTo ( anchor : string ) {
222
+ setTimeout ( ( ) => {
223
+ document . querySelector ( '#' + anchor ) . scrollIntoView ( ) ;
224
+ } ) ;
261
225
}
262
226
263
227
ngOnDestroy ( ) {
0 commit comments