import { AfterViewInit , Component , ElementRef , EventEmitter , Input , Optional , Output , Renderer , ViewChild , ViewContainerRef , ViewEncapsulation , forwardRef } from '@angular/core' ; import { Subject } from 'rxjs/Subject' ; import 'rxjs/add/operator/takeUntil' ; import { App } from '../app/app' ; import { Config } from '../../config/config' ; import { DeepLinker } from '../../navigation/deep-linker' ; import { Ion } from '../ion' ; import { assert , isBlank , isPresent } from '../../util/util' ; import { Keyboard } from '../../platform/keyboard' ; import { Tabs as ITabs } from '../../navigation/nav-interfaces' ; import { NavController } from '../../navigation/nav-controller' ; import { NavControllerBase } from '../../navigation/nav-controller-base' ; import { NavigationContainer } from '../../navigation/navigation-container' ; import { DIRECTION_SWITCH , NavOptions , getComponent } from '../../navigation/nav-util' ; import { formatUrlPart } from '../../navigation/url-serializer' ; import { RootNode } from '../split-pane/split-pane' ; import { Platform } from '../../platform/platform' ; import { Tab } from './tab' ; import { TabHighlight } from './tab-highlight' ; import { ViewController } from '../../navigation/view-controller' ; name description usage Component ViewChild demo see {@link /docs/components#tabs Tabs Component Docs} see {@link ../Tab Tab API Docs} see {@link ../../config/Config Config API Docs} @ Component ( { selector : 'ion-tabs' , template : '<div class="tabbar" role="tablist" #tabbar>' + '<a *ngFor="let t of _tabs" [tab]="t" class="tab-button" role="tab" href="#" (ionSelect)="select(t)"></a>' + '<div class="tab-highlight"></div>' + '</div>' + '<ng-content></ng-content>' + '<div #portal tab-portal></div>' , encapsulation : ViewEncapsulation . None , providers : [ { provide : RootNode , useExisting : forwardRef ( ( ) => Tabs ) } ] } ) export class Tabs extends Ion implements AfterViewInit , RootNode , ITabs , NavigationContainer { internal _ids : number = - 1 ; internal _tabs : Tab [ ] = [ ] ; internal _sbPadding : boolean ; internal _top : number ; internal _bottom : number ; internal id : string ; internal _selectHistory : string [ ] = [ ] ; internal _onDestroy = new Subject < void > ( ) ; input @ Input ( ) name : string ; input @ Input ( ) selectedIndex : number ; input @ Input ( ) tabsLayout : string ; input @ Input ( ) tabsPlacement : string ; input @ Input ( ) tabsHighlight : boolean ; output @ Output ( ) ionChange : EventEmitter < Tab > = new EventEmitter < Tab > ( ) ; internal @ ViewChild ( TabHighlight ) _highlight : TabHighlight ; internal @ ViewChild ( 'tabbar' ) _tabbar : ElementRef ; internal @ ViewChild ( 'portal' , { read : ViewContainerRef } ) portal : ViewContainerRef ; hidden parent : NavControllerBase ; constructor ( @ Optional ( ) parent , @ Optional ( ) public viewCtrl , private _app , config , elementRef , private _plt , renderer , private _linker , keyboard ? ) { super ( config , elementRef , renderer , 'tabs' ) ; this . parent = < NavControllerBase > parent ; this . id = 't' + ( ++ tabIds ) ; this . _sbPadding = config . getBoolean ( 'statusbarPadding' ) ; this . tabsHighlight = config . getBoolean ( 'tabsHighlight' ) ; if ( this . parent ) { this . parent . registerChildNav ( this ) ; } else if ( viewCtrl && viewCtrl . getNav ( ) ) { this . parent = < any > viewCtrl . getNav ( ) ; this . parent . registerChildNav ( this ) ; } else if ( this . _app ) { this . _app . registerRootNav ( this ) ; } if ( viewCtrl ) { viewCtrl . _setContent ( this ) ; viewCtrl . _setContentRef ( elementRef ) ; } const keyboardResizes = config . getBoolean ( 'keyboardResizes' , false ) ; if ( keyboard && keyboardResizes ) { keyboard . willHide . takeUntil ( this . _onDestroy ) . subscribe ( ( ) => { this . _plt . timeout ( ( ) => this . setTabbarHidden ( false ) , 50 ) ; } ) ; keyboard . willShow . takeUntil ( this . _onDestroy ) . subscribe ( ( ) => this . setTabbarHidden ( true ) ) ; } } internal setTabbarHidden ( tabbarHidden ) { this . setElementClass ( 'tabbar-hidden' , tabbarHidden ) ; this . resize ( ) ; } internal ngOnDestroy ( ) { this . _onDestroy . next ( ) ; if ( this . parent ) { this . parent . unregisterChildNav ( this ) ; } else { this . _app . unregisterRootNav ( this ) ; } } internal ngAfterViewInit ( ) { this . _setConfig ( 'tabsPlacement' , 'bottom' ) ; this . _setConfig ( 'tabsLayout' , 'icon-top' ) ; this . _setConfig ( 'tabsHighlight' , this . tabsHighlight ) ; if ( this . tabsHighlight ) { this . _plt . resize . takeUntil ( this . _onDestroy ) . subscribe ( ( ) => this . _highlight . select ( this . getSelected ( ) ) ) ; } this . initTabs ( ) ; } internal initTabs ( ) { let selectedIndex = ( isBlank ( this . selectedIndex ) ? 0 : parseInt ( < any > this . selectedIndex , 10 ) ) ; const tabsSegment = this . _linker . getSegmentByNavIdOrName ( this . id , this . name ) ; if ( tabsSegment ) { selectedIndex = this . _getSelectedTabIndex ( tabsSegment . secondaryId , selectedIndex ) ; } let selectedTab = this . _tabs . find ( ( t , i ) => i === selectedIndex && t . enabled && t . show ) ; if ( ! selectedTab ) { selectedTab = this . _tabs . find ( t => t . enabled && t . show ) ; } let promise = Promise . resolve ( ) ; if ( selectedTab ) { selectedTab . _segment = tabsSegment ; promise = this . select ( selectedTab ) ; } return promise . then ( ( ) => { this . _tabs . forEach ( t => { t . updateHref ( t . root , t . rootParams ) ; } ) ; } ) ; } internal _setConfig ( attrKey , fallback ) { let val = ( < any > this ) [ attrKey ] ; if ( isBlank ( val ) ) { val = this . _config . get ( attrKey , fallback ) ; } this . setElementAttribute ( attrKey , val ) ; } hidden add ( tab ) { this . _tabs . push ( tab ) ; return this . id + '-' + ( ++ this . _ids ) ; } param { number | Tab } tabOrIndex select ( tabOrIndex , opts = { } , fromUrl = false ) { const selectedTab = ( typeof tabOrIndex === 'number' ? this . getByIndex ( tabOrIndex ) : tabOrIndex ) ; if ( isBlank ( selectedTab ) ) { return Promise . resolve ( ) ; } const currentTab = this . getSelected ( ) ; if ( selectedTab === currentTab && currentTab . getActive ( ) ) { return this . _updateCurrentTab ( selectedTab , fromUrl ) ; } if ( selectedTab . root ) { var currentPage ; if ( currentTab ) { currentPage = currentTab . getActive ( ) ; currentPage && currentPage . _willLeave ( false ) ; } const selectedPage = selectedTab . getActive ( ) ; selectedPage && selectedPage . _willEnter ( ) ; opts . animate = false ; return selectedTab . load ( opts ) . then ( ( ) => { this . _tabSwitchEnd ( selectedTab , selectedPage , currentPage ) ; if ( opts . updateUrl !== false ) { this . _linker . navChange ( DIRECTION_SWITCH ) ; } assert ( this . getSelected ( ) === selectedTab , 'selected tab does not match' ) ; this . _fireChangeEvent ( selectedTab ) ; } ) ; } else { this . _fireChangeEvent ( selectedTab ) ; return Promise . resolve ( ) ; } } _fireChangeEvent ( selectedTab ) { selectedTab . ionSelect . emit ( selectedTab ) ; this . ionChange . emit ( selectedTab ) ; } _tabSwitchEnd ( selectedTab , selectedPage , currentPage ) { assert ( selectedTab , 'selectedTab must be valid' ) ; assert ( this . _tabs . indexOf ( selectedTab ) >= 0 , 'selectedTab must be one of the tabs' ) ; const tabs = this . _tabs ; let tab ; for ( var i = 0 ; i < tabs . length ; i ++ ) { tab = tabs [ i ] ; tab . setSelected ( tab === selectedTab ) ; } if ( this . tabsHighlight ) { this . _highlight . select ( selectedTab ) ; } if ( selectedPage ) { selectedPage . _didEnter ( ) ; this . _app . viewDidEnter . emit ( selectedPage ) ; } if ( currentPage ) { currentPage && currentPage . _didLeave ( ) ; this . _app . viewDidLeave . emit ( currentPage ) ; } if ( this . _selectHistory [ this . _selectHistory . length - 1 ] !== selectedTab . id ) { this . _selectHistory . push ( selectedTab . id ) ; } } param { boolean } trimHistory returns { Tab } previousTab ( trimHistory = true ) { console . debug ( 'run previousTab' , this . _selectHistory ) ; for ( var i = this . _selectHistory . length - 2 ; i >= 0 ; i -- ) { var tab = this . _tabs . find ( t => t . id === this . _selectHistory [ i ] ) ; if ( tab && tab . enabled && tab . show ) { if ( trimHistory ) { this . _selectHistory . splice ( i + 1 ) ; } return tab ; } } return null ; } param { number } index returns { Tab } getByIndex ( index ) { return this . _tabs [ index ] ; } return { Tab } getSelected ( ) { const tabs = this . _tabs ; for ( var i = 0 ; i < tabs . length ; i ++ ) { if ( tabs [ i ] . isSelected ) { return tabs [ i ] ; } } return null ; } internal getActiveChildNavs ( ) { const selected = this . getSelected ( ) ; return selected ? [ selected ] : [ ] ; } internal getAllChildNavs ( ) { return this . _tabs ; } internal getIndex ( tab ) { return this . _tabs . indexOf ( tab ) ; } internal length ( ) { return this . _tabs . length ; } /**
     * "Touch" the active tab, going back to the root view of the tab
     * or optionally letting the tab handle the event
     */ private _updateCurrentTab ( tab , fromUrl ) { const active = tab . getActive ( ) ; if ( active ) { if ( fromUrl && tab . _segment ) { const vc = tab . getViewById ( tab . _segment . name ) ; if ( vc ) { return tab . popTo ( vc , { animate : false , updateUrl : false , } ) ; } else if ( tab . _views . length === 0 && tab . _segment . defaultHistory && tab . _segment . defaultHistory . length ) { return this . _linker . initViews ( tab . _segment ) . then ( ( views ) => { return tab . setPages ( views , { animate : false , updateUrl : false } ) ; } ) . then ( ( ) => { tab . _segment = null ; } ) ; } else { return tab . setRoot ( tab . _segment . name , tab . _segment . data , { animate : false , updateUrl : false } ) . then ( ( ) => { tab . _segment = null ; } ) ; } } else if ( active . _cmp && active . _cmp . instance . ionSelected ) { active . _cmp . instance . ionSelected ( ) ; return Promise . resolve ( ) ; } else if ( tab . length ( ) > 1 ) { return tab . popToRoot ( ) ; } else { return getComponent ( this . _linker , tab . root ) . then ( viewController => { if ( viewController . component !== active . component ) { return tab . setRoot ( tab . root ) ; } } ) . catch ( ( ) => { console . debug ( 'Tabs: reset root was cancelled' ) ; } ) ; } } } internal setTabbarPosition ( top , bottom ) { if ( this . _top !== top || this . _bottom !== bottom ) { var tabbarEle = < HTMLElement > this . _tabbar . nativeElement ; tabbarEle . style . top = ( top > - 1 ? top + 'px' : '' ) ; tabbarEle . style . bottom = ( bottom > - 1 ? bottom + 'px' : '' ) ; tabbarEle . classList . add ( 'show-tabbar' ) ; this . _top = top ; this . _bottom = bottom ; } } internal resize ( ) { const tab = this . getSelected ( ) ; tab && tab . resize ( ) ; } internal initPane ( ) { const isMain = this . _elementRef . nativeElement . hasAttribute ( 'main' ) ; return isMain ; } internal paneChanged ( isPane ) { if ( isPane ) { this . resize ( ) ; } } goToRoot ( opts ) { if ( this . _tabs . length ) { return this . select ( this . _tabs [ 0 ] , opts ) ; } } getType ( ) { return 'tabs' ; } getSecondaryIdentifier ( ) { const tabs = this . getActiveChildNavs ( ) ; if ( tabs && tabs . length ) { return this . _linker . _getTabSelector ( tabs [ 0 ] ) ; } return '' ; } private _getSelectedTabIndex ( secondaryId = '' , fallbackIndex = 0 ) { const indexMatch = secondaryId . match ( /tab-(\d+)/ ) ; if ( indexMatch ) { return parseInt ( indexMatch [ 1 ] , 10 ) ; } const tab = this . _tabs . find ( t => { return ( isPresent ( t . tabUrlPath ) && t . tabUrlPath === secondaryId ) || ( isPresent ( t . tabTitle ) && formatUrlPart ( t . tabTitle ) === secondaryId ) ; } ) ; return isPresent ( tab ) ? tab . index : fallbackIndex ; } } let tabIds = - 1 ;