import { 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 { isBlank, isPresent } from '../../util/util'; import { Keyboard } from '../../platform/keyboard'; import { NavController } from '../../navigation/nav-controller'; import { DIRECTION_SWITCH, getComponent } from '../../navigation/nav-util'; import { formatUrlPart } from '../../navigation/url-serializer'; import { RootNode } from '../split-pane/split-pane'; import { Platform } from '../../platform/platform'; import { TabHighlight } from './tab-highlight'; import { ViewController } from '../../navigation/view-controller'; /** * @name Tabs * @description * Tabs make it easy to navigate between different pages or functional * aspects of an app. The Tabs component, written as ``, is * a container of individual [Tab](../Tab/) components. Each individual `ion-tab` * is a declarative component for a [NavController](../../../navigation/NavController/) * * For more information on using nav controllers like Tab or [Nav](../../nav/Nav/), * take a look at the [NavController API Docs](../../../navigation/NavController/). * * ### Placement * * The position of the tabs relative to the content varies based on * the mode. The tabs are placed at the bottom of the screen * for iOS and Android, and at the top for Windows by default. The position can * be configured using the `tabsPlacement` attribute on the `` component, * or in an app's [config](../../config/Config/). * See the [Input Properties](#input-properties) below for the available * values of `tabsPlacement`. * * ### Layout * * The layout for all of the tabs can be defined using the `tabsLayout` * property. If the individual tab has a title and icon, the icons will * show on top of the title by default. All tabs can be changed by setting * the value of `tabsLayout` on the `` element, or in your * app's [config](../../config/Config/). For example, this is useful if * you want to show tabs with a title only on Android, but show icons * and a title for iOS. See the [Input Properties](#input-properties) * below for the available values of `tabsLayout`. * * ### Selecting a Tab * * There are different ways you can select a specific tab from the tabs * component. You can use the `selectedIndex` property to set the index * on the `` element, or you can call `select()` from the `Tabs` * instance after creation. See [usage](#usage) below for more information. * * @usage * * You can add a basic tabs template to a `@Component` using the following * template: * * ```html * * * * * * ``` * * Where `tab1Root`, `tab2Root`, and `tab3Root` are each a page: * *```ts * @Component({ * templateUrl: 'build/pages/tabs/tabs.html' * }) * export class TabsPage { * // this tells the tabs component which Pages * // should be each tab's root Page * tab1Root = Page1; * tab2Root = Page2; * tab3Root = Page3; * * constructor() { * * } * } *``` * * By default, the first tab will be selected upon navigation to the * Tabs page. We can change the selected tab by using `selectedIndex` * on the `` element: * * ```html * * * * * * ``` * * Since the index starts at `0`, this will select the 3rd tab which has * root set to `tab3Root`. If you wanted to change it dynamically from * your class, you could use [property binding](https://angular.io/docs/ts/latest/guide/template-syntax.html#!#property-binding). * * Alternatively, you can grab the `Tabs` instance and call the `select()` * method. This requires the `` element to have an `id`. For * example, set the value of `id` to `myTabs`: * * ```html * * * * * * ``` * * Then in your class you can grab the `Tabs` instance and call `select()`, * passing the index of the tab as the argument. Here we're grabbing the tabs * by using ViewChild. * *```ts * export class TabsPage { * * @ViewChild('myTabs') tabRef: Tabs; * * ionViewDidEnter() { * this.tabRef.select(2); * } * * } *``` * * You can also switch tabs from a child component by calling `select()` on the * parent view using the `NavController` instance. For example, assuming you have * a `TabsPage` component, you could call the following from any of the child * components to switch to `TabsRoot3`: * *```ts * switchTabs() { * this.navCtrl.parent.select(2); * } *``` * @demo /docs/demos/src/tabs/ * * @see {@link /docs/components#tabs Tabs Component Docs} * @see {@link ../Tab Tab API Docs} * @see {@link ../../config/Config Config API Docs} * */ export class Tabs extends Ion { constructor(parent, viewCtrl, _app, config, elementRef, _plt, renderer, _linker, keyboard) { super(config, elementRef, renderer, 'tabs'); this.viewCtrl = viewCtrl; this._app = _app; this._plt = _plt; this._linker = _linker; /** @internal */ this._ids = -1; /** @internal */ this._tabs = []; /** @internal */ this._selectHistory = []; /** @internal */ this._onDestroy = new Subject(); /** * @output {any} Emitted when the tab changes. */ this.ionChange = new EventEmitter(); this.parent = parent; this.id = 't' + (++tabIds); this._sbPadding = config.getBoolean('statusbarPadding'); this.tabsHighlight = config.getBoolean('tabsHighlight'); if (this.parent) { // this Tabs has a parent Nav this.parent.registerChildNav(this); } else if (viewCtrl && viewCtrl.getNav()) { // this Nav was opened from a modal this.parent = viewCtrl.getNav(); this.parent.registerChildNav(this); } else if (this._app) { // this is the root navcontroller for the entire app this._app.registerRootNav(this); } // Tabs may also be an actual ViewController which was navigated to // if Tabs is static and not navigated to within a NavController // then skip this and don't treat it as it's own ViewController 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() { // get the selected index from the input // otherwise default it to use the first index let selectedIndex = (isBlank(this.selectedIndex) ? 0 : parseInt(this.selectedIndex, 10)); // now see if the deep linker can find a tab index const tabsSegment = this._linker.getSegmentByNavIdOrName(this.id, this.name); if (tabsSegment) { // we found a segment which probably represents which tab to select selectedIndex = this._getSelectedTabIndex(tabsSegment.secondaryId, selectedIndex); } // get the selectedIndex and ensure it isn't hidden or disabled let selectedTab = this._tabs.find((t, i) => i === selectedIndex && t.enabled && t.show); if (!selectedTab) { // wasn't able to select the tab they wanted // try to find the first tab that's available 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(() => { // set the initial href attribute values for each tab this._tabs.forEach(t => { t.updateHref(t.root, t.rootParams); }); }); } /** * @internal */ _setConfig(attrKey, fallback) { let val = 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 Index, or the Tab instance, of the tab to select. */ select(tabOrIndex, opts = {}, fromUrl = false) { const selectedTab = (typeof tabOrIndex === 'number' ? this.getByIndex(tabOrIndex) : tabOrIndex); if (isBlank(selectedTab)) { return Promise.resolve(); } // If the selected tab is the current selected tab, we do not switch const currentTab = this.getSelected(); if (selectedTab === currentTab && currentTab.getActive()) { return this._updateCurrentTab(selectedTab, fromUrl); } // If the selected tab does not have a root, we do not switch (#9392) // it's possible the tab is only for opening modal's or signing out // and doesn't actually have content. In the case there's no content // for a tab then do nothing and leave the current view as is if (selectedTab.root) { // At this point we are going to perform a page switch // Let's fire willLeave in the current tab page var currentPage; if (currentTab) { currentPage = currentTab.getActive(); currentPage && currentPage._willLeave(false); } // Fire willEnter in the new selected tab const selectedPage = selectedTab.getActive(); selectedPage && selectedPage._willEnter(); // Let's start the transition opts.animate = false; return selectedTab.load(opts).then(() => { this._tabSwitchEnd(selectedTab, selectedPage, currentPage); if (opts.updateUrl !== false) { this._linker.navChange(DIRECTION_SWITCH); } (void 0) /* assert */; this._fireChangeEvent(selectedTab); }); } else { this._fireChangeEvent(selectedTab); return Promise.resolve(); } } _fireChangeEvent(selectedTab) { selectedTab.ionSelect.emit(selectedTab); this.ionChange.emit(selectedTab); } _tabSwitchEnd(selectedTab, selectedPage, currentPage) { (void 0) /* assert */; (void 0) /* assert */; // Update tabs selection state 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); } // Fire didEnter/didLeave lifecycle events if (selectedPage) { selectedPage._didEnter(); this._app.viewDidEnter.emit(selectedPage); } if (currentPage) { currentPage && currentPage._didLeave(); this._app.viewDidLeave.emit(currentPage); } // track the order of which tabs have been selected, by their index // do not track if the tab index is the same as the previous if (this._selectHistory[this._selectHistory.length - 1] !== selectedTab.id) { this._selectHistory.push(selectedTab.id); } } /** * Get the previously selected Tab which is currently not disabled or hidden. * @param {boolean} trimHistory If the selection history should be trimmed up to the previous tab selection or not. * @returns {Tab} */ previousTab(trimHistory = true) { // walk backwards through the tab selection history // and find the first previous tab that is enabled and shown (void 0) /* console.debug */; 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 Index of the tab you want to get * @returns {Tab} Returns the tab who's index matches the one passed */ getByIndex(index) { return this._tabs[index]; } /** * @return {Tab} Returns the currently selected 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 */ _updateCurrentTab(tab, fromUrl) { const active = tab.getActive(); if (active) { if (fromUrl && tab._segment) { // see if the view controller exists const vc = tab.getViewById(tab._segment.name); if (vc) { // the view is already in the stack 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) { // if they have a custom tab selected handler, call it active._cmp.instance.ionSelected(); return Promise.resolve(); } else if (tab.length() > 1) { // if we're a few pages deep, pop to root return tab.popToRoot(); } else { return getComponent(this._linker, tab.root).then(viewController => { if (viewController.component !== active.component) { // Otherwise, if the page we're on is not our real root // reset it to our default root type return tab.setRoot(tab.root); } }).catch(() => { (void 0) /* console.debug */; }); } } } /** * @internal * DOM WRITE */ setTabbarPosition(top, bottom) { if (this._top !== top || this._bottom !== bottom) { var tabbarEle = 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); } } /* * @private */ getType() { return 'tabs'; } /* * @private */ getSecondaryIdentifier() { const tabs = this.getActiveChildNavs(); if (tabs && tabs.length) { return this._linker._getTabSelector(tabs[0]); } return ''; } /** * @private */ _getSelectedTabIndex(secondaryId = '', fallbackIndex = 0) { // we found a segment which probably represents which tab to select const indexMatch = secondaryId.match(/tab-(\d+)/); if (indexMatch) { // awesome, the segment name was something "tab-0", and // the numbe represents which tab to select return parseInt(indexMatch[1], 10); } // wasn't in the "tab-0" format so maybe it's using a word 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; } } Tabs.decorators = [ { type: Component, args: [{ selector: 'ion-tabs', template: '
' + '' + '
' + '
' + '' + '
', encapsulation: ViewEncapsulation.None, providers: [{ provide: RootNode, useExisting: forwardRef(() => Tabs) }] },] }, ]; /** @nocollapse */ Tabs.ctorParameters = () => [ { type: NavController, decorators: [{ type: Optional },] }, { type: ViewController, decorators: [{ type: Optional },] }, { type: App, }, { type: Config, }, { type: ElementRef, }, { type: Platform, }, { type: Renderer, }, { type: DeepLinker, }, { type: Keyboard, }, ]; Tabs.propDecorators = { 'name': [{ type: Input },], 'selectedIndex': [{ type: Input },], 'tabsLayout': [{ type: Input },], 'tabsPlacement': [{ type: Input },], 'tabsHighlight': [{ type: Input },], 'ionChange': [{ type: Output },], '_highlight': [{ type: ViewChild, args: [TabHighlight,] },], '_tabbar': [{ type: ViewChild, args: ['tabbar',] },], 'portal': [{ type: ViewChild, args: ['portal', { read: ViewContainerRef },] },], }; let tabIds = -1; //# sourceMappingURL=tabs.js.map