import { Component , ChangeDetectorRef , Injectable , Input , ElementRef , OnInit , AfterViewInit , OnDestroy , ChangeDetectionStrategy } from '@angular/core' ; import { ScrollSpyService } from '../core/service' ; import { ScrollSpyIndexService } from './index.service' ; export interface ScrollSpyIndexComponentOptions { id ? : string ; spyId ? : string ; topMargin ? : number ; } @ Injectable ( ) @ Component ( { selector : 'scrollSpy-index-render' , template : `
  <div #container>
    <ul class="nav flex-column menu">
      <li *ngFor="let item of items" [class.active]="highlight(item.link)">
        <a [routerLink]="" fragment="{{item.link}}" (click)="goTo(item.link)">{{item.text}}</a>
        <ul *ngIf="item.children.length" class="nav menu">
          <li *ngFor="let itemChild of item.children" [class.active]="highlight(itemChild.link)">
            <a [routerLink]="" fragment="{{itemChild.link}}" (click)="goTo(itemChild.link)">{{itemChild.text}}</a>
            <ul *ngIf="itemChild.children.length" class="nav menu">
              <li *ngFor="let itemChild1 of itemChild.children" [class.active]="highlight(itemChild1.link)">
                <a [routerLink]="" fragment="{{itemChild1.link}}" (click)="goTo(itemChild1.link)">{{itemChild1.text}}</a>
                 <ul *ngIf="itemChild1.children.length" class="nav menu">
                  <li *ngFor="let itemChild2 of itemChild1.children" [class.active]="highlight(itemChild2.link)">
                    <a [routerLink]="" fragment="{{itemChild2.link}}" (click)="goTo(itemChild2.link)">{{itemChild2.text}}</a>
                  </li>
                </ul>
              </li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </div>
  ` , changeDetection : ChangeDetectionStrategy . OnPush } ) export class ScrollSpyIndexRenderComponent implements OnInit , AfterViewInit , OnDestroy { @ Input ( ) public scrollSpyIndexRenderOptions : ScrollSpyIndexComponentOptions ; public currentScrollPosition : number ; public items : any [ ] = [ ] ; public itemsHash : any = { } ; public itemsToHighlight : Array < string > = [ ] ; public defaultOptions : ScrollSpyIndexComponentOptions = { spyId : 'window' , topMargin : 0 } ; public changeStream$ : any ; public scrollStream$ : any ; public el : HTMLElement ; constructor ( private ref , private elRef , private scrollSpy , private scrollSpyIndex ) { this . el = elRef . nativeElement ; } ngOnInit ( ) { if ( ! this . scrollSpyIndexRenderOptions ) { this . scrollSpyIndexRenderOptions = { } ; } if ( ! this . scrollSpyIndexRenderOptions . id ) { return console . warn ( 'ScrollSpyIndex: Missing id.' ) ; } this . scrollSpyIndexRenderOptions = Object . assign ( this . defaultOptions , this . scrollSpyIndexRenderOptions ) ; this . changeStream$ = this . scrollSpyIndex . changes$ . subscribe ( ( e ) => { if ( e . index === this . scrollSpyIndexRenderOptions . id ) { if ( e . change === 'delete' ) { this . update ( ) ; } else if ( e . change === 'set' ) { this . update ( ) ; } } } ) ; } ngAfterViewInit ( ) { if ( ! ! this . scrollSpy . getObservable ( this . scrollSpyIndexRenderOptions . spyId ) ) { this . scrollStream$ = this . scrollSpy . getObservable ( this . scrollSpyIndexRenderOptions . spyId ) . subscribe ( ( e ) => { if ( typeof e . target . scrollingElement !== 'undefined' ) { this . currentScrollPosition = e . target . scrollingElement . scrollTop ; } else if ( typeof e . target . scrollY !== 'undefined' ) { this . currentScrollPosition = e . target . scrollY ; } else if ( typeof e . target . pageYOffset !== 'undefined' ) { this . currentScrollPosition = e . target . pageYOffset ; } this . calculateHighlight ( ) ; } ) ; } else { return console . warn ( 'ScrollSpyIndexComponent: No ScrollSpy observable for id "' + this . scrollSpyIndexRenderOptions . spyId + '"' ) ; } } update ( ) { const data = this . scrollSpyIndex . getIndex ( this . scrollSpyIndexRenderOptions . id ) || [ ] ; let stack = [ ] ; let parentStack = [ ] ; let lastItem ; this . items = [ ] ; this . itemsHash = { } ; for ( var i = 0 ; i < data . length ; ++ i ) { var item = { link : data [ i ] . id , text : data [ i ] . textContent || data [ i ] . innerText , parents : [ ] , children : [ ] } ; var level = data [ i ] . tagName ; for ( var n = 0 ; n < data [ i ] . classList . length ; n ++ ) { level += ',' + data [ i ] . classList [ n ] ; } var stacksize = stack . length ; if ( stacksize === 0 ) { stack . push ( level ) ; } else if ( level !== stack [ stacksize - 1 ] ) { for ( var j = stacksize - 1 ; j >= 0 ; j -- ) { if ( level === stack [ j ] ) { break ; } } if ( j < 0 ) { stack . push ( level ) ; parentStack . push ( lastItem ) ; } else { while ( stack . length > j + 1 ) { stack . pop ( ) ; parentStack . pop ( ) ; } } } lastItem = item . link ; if ( parentStack . length > 0 ) { item . parents = [ ... parentStack ] ; let temp = this . items ; for ( var t = 0 ; t < parentStack . length ; ++ t ) { if ( t < parentStack . length - 1 ) { temp = temp . filter ( ( e ) => { return e . link === parentStack [ t ] ; } ) [ 0 ] . children ; } else { temp . filter ( ( e ) => { return e . link === parentStack [ t ] ; } ) [ 0 ] . children . push ( item ) ; } } } else { this . items . push ( item ) ; } this . itemsHash [ item . link ] = item ; } setTimeout ( ( ) => { this . calculateHighlight ( ) ; } ) ; } calculateHighlight ( ) { var items = this . scrollSpyIndex . getIndex ( this . scrollSpyIndexRenderOptions . id ) ; this . itemsToHighlight = [ ] ; if ( ! items || ! items . length ) { return ; } var highlightItem ; for ( var i = items . length - 1 ; i >= 0 ; i -- ) { if ( this . currentScrollPosition - ( items [ i ] . offsetTop + this . scrollSpyIndexRenderOptions . topMargin ) >= 0 ) { highlightItem = items [ i ] . id ; break ; } } if ( ! highlightItem ) { highlightItem = items [ 0 ] . id ; } this . itemsToHighlight = [ highlightItem , ... this . itemsHash [ highlightItem ] . parents ] ; this . ref . markForCheck ( ) ; } highlight ( id ) { return this . itemsToHighlight . indexOf ( id ) !== - 1 ; } goTo ( anchor ) { setTimeout ( ( ) => { document . querySelector ( '#' + anchor ) . scrollIntoView ( ) ; } ) ; } ngOnDestroy ( ) { this . changeStream$ . unsubscribe ( ) ; this . scrollStream$ . unsubscribe ( ) ; } }