mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
refactor(nav): queue based transitions
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,18 +0,0 @@
|
||||
|
||||
export interface NavOptions {
|
||||
animate?: boolean;
|
||||
animation?: string;
|
||||
direction?: string;
|
||||
duration?: number;
|
||||
easing?: string;
|
||||
id?: string;
|
||||
keyboardClose?: boolean;
|
||||
preload?: boolean;
|
||||
transitionDelay?: number;
|
||||
progressAnimation?: boolean;
|
||||
climbNav?: boolean;
|
||||
ev?: any;
|
||||
}
|
||||
|
||||
export const DIRECTION_BACK = 'back';
|
||||
export const DIRECTION_FORWARD = 'forward';
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Directive, HostListener, Input, Optional } from '@angular/core';
|
||||
import { NavController } from './nav-controller';
|
||||
import { noop } from '../../util/util';
|
||||
import { AfterViewInit, Directive, HostBinding, HostListener, OnChanges, Optional } from '@angular/core';
|
||||
|
||||
import { DeepLinker } from '../../navigation/deep-linker';
|
||||
import { NavController } from '../../navigation/nav-controller';
|
||||
import { ViewController } from '../../navigation/view-controller';
|
||||
|
||||
|
||||
/**
|
||||
* @name NavPop
|
||||
@@ -27,9 +30,9 @@ import { noop } from '../../util/util';
|
||||
})
|
||||
export class NavPop {
|
||||
|
||||
constructor(@Optional() private _nav: NavController) {
|
||||
constructor(@Optional() public _nav: NavController) {
|
||||
if (!_nav) {
|
||||
console.error('nav-pop must be within a NavController');
|
||||
console.error('navPop must be within a NavController');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +40,7 @@ export class NavPop {
|
||||
onClick(): boolean {
|
||||
// If no target, or if target is _self, prevent default browser behavior
|
||||
if (this._nav) {
|
||||
this._nav.pop(null, noop);
|
||||
this._nav.pop(null, null);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -45,3 +48,39 @@ export class NavPop {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'a[navPop]'
|
||||
})
|
||||
export class NavPopAnchor implements OnChanges, AfterViewInit {
|
||||
|
||||
constructor(
|
||||
@Optional() public host: NavPop,
|
||||
public linker: DeepLinker,
|
||||
@Optional() public viewCtrl: ViewController) {}
|
||||
|
||||
@HostBinding() href: string;
|
||||
|
||||
updateHref() {
|
||||
if (this.host && this.viewCtrl) {
|
||||
const previousView = this.host._nav.getPrevious(this.viewCtrl);
|
||||
this.href = (previousView && this.linker.createUrl(this.host._nav, this.viewCtrl.component, this.viewCtrl.data)) || '#';
|
||||
|
||||
} else {
|
||||
this.href = '#';
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.updateHref();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.updateHref();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Directive, HostListener, Input, Optional } from '@angular/core';
|
||||
import { AfterViewInit, Directive, Host, HostBinding, HostListener, Input, Optional, OnChanges } from '@angular/core';
|
||||
|
||||
import { NavController } from './nav-controller';
|
||||
import { noop } from '../../util/util';
|
||||
import { DeepLinker } from '../../navigation/deep-linker';
|
||||
import { NavController } from '../../navigation/nav-controller';
|
||||
|
||||
/**
|
||||
* @name NavPush
|
||||
@@ -60,7 +60,7 @@ export class NavPush {
|
||||
@Input() navParams: {[k: string]: any};
|
||||
|
||||
|
||||
constructor(@Optional() private _nav: NavController) {
|
||||
constructor(@Optional() public _nav: NavController) {
|
||||
if (!_nav) {
|
||||
console.error('navPush must be within a NavController');
|
||||
}
|
||||
@@ -68,13 +68,44 @@ export class NavPush {
|
||||
|
||||
@HostListener('click')
|
||||
onClick(): boolean {
|
||||
// If no target, or if target is _self, prevent default browser behavior
|
||||
if (this._nav) {
|
||||
this._nav.push(this.navPush, this.navParams, noop);
|
||||
this._nav.push(this.navPush, this.navParams, null);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'a[navPush]'
|
||||
})
|
||||
export class NavPushAnchor implements OnChanges, AfterViewInit {
|
||||
|
||||
constructor(
|
||||
@Host() public host: NavPush,
|
||||
@Optional() public linker: DeepLinker) {}
|
||||
|
||||
@HostBinding() href: string;
|
||||
|
||||
updateHref() {
|
||||
if (this.host && this.linker) {
|
||||
this.href = this.linker.createUrl(this.host._nav, this.host.navPush, this.host.navParams) || '#';
|
||||
|
||||
} else {
|
||||
this.href = '#';
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.updateHref();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.updateHref();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { AfterViewInit, Component, ComponentResolver, ElementRef, Input, Optional, NgZone, Renderer, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
|
||||
import { AfterViewInit, Component, ComponentFactoryResolver, ElementRef, Input, Optional, NgZone, Renderer, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
|
||||
|
||||
import { App } from '../app/app';
|
||||
import { Config } from '../../config/config';
|
||||
import { Keyboard } from '../../util/keyboard';
|
||||
import { DeepLinker } from '../../navigation/deep-linker';
|
||||
import { GestureController } from '../../gestures/gesture-controller';
|
||||
import { isTrueProperty } from '../../util/util';
|
||||
import { NavControllerBase } from './nav-controller-base';
|
||||
import { ViewController } from './view-controller';
|
||||
import { Keyboard } from '../../util/keyboard';
|
||||
import { NavControllerBase } from '../../navigation/nav-controller-base';
|
||||
import { NavOptions } from '../../navigation/nav-util';
|
||||
import { TransitionController } from '../../transitions/transition-controller';
|
||||
import { ViewController } from '../../navigation/view-controller';
|
||||
|
||||
/**
|
||||
* @name Nav
|
||||
@@ -24,32 +27,27 @@ import { ViewController } from './view-controller';
|
||||
*
|
||||
* ```ts
|
||||
* import { Component } from '@angular/core';
|
||||
* import { ionicBootstrap } from 'ionic-angular';
|
||||
* import { GettingStartedPage } from './getting-started';
|
||||
*
|
||||
* @Component({
|
||||
* template: `<ion-nav [root]="root"></ion-nav>`
|
||||
* })
|
||||
* class MyApp {
|
||||
* private root: any = GettingStartedPage;
|
||||
* root = GettingStartedPage;
|
||||
*
|
||||
* constructor(){
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ionicBootstrap(MyApp);
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* @demo /docs/v2/demos/navigation/
|
||||
* @see {@link /docs/v2/components#navigation Navigation Component Docs}
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ion-nav',
|
||||
template: `
|
||||
<div #viewport nav-viewport></div>
|
||||
<div class="nav-decor"></div>
|
||||
`,
|
||||
template:
|
||||
'<div #viewport nav-viewport></div>' +
|
||||
'<div class="nav-decor"></div>',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class Nav extends NavControllerBase implements AfterViewInit {
|
||||
@@ -65,16 +63,17 @@ export class Nav extends NavControllerBase implements AfterViewInit {
|
||||
elementRef: ElementRef,
|
||||
zone: NgZone,
|
||||
renderer: Renderer,
|
||||
compiler: ComponentResolver,
|
||||
gestureCtrl: GestureController
|
||||
cfr: ComponentFactoryResolver,
|
||||
gestureCtrl: GestureController,
|
||||
transCtrl: TransitionController,
|
||||
@Optional() linker: DeepLinker
|
||||
) {
|
||||
super(parent, app, config, keyboard, elementRef, zone, renderer, compiler, gestureCtrl);
|
||||
super(parent, app, config, keyboard, elementRef, zone, renderer, cfr, gestureCtrl, transCtrl, linker);
|
||||
|
||||
if (viewCtrl) {
|
||||
// an ion-nav can also act as an ion-page within a parent ion-nav
|
||||
// this would happen when an ion-nav nests a child ion-nav.
|
||||
viewCtrl.setContent(this);
|
||||
viewCtrl.setContentRef(elementRef);
|
||||
viewCtrl._setContent(this);
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
@@ -89,7 +88,7 @@ export class Nav extends NavControllerBase implements AfterViewInit {
|
||||
} else if (app && !app.getRootNav()) {
|
||||
// a root nav has not been registered yet with the app
|
||||
// this is the root navcontroller for the entire app
|
||||
app.setRootNav(this);
|
||||
app._setRootNav(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,17 +100,26 @@ export class Nav extends NavControllerBase implements AfterViewInit {
|
||||
this.setViewport(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ngAfterViewInit() {
|
||||
this._hasInit = true;
|
||||
|
||||
if (this._root) {
|
||||
this.push(this._root);
|
||||
let navSegment = this._linker.initNav(this);
|
||||
if (navSegment && navSegment.component) {
|
||||
// there is a segment match in the linker
|
||||
this.setPages(this._linker.initViews(navSegment), null, null);
|
||||
|
||||
} else if (this._root) {
|
||||
// no segment match, so use the root property
|
||||
this.push(this._root, this.rootParams, {
|
||||
isNavRoot: (<any>this._app.getRootNav() === this)
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
|
||||
goToRoot(opts: NavOptions) {
|
||||
this.setRoot(this._root, this.rootParams, opts, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @input {Page} The Page component to load as the root page within this nav.
|
||||
*/
|
||||
@@ -127,6 +135,11 @@ export class Nav extends NavControllerBase implements AfterViewInit {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @input {object} Any nav-params to pass to the root page of this nav.
|
||||
*/
|
||||
@Input() rootParams: any;
|
||||
|
||||
/**
|
||||
* @input {boolean} Whether it's possible to swipe-to-go-back on this nav controller or not.
|
||||
*/
|
||||
@@ -138,4 +151,11 @@ export class Nav extends NavControllerBase implements AfterViewInit {
|
||||
this._sbEnabled = isTrueProperty(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
destroy() {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { ComponentResolver, Directive, ElementRef, forwardRef, Inject, NgZone, Optional, Renderer, ViewContainerRef } from '@angular/core';
|
||||
import { ComponentFactoryResolver, Directive, ElementRef, forwardRef, Inject, NgZone, Optional, Renderer, ViewContainerRef } from '@angular/core';
|
||||
|
||||
import { App } from '../app/app';
|
||||
import { Config } from '../../config/config';
|
||||
import { DeepLinker } from '../../navigation/deep-linker';
|
||||
import { GestureController } from '../../gestures/gesture-controller';
|
||||
import { Keyboard } from '../../util/keyboard';
|
||||
import { NavControllerBase } from '../nav/nav-controller-base';
|
||||
import { NavControllerBase } from '../../navigation/nav-controller-base';
|
||||
import { TransitionController } from '../../transitions/transition-controller';
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[nav-portal]'
|
||||
selector: '[overlay-portal]'
|
||||
})
|
||||
export class NavPortal extends NavControllerBase {
|
||||
export class OverlayPortal extends NavControllerBase {
|
||||
constructor(
|
||||
@Inject(forwardRef(() => App)) app: App,
|
||||
config: Config,
|
||||
@@ -20,15 +22,16 @@ export class NavPortal extends NavControllerBase {
|
||||
elementRef: ElementRef,
|
||||
zone: NgZone,
|
||||
renderer: Renderer,
|
||||
compiler: ComponentResolver,
|
||||
cfr: ComponentFactoryResolver,
|
||||
gestureCtrl: GestureController,
|
||||
transCtrl: TransitionController,
|
||||
@Optional() linker: DeepLinker,
|
||||
viewPort: ViewContainerRef
|
||||
) {
|
||||
super(null, app, config, keyboard, elementRef, zone, renderer, compiler, gestureCtrl);
|
||||
super(null, app, config, keyboard, elementRef, zone, renderer, cfr, gestureCtrl, transCtrl, linker);
|
||||
this._isPortal = true;
|
||||
this._init = true;
|
||||
this.setViewport(viewPort);
|
||||
app.setPortal(this);
|
||||
|
||||
// on every page change make sure the portal has
|
||||
// dismissed any views that should be auto dismissed on page change
|
||||
@@ -1,116 +0,0 @@
|
||||
import { LifeCycleEvent, ViewController } from '../../../../src';
|
||||
|
||||
export function run() {
|
||||
describe('ViewController', () => {
|
||||
|
||||
afterEach(() => {
|
||||
if ( subscription ) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
describe('willEnter', () => {
|
||||
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||
// arrange
|
||||
let viewController = new ViewController(FakePage);
|
||||
subscription = viewController.willEnter.subscribe((event: LifeCycleEvent) => {
|
||||
// assert
|
||||
expect(event).toEqual(null);
|
||||
done();
|
||||
}, (err: any) => {
|
||||
done(err);
|
||||
});
|
||||
|
||||
// act
|
||||
viewController.fireWillEnter();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('didEnter', () => {
|
||||
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||
// arrange
|
||||
let viewController = new ViewController(FakePage);
|
||||
subscription = viewController.didEnter.subscribe((event: LifeCycleEvent) => {
|
||||
// assert
|
||||
expect(event).toEqual(null);
|
||||
done();
|
||||
}, (err: any) => {
|
||||
done(err);
|
||||
});
|
||||
|
||||
// act
|
||||
viewController.fireDidEnter();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('willLeave', () => {
|
||||
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||
// arrange
|
||||
let viewController = new ViewController(FakePage);
|
||||
subscription = viewController.willLeave.subscribe((event: LifeCycleEvent) => {
|
||||
// assert
|
||||
expect(event).toEqual(null);
|
||||
done();
|
||||
}, (err: any) => {
|
||||
done(err);
|
||||
});
|
||||
|
||||
// act
|
||||
viewController.fireWillLeave();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('didLeave', () => {
|
||||
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||
// arrange
|
||||
let viewController = new ViewController(FakePage);
|
||||
subscription = viewController.didLeave.subscribe((event: LifeCycleEvent) => {
|
||||
// assert
|
||||
expect(event).toEqual(null);
|
||||
done();
|
||||
}, (err: any) => {
|
||||
done(err);
|
||||
});
|
||||
|
||||
// act
|
||||
viewController.fireDidLeave();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('willUnload', () => {
|
||||
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||
// arrange
|
||||
let viewController = new ViewController(FakePage);
|
||||
subscription = viewController.willUnload.subscribe((event: LifeCycleEvent) => {
|
||||
expect(event).toEqual(null);
|
||||
done();
|
||||
}, (err: any) => {
|
||||
done(err);
|
||||
});
|
||||
|
||||
// act
|
||||
viewController.fireWillUnload();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('destroy', () => {
|
||||
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||
// arrange
|
||||
let viewController = new ViewController(FakePage);
|
||||
subscription = viewController.didUnload.subscribe((event: LifeCycleEvent) => {
|
||||
// assert
|
||||
expect(event).toEqual(null);
|
||||
done();
|
||||
}, (err: any) => {
|
||||
done(err);
|
||||
});
|
||||
|
||||
// act
|
||||
viewController.destroy();
|
||||
}, 10000);
|
||||
});
|
||||
});
|
||||
|
||||
let subscription: any = null;
|
||||
class FakePage {}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
import { ChangeDetectorRef, Component, ComponentResolver, ElementRef, EventEmitter, forwardRef, Input, Inject, NgZone, Optional, Output, Renderer, ViewChild, ViewEncapsulation, ViewContainerRef } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, ElementRef, EventEmitter, Input, NgZone, Optional, Output, Renderer, ViewChild, ViewEncapsulation, ViewContainerRef } from '@angular/core';
|
||||
|
||||
import { App } from '../app/app';
|
||||
import { Config } from '../../config/config';
|
||||
import { DeepLinker } from '../../navigation/deep-linker';
|
||||
import { GestureController } from '../../gestures/gesture-controller';
|
||||
import { isTrueProperty } from '../../util/util';
|
||||
import { Keyboard } from '../../util/keyboard';
|
||||
import { NavControllerBase } from '../nav/nav-controller-base';
|
||||
import { NavOptions } from '../nav/nav-interfaces';
|
||||
import { NavControllerBase } from '../../navigation/nav-controller-base';
|
||||
import { NavOptions } from '../../navigation/nav-util';
|
||||
import { TabButton } from './tab-button';
|
||||
import { Tabs } from './tabs';
|
||||
import { ViewController } from '../nav/view-controller';
|
||||
import { TransitionController } from '../../transitions/transition-controller';
|
||||
import { ViewController } from '../../navigation/view-controller';
|
||||
|
||||
|
||||
/**
|
||||
@@ -93,14 +95,14 @@ import { ViewController } from '../nav/view-controller';
|
||||
* the tabs.
|
||||
*
|
||||
* ```html
|
||||
* <ion-tabs preloadTabs="false">
|
||||
* <ion-tabs>
|
||||
* <ion-tab (ionSelect)="chat()"></ion-tab>
|
||||
* </ion-tabs>
|
||||
* ```
|
||||
*
|
||||
* ```ts
|
||||
* export class Tabs {
|
||||
* constructor(private modalCtrl: ModalController) {
|
||||
* constructor(public modalCtrl: ModalController) {
|
||||
*
|
||||
* }
|
||||
*
|
||||
@@ -120,23 +122,40 @@ import { ViewController } from '../nav/view-controller';
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ion-tab',
|
||||
template:
|
||||
'<div #viewport></div><div class="nav-decor"></div>',
|
||||
host: {
|
||||
'[class.show-tab]': 'isSelected',
|
||||
'[attr.id]': '_tabId',
|
||||
'[attr.aria-labelledby]': '_btnId',
|
||||
'role': 'tabpanel'
|
||||
},
|
||||
template: '<div #viewport></div><div class="nav-decor"></div>',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class Tab extends NavControllerBase {
|
||||
private _isInitial: boolean;
|
||||
private _isEnabled: boolean = true;
|
||||
private _isShown: boolean = true;
|
||||
private _tabId: string;
|
||||
private _btnId: string;
|
||||
private _loaded: boolean;
|
||||
private _loadTmr: any;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_isInitial: boolean;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_isEnabled: boolean = true;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_isShown: boolean = true;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_tabId: string;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_btnId: string;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_loaded: boolean;
|
||||
|
||||
/**
|
||||
* @private
|
||||
@@ -158,6 +177,11 @@ export class Tab extends NavControllerBase {
|
||||
*/
|
||||
@Input() rootParams: any;
|
||||
|
||||
/**
|
||||
* @input {string} The URL path name to represent this tab within the URL.
|
||||
*/
|
||||
@Input() tabUrlPath: string;
|
||||
|
||||
/**
|
||||
* @input {string} The title of the tab button.
|
||||
*/
|
||||
@@ -221,21 +245,23 @@ export class Tab extends NavControllerBase {
|
||||
@Output() ionSelect: EventEmitter<Tab> = new EventEmitter<Tab>();
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => Tabs)) public parent: Tabs,
|
||||
parent: Tabs,
|
||||
app: App,
|
||||
config: Config,
|
||||
keyboard: Keyboard,
|
||||
elementRef: ElementRef,
|
||||
zone: NgZone,
|
||||
renderer: Renderer,
|
||||
compiler: ComponentResolver,
|
||||
cfr: ComponentFactoryResolver,
|
||||
private _cd: ChangeDetectorRef,
|
||||
gestureCtrl: GestureController
|
||||
gestureCtrl: GestureController,
|
||||
transCtrl: TransitionController,
|
||||
@Optional() private linker: DeepLinker
|
||||
) {
|
||||
// A Tab is a NavController for its child pages
|
||||
super(parent, app, config, keyboard, elementRef, zone, renderer, compiler, gestureCtrl);
|
||||
super(parent, app, config, keyboard, elementRef, zone, renderer, cfr, gestureCtrl, transCtrl, linker);
|
||||
|
||||
parent.add(this);
|
||||
this.id = parent.add(this);
|
||||
|
||||
this._tabId = 'tabpanel-' + this.id;
|
||||
this._btnId = 'tab-' + this.id;
|
||||
@@ -261,52 +287,33 @@ export class Tab extends NavControllerBase {
|
||||
*/
|
||||
load(opts: NavOptions, done?: Function) {
|
||||
if (!this._loaded && this.root) {
|
||||
this.push(this.root, this.rootParams, opts, () => {
|
||||
done(true);
|
||||
});
|
||||
this.push(this.root, this.rootParams, opts, done);
|
||||
this._loaded = true;
|
||||
|
||||
} else {
|
||||
done(false);
|
||||
done(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
preload(wait: number) {
|
||||
this._loadTmr = setTimeout(() => {
|
||||
if (!this._loaded) {
|
||||
console.debug('Tabs, preload', this.id);
|
||||
this.load({
|
||||
animate: false,
|
||||
preload: true
|
||||
}, function(){});
|
||||
}
|
||||
}, wait);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
loadPage(viewCtrl: ViewController, viewport: ViewContainerRef, opts: NavOptions, done: Function) {
|
||||
let isTabSubPage = (this.parent.subPages && viewCtrl.index > 0);
|
||||
_viewInsert(viewCtrl: ViewController, componentRef: ComponentRef<any>, viewport: ViewContainerRef) {
|
||||
const isTabSubPage = (this.parent._subPages && viewCtrl.index > 0);
|
||||
|
||||
if (isTabSubPage) {
|
||||
viewport = this.parent.portal;
|
||||
}
|
||||
|
||||
super.loadPage(viewCtrl, viewport, opts, () => {
|
||||
if (isTabSubPage) {
|
||||
// add the .tab-subpage css class to tabs pages that should act like subpages
|
||||
let pageEleRef = viewCtrl.pageRef();
|
||||
if (pageEleRef) {
|
||||
this._renderer.setElementClass(pageEleRef.nativeElement, 'tab-subpage', true);
|
||||
}
|
||||
super._viewInsert(viewCtrl, componentRef, viewport);
|
||||
|
||||
if (isTabSubPage) {
|
||||
// add the .tab-subpage css class to tabs pages that should act like subpages
|
||||
const pageEleRef = viewCtrl.pageRef();
|
||||
if (pageEleRef) {
|
||||
this._renderer.setElementClass(pageEleRef.nativeElement, 'tab-subpage', true);
|
||||
}
|
||||
done();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -315,6 +322,9 @@ export class Tab extends NavControllerBase {
|
||||
setSelected(isSelected: boolean) {
|
||||
this.isSelected = isSelected;
|
||||
|
||||
this.setElementClass('show-tab', isSelected);
|
||||
this.setElementAttribute('aria-hidden', (!isSelected).toString());
|
||||
|
||||
if (isSelected) {
|
||||
// this is the selected tab, detect changes
|
||||
this._cd.reattach();
|
||||
@@ -335,9 +345,18 @@ export class Tab extends NavControllerBase {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
clearTimeout(this._loadTmr);
|
||||
super.ngOnDestroy();
|
||||
updateHref(component: any, data: any) {
|
||||
if (this.btn && this.linker) {
|
||||
let href = this.linker.createUrl(this, component, data) || '#';
|
||||
this.btn.updateHref(href);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
destroy() {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import { Component, ElementRef, EventEmitter, Input, Output, Optional, Renderer, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
|
||||
import { NgClass, NgFor, NgIf } from '@angular/common';
|
||||
import { AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, Optional, Renderer, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
|
||||
|
||||
import { App } from '../app/app';
|
||||
import { Badge } from '../badge/badge';
|
||||
import { Config } from '../../config/config';
|
||||
import { Content } from '../content/content';
|
||||
import { Icon } from '../icon/icon';
|
||||
import { DeepLinker } from '../../navigation/deep-linker';
|
||||
import { Ion } from '../ion';
|
||||
import { isBlank, isPresent, isTrueProperty } from '../../util/util';
|
||||
import { isBlank } from '../../util/util';
|
||||
import { nativeRaf } from '../../util/dom';
|
||||
import { NavController } from '../nav/nav-controller';
|
||||
import { NavControllerBase } from '../nav/nav-controller-base';
|
||||
import { NavOptions, DIRECTION_FORWARD } from '../nav/nav-interfaces';
|
||||
import { NavController } from '../../navigation/nav-controller';
|
||||
import { NavControllerBase } from '../../navigation/nav-controller-base';
|
||||
import { NavOptions, DIRECTION_SWITCH } from '../../navigation/nav-util';
|
||||
import { Platform } from '../../platform/platform';
|
||||
import { Tab } from './tab';
|
||||
import { TabButton } from './tab-button';
|
||||
import { TabHighlight } from './tab-highlight';
|
||||
import { ViewController } from '../nav/view-controller';
|
||||
import { ViewController } from '../../navigation/view-controller';
|
||||
|
||||
|
||||
/**
|
||||
@@ -143,72 +140,61 @@ import { ViewController } from '../nav/view-controller';
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ion-tabs',
|
||||
template: `
|
||||
<ion-tabbar role="tablist" #tabbar>
|
||||
<a *ngFor="let t of _tabs" [tab]="t" class="tab-button" [class.tab-disabled]="!t.enabled" [class.tab-hidden]="!t.show" role="tab" href="#" (ionSelect)="select($event)">
|
||||
<ion-icon *ngIf="t.tabIcon" [name]="t.tabIcon" [isActive]="t.isSelected" class="tab-button-icon"></ion-icon>
|
||||
<span *ngIf="t.tabTitle" class="tab-button-text">{{t.tabTitle}}</span>
|
||||
<ion-badge *ngIf="t.tabBadge" class="tab-badge" [ngClass]="\'badge-\' + t.tabBadgeStyle">{{t.tabBadge}}</ion-badge>
|
||||
<ion-button-effect></ion-button-effect>
|
||||
</a>
|
||||
<tab-highlight></tab-highlight>
|
||||
</ion-tabbar>
|
||||
<ng-content></ng-content>
|
||||
<div #portal tab-portal></div>
|
||||
`,
|
||||
directives: [Badge, Icon, NgClass, NgFor, NgIf, TabButton, TabHighlight],
|
||||
template:
|
||||
'<div class="tabbar" role="tablist" #tabbar>' +
|
||||
'<a *ngFor="let t of _tabs" [tab]="t" class="tab-button" [class.tab-disabled]="!t.enabled" [class.tab-hidden]="!t.show" role="tab" href="#" (ionSelect)="select($event)">' +
|
||||
'<ion-icon *ngIf="t.tabIcon" [name]="t.tabIcon" [isActive]="t.isSelected" class="tab-button-icon"></ion-icon>' +
|
||||
'<span *ngIf="t.tabTitle" class="tab-button-text">{{t.tabTitle}}</span>' +
|
||||
'<ion-badge *ngIf="t.tabBadge" class="tab-badge" [ngClass]="\'badge-\' + t.tabBadgeStyle">{{t.tabBadge}}</ion-badge>' +
|
||||
'<div class="button-effect"></div>' +
|
||||
'</a>' +
|
||||
'<div class="tab-highlight"></div>' +
|
||||
'</div>' +
|
||||
'<ng-content></ng-content>' +
|
||||
'<div #portal tab-portal></div>',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class Tabs extends Ion {
|
||||
private _ids: number = -1;
|
||||
private _tabs: Tab[] = [];
|
||||
private _onReady: any = null;
|
||||
private _sbPadding: boolean;
|
||||
private _top: number;
|
||||
private _bottom: number;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
selectHistory: string[] = [];
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
subPages: boolean;
|
||||
|
||||
export class Tabs extends Ion implements AfterViewInit {
|
||||
/** @internal */
|
||||
_color: string;
|
||||
_ids: number = -1;
|
||||
/** @internal */
|
||||
_tabs: Tab[] = [];
|
||||
/** @internal */
|
||||
_sbPadding: boolean;
|
||||
/** @internal */
|
||||
_top: number;
|
||||
/** @internal */
|
||||
_bottom: number;
|
||||
/** @internal */
|
||||
id: string;
|
||||
/** @internal */
|
||||
_selectHistory: string[] = [];
|
||||
/** @internal */
|
||||
_subPages: boolean;
|
||||
|
||||
/**
|
||||
* @input {string} The predefined color to use. For example: `"primary"`, `"secondary"`, `"danger"`.
|
||||
*/
|
||||
@Input()
|
||||
get color(): string {
|
||||
return this._color;
|
||||
set color(value: string) {
|
||||
this._setColor('tabs', value);
|
||||
}
|
||||
|
||||
set color(value: string) {
|
||||
this._updateColor(value);
|
||||
/**
|
||||
* @input {string} The mode to apply to this component.
|
||||
*/
|
||||
@Input()
|
||||
set mode(val: string) {
|
||||
this._setMode('tabs', val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @input {number} The default selected tab index when first loaded. If a selected index isn't provided then it will use `0`, the first tab.
|
||||
*/
|
||||
@Input() selectedIndex: any;
|
||||
@Input() selectedIndex: number;
|
||||
|
||||
/**
|
||||
* @input {boolean} Set whether to preload all the tabs: `true`, `false`.
|
||||
*/
|
||||
@Input() preloadTabs: any;
|
||||
|
||||
/**
|
||||
* @private DEPRECATED. Please use `tabsLayout` instead.
|
||||
* @internal DEPRECATED. Please use `tabsLayout` instead.
|
||||
*/
|
||||
@Input() private tabbarLayout: string;
|
||||
|
||||
@@ -218,7 +204,7 @@ export class Tabs extends Ion {
|
||||
@Input() tabsLayout: string;
|
||||
|
||||
/**
|
||||
* @private DEPRECATED. Please use `tabsPlacement` instead.
|
||||
* @internal DEPRECATED. Please use `tabsPlacement` instead.
|
||||
*/
|
||||
@Input() private tabbarPlacement: string;
|
||||
|
||||
@@ -238,17 +224,17 @@ export class Tabs extends Ion {
|
||||
@Output() ionChange: EventEmitter<Tab> = new EventEmitter<Tab>();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @internal
|
||||
*/
|
||||
@ViewChild(TabHighlight) private _highlight: TabHighlight;
|
||||
@ViewChild(TabHighlight) _highlight: TabHighlight;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @internal
|
||||
*/
|
||||
@ViewChild('tabbar') private _tabbar: ElementRef;
|
||||
@ViewChild('tabbar') _tabbar: ElementRef;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @internal
|
||||
*/
|
||||
@ViewChild('portal', {read: ViewContainerRef}) portal: ViewContainerRef;
|
||||
|
||||
@@ -261,29 +247,31 @@ export class Tabs extends Ion {
|
||||
@Optional() parent: NavController,
|
||||
@Optional() public viewCtrl: ViewController,
|
||||
private _app: App,
|
||||
private _config: Config,
|
||||
private _elementRef: ElementRef,
|
||||
config: Config,
|
||||
elementRef: ElementRef,
|
||||
private _platform: Platform,
|
||||
private _renderer: Renderer
|
||||
renderer: Renderer,
|
||||
private _linker: DeepLinker
|
||||
) {
|
||||
super(_elementRef);
|
||||
super(config, elementRef, renderer);
|
||||
|
||||
this.mode = config.get('mode');
|
||||
this.parent = <NavControllerBase>parent;
|
||||
this.id = 't' + (++tabIds);
|
||||
this._sbPadding = _config.getBoolean('statusbarPadding');
|
||||
this.subPages = _config.getBoolean('tabsHideOnSubPages');
|
||||
this.tabsHighlight = _config.getBoolean('tabsHighlight');
|
||||
this._sbPadding = config.getBoolean('statusbarPadding');
|
||||
this._subPages = config.getBoolean('tabsHideOnSubPages');
|
||||
this.tabsHighlight = config.getBoolean('tabsHighlight');
|
||||
|
||||
// TODO deprecated 07-07-2016 beta.11
|
||||
if (_config.get('tabSubPages') !== null) {
|
||||
if (config.get('tabSubPages') !== null) {
|
||||
console.warn('Config option "tabSubPages" has been deprecated. Please use "tabsHideOnSubPages" instead.');
|
||||
this.subPages = _config.getBoolean('tabSubPages');
|
||||
this._subPages = config.getBoolean('tabSubPages');
|
||||
}
|
||||
|
||||
// TODO deprecated 07-07-2016 beta.11
|
||||
if (_config.get('tabbarHighlight') !== null) {
|
||||
if (config.get('tabbarHighlight') !== null) {
|
||||
console.warn('Config option "tabbarHighlight" has been deprecated. Please use "tabsHighlight" instead.');
|
||||
this.tabsHighlight = _config.getBoolean('tabbarHighlight');
|
||||
this.tabsHighlight = config.getBoolean('tabbarHighlight');
|
||||
}
|
||||
|
||||
if (this.parent) {
|
||||
@@ -297,24 +285,20 @@ export class Tabs extends Ion {
|
||||
|
||||
} else if (this._app) {
|
||||
// this is the root navcontroller for the entire app
|
||||
this._app.setRootNav(this);
|
||||
this._app._setRootNav(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);
|
||||
|
||||
viewCtrl.loaded = (done) => {
|
||||
this._onReady = done;
|
||||
};
|
||||
viewCtrl._setContent(this);
|
||||
viewCtrl._setContentRef(elementRef);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @internal
|
||||
*/
|
||||
ngAfterViewInit() {
|
||||
this._setConfig('tabsPlacement', 'bottom');
|
||||
@@ -328,27 +312,27 @@ export class Tabs extends Ion {
|
||||
// TODO deprecated 07-07-2016 beta.11
|
||||
if (this.tabbarPlacement !== undefined) {
|
||||
console.warn('Input "tabbarPlacement" has been deprecated. Please use "tabsPlacement" instead.');
|
||||
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'tabsPlacement', this.tabbarPlacement);
|
||||
this.setElementAttribute('tabsPlacement', this.tabbarPlacement);
|
||||
this.tabsPlacement = this.tabbarPlacement;
|
||||
}
|
||||
|
||||
// TODO deprecated 07-07-2016 beta.11
|
||||
if (this._config.get('tabbarPlacement') !== null) {
|
||||
console.warn('Config option "tabbarPlacement" has been deprecated. Please use "tabsPlacement" instead.');
|
||||
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'tabsPlacement', this._config.get('tabbarPlacement'));
|
||||
this.setElementAttribute('tabsPlacement', this._config.get('tabbarPlacement'));
|
||||
}
|
||||
|
||||
// TODO deprecated 07-07-2016 beta.11
|
||||
if (this.tabbarLayout !== undefined) {
|
||||
console.warn('Input "tabbarLayout" has been deprecated. Please use "tabsLayout" instead.');
|
||||
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'tabsLayout', this.tabbarLayout);
|
||||
this.setElementAttribute('tabsLayout', this.tabbarLayout);
|
||||
this.tabsLayout = this.tabbarLayout;
|
||||
}
|
||||
|
||||
// TODO deprecated 07-07-2016 beta.11
|
||||
if (this._config.get('tabbarLayout') !== null) {
|
||||
console.warn('Config option "tabbarLayout" has been deprecated. Please use "tabsLayout" instead.');
|
||||
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'tabsLayout', this._config.get('tabsLayout'));
|
||||
this.setElementAttribute('tabsLayout', this._config.get('tabsLayout'));
|
||||
}
|
||||
|
||||
if (this.tabsHighlight) {
|
||||
@@ -361,12 +345,19 @@ export class Tabs extends Ion {
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @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));
|
||||
let selectedIndex = (isBlank(this.selectedIndex) ? 0 : parseInt(<any>this.selectedIndex, 10));
|
||||
|
||||
// now see if the deep linker can find a tab index
|
||||
const tabsSegment = this._linker.initNav(this);
|
||||
if (tabsSegment && isBlank(tabsSegment.component)) {
|
||||
// we found a segment which probably represents which tab to select
|
||||
selectedIndex = this._linker.getSelectedTabIndex(this, tabsSegment.name, 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);
|
||||
@@ -378,91 +369,73 @@ export class Tabs extends Ion {
|
||||
|
||||
if (selectedTab) {
|
||||
// we found a tab to select
|
||||
this.select(selectedTab);
|
||||
}
|
||||
|
||||
// check if preloadTab is set as an input @Input
|
||||
// otherwise check the preloadTabs config
|
||||
let shouldPreloadTabs = (isBlank(this.preloadTabs) ? this._config.getBoolean('preloadTabs') : isTrueProperty(this.preloadTabs));
|
||||
if (shouldPreloadTabs) {
|
||||
// preload all the tabs which isn't the selected tab
|
||||
this._tabs.filter((t) => t !== selectedTab).forEach((tab, index) => {
|
||||
tab.preload(this._config.getNumber('tabsPreloadDelay', 1000) * index);
|
||||
// get the segment the deep linker says this tab should load with
|
||||
let pageId: string = null;
|
||||
if (tabsSegment) {
|
||||
let selectedTabSegment = this._linker.initNav(selectedTab);
|
||||
if (selectedTabSegment && selectedTabSegment.component) {
|
||||
selectedTab.root = selectedTabSegment.component;
|
||||
selectedTab.rootParams = selectedTabSegment.data;
|
||||
pageId = selectedTabSegment.id;
|
||||
}
|
||||
}
|
||||
this.select(selectedTab, {
|
||||
id: pageId
|
||||
});
|
||||
}
|
||||
|
||||
// set the initial href attribute values for each tab
|
||||
this._tabs.forEach(t => {
|
||||
t.updateHref(t.root, t.rootParams);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @internal
|
||||
*/
|
||||
private _setConfig(attrKey: string, fallback: any) {
|
||||
var val = (<any>this)[attrKey];
|
||||
_setConfig(attrKey: string, fallback: any) {
|
||||
let val = (<any>this)[attrKey];
|
||||
if (isBlank(val)) {
|
||||
val = this._config.get(attrKey, fallback);
|
||||
}
|
||||
this._renderer.setElementAttribute(this._elementRef.nativeElement, attrKey, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_updateColor(newColor: string) {
|
||||
this._setElementColor(this._color, false);
|
||||
this._setElementColor(newColor, true);
|
||||
this._color = newColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_setElementColor(color: string, isAdd: boolean) {
|
||||
if (color !== null && color !== '') {
|
||||
this._renderer.setElementClass(this._elementRef.nativeElement, `tabs-${color}`, isAdd);
|
||||
}
|
||||
this.setElementAttribute(attrKey, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
add(tab: Tab) {
|
||||
tab.id = this.id + '-' + (++this._ids);
|
||||
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: number | Tab, opts: NavOptions = {}, done?: Function): Promise<any> {
|
||||
let promise: Promise<any>;
|
||||
if (!done) {
|
||||
promise = new Promise(res => { done = res; });
|
||||
}
|
||||
|
||||
let selectedTab: Tab = (typeof tabOrIndex === 'number' ? this.getByIndex(tabOrIndex) : tabOrIndex);
|
||||
select(tabOrIndex: number | Tab, opts: NavOptions = {}) {
|
||||
const selectedTab: Tab = (typeof tabOrIndex === 'number' ? this.getByIndex(tabOrIndex) : tabOrIndex);
|
||||
if (isBlank(selectedTab)) {
|
||||
return Promise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
let deselectedTab = this.getSelected();
|
||||
const deselectedTab = this.getSelected();
|
||||
if (selectedTab === deselectedTab) {
|
||||
// no change
|
||||
this._touchActive(selectedTab);
|
||||
return Promise.resolve();
|
||||
return this._touchActive(selectedTab);
|
||||
}
|
||||
console.debug(`Tabs, select: ${selectedTab.id}`);
|
||||
|
||||
let deselectedPage: ViewController;
|
||||
if (deselectedTab) {
|
||||
deselectedPage = deselectedTab.getActive();
|
||||
deselectedPage && deselectedPage.fireWillLeave();
|
||||
deselectedPage && deselectedPage._willLeave();
|
||||
}
|
||||
|
||||
opts.animate = false;
|
||||
|
||||
let selectedPage = selectedTab.getActive();
|
||||
selectedPage && selectedPage.fireWillEnter();
|
||||
const selectedPage = selectedTab.getActive();
|
||||
selectedPage && selectedPage._willEnter();
|
||||
|
||||
selectedTab.load(opts, (initialLoad: boolean) => {
|
||||
selectedTab.load(opts, (alreadyLoaded: boolean) => {
|
||||
selectedTab.ionSelect.emit(selectedTab);
|
||||
this.ionChange.emit(selectedTab);
|
||||
|
||||
@@ -478,27 +451,26 @@ export class Tabs extends Ion {
|
||||
if (this.tabsHighlight) {
|
||||
this._highlight.select(selectedTab);
|
||||
}
|
||||
|
||||
if (opts.updateUrl !== false) {
|
||||
this._linker.navChange(DIRECTION_SWITCH);
|
||||
}
|
||||
}
|
||||
|
||||
selectedPage && selectedPage.fireDidEnter();
|
||||
deselectedPage && deselectedPage.fireDidLeave();
|
||||
|
||||
if (this._onReady) {
|
||||
this._onReady();
|
||||
this._onReady = null;
|
||||
}
|
||||
selectedPage && selectedPage._didEnter();
|
||||
deselectedPage && deselectedPage._didLeave();
|
||||
|
||||
// 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);
|
||||
if (this._selectHistory[this._selectHistory.length - 1] !== selectedTab.id) {
|
||||
this._selectHistory.push(selectedTab.id);
|
||||
}
|
||||
|
||||
// if this is not the Tab's initial load then we need
|
||||
// to refresh the tabbar and content dimensions to be sure
|
||||
// they're lined up correctly
|
||||
if (!initialLoad && selectedPage) {
|
||||
var content = <Content>selectedPage.getContent();
|
||||
if (alreadyLoaded && selectedPage) {
|
||||
let content = <Content>selectedPage.getContent();
|
||||
if (content && content instanceof Content) {
|
||||
nativeRaf(() => {
|
||||
content.readDimensions();
|
||||
@@ -506,11 +478,7 @@ export class Tabs extends Ion {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -521,12 +489,12 @@ export class Tabs extends Ion {
|
||||
previousTab(trimHistory: boolean = true): Tab {
|
||||
// walk backwards through the tab selection history
|
||||
// and find the first previous tab that is enabled and shown
|
||||
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]);
|
||||
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);
|
||||
this._selectHistory.splice(i + 1);
|
||||
}
|
||||
return tab;
|
||||
}
|
||||
@@ -540,17 +508,14 @@ export class Tabs extends Ion {
|
||||
* @returns {Tab} Returns the tab who's index matches the one passed
|
||||
*/
|
||||
getByIndex(index: number): Tab {
|
||||
if (index < this._tabs.length && index > -1) {
|
||||
return this._tabs[index];
|
||||
}
|
||||
return null;
|
||||
return this._tabs[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Tab} Returns the currently selected tab
|
||||
*/
|
||||
getSelected(): Tab {
|
||||
for (let i = 0; i < this._tabs.length; i++) {
|
||||
for (var i = 0; i < this._tabs.length; i++) {
|
||||
if (this._tabs[i].isSelected) {
|
||||
return this._tabs[i];
|
||||
}
|
||||
@@ -559,68 +524,57 @@ export class Tabs extends Ion {
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @internal
|
||||
*/
|
||||
getActiveChildNav() {
|
||||
return this.getSelected();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @internal
|
||||
*/
|
||||
getIndex(tab: Tab): number {
|
||||
return this._tabs.indexOf(tab);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @internal
|
||||
*/
|
||||
length(): number {
|
||||
return this._tabs.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* "Touch" the active tab, going back to the root view of the tab
|
||||
* or optionally letting the tab handle the event
|
||||
*/
|
||||
private _touchActive(tab: Tab) {
|
||||
let active = tab.getActive();
|
||||
const active = tab.getActive();
|
||||
|
||||
if (!active) {
|
||||
return Promise.resolve();
|
||||
if (active) {
|
||||
if (active._cmp && active._cmp.instance.ionSelected) {
|
||||
// if they have a custom tab selected handler, call it
|
||||
active._cmp.instance.ionSelected();
|
||||
|
||||
} else if (tab.length() > 1) {
|
||||
// if we're a few pages deep, pop to root
|
||||
tab.popToRoot(null, null);
|
||||
|
||||
} else if (tab.root !== active.component) {
|
||||
// Otherwise, if the page we're on is not our real root, reset it to our
|
||||
// default root type
|
||||
tab.setRoot(tab.root);
|
||||
}
|
||||
}
|
||||
|
||||
let instance = active.instance;
|
||||
|
||||
// If they have a custom tab selected handler, call it
|
||||
if (instance.ionSelected) {
|
||||
return instance.ionSelected();
|
||||
}
|
||||
|
||||
// If we're a few pages deep, pop to root
|
||||
if (tab.length() > 1) {
|
||||
// Pop to the root view
|
||||
return tab.popToRoot();
|
||||
}
|
||||
|
||||
// Otherwise, if the page we're on is not our real root, reset it to our
|
||||
// default root type
|
||||
if (tab.root !== active.componentType) {
|
||||
return tab.setRoot(tab.root);
|
||||
}
|
||||
|
||||
// And failing all of that, we do something safe and secure
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @internal
|
||||
* DOM WRITE
|
||||
*/
|
||||
setTabbarPosition(top: number, bottom: number) {
|
||||
if (this._top !== top || this._bottom !== bottom) {
|
||||
let tabbarEle = <HTMLElement>this._tabbar.nativeElement;
|
||||
const tabbarEle = <HTMLElement>this._tabbar.nativeElement;
|
||||
tabbarEle.style.top = (top > -1 ? top + 'px' : '');
|
||||
tabbarEle.style.bottom = (bottom > -1 ? bottom + 'px' : '');
|
||||
tabbarEle.classList.add('show-tabbar');
|
||||
|
||||
959
src/navigation/nav-controller-base.ts
Normal file
959
src/navigation/nav-controller-base.ts
Normal file
@@ -0,0 +1,959 @@
|
||||
import { ComponentRef, ComponentFactoryResolver, ElementRef, EventEmitter, NgZone, ReflectiveInjector, Renderer, ViewContainerRef } from '@angular/core';
|
||||
|
||||
import { AnimationOptions } from '../animations/animation';
|
||||
import { App } from '../components/app/app';
|
||||
import { Config } from '../config/config';
|
||||
import { convertToView, convertToViews, NavOptions, DIRECTION_BACK, DIRECTION_FORWARD, INIT_ZINDEX,
|
||||
TransitionResolveFn, TransitionRejectFn, TransitionInstruction, ViewState } from './nav-util';
|
||||
import { setZIndex } from './nav-util';
|
||||
import { DeepLinker } from './deep-linker';
|
||||
import { GestureController } from '../gestures/gesture-controller';
|
||||
import { isBlank, isNumber, isPresent, pascalCaseToDashCase } from '../util/util';
|
||||
import { isViewController, ViewController } from './view-controller';
|
||||
import { Ion } from '../components/ion';
|
||||
import { Keyboard } from '../util/keyboard';
|
||||
import { NavController } from './nav-controller';
|
||||
import { NavParams } from './nav-params';
|
||||
import { SwipeBackGesture } from './swipe-back';
|
||||
import { Transition } from '../transitions/transition';
|
||||
import { TransitionController } from '../transitions/transition-controller';
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
* This class is for internal use only. It is not exported publicly.
|
||||
*/
|
||||
export class NavControllerBase extends Ion implements NavController {
|
||||
_children: any[] = [];
|
||||
_ids: number = -1;
|
||||
_init = false;
|
||||
_isPortal: boolean;
|
||||
_queue: TransitionInstruction[] = [];
|
||||
_sbEnabled: boolean;
|
||||
_sbGesture: SwipeBackGesture;
|
||||
_sbThreshold: number;
|
||||
_sbTrns: Transition;
|
||||
_trnsId: number = null;
|
||||
_trnsTm: number = 0;
|
||||
_viewport: ViewContainerRef;
|
||||
_views: ViewController[] = [];
|
||||
|
||||
viewDidLoad: EventEmitter<any>;
|
||||
viewWillEnter: EventEmitter<any>;
|
||||
viewDidEnter: EventEmitter<any>;
|
||||
viewWillLeave: EventEmitter<any>;
|
||||
viewDidLeave: EventEmitter<any>;
|
||||
viewWillUnload: EventEmitter<any>;
|
||||
|
||||
id: string;
|
||||
parent: any;
|
||||
config: Config;
|
||||
|
||||
constructor(
|
||||
parent: any,
|
||||
public _app: App,
|
||||
config: Config,
|
||||
public _keyboard: Keyboard,
|
||||
elementRef: ElementRef,
|
||||
public _zone: NgZone,
|
||||
renderer: Renderer,
|
||||
public _cfr: ComponentFactoryResolver,
|
||||
public _gestureCtrl: GestureController,
|
||||
public _trnsCtrl: TransitionController,
|
||||
public _linker: DeepLinker
|
||||
) {
|
||||
super(config, elementRef, renderer);
|
||||
|
||||
this.parent = parent;
|
||||
this.config = config;
|
||||
|
||||
this._sbEnabled = config.getBoolean('swipeBackEnabled');
|
||||
this._sbThreshold = config.getNumber('swipeBackThreshold', 40);
|
||||
|
||||
this.id = 'n' + (++ctrlIds);
|
||||
|
||||
this.viewDidLoad = new EventEmitter();
|
||||
this.viewWillEnter = new EventEmitter();
|
||||
this.viewDidEnter = new EventEmitter();
|
||||
this.viewWillLeave = new EventEmitter();
|
||||
this.viewDidLeave = new EventEmitter();
|
||||
this.viewWillUnload = new EventEmitter();
|
||||
}
|
||||
|
||||
push(page: any, params?: any, opts?: NavOptions, done?: Function): Promise<any> {
|
||||
return this._queueTrns({
|
||||
insertStart: -1,
|
||||
insertViews: [convertToView(this._linker, page, params)],
|
||||
opts: opts,
|
||||
}, done);
|
||||
}
|
||||
|
||||
insert(insertIndex: number, page: any, params?: any, opts?: NavOptions, done?: Function): Promise<any> {
|
||||
return this._queueTrns({
|
||||
insertStart: insertIndex,
|
||||
insertViews: [convertToView(this._linker, page, params)],
|
||||
opts: opts,
|
||||
}, done);
|
||||
}
|
||||
|
||||
insertPages(insertIndex: number, insertPages: any[], opts?: NavOptions, done?: Function): Promise<any> {
|
||||
return this._queueTrns({
|
||||
insertStart: insertIndex,
|
||||
insertViews: convertToViews(this._linker, insertPages),
|
||||
opts: opts,
|
||||
}, done);
|
||||
}
|
||||
|
||||
pop(opts?: NavOptions, done?: Function): Promise<any> {
|
||||
return this._queueTrns({
|
||||
removeStart: -1,
|
||||
removeCount: 1,
|
||||
opts: opts,
|
||||
}, done);
|
||||
}
|
||||
|
||||
popTo(indexOrViewCtrl: any, opts?: NavOptions, done?: Function): Promise<any> {
|
||||
const startIndex = isViewController(indexOrViewCtrl) ? this.indexOf(indexOrViewCtrl) : isNumber(indexOrViewCtrl) ? indexOrViewCtrl : -1;
|
||||
return this._queueTrns({
|
||||
removeStart: startIndex + 1,
|
||||
removeCount: -1,
|
||||
opts: opts,
|
||||
}, done);
|
||||
}
|
||||
|
||||
popToRoot(opts?: NavOptions, done?: Function): Promise<any> {
|
||||
return this._queueTrns({
|
||||
removeStart: 1,
|
||||
removeCount: -1,
|
||||
opts: opts,
|
||||
}, done);
|
||||
}
|
||||
|
||||
popAll() {
|
||||
let promises: any[] = [];
|
||||
for (var i = this._views.length - 1; i >= 0; i--) {
|
||||
promises.push(this.pop(null));
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
remove(startIndex: number, removeCount: number = 1, opts?: NavOptions, done?: Function): Promise<any> {
|
||||
return this._queueTrns({
|
||||
removeStart: startIndex,
|
||||
removeCount: removeCount,
|
||||
opts: opts,
|
||||
}, done);
|
||||
}
|
||||
|
||||
setRoot(pageOrViewCtrl: any, params?: any, opts?: NavOptions, done?: Function): Promise<any> {
|
||||
return this._queueTrns({
|
||||
insertStart: 0,
|
||||
insertViews: [convertToView(this._linker, pageOrViewCtrl, params)],
|
||||
removeStart: 0,
|
||||
removeCount: -1,
|
||||
opts: opts
|
||||
}, done);
|
||||
}
|
||||
|
||||
setPages(pages: any[], opts?: NavOptions, done?: Function): Promise<any> {
|
||||
return this._queueTrns({
|
||||
insertStart: 0,
|
||||
insertViews: convertToViews(this._linker, pages),
|
||||
removeStart: 0,
|
||||
removeCount: -1,
|
||||
opts: opts
|
||||
}, done);
|
||||
}
|
||||
|
||||
_queueTrns(ti: TransitionInstruction, done: Function): Promise<any> {
|
||||
let promise: Promise<any>;
|
||||
let resolve: Function = done;
|
||||
let reject: Function = done;
|
||||
|
||||
if (done === undefined) {
|
||||
// only create a promise if a done callback wasn't provided
|
||||
// done can be a null, which avoids any functions
|
||||
promise = new Promise((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
}
|
||||
|
||||
ti.resolve = (hasCompleted: boolean, isAsync: boolean, enteringName: string, leavingName: string, direction: string) => {
|
||||
// transition has successfully resolved
|
||||
this._trnsId = null;
|
||||
resolve && resolve(hasCompleted, isAsync, enteringName, leavingName, direction);
|
||||
this._sbCheck();
|
||||
|
||||
// let's see if there's another to kick off
|
||||
this.setTransitioning(false);
|
||||
this._nextTrns();
|
||||
};
|
||||
|
||||
ti.reject = (rejectReason: any, trns: Transition) => {
|
||||
// rut row raggy, something rejected this transition
|
||||
this._trnsId = null;
|
||||
this._queue.length = 0;
|
||||
|
||||
while (trns) {
|
||||
if (trns.enteringView && (trns.enteringView._state !== ViewState.LOADED)) {
|
||||
// destroy the entering views and all of their hopes and dreams
|
||||
trns.enteringView._destroy(this._renderer);
|
||||
}
|
||||
if (!trns.parent) break;
|
||||
}
|
||||
|
||||
if (trns) {
|
||||
this._trnsCtrl.destroy(trns.trnsId);
|
||||
}
|
||||
|
||||
this._sbCheck();
|
||||
|
||||
reject && reject(false, false, rejectReason);
|
||||
|
||||
this.setTransitioning(false);
|
||||
this._nextTrns();
|
||||
};
|
||||
|
||||
if (ti.insertViews) {
|
||||
// ensure we've got good views to insert
|
||||
ti.insertViews = ti.insertViews.filter(v => v !== null);
|
||||
if (!ti.insertViews.length) {
|
||||
ti.reject('invalid views to insert');
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (isPresent(ti.removeStart) && !this._views.length && !this._isPortal) {
|
||||
ti.reject('no views in the stack to be removed');
|
||||
return;
|
||||
}
|
||||
|
||||
this._queue.push(ti);
|
||||
|
||||
// if there isn't a transitoin already happening
|
||||
// then this will kick off this transition
|
||||
this._nextTrns();
|
||||
|
||||
// promise is undefined if a done callbacks was provided
|
||||
return promise;
|
||||
}
|
||||
|
||||
_nextTrns(): boolean {
|
||||
// this is the framework's bread 'n butta function
|
||||
// only one transition is allowed at any given time
|
||||
if (this.isTransitioning()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// there is no transition happening right now
|
||||
// get the next instruction
|
||||
const ti = this._queue.shift();
|
||||
if (!ti) {
|
||||
this.setTransitioning(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.setTransitioning(true, ACTIVE_TRANSITION_MAX_TIME);
|
||||
const viewsLength = this._views.length;
|
||||
const activeView = this.getActive();
|
||||
|
||||
let enteringView: ViewController;
|
||||
let leavingView: ViewController = activeView;
|
||||
const destroyQueue: ViewController[] = [];
|
||||
|
||||
const opts = ti.opts || {};
|
||||
const resolve = ti.resolve;
|
||||
const reject = ti.reject;
|
||||
let insertViews = ti.insertViews;
|
||||
ti.resolve = ti.reject = ti.opts = ti.insertViews = null;
|
||||
|
||||
let enteringRequiresTransition = false;
|
||||
let leavingRequiresTransition = false;
|
||||
|
||||
if (isPresent(ti.removeStart)) {
|
||||
if (ti.removeStart < 0) {
|
||||
ti.removeStart = (viewsLength - 1);
|
||||
}
|
||||
if (ti.removeCount < 0) {
|
||||
ti.removeCount = (viewsLength - ti.removeStart);
|
||||
}
|
||||
|
||||
leavingRequiresTransition = (ti.removeStart + ti.removeCount === viewsLength);
|
||||
|
||||
for (var i = ti.removeStart; i <= ti.removeCount; i++) {
|
||||
destroyQueue.push(this._views[i]);
|
||||
}
|
||||
|
||||
for (var i = viewsLength - 1; i >= 0; i--) {
|
||||
var view = this._views[i];
|
||||
if (destroyQueue.indexOf(view) < 0 && view !== leavingView) {
|
||||
enteringView = view;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// default the direction to "back"
|
||||
opts.direction = opts.direction || DIRECTION_BACK;
|
||||
}
|
||||
|
||||
if (insertViews) {
|
||||
// allow -1 to be passed in to auto push it on the end
|
||||
// and clean up the index if it's larger then the size of the stack
|
||||
if (ti.insertStart < 0 || ti.insertStart > viewsLength) {
|
||||
ti.insertStart = viewsLength;
|
||||
}
|
||||
|
||||
// only requires a transition if it's going at the end
|
||||
enteringRequiresTransition = (ti.insertStart === viewsLength);
|
||||
|
||||
// grab the very last view of the views to be inserted
|
||||
// and initialize it as the new entering view
|
||||
enteringView = insertViews[insertViews.length - 1];
|
||||
|
||||
// manually set the new view's id if an id was passed in the options
|
||||
if (isPresent(opts.id)) {
|
||||
enteringView.id = opts.id;
|
||||
}
|
||||
|
||||
// add the views to the
|
||||
for (var i = 0; i < insertViews.length; i++) {
|
||||
var view = insertViews[i];
|
||||
|
||||
var existingIndex = this._views.indexOf(view);
|
||||
if (existingIndex > -1) {
|
||||
// this view is already in the stack!!
|
||||
// move it to its new location
|
||||
this._views.splice(ti.insertStart + i, 0, this._views.splice(existingIndex, 1)[0]);
|
||||
|
||||
} else {
|
||||
// this is a new view to add to the stack
|
||||
// create the new entering view
|
||||
view._setNav(this);
|
||||
|
||||
// give this inserted view an ID
|
||||
view.id = this.id + '-' + (++this._ids);
|
||||
|
||||
// insert the entering view into the correct index in the stack
|
||||
this._views.splice(ti.insertStart + i, 0, view);
|
||||
}
|
||||
}
|
||||
|
||||
if (enteringRequiresTransition) {
|
||||
// default to forward if not already set
|
||||
opts.direction = opts.direction || DIRECTION_FORWARD;
|
||||
}
|
||||
}
|
||||
|
||||
// if the views to be removed are in the beginning or middle
|
||||
// and there is not a view that needs to visually transition out
|
||||
// then just destroy them and don't transition anything
|
||||
for (var i = 0; i < destroyQueue.length; i++) {
|
||||
// batch all of lifecycles together
|
||||
var view = destroyQueue[i];
|
||||
if (view && view !== enteringView && view !== leavingView) {
|
||||
view._willLeave();
|
||||
this.viewWillLeave.emit(view);
|
||||
this._app.viewWillLeave.emit(view);
|
||||
|
||||
view._didLeave();
|
||||
this.viewDidLeave.emit(view);
|
||||
this._app.viewDidLeave.emit(view);
|
||||
|
||||
view._willUnload();
|
||||
this.viewWillUnload.emit(view);
|
||||
this._app.viewWillUnload.emit(view);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < destroyQueue.length; i++) {
|
||||
// batch all of the destroys together
|
||||
var view = destroyQueue[i];
|
||||
if (view && view !== enteringView && view !== leavingView) {
|
||||
view._destroy(this._renderer);
|
||||
}
|
||||
}
|
||||
destroyQueue.length = 0;
|
||||
|
||||
if (enteringRequiresTransition || leavingRequiresTransition && enteringView !== leavingView) {
|
||||
// set which animation it should use if it wasn't set yet
|
||||
if (!opts.animation) {
|
||||
if (isPresent(ti.removeStart)) {
|
||||
opts.animation = (leavingView || enteringView).getTransitionName(opts.direction);
|
||||
} else {
|
||||
opts.animation = (enteringView || leavingView).getTransitionName(opts.direction);
|
||||
}
|
||||
}
|
||||
|
||||
// huzzah! let us transition these views
|
||||
this._transition(enteringView, leavingView, opts, resolve, reject);
|
||||
|
||||
} else {
|
||||
// they're inserting/removing the views somewhere in the middle or
|
||||
// beginning, so visually nothing needs to animate/transition
|
||||
// resolve immediately because there's no animation that's happening
|
||||
resolve(true, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_transition(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, resolve: TransitionResolveFn, reject: TransitionRejectFn): void {
|
||||
// figure out if this transition is the root one or a
|
||||
// child of a parent nav that has the root transition
|
||||
this._trnsId = this._trnsCtrl.getRootTrnsId(this);
|
||||
if (this._trnsId === null) {
|
||||
// this is the root transition, meaning all child navs and their views
|
||||
// should be added as a child transition to this one
|
||||
this._trnsId = this._trnsCtrl.nextId();
|
||||
}
|
||||
|
||||
// create the transition options
|
||||
const animationOpts: AnimationOptions = {
|
||||
animation: opts.animation,
|
||||
direction: opts.direction,
|
||||
duration: (opts.animate === false ? 0 : opts.duration),
|
||||
easing: opts.easing,
|
||||
isRTL: this.config.platform.isRTL(),
|
||||
ev: opts.ev,
|
||||
};
|
||||
|
||||
// create the transition animation from the TransitionController
|
||||
// this will either create the root transition, or add it as a child transition
|
||||
const trns = this._trnsCtrl.get(this._trnsId, enteringView, leavingView, animationOpts);
|
||||
|
||||
// ensure any swipeback transitions are cleared out
|
||||
this._sbTrns && this._sbTrns.destroy();
|
||||
|
||||
if (trns.parent) {
|
||||
// this is important for later to know if there
|
||||
// are any more child tests to check for
|
||||
trns.parent.hasChildTrns = true;
|
||||
|
||||
} else {
|
||||
// this is the root transition
|
||||
if (opts.progressAnimation) {
|
||||
this._sbTrns = trns;
|
||||
}
|
||||
}
|
||||
|
||||
trns.registerStart(() => {
|
||||
this._trnsStart(trns, enteringView, leavingView, opts, resolve);
|
||||
if (trns.parent) {
|
||||
trns.parent.start();
|
||||
}
|
||||
});
|
||||
|
||||
if (enteringView && isBlank(enteringView._state)) {
|
||||
// render the entering view, and all child navs and views
|
||||
// ******** DOM WRITE ****************
|
||||
this._viewInit(trns, enteringView, opts);
|
||||
}
|
||||
|
||||
// views have been initialized, now let's test
|
||||
// to see if the transition is even allowed or not
|
||||
const shouldContinue = this._viewTest(trns, enteringView, leavingView, opts, resolve, reject);
|
||||
if (shouldContinue) {
|
||||
// synchronous and all tests passed! let's continue
|
||||
this._postViewInit(trns, enteringView, leavingView, opts, resolve);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DOM WRITE
|
||||
*/
|
||||
_viewInit(trns: Transition, enteringView: ViewController, opts: NavOptions) {
|
||||
// entering view has not been initialized yet
|
||||
const componentProviders = ReflectiveInjector.resolve([
|
||||
{ provide: NavController, useValue: this },
|
||||
{ provide: ViewController, useValue: enteringView },
|
||||
{ provide: NavParams, useValue: enteringView.getNavParams() }
|
||||
]);
|
||||
const componentFactory = this._cfr.resolveComponentFactory(enteringView.component);
|
||||
const childInjector = ReflectiveInjector.fromResolvedProviders(componentProviders, this._viewport.parentInjector);
|
||||
|
||||
// create ComponentRef and set it to the entering view
|
||||
enteringView.init(componentFactory.create(childInjector, []));
|
||||
enteringView._state = ViewState.INITIALIZED;
|
||||
}
|
||||
|
||||
_viewTest(trns: Transition, enteringView: ViewController, leavingView: ViewController, opts: NavOptions, resolve: TransitionResolveFn, reject: TransitionRejectFn): boolean {
|
||||
const promises: Promise<any>[] = [];
|
||||
|
||||
if (leavingView) {
|
||||
const leavingTestResult = leavingView._lifecycleTest('Leave');
|
||||
|
||||
if (isPresent(leavingTestResult) && leavingTestResult !== true) {
|
||||
if (leavingTestResult instanceof Promise) {
|
||||
// async promise
|
||||
promises.push(leavingTestResult);
|
||||
|
||||
} else {
|
||||
// synchronous reject
|
||||
reject((leavingTestResult !== false ? leavingTestResult : `ionViewCanLeave rejected`), trns);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (enteringView) {
|
||||
const enteringTestResult = enteringView._lifecycleTest('Enter');
|
||||
|
||||
if (isPresent(enteringTestResult) && enteringTestResult !== true) {
|
||||
if (enteringTestResult instanceof Promise) {
|
||||
// async promise
|
||||
promises.push(enteringTestResult);
|
||||
|
||||
} else {
|
||||
// synchronous reject
|
||||
reject((enteringTestResult !== false ? enteringTestResult : `ionViewCanEnter rejected`), trns);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (promises.length) {
|
||||
// darn, async promises, gotta wait for them to resolve
|
||||
Promise.all(promises).then(() => {
|
||||
// all promises resolved! let's continue
|
||||
this._postViewInit(trns, enteringView, leavingView, opts, resolve);
|
||||
|
||||
}, (rejectReason: any) => {
|
||||
// darn, one of the promises was rejected!!
|
||||
reject(rejectReason, trns);
|
||||
|
||||
}).catch((rejectReason) => {
|
||||
// idk, who knows
|
||||
reject(rejectReason, trns);
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// synchronous and all tests passed! let's move on already
|
||||
return true;
|
||||
}
|
||||
|
||||
_postViewInit(trns: Transition, enteringView: ViewController, leavingView: ViewController, opts: NavOptions, resolve: TransitionResolveFn): void {
|
||||
// passed both the enter and leave tests
|
||||
if (enteringView && enteringView._state === ViewState.INITIALIZED) {
|
||||
// render the entering component in the DOM
|
||||
// this would also render new child navs/views
|
||||
// which may have their very own async canEnter/Leave tests
|
||||
// ******** DOM WRITE ****************
|
||||
this._viewInsert(enteringView, enteringView._cmp, this._viewport);
|
||||
}
|
||||
|
||||
if (!trns.hasChildTrns) {
|
||||
// lowest level transition, so kick it off and let it bubble up to start all of them
|
||||
trns.start();
|
||||
}
|
||||
}
|
||||
|
||||
_viewInsert(view: ViewController, componentRef: ComponentRef<any>, viewport: ViewContainerRef) {
|
||||
// successfully finished loading the entering view
|
||||
// fire off the "loaded" lifecycle events
|
||||
view._didLoad();
|
||||
this.viewDidLoad.emit(view);
|
||||
this._app.viewDidLoad.emit(view);
|
||||
|
||||
// render the component ref instance to the DOM
|
||||
// ******** DOM WRITE ****************
|
||||
viewport.insert(componentRef.hostView, viewport.length);
|
||||
view._state = ViewState.PRE_RENDERED;
|
||||
|
||||
// the ElementRef of the actual ion-page created
|
||||
const pageElement = componentRef.location.nativeElement;
|
||||
|
||||
if (view._cssClass) {
|
||||
// ******** DOM WRITE ****************
|
||||
this._renderer.setElementClass(pageElement, view._cssClass, true);
|
||||
}
|
||||
|
||||
// auto-add page css className created from component JS class name
|
||||
// ******** DOM WRITE ****************
|
||||
const cssClassName = pascalCaseToDashCase(view.component.name);
|
||||
this._renderer.setElementClass(pageElement, cssClassName, true);
|
||||
|
||||
componentRef.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
_trnsStart(trns: Transition, enteringView: ViewController, leavingView: ViewController, opts: NavOptions, resolve: TransitionResolveFn) {
|
||||
this._trnsId = null;
|
||||
|
||||
// set the correct zIndex for the entering and leaving views
|
||||
// ******** DOM WRITE ****************
|
||||
setZIndex(this, enteringView, leavingView, opts.direction, this._renderer);
|
||||
|
||||
// always ensure the entering view is viewable
|
||||
// ******** DOM WRITE ****************
|
||||
enteringView && enteringView._domShow(true, this._renderer);
|
||||
|
||||
if (leavingView) {
|
||||
// always ensure the leaving view is viewable
|
||||
// ******** DOM WRITE ****************
|
||||
leavingView._domShow(true, this._renderer);
|
||||
}
|
||||
|
||||
// initialize the transition
|
||||
trns.init();
|
||||
|
||||
if ((!this._init && this._views.length === 1 && !this._isPortal) || this.config.get('animate') === false) {
|
||||
// the initial load shouldn't animate, unless it's a portal
|
||||
opts.animate = false;
|
||||
}
|
||||
if (opts.animate === false) {
|
||||
// if it was somehow set to not animation, then make the duration zero
|
||||
trns.duration(0);
|
||||
}
|
||||
|
||||
// create a callback that needs to run within zone
|
||||
// that will fire off the willEnter/Leave lifecycle events at the right time
|
||||
trns.beforeAddRead(() => {
|
||||
this._zone.run(this._viewsWillLifecycles.bind(this, enteringView, leavingView));
|
||||
});
|
||||
|
||||
// create a callback for when the animation is done
|
||||
trns.onFinish(() => {
|
||||
// transition animation has ended
|
||||
this._zone.run(this._trnsFinish.bind(this, trns, opts, resolve));
|
||||
});
|
||||
|
||||
// get the set duration of this transition
|
||||
const duration = trns.getDuration();
|
||||
|
||||
// set that this nav is actively transitioning
|
||||
this.setTransitioning(true, duration);
|
||||
|
||||
if (!trns.parent) {
|
||||
// this is the top most, or only active transition, so disable the app
|
||||
// add XXms to the duration the app is disabled when the keyboard is open
|
||||
|
||||
if (duration > DISABLE_APP_MINIMUM_DURATION) {
|
||||
// if this transition has a duration and this is the root transition
|
||||
// then set that the app is actively disabled
|
||||
this._app.setEnabled(false, duration);
|
||||
}
|
||||
|
||||
// cool, let's do this, start the transition
|
||||
if (opts.progressAnimation) {
|
||||
// this is a swipe to go back, just get the transition progress ready
|
||||
// kick off the swipe animation start
|
||||
trns.progressStart();
|
||||
|
||||
} else {
|
||||
// only the top level transition should actually start "play"
|
||||
// kick it off and let it play through
|
||||
// ******** DOM WRITE ****************
|
||||
trns.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_viewsWillLifecycles(enteringView: ViewController, leavingView: ViewController) {
|
||||
// call each view's lifecycle events
|
||||
if (enteringView) {
|
||||
enteringView._willEnter();
|
||||
this.viewWillEnter.emit(enteringView);
|
||||
this._app.viewWillEnter.emit(enteringView);
|
||||
}
|
||||
|
||||
if (leavingView) {
|
||||
leavingView._willLeave();
|
||||
this.viewWillLeave.emit(leavingView);
|
||||
this._app.viewWillLeave.emit(leavingView);
|
||||
}
|
||||
}
|
||||
|
||||
_trnsFinish(trns: Transition, opts: NavOptions, resolve: TransitionResolveFn) {
|
||||
const hasCompleted = trns.hasCompleted;
|
||||
|
||||
// mainly for testing
|
||||
let enteringName: string;
|
||||
let leavingName: string;
|
||||
|
||||
if (hasCompleted) {
|
||||
// transition has completed (went from 0 to 1)
|
||||
if (trns.enteringView) {
|
||||
enteringName = trns.enteringView.name;
|
||||
trns.enteringView._didEnter();
|
||||
this.viewDidEnter.emit(trns.enteringView);
|
||||
this._app.viewDidEnter.emit(trns.enteringView);
|
||||
}
|
||||
|
||||
if (trns.leavingView) {
|
||||
leavingName = trns.leavingView.name;
|
||||
trns.leavingView._didLeave();
|
||||
this.viewDidLeave.emit(trns.leavingView);
|
||||
this._app.viewDidLeave.emit(trns.leavingView);
|
||||
}
|
||||
|
||||
this._cleanup(trns.enteringView);
|
||||
}
|
||||
|
||||
if (!trns.parent) {
|
||||
// this is the root transition
|
||||
// it's save to destroy this transition
|
||||
this._trnsCtrl.destroy(trns.trnsId);
|
||||
|
||||
// it's safe to enable the app again
|
||||
this._app.setEnabled(true);
|
||||
|
||||
if (opts.updateUrl !== false) {
|
||||
// notify deep linker of the nav change
|
||||
// if a direction was provided and should update url
|
||||
this._linker.navChange(opts.direction);
|
||||
}
|
||||
|
||||
if (opts.keyboardClose !== false && this._keyboard.isOpen()) {
|
||||
// the keyboard is still open!
|
||||
// no problem, let's just close for them
|
||||
this._keyboard.close();
|
||||
}
|
||||
}
|
||||
|
||||
// congrats, we did it!
|
||||
resolve(hasCompleted, true, enteringName, leavingName, opts.direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* DOM WRITE
|
||||
*/
|
||||
_cleanup(activeView: ViewController) {
|
||||
// ok, cleanup time!! Destroy all of the views that are
|
||||
// INACTIVE and come after the active view
|
||||
const activeViewIndex = this.indexOf(activeView);
|
||||
|
||||
let reorderZIndexes = false;
|
||||
for (var i = this._views.length - 1; i >= 0; i--) {
|
||||
var view = this._views[i];
|
||||
if (i > activeViewIndex) {
|
||||
// this view comes after the active view
|
||||
// let's unload it
|
||||
view._willUnload();
|
||||
this.viewWillUnload.emit(view);
|
||||
this._app.viewWillUnload.emit(view);
|
||||
view._destroy(this._renderer);
|
||||
|
||||
} else if (i < activeViewIndex && !this._isPortal) {
|
||||
// this view comes before the active view
|
||||
// and it is not a portal then ensure it is hidden
|
||||
view._domShow(false, this._renderer);
|
||||
}
|
||||
if (view._zIndex <= 0) {
|
||||
reorderZIndexes = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._isPortal) {
|
||||
if (reorderZIndexes) {
|
||||
this._views.forEach(view => {
|
||||
// ******** DOM WRITE ****************
|
||||
view._setZIndex(view._zIndex + INIT_ZINDEX + 1, this._renderer);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getActiveChildNav(): any {
|
||||
return this._children[this._children.length - 1];
|
||||
}
|
||||
|
||||
registerChildNav(nav: any) {
|
||||
this._children.push(nav);
|
||||
}
|
||||
|
||||
unregisterChildNav(nav: any) {
|
||||
const index = this._children.indexOf(nav);
|
||||
if (index > -1) {
|
||||
this._children.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
for (var i = this._views.length - 1; i >= 0; i--) {
|
||||
this._views[i]._willUnload();
|
||||
this._views[i]._destroy(this._renderer);
|
||||
}
|
||||
this._views.length = 0;
|
||||
|
||||
this._sbGesture && this._sbGesture.destroy();
|
||||
this._sbTrns && this._sbTrns.destroy();
|
||||
this._sbGesture = this._sbTrns = null;
|
||||
|
||||
if (this.parent && this.parent.unregisterChildNav) {
|
||||
this.parent.unregisterChildNav(this);
|
||||
}
|
||||
}
|
||||
|
||||
swipeBackStart() {
|
||||
if (this.isTransitioning() || this._queue.length > 0) return;
|
||||
|
||||
// default the direction to "back";
|
||||
const opts: NavOptions = {
|
||||
direction: DIRECTION_BACK,
|
||||
progressAnimation: true
|
||||
};
|
||||
|
||||
this._queueTrns({
|
||||
removeStart: -1,
|
||||
removeCount: 1,
|
||||
opts: opts,
|
||||
}, null);
|
||||
|
||||
}
|
||||
|
||||
swipeBackProgress(stepValue: number) {
|
||||
if (this._sbTrns && this._sbGesture) {
|
||||
// continue to disable the app while actively dragging
|
||||
this._app.setEnabled(false, ACTIVE_TRANSITION_MAX_TIME);
|
||||
this.setTransitioning(true, ACTIVE_TRANSITION_MAX_TIME);
|
||||
|
||||
// set the transition animation's progress
|
||||
this._sbTrns.progressStep(stepValue);
|
||||
}
|
||||
}
|
||||
|
||||
swipeBackEnd(shouldComplete: boolean, currentStepValue: number) {
|
||||
if (this._sbTrns && this._sbGesture) {
|
||||
// the swipe back gesture has ended
|
||||
this._sbTrns.progressEnd(shouldComplete, currentStepValue);
|
||||
}
|
||||
}
|
||||
|
||||
_sbCheck() {
|
||||
if (this._sbEnabled && !this._isPortal) {
|
||||
// this nav controller can have swipe to go back
|
||||
|
||||
if (!this._sbGesture) {
|
||||
// create the swipe back gesture if we haven't already
|
||||
const opts = {
|
||||
edge: 'left',
|
||||
threshold: this._sbThreshold
|
||||
};
|
||||
this._sbGesture = new SwipeBackGesture(this.getNativeElement(), opts, this, this._gestureCtrl);
|
||||
}
|
||||
|
||||
if (this.canSwipeBack()) {
|
||||
// it is be possible to swipe back
|
||||
if (!this._sbGesture.isListening) {
|
||||
this._zone.runOutsideAngular(() => {
|
||||
// start listening if it's not already
|
||||
console.debug('swipeBack gesture, listen');
|
||||
this._sbGesture.listen();
|
||||
});
|
||||
}
|
||||
|
||||
} else if (this._sbGesture.isListening) {
|
||||
// it should not be possible to swipe back
|
||||
// but the gesture is still listening
|
||||
console.debug('swipeBack gesture, unlisten');
|
||||
this._sbGesture.unlisten();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
canSwipeBack(): boolean {
|
||||
return (this._sbEnabled &&
|
||||
!this._children.length &&
|
||||
!this.isTransitioning() &&
|
||||
this._app.isEnabled() &&
|
||||
this.canGoBack());
|
||||
}
|
||||
|
||||
canGoBack(): boolean {
|
||||
const activeView = this.getActive();
|
||||
return !!(activeView && activeView.enableBack()) || false;
|
||||
}
|
||||
|
||||
isTransitioning(): boolean {
|
||||
// using a timestamp instead of boolean incase something goes wrong
|
||||
return (this._trnsTm > Date.now());
|
||||
}
|
||||
|
||||
setTransitioning(isTransitioning: boolean, durationPadding: number = 2000) {
|
||||
this._trnsTm = (isTransitioning ? Date.now() + durationPadding : 0);
|
||||
}
|
||||
|
||||
getActive() {
|
||||
return this._views[this._views.length - 1];
|
||||
}
|
||||
|
||||
isActive(view: ViewController) {
|
||||
return (view === this.getActive());
|
||||
}
|
||||
|
||||
getByIndex(index: number): ViewController {
|
||||
return this._views[index];
|
||||
}
|
||||
|
||||
getPrevious(view?: ViewController): ViewController {
|
||||
// returns the view controller which is before the given view controller.
|
||||
if (!view) {
|
||||
view = this.getActive();
|
||||
}
|
||||
return this._views[this.indexOf(view) - 1];
|
||||
}
|
||||
|
||||
first(): ViewController {
|
||||
// returns the first view controller in this nav controller's stack.
|
||||
return this._views[0];
|
||||
}
|
||||
|
||||
last(): ViewController {
|
||||
// returns the last page in this nav controller's stack.
|
||||
return this._views[this._views.length - 1];
|
||||
}
|
||||
|
||||
indexOf(view: ViewController): number {
|
||||
// returns the index number of the given view controller.
|
||||
return this._views.indexOf(view);
|
||||
}
|
||||
|
||||
length(): number {
|
||||
return this._views.length;
|
||||
}
|
||||
|
||||
isSwipeBackEnabled(): boolean {
|
||||
return this._sbEnabled;
|
||||
}
|
||||
|
||||
dismissPageChangeViews() {
|
||||
for (var i = 0; i < this._views.length; i++) {
|
||||
var view = this._views[i];
|
||||
if (view.data && view.data.dismissOnPageChange) {
|
||||
view.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setViewport(val: ViewContainerRef) {
|
||||
this._viewport = val;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
* DEPRECATED: Please use app.getRootNav() instead
|
||||
*/
|
||||
get rootNav(): NavController {
|
||||
// deprecated 07-14-2016 beta.11
|
||||
console.warn('nav.rootNav() has been deprecated, please use app.getRootNav() instead');
|
||||
return this._app.getRootNav();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* DEPRECATED: Please use inject the overlays controller and use the present method on the instance instead.
|
||||
*/
|
||||
present(): Promise<any> {
|
||||
// deprecated warning: added beta.11 2016-06-27
|
||||
console.warn('nav.present() has been deprecated.\n' +
|
||||
'Please inject the overlay\'s controller and use the present method on the instance instead.');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let ctrlIds = -1;
|
||||
|
||||
const DISABLE_APP_MINIMUM_DURATION = 64;
|
||||
const ACTIVE_TRANSITION_MAX_TIME = 5000;
|
||||
@@ -1,11 +1,7 @@
|
||||
import { EventEmitter } from '@angular/core';
|
||||
|
||||
import { Config } from '../../config/config';
|
||||
import { GestureController } from '../../gestures/gesture-controller';
|
||||
import { Ion } from '../ion';
|
||||
import { isBlank, pascalCaseToDashCase } from '../../util/util';
|
||||
import { Keyboard } from '../../util/keyboard';
|
||||
import { NavOptions } from './nav-interfaces';
|
||||
import { Config } from '../config/config';
|
||||
import { NavOptions } from './nav-util';
|
||||
import { ViewController } from './view-controller';
|
||||
|
||||
|
||||
@@ -96,7 +92,7 @@ import { ViewController } from './view-controller';
|
||||
* template: '<ion-nav #myNav [root]="rootPage"></ion-nav>'
|
||||
* })
|
||||
* export class MyApp {
|
||||
* @ViewChild('myNav') nav : NavController
|
||||
* @ViewChild('myNav') nav: NavController
|
||||
* private rootPage = TabsPage;
|
||||
*
|
||||
* // Wait for the components in MyApp's template to be initialized
|
||||
@@ -199,11 +195,11 @@ import { ViewController } from './view-controller';
|
||||
* <ion-content>I'm the other page!</ion-content>`
|
||||
* })
|
||||
* class OtherPage {
|
||||
* constructor(private navController: NavController ){
|
||||
* constructor(private navCtrl: NavController ){
|
||||
* }
|
||||
*
|
||||
* popView(){
|
||||
* this.navController.pop();
|
||||
* this.navCtrl.pop();
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
@@ -219,7 +215,7 @@ import { ViewController } from './view-controller';
|
||||
* template: 'Hello World'
|
||||
* })
|
||||
* class HelloWorld {
|
||||
* ionViewLoaded() {
|
||||
* ionViewDidLoad() {
|
||||
* console.log("I'm alive!");
|
||||
* }
|
||||
* ionViewWillLeave() {
|
||||
@@ -230,13 +226,12 @@ import { ViewController } from './view-controller';
|
||||
*
|
||||
* | Page Event | Description |
|
||||
* |---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
* | `ionViewLoaded` | Runs when the page has loaded. This event only happens once per page being created and added to the DOM. If a page leaves but is cached, then this event will not fire again on a subsequent viewing. The `ionViewLoaded` event is good place to put your setup code for the page. |
|
||||
* | `ionViewDidLoad` | Runs when the page has loaded. This event only happens once per page being created. If a page leaves but is cached, then this event will not fire again on a subsequent viewing. The `ionViewDidLoad` event is good place to put your setup code for the page. |
|
||||
* | `ionViewWillEnter` | Runs when the page is about to enter and become the active page. |
|
||||
* | `ionViewDidEnter` | Runs when the page has fully entered and is now the active page. This event will fire, whether it was the first load or a cached page. |
|
||||
* | `ionViewWillLeave` | Runs when the page is about to leave and no longer be the active page. |
|
||||
* | `ionViewDidLeave` | Runs when the page has finished leaving and is no longer the active page. |
|
||||
* | `ionViewWillUnload` | Runs when the page is about to be destroyed and have its elements removed. |
|
||||
* | `ionViewDidUnload` | Runs after the page has been destroyed and its elements have been removed.
|
||||
* | `ionViewWillUnload` | Runs when the page is about to be destroyed and have its elements removed.
|
||||
*
|
||||
*
|
||||
* ## Asynchronous Nav Transitions
|
||||
@@ -254,14 +249,14 @@ import { ViewController } from './view-controller';
|
||||
* operations in order. Navigation actions can be chained together very easily using promises.
|
||||
*
|
||||
* ```typescript
|
||||
* let navTransitionPromise = this.navController.push(Page2);
|
||||
* navTransitionPromise.then( () => {
|
||||
* let navTransitionPromise = this.navCtrl.push(Page2);
|
||||
* navTransitionPromise.then(() => {
|
||||
* // the transition has completed, so I can push another page now
|
||||
* return this.navController.push(Page3);
|
||||
* }).then( () => {
|
||||
* return this.navCtrl.push(Page3);
|
||||
* }).then(() => {
|
||||
* // the second transition has completed, so I can push yet another page
|
||||
return this.navController.push(Page4);
|
||||
* }).then( () => {
|
||||
return this.navCtrl.push(Page4);
|
||||
* }).then(() => {
|
||||
* console.log('The transitions are complete!');
|
||||
* })
|
||||
* ```
|
||||
@@ -322,12 +317,6 @@ export abstract class NavController {
|
||||
*/
|
||||
viewWillUnload: EventEmitter<any>;
|
||||
|
||||
/**
|
||||
* Observable to be subscribed to when a component has fully been unloaded and destroyed.
|
||||
* @returns {Observable} Returns an observable
|
||||
*/
|
||||
viewDidUnload: EventEmitter<any>;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@@ -345,27 +334,6 @@ export abstract class NavController {
|
||||
*/
|
||||
config: Config;
|
||||
|
||||
/**
|
||||
* Set the root for the current navigation stack.
|
||||
* @param {Page} page The name of the component you want to push on the navigation stack.
|
||||
* @param {object} [params={}] Any nav-params you want to pass along to the next view.
|
||||
* @param {object} [opts={}] Any options you want to use pass to transtion.
|
||||
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
|
||||
*/
|
||||
abstract setRoot(page: any, params?: any, opts?: NavOptions, done?: Function): Promise<any>;
|
||||
|
||||
/**
|
||||
* Set the views of the current navigation stack and navigate to the
|
||||
* last view. By default animations are disabled, but they can be enabled
|
||||
* by passing options to the navigation controller.You can also pass any
|
||||
* navigation params to the individual pages in the array.
|
||||
*
|
||||
* @param {array<Page>} pages An arry of page components and their params to load in the stack.
|
||||
* @param {object} [opts={}] Nav options to go with this transition.
|
||||
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
|
||||
*/
|
||||
abstract setPages(pages: Array<{page: any, params?: any}>, opts?: NavOptions, done?: Function): Promise<any>;
|
||||
|
||||
/**
|
||||
* Push a new component onto the current navication stack. Pass any aditional information
|
||||
* along as an object. This additional information is acessible through NavParams
|
||||
@@ -421,13 +389,20 @@ export abstract class NavController {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Pop to a specific view in the history stack.
|
||||
* Pop to a specific view in the history stack. If an already created
|
||||
* instance of the page is not found in the stack, then it'll `setRoot`
|
||||
* to the nav stack by removing all current pages and pushing on a
|
||||
* new instance of the given page. Note that any params passed to
|
||||
* this method are not used when an existing page instance has already
|
||||
* been found in the stack. Nav params are only used by this method
|
||||
* when a new instance needs to be created.
|
||||
*
|
||||
* @param {ViewController} view to pop to
|
||||
* @param {any} page A page can be a ViewController instance or string.
|
||||
* @param {object} [params={}] Any nav-params to be used when a new view instance is created at the root.
|
||||
* @param {object} [opts={}] Nav options to go with this transition.
|
||||
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
|
||||
*/
|
||||
abstract popTo(view: ViewController, opts?: NavOptions, done?: Function): Promise<any>;
|
||||
abstract popTo(page: any, params?: any, opts?: NavOptions, done?: Function): Promise<any>;
|
||||
|
||||
/**
|
||||
* Removes a page from the nav stack at the specified index.
|
||||
@@ -439,6 +414,27 @@ export abstract class NavController {
|
||||
*/
|
||||
abstract remove(startIndex: number, removeCount?: number, opts?: NavOptions, done?: Function): Promise<any>;
|
||||
|
||||
/**
|
||||
* Set the root for the current navigation stack.
|
||||
* @param {Page|ViewController} page The name of the component you want to push on the navigation stack.
|
||||
* @param {object} [params={}] Any nav-params you want to pass along to the next view.
|
||||
* @param {object} [opts={}] Any options you want to use pass to transtion.
|
||||
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
|
||||
*/
|
||||
abstract setRoot(pageOrViewCtrl: any, params?: any, opts?: NavOptions, done?: Function): Promise<any>;
|
||||
|
||||
/**
|
||||
* Set the views of the current navigation stack and navigate to the
|
||||
* last view. By default animations are disabled, but they can be enabled
|
||||
* by passing options to the navigation controller.You can also pass any
|
||||
* navigation params to the individual pages in the array.
|
||||
*
|
||||
* @param {array<Page>} pages An arry of page components and their params to load in the stack.
|
||||
* @param {object} [opts={}] Nav options to go with this transition.
|
||||
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
|
||||
*/
|
||||
abstract setPages(pages: any[], opts?: NavOptions, done?: Function): Promise<any>;
|
||||
|
||||
/**
|
||||
* @param {number} index The index of the page to get.
|
||||
* @returns {ViewController} Returns the view controller that matches the given index.
|
||||
@@ -448,7 +444,7 @@ export abstract class NavController {
|
||||
/**
|
||||
* @returns {ViewController} Returns the active page's view controller.
|
||||
*/
|
||||
abstract getActive(): ViewController;
|
||||
abstract getActive(includeEntering?: boolean): ViewController;
|
||||
|
||||
/**
|
||||
* Returns if the given view is the active view or not.
|
||||
@@ -459,10 +455,11 @@ export abstract class NavController {
|
||||
|
||||
/**
|
||||
* Returns the view controller which is before the given view controller.
|
||||
* If no view controller is passed in, then it'll default to the active view.
|
||||
* @param {ViewController} view
|
||||
* @returns {viewController}
|
||||
*/
|
||||
abstract getPrevious(view: ViewController): ViewController;
|
||||
abstract getPrevious(view?: ViewController): ViewController;
|
||||
|
||||
/**
|
||||
* Returns the first view controller in this nav controller's stack.
|
||||
176
src/navigation/nav-util.ts
Normal file
176
src/navigation/nav-util.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import { Renderer, TypeDecorator } from '@angular/core';
|
||||
|
||||
import { DeepLinker } from './deep-linker';
|
||||
import { isPresent } from '../util/util';
|
||||
import { isViewController, ViewController } from './view-controller';
|
||||
import { NavControllerBase } from './nav-controller-base';
|
||||
import { Transition } from '../transitions/transition';
|
||||
|
||||
|
||||
export function convertToView(linker: DeepLinker, nameOrPageOrView: any, params: any): ViewController {
|
||||
if (nameOrPageOrView) {
|
||||
if (isViewController(nameOrPageOrView)) {
|
||||
// is already a ViewController
|
||||
return nameOrPageOrView;
|
||||
}
|
||||
if (typeof nameOrPageOrView === 'function') {
|
||||
// is a page component, now turn it into a ViewController
|
||||
return new ViewController(nameOrPageOrView, params);
|
||||
}
|
||||
if (typeof nameOrPageOrView === 'string') {
|
||||
// is a string, see if it matches a
|
||||
const component = linker.getComponentFromName(nameOrPageOrView);
|
||||
if (component) {
|
||||
// found a page component in the link config by name
|
||||
return new ViewController(component, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.error(`invalid page component: ${nameOrPageOrView}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
export function convertToViews(linker: DeepLinker, pages: any[]): ViewController[] {
|
||||
const views: ViewController[] = [];
|
||||
if (pages) {
|
||||
for (var i = 0; i < pages.length; i++) {
|
||||
var page = pages[i];
|
||||
if (page) {
|
||||
if (isViewController(page)) {
|
||||
views.push(page);
|
||||
|
||||
} else {
|
||||
views.push(convertToView(linker, page.page, page.params));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return views;
|
||||
}
|
||||
|
||||
export function setZIndex(nav: NavControllerBase, enteringView: ViewController, leavingView: ViewController, direction: string, renderer: Renderer) {
|
||||
if (enteringView) {
|
||||
|
||||
leavingView = leavingView || nav.getPrevious(enteringView);
|
||||
|
||||
if (leavingView && isPresent(leavingView._zIndex)) {
|
||||
if (direction === DIRECTION_BACK) {
|
||||
enteringView._setZIndex(leavingView._zIndex - 1, renderer);
|
||||
|
||||
} else {
|
||||
enteringView._setZIndex(leavingView._zIndex + 1, renderer);
|
||||
}
|
||||
|
||||
} else {
|
||||
enteringView._setZIndex(nav._isPortal ? PORTAL_ZINDEX : INIT_ZINDEX, renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function isTabs(nav: any) {
|
||||
// Tabs (ion-tabs)
|
||||
return !!nav && !!nav.getSelected;
|
||||
}
|
||||
|
||||
export function isTab(nav: any) {
|
||||
// Tab (ion-tab)
|
||||
return !!nav && isPresent(nav._tabId);
|
||||
}
|
||||
|
||||
export function isNav(nav: any) {
|
||||
// Nav (ion-nav), Tab (ion-tab), Portal (ion-portal)
|
||||
return !!nav && !!nav.push;
|
||||
}
|
||||
|
||||
// public link interface
|
||||
export interface DeepLinkMetadataType {
|
||||
name: string;
|
||||
segment?: string;
|
||||
defaultHistory?: any[];
|
||||
}
|
||||
|
||||
export class DeepLinkMetadata implements DeepLinkMetadataType {
|
||||
component: any;
|
||||
name: string;
|
||||
segment?: string;
|
||||
defaultHistory?: any[];
|
||||
}
|
||||
|
||||
export interface DeepLinkDecorator extends TypeDecorator {}
|
||||
|
||||
export interface DeepLinkMetadataFactory {
|
||||
(obj: DeepLinkMetadataType): DeepLinkDecorator;
|
||||
new (obj: DeepLinkMetadataType): DeepLinkMetadata;
|
||||
}
|
||||
|
||||
export var DeepLink: DeepLinkMetadataFactory;
|
||||
|
||||
export interface DeepLinkConfig {
|
||||
links: DeepLinkMetadata[];
|
||||
}
|
||||
|
||||
// internal link interface, not exposed publicly
|
||||
export interface NavLink {
|
||||
component: any;
|
||||
name?: string;
|
||||
segment?: string;
|
||||
parts?: string[];
|
||||
partsLen?: number;
|
||||
staticLen?: number;
|
||||
dataLen?: number;
|
||||
defaultHistory?: any[];
|
||||
}
|
||||
|
||||
export interface NavSegment {
|
||||
id: string;
|
||||
name: string;
|
||||
component: any;
|
||||
data: any;
|
||||
navId?: string;
|
||||
defaultHistory?: NavSegment[];
|
||||
}
|
||||
|
||||
export interface NavOptions {
|
||||
animate?: boolean;
|
||||
animation?: string;
|
||||
direction?: string;
|
||||
duration?: number;
|
||||
easing?: string;
|
||||
id?: string;
|
||||
keyboardClose?: boolean;
|
||||
progressAnimation?: boolean;
|
||||
ev?: any;
|
||||
updateUrl?: boolean;
|
||||
isNavRoot?: boolean;
|
||||
}
|
||||
|
||||
export interface TransitionResolveFn {
|
||||
(hasCompleted: boolean, requiresTransition: boolean, enteringName?: string, leavingName?: string, direction?: string): void;
|
||||
}
|
||||
|
||||
export interface TransitionRejectFn {
|
||||
(rejectReason: any, transition?: Transition): void;
|
||||
}
|
||||
|
||||
export interface TransitionInstruction {
|
||||
opts: NavOptions;
|
||||
insertStart?: number;
|
||||
insertViews?: ViewController[];
|
||||
removeStart?: number;
|
||||
removeCount?: number;
|
||||
resolve?: TransitionResolveFn;
|
||||
reject?: TransitionRejectFn;
|
||||
}
|
||||
|
||||
export enum ViewState {
|
||||
INITIALIZED,
|
||||
PRE_RENDERED,
|
||||
LOADED,
|
||||
}
|
||||
|
||||
export const INIT_ZINDEX = 100;
|
||||
export const PORTAL_ZINDEX = 9999;
|
||||
|
||||
export const DIRECTION_BACK = 'back';
|
||||
export const DIRECTION_FORWARD = 'forward';
|
||||
export const DIRECTION_SWITCH = 'switch';
|
||||
@@ -1,9 +1,8 @@
|
||||
import { assign } from '../../util/util';
|
||||
import { GestureController, GestureDelegate, GesturePriority } from '../../gestures/gesture-controller';
|
||||
import { MenuController } from '../menu/menu-controller';
|
||||
import { assign } from '../util/util';
|
||||
import { GestureController, GesturePriority } from '../gestures/gesture-controller';
|
||||
import { NavControllerBase } from './nav-controller-base';
|
||||
import { SlideData } from '../../gestures/slide-gesture';
|
||||
import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture';
|
||||
import { SlideData } from '../gestures/slide-gesture';
|
||||
import { SlideEdgeGesture } from '../gestures/slide-edge-gesture';
|
||||
|
||||
|
||||
export class SwipeBackGesture extends SlideEdgeGesture {
|
||||
994
src/navigation/test/nav-controller.spec.ts
Normal file
994
src/navigation/test/nav-controller.spec.ts
Normal file
@@ -0,0 +1,994 @@
|
||||
import { mockNavController, mockView, mockViews,
|
||||
MockView1, MockView2, MockView3, MockView4, MockView5 } from '../../util/mock-providers';
|
||||
import { NavControllerBase } from '../nav-controller-base';
|
||||
import { NavOptions, DIRECTION_FORWARD, DIRECTION_BACK } from '../nav-util';
|
||||
import { ViewController } from '../view-controller';
|
||||
|
||||
|
||||
describe('NavController', () => {
|
||||
|
||||
describe('push and pop', () => {
|
||||
|
||||
it('should push multiple times and pop multiple times', () => {
|
||||
let push1Done = jasmine.createSpy('PushDone');
|
||||
let push2Done = jasmine.createSpy('PushDone');
|
||||
let push3Done = jasmine.createSpy('PushDone');
|
||||
let push4Done = jasmine.createSpy('PushDone');
|
||||
let pop1Done = jasmine.createSpy('PopDone');
|
||||
let pop2Done = jasmine.createSpy('PopDone');
|
||||
let pop3Done = jasmine.createSpy('PopDone');
|
||||
|
||||
// Push 1
|
||||
nav.push(MockView1, null, { animate: false }, push1Done);
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = true;
|
||||
expect(push1Done).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView1', undefined, DIRECTION_FORWARD
|
||||
);
|
||||
expect(nav.length()).toEqual(1);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
|
||||
// Push 2
|
||||
nav.push(MockView2, null, { animate: false }, push2Done);
|
||||
|
||||
expect(push2Done).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView2', 'MockView1', DIRECTION_FORWARD
|
||||
);
|
||||
expect(nav.length()).toEqual(2);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
expect(nav.getByIndex(1).component).toEqual(MockView2);
|
||||
|
||||
// Push 3
|
||||
nav.push(MockView3, null, { animate: false }, push3Done);
|
||||
|
||||
expect(push3Done).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView3', 'MockView2', DIRECTION_FORWARD
|
||||
);
|
||||
expect(nav.length()).toEqual(3);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
expect(nav.getByIndex(1).component).toEqual(MockView2);
|
||||
expect(nav.getByIndex(2).component).toEqual(MockView3);
|
||||
|
||||
// Push 4
|
||||
nav.push(MockView4, null, { animate: false }, push4Done);
|
||||
|
||||
expect(push4Done).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView4', 'MockView3', DIRECTION_FORWARD
|
||||
);
|
||||
expect(nav.length()).toEqual(4);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
expect(nav.getByIndex(1).component).toEqual(MockView2);
|
||||
expect(nav.getByIndex(2).component).toEqual(MockView3);
|
||||
expect(nav.getByIndex(3).component).toEqual(MockView4);
|
||||
|
||||
// Pop 1
|
||||
nav.pop({ animate: false }, pop1Done);
|
||||
|
||||
expect(pop1Done).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView3', 'MockView4', DIRECTION_BACK
|
||||
);
|
||||
expect(nav.length()).toEqual(3);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
expect(nav.getByIndex(1).component).toEqual(MockView2);
|
||||
expect(nav.getByIndex(2).component).toEqual(MockView3);
|
||||
|
||||
// Pop 2
|
||||
nav.pop({ animate: false }, pop2Done);
|
||||
|
||||
expect(pop2Done).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView2', 'MockView3', DIRECTION_BACK
|
||||
);
|
||||
expect(nav.length()).toEqual(2);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
expect(nav.getByIndex(1).component).toEqual(MockView2);
|
||||
|
||||
// Pop 3
|
||||
nav.pop({ animate: false }, pop3Done);
|
||||
|
||||
expect(pop3Done).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView1', 'MockView2', DIRECTION_BACK
|
||||
);
|
||||
expect(nav.length()).toEqual(1);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('push', () => {
|
||||
|
||||
it('should push a component as the first view', () => {
|
||||
nav.push(MockView1, null, null, trnsDone);
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = true;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView1', undefined, DIRECTION_FORWARD
|
||||
);
|
||||
expect(nav.length()).toEqual(1);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
expect(nav.isTransitioning()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should push a component as the second view at the end', () => {
|
||||
mockViews(nav, [mockView(MockView1)]);
|
||||
nav.push(MockView2, null, null, trnsDone);
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = true;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView2', 'MockView1', DIRECTION_FORWARD
|
||||
);
|
||||
expect(nav.length()).toEqual(2);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
expect(nav.getByIndex(1).component).toEqual(MockView2);
|
||||
expect(nav.isTransitioning()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should push a ViewController as the second view and fire lifecycles', () => {
|
||||
let view1 = mockView();
|
||||
let view2 = mockView();
|
||||
|
||||
let instance1 = spyOnLifecycles(view1);
|
||||
let instance2 = spyOnLifecycles(view2);
|
||||
|
||||
mockViews(nav, [view1]);
|
||||
nav.push(view2, null, null, trnsDone);
|
||||
|
||||
expect(instance1.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanLeave).toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillUnload).not.toHaveBeenCalled();
|
||||
|
||||
expect(instance2.ionViewDidLoad).toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanEnter).toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillEnter).toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidEnter).toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillLeave).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidLeave).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillUnload).not.toHaveBeenCalled();
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = true;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView', 'MockView', DIRECTION_FORWARD
|
||||
);
|
||||
expect(nav.length()).toEqual(2);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('insert', () => {
|
||||
|
||||
it('should insert at the begining with no async transition', () => {
|
||||
let view4 = mockView(MockView4);
|
||||
let instance4 = spyOnLifecycles(view4);
|
||||
let opts: NavOptions = {};
|
||||
|
||||
mockViews(nav, [mockView(MockView1), mockView(MockView2), mockView(MockView3)]);
|
||||
nav.insert(0, view4, null, opts, trnsDone);
|
||||
|
||||
expect(instance4.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillLeave).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewDidLeave).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillUnload).not.toHaveBeenCalled();
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = false;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, undefined, undefined, undefined
|
||||
);
|
||||
expect(nav.length()).toEqual(4);
|
||||
expect(nav.first().component).toEqual(MockView4);
|
||||
expect(nav.last().component).toEqual(MockView3);
|
||||
});
|
||||
|
||||
it('should insert at the end when given -1', () => {
|
||||
let opts: NavOptions = {};
|
||||
mockViews(nav, [mockView(MockView1)]);
|
||||
nav.insert(-1, MockView2, null, opts, trnsDone);
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = true;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView2', 'MockView1', DIRECTION_FORWARD
|
||||
);
|
||||
expect(nav.length()).toEqual(2);
|
||||
expect(nav.last().component).toEqual(MockView2);
|
||||
});
|
||||
|
||||
it('should insert at the end when given a number greater than actual length', () => {
|
||||
mockViews(nav, [mockView(MockView1)]);
|
||||
nav.insert(9999, MockView2, null, null, trnsDone);
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = true;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView2', 'MockView1', DIRECTION_FORWARD
|
||||
);
|
||||
expect(nav.length()).toEqual(2);
|
||||
expect(nav.last().component).toEqual(MockView2);
|
||||
});
|
||||
|
||||
it('should not insert if null view', () => {
|
||||
mockViews(nav, [mockView(MockView1)]);
|
||||
nav.insert(-1, null, null, null, trnsDone);
|
||||
|
||||
let hasCompleted = false;
|
||||
let requiresTransition = false;
|
||||
let rejectReason = 'invalid views to insert';
|
||||
expect(trnsDone).toHaveBeenCalledWith(hasCompleted, requiresTransition, rejectReason);
|
||||
expect(nav.length()).toEqual(1);
|
||||
expect(nav.last().component).toEqual(MockView1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('insertPages', () => {
|
||||
|
||||
it('should insert all pages in the middle', () => {
|
||||
let view4 = mockView(MockView4);
|
||||
let instance4 = spyOnLifecycles(view4);
|
||||
mockViews(nav, [mockView(MockView1), mockView(MockView2), mockView(MockView3)]);
|
||||
nav.insertPages(1, [view4, mockView(MockView5)], null, trnsDone);
|
||||
|
||||
expect(instance4.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillLeave).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewDidLeave).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillUnload).not.toHaveBeenCalled();
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = false;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, undefined, undefined, undefined
|
||||
);
|
||||
expect(nav.length()).toEqual(5);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
expect(nav.getByIndex(1).component).toEqual(MockView4);
|
||||
expect(nav.getByIndex(2).component).toEqual(MockView5);
|
||||
expect(nav.getByIndex(3).component).toEqual(MockView2);
|
||||
expect(nav.getByIndex(4).component).toEqual(MockView3);
|
||||
|
||||
expect(nav.getByIndex(1)._nav).toEqual(nav);
|
||||
expect(nav.getByIndex(2)._nav).toEqual(nav);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('pop', () => {
|
||||
|
||||
it('should not pop when no views in the stack', () => {
|
||||
nav.pop(null, trnsDone);
|
||||
|
||||
let hasCompleted = false;
|
||||
let requiresTransition = false;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'no views in the stack to be removed'
|
||||
);
|
||||
expect(nav.length()).toEqual(0);
|
||||
expect(nav.isTransitioning()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should remove the last view and fire lifecycles', () => {
|
||||
let view1 = mockView(MockView1);
|
||||
let view2 = mockView(MockView2);
|
||||
mockViews(nav, [view1, view2]);
|
||||
let instance1 = spyOnLifecycles(view1);
|
||||
let instance2 = spyOnLifecycles(view2);
|
||||
|
||||
nav.pop(null, trnsDone);
|
||||
|
||||
expect(instance1.ionViewDidLoad).toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanEnter).toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillEnter).toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidEnter).toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillUnload).not.toHaveBeenCalled();
|
||||
|
||||
expect(instance2.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanLeave).toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = true;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView1', 'MockView2', DIRECTION_BACK
|
||||
);
|
||||
expect(nav.length()).toEqual(1);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
expect(nav.isTransitioning()).toEqual(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('popTo', () => {
|
||||
|
||||
it('should pop to a view', () => {
|
||||
let view1 = mockView(MockView1);
|
||||
let view2 = mockView(MockView2);
|
||||
let view3 = mockView(MockView3);
|
||||
mockViews(nav, [view1, view2, view3]);
|
||||
|
||||
nav.popTo(view2, null, trnsDone);
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = true;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView2', 'MockView3', DIRECTION_BACK
|
||||
);
|
||||
expect(nav.length()).toEqual(2);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
expect(nav.getByIndex(1).component).toEqual(MockView2);
|
||||
});
|
||||
|
||||
it('should pop to using an index number', () => {
|
||||
let view1 = mockView(MockView1);
|
||||
let view2 = mockView(MockView2);
|
||||
let view3 = mockView(MockView3);
|
||||
let view4 = mockView(MockView4);
|
||||
mockViews(nav, [view1, view2, view3, view4]);
|
||||
|
||||
nav.popTo(1, null, trnsDone);
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = true;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView2', 'MockView4', DIRECTION_BACK
|
||||
);
|
||||
expect(nav.length()).toEqual(2);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
expect(nav.getByIndex(1).component).toEqual(MockView2);
|
||||
});
|
||||
|
||||
it('should pop to first using an index number', () => {
|
||||
let view1 = mockView(MockView1);
|
||||
let view2 = mockView(MockView2);
|
||||
let view3 = mockView(MockView3);
|
||||
let view4 = mockView(MockView4);
|
||||
mockViews(nav, [view1, view2, view3, view4]);
|
||||
|
||||
let instance1 = spyOnLifecycles(view1);
|
||||
let instance2 = spyOnLifecycles(view2);
|
||||
let instance3 = spyOnLifecycles(view3);
|
||||
let instance4 = spyOnLifecycles(view4);
|
||||
|
||||
nav.popTo(0, null, trnsDone);
|
||||
|
||||
expect(instance1.ionViewDidLoad).toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanEnter).toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillEnter).toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidEnter).toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillUnload).not.toHaveBeenCalled();
|
||||
|
||||
expect(instance2.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
expect(instance3.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance3.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
expect(instance4.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewCanLeave).toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance4.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = true;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView1', 'MockView4', DIRECTION_BACK
|
||||
);
|
||||
expect(nav.length()).toEqual(1);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('popToRoot', () => {
|
||||
|
||||
it('should pop to the first view', () => {
|
||||
let view1 = mockView(MockView1);
|
||||
let view2 = mockView(MockView2);
|
||||
let view3 = mockView(MockView3);
|
||||
let view4 = mockView(MockView4);
|
||||
mockViews(nav, [view1, view2, view3, view4]);
|
||||
|
||||
let instance1 = spyOnLifecycles(view1);
|
||||
let instance2 = spyOnLifecycles(view2);
|
||||
let instance3 = spyOnLifecycles(view3);
|
||||
let instance4 = spyOnLifecycles(view4);
|
||||
|
||||
nav.popToRoot(null, trnsDone);
|
||||
|
||||
expect(instance1.ionViewDidLoad).toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanEnter).toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillEnter).toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidEnter).toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillUnload).not.toHaveBeenCalled();
|
||||
|
||||
expect(instance2.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
expect(instance3.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance3.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
expect(instance4.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewCanLeave).toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance4.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = true;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView1', 'MockView4', DIRECTION_BACK
|
||||
);
|
||||
expect(nav.length()).toEqual(1);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
|
||||
it('should remove the first three views in the beginning, no last view transition', () => {
|
||||
let view1 = mockView(MockView1);
|
||||
let view2 = mockView(MockView2);
|
||||
let view3 = mockView(MockView3);
|
||||
let view4 = mockView(MockView4);
|
||||
mockViews(nav, [view1, view2, view3, view4]);
|
||||
|
||||
let instance1 = spyOnLifecycles(view1);
|
||||
let instance2 = spyOnLifecycles(view2);
|
||||
let instance3 = spyOnLifecycles(view3);
|
||||
let instance4 = spyOnLifecycles(view4);
|
||||
|
||||
nav.remove(0, 3, null, trnsDone);
|
||||
|
||||
expect(instance1.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
expect(instance2.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
expect(instance3.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance3.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
expect(instance4.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillLeave).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewDidLeave).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillUnload).not.toHaveBeenCalled();
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = false;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, undefined, undefined, undefined
|
||||
);
|
||||
expect(nav.length()).toEqual(1);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView4);
|
||||
});
|
||||
|
||||
it('should remove two views in the middle', () => {
|
||||
let view1 = mockView(MockView1);
|
||||
let view2 = mockView(MockView2);
|
||||
let view3 = mockView(MockView3);
|
||||
let view4 = mockView(MockView4);
|
||||
mockViews(nav, [view1, view2, view3, view4]);
|
||||
|
||||
let instance1 = spyOnLifecycles(view1);
|
||||
let instance2 = spyOnLifecycles(view2);
|
||||
let instance3 = spyOnLifecycles(view3);
|
||||
let instance4 = spyOnLifecycles(view4);
|
||||
|
||||
nav.remove(1, 2, null, trnsDone);
|
||||
|
||||
expect(instance1.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillUnload).not.toHaveBeenCalled();
|
||||
|
||||
expect(instance2.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
expect(instance3.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance3.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
expect(instance4.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillLeave).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewDidLeave).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillUnload).not.toHaveBeenCalled();
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = false;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, undefined, undefined, undefined
|
||||
);
|
||||
expect(nav.length()).toEqual(2);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
expect(nav.getByIndex(1).component).toEqual(MockView4);
|
||||
});
|
||||
|
||||
it('should remove the last two views at the end', () => {
|
||||
let view1 = mockView(MockView1);
|
||||
let view2 = mockView(MockView2);
|
||||
let view3 = mockView(MockView3);
|
||||
let view4 = mockView(MockView4);
|
||||
mockViews(nav, [view1, view2, view3, view4]);
|
||||
|
||||
let instance1 = spyOnLifecycles(view1);
|
||||
let instance2 = spyOnLifecycles(view2);
|
||||
let instance3 = spyOnLifecycles(view3);
|
||||
let instance4 = spyOnLifecycles(view4);
|
||||
|
||||
nav.remove(2, 2, null, trnsDone);
|
||||
|
||||
expect(instance1.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillUnload).not.toHaveBeenCalled();
|
||||
|
||||
expect(instance2.ionViewDidLoad).toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanEnter).toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillEnter).toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidEnter).toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillLeave).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidLeave).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillUnload).not.toHaveBeenCalled();
|
||||
|
||||
expect(instance3.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance3.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
expect(instance4.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance4.ionViewCanLeave).toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance4.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance4.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = true;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView2', 'MockView4', DIRECTION_BACK
|
||||
);
|
||||
expect(nav.length()).toEqual(2);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
expect(nav.getByIndex(1).component).toEqual(MockView2);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('setRoot', () => {
|
||||
|
||||
it('should set a ViewController as the root when its the last view, no transition', () => {
|
||||
let view1 = mockView(MockView1);
|
||||
let view2 = mockView(MockView2);
|
||||
let view3 = mockView(MockView3);
|
||||
mockViews(nav, [view1, view2, view3]);
|
||||
|
||||
let instance1 = spyOnLifecycles(view1);
|
||||
let instance2 = spyOnLifecycles(view2);
|
||||
let instance3 = spyOnLifecycles(view3);
|
||||
|
||||
nav.setRoot(view3, null, null, trnsDone);
|
||||
|
||||
expect(instance1.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
expect(instance2.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
expect(instance3.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillLeave).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewDidLeave).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillUnload).not.toHaveBeenCalled();
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = false;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, undefined, undefined, undefined
|
||||
);
|
||||
expect(nav.length()).toEqual(1);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView3);
|
||||
});
|
||||
|
||||
it('should set a ViewController as the root when its the middle view, with transition', () => {
|
||||
let view1 = mockView(MockView1);
|
||||
let view2 = mockView(MockView2);
|
||||
let view3 = mockView(MockView3);
|
||||
mockViews(nav, [view1, view2, view3]);
|
||||
|
||||
let instance1 = spyOnLifecycles(view1);
|
||||
let instance2 = spyOnLifecycles(view2);
|
||||
let instance3 = spyOnLifecycles(view3);
|
||||
|
||||
nav.setRoot(view2, null, null, trnsDone);
|
||||
|
||||
expect(instance1.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
expect(instance2.ionViewDidLoad).toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanEnter).toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillEnter).toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidEnter).toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillLeave).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidLeave).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillUnload).not.toHaveBeenCalled();
|
||||
|
||||
expect(instance3.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewCanLeave).toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance3.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = true;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView2', 'MockView3', DIRECTION_BACK
|
||||
);
|
||||
expect(nav.length()).toEqual(1);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView2);
|
||||
});
|
||||
|
||||
it('should set a ViewController as the root when its the first view, with transition', () => {
|
||||
let view1 = mockView(MockView1);
|
||||
let view2 = mockView(MockView2);
|
||||
let view3 = mockView(MockView3);
|
||||
mockViews(nav, [view1, view2, view3]);
|
||||
|
||||
let instance1 = spyOnLifecycles(view1);
|
||||
let instance2 = spyOnLifecycles(view2);
|
||||
let instance3 = spyOnLifecycles(view3);
|
||||
|
||||
nav.setRoot(view1, null, null, trnsDone);
|
||||
|
||||
expect(instance1.ionViewDidLoad).toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanEnter).toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillEnter).toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidEnter).toHaveBeenCalled();
|
||||
expect(instance1.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewDidLeave).not.toHaveBeenCalled();
|
||||
expect(instance1.ionViewWillUnload).not.toHaveBeenCalled();
|
||||
|
||||
expect(instance2.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewCanLeave).not.toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance2.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
expect(instance3.ionViewDidLoad).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewCanEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewDidEnter).not.toHaveBeenCalled();
|
||||
expect(instance3.ionViewCanLeave).toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillLeave).toHaveBeenCalled();
|
||||
expect(instance3.ionViewDidLeave).toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = true;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView1', 'MockView3', DIRECTION_BACK
|
||||
);
|
||||
expect(nav.length()).toEqual(1);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView1);
|
||||
});
|
||||
|
||||
it('should set a page component as the root, with transition', () => {
|
||||
let view1 = mockView(MockView1);
|
||||
let view2 = mockView(MockView2);
|
||||
let view3 = mockView(MockView3);
|
||||
mockViews(nav, [view1, view2, view3]);
|
||||
|
||||
let instance1 = spyOnLifecycles(view1);
|
||||
let instance2 = spyOnLifecycles(view2);
|
||||
let instance3 = spyOnLifecycles(view3);
|
||||
|
||||
nav.setRoot(MockView4, null, null, trnsDone);
|
||||
|
||||
expect(instance1.ionViewWillUnload).toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillUnload).toHaveBeenCalled();
|
||||
expect(instance3.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = true;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView4', 'MockView3', DIRECTION_BACK
|
||||
);
|
||||
expect(nav.length()).toEqual(1);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView4);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('setPages', () => {
|
||||
|
||||
it('should set the pages from an array, starting at the root, with transition', () => {
|
||||
let view1 = mockView(MockView1);
|
||||
let view2 = mockView(MockView2);
|
||||
mockViews(nav, [view1, view2]);
|
||||
|
||||
let instance1 = spyOnLifecycles(view1);
|
||||
let instance2 = spyOnLifecycles(view2);
|
||||
|
||||
nav.setPages([{page: MockView4}, {page: MockView5}], null, trnsDone);
|
||||
|
||||
expect(instance1.ionViewWillUnload).toHaveBeenCalled();
|
||||
expect(instance2.ionViewWillUnload).toHaveBeenCalled();
|
||||
|
||||
let hasCompleted = true;
|
||||
let requiresTransition = true;
|
||||
expect(trnsDone).toHaveBeenCalledWith(
|
||||
hasCompleted, requiresTransition, 'MockView5', 'MockView2', DIRECTION_BACK
|
||||
);
|
||||
expect(nav.length()).toEqual(2);
|
||||
expect(nav.getByIndex(0).component).toEqual(MockView4);
|
||||
expect(nav.getByIndex(1).component).toEqual(MockView5);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('_nextTrns', () => {
|
||||
|
||||
it('should not start next transition when already transitioning', () => {
|
||||
nav.setTransitioning(true);
|
||||
expect(nav._nextTrns()).toEqual(false);
|
||||
});
|
||||
|
||||
it('should not start next transition nothing in the queue', () => {
|
||||
expect(nav._nextTrns()).toEqual(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// describe('_cleanup', () => {
|
||||
// it('should destroy views that are inactive after the active view', () => {
|
||||
// let view1 = new ViewController(Page1);
|
||||
// view1.state = STATE_INACTIVE;
|
||||
// let view2 = new ViewController(Page2);
|
||||
// view2.state = STATE_ACTIVE;
|
||||
// let view3 = new ViewController(Page3);
|
||||
// view3.state = STATE_INACTIVE;
|
||||
// let view4 = new ViewController(Page4);
|
||||
// view4.state = STATE_TRANS_ENTER;
|
||||
// let view5 = new ViewController(Page5);
|
||||
// view5.state = STATE_INACTIVE;
|
||||
// nav._views = [view1, view2, view3, view4, view5];
|
||||
// nav._cleanup();
|
||||
|
||||
// expect(nav.length()).toBe(3);
|
||||
// expect(nav.getByIndex(0).state).toBe(STATE_INACTIVE);
|
||||
// expect(nav.getByIndex(0).component).toBe(Page1);
|
||||
// expect(nav.getByIndex(1).state).toBe(STATE_ACTIVE);
|
||||
// expect(nav.getByIndex(1).component).toBe(Page2);
|
||||
// expect(nav.getByIndex(2).state).toBe(STATE_TRANS_ENTER);
|
||||
// expect(nav.getByIndex(2).component).toBe(Page4);
|
||||
// });
|
||||
|
||||
// it('should not destroy any views since the last is active', () => {
|
||||
// let view1 = new ViewController(Page1);
|
||||
// view1.state = STATE_INACTIVE;
|
||||
// let view2 = new ViewController(Page2);
|
||||
// view2.state = STATE_ACTIVE;
|
||||
// nav._views = [view1, view2];
|
||||
// nav._cleanup();
|
||||
// expect(nav.length()).toBe(2);
|
||||
// });
|
||||
|
||||
// it('should call destroy for each view to be destroyed', () => {
|
||||
// let view1 = new ViewController(Page1);
|
||||
// view1.state = STATE_ACTIVE;
|
||||
// let view2 = new ViewController(Page2);
|
||||
// view2.state = STATE_INACTIVE;
|
||||
// let view3 = new ViewController(Page3);
|
||||
// view3.state = STATE_INACTIVE;
|
||||
// nav._views = [view1, view2, view3];
|
||||
|
||||
// spyOn(view1, 'destroy');
|
||||
// spyOn(view2, 'destroy');
|
||||
// spyOn(view3, 'destroy');
|
||||
|
||||
// nav._cleanup();
|
||||
|
||||
// expect(nav.length()).toBe(1);
|
||||
// expect(view1._willUnload).not.toHaveBeenCalled();
|
||||
// expect(view2._willUnload).toHaveBeenCalled();
|
||||
// expect(view3._willUnload).toHaveBeenCalled();
|
||||
// });
|
||||
|
||||
// it('should reset zIndexes if their is a negative zindex', () => {
|
||||
// let view1 = new ViewController(Page1);
|
||||
// view1._setPageElementRef( mockElementRef() );
|
||||
// view1.state = STATE_INACTIVE;
|
||||
// view1._zIndex = -1;
|
||||
|
||||
// let view2 = new ViewController(Page2);
|
||||
// view2._setPageElementRef( mockElementRef() );
|
||||
// view2.state = STATE_INACTIVE;
|
||||
// view2._zIndex = 0;
|
||||
|
||||
// let view3 = new ViewController(Page3);
|
||||
// view3._setPageElementRef( mockElementRef() );
|
||||
// view3.state = STATE_ACTIVE;
|
||||
// view3._zIndex = 1;
|
||||
|
||||
// nav._views = [view1, view2, view3];
|
||||
// nav._cleanup();
|
||||
|
||||
// expect(view1._zIndex).toEqual(100);
|
||||
// expect(view2._zIndex).toEqual(101);
|
||||
// expect(view3._zIndex).toEqual(102);
|
||||
// });
|
||||
// });
|
||||
|
||||
let nav: NavControllerBase;
|
||||
let trnsDone: jasmine.Spy;
|
||||
|
||||
function spyOnLifecycles(view: ViewController) {
|
||||
let instance = view.instance = {
|
||||
ionViewDidLoad: () => {},
|
||||
ionViewCanEnter: () => { return true; },
|
||||
ionViewWillEnter: () => {},
|
||||
ionViewDidEnter: () => {},
|
||||
ionViewCanLeave: () => {},
|
||||
ionViewWillLeave: () => { return true; },
|
||||
ionViewDidLeave: () => {},
|
||||
ionViewWillUnload: () => {},
|
||||
};
|
||||
spyOn(instance, 'ionViewDidLoad');
|
||||
spyOn(instance, 'ionViewCanEnter');
|
||||
spyOn(instance, 'ionViewWillEnter');
|
||||
spyOn(instance, 'ionViewDidEnter');
|
||||
spyOn(instance, 'ionViewCanLeave');
|
||||
spyOn(instance, 'ionViewWillLeave');
|
||||
spyOn(instance, 'ionViewDidLeave');
|
||||
spyOn(instance, 'ionViewWillUnload');
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
trnsDone = jasmine.createSpy('TransitionDone');
|
||||
nav = mockNavController();
|
||||
});
|
||||
|
||||
});
|
||||
129
src/navigation/test/nav-util.spec.ts
Normal file
129
src/navigation/test/nav-util.spec.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { convertToView, convertToViews, DIRECTION_BACK, DIRECTION_FORWARD, setZIndex } from '../nav-util';
|
||||
import { mockDeepLinker, mockNavController, MockView, mockRenderer, mockView, mockViews } from '../../util/mock-providers';
|
||||
import { ViewController } from '../view-controller';
|
||||
|
||||
|
||||
describe('NavUtil', () => {
|
||||
|
||||
describe('convertToViews', () => {
|
||||
|
||||
it('should convert all page components', () => {
|
||||
let linker = mockDeepLinker();
|
||||
let pages = [{ page: MockView }, { page: MockView }, { page: MockView }];
|
||||
let views = convertToViews(linker, pages);
|
||||
expect(views.length).toEqual(3);
|
||||
expect(views[0].component).toEqual(MockView);
|
||||
expect(views[1].component).toEqual(MockView);
|
||||
expect(views[2].component).toEqual(MockView);
|
||||
});
|
||||
|
||||
it('should convert all string names', () => {
|
||||
let linker = mockDeepLinker({
|
||||
links: [{ component: MockView, name: 'someName' }]
|
||||
});
|
||||
let pages = [{ page: 'someName' }, { page: 'someName' }, { page: 'someName' }];
|
||||
let views = convertToViews(linker, pages);
|
||||
expect(views.length).toEqual(3);
|
||||
expect(views[0].component).toEqual(MockView);
|
||||
expect(views[1].component).toEqual(MockView);
|
||||
expect(views[2].component).toEqual(MockView);
|
||||
});
|
||||
|
||||
it('should convert all ViewControllers', () => {
|
||||
let pages = [mockView(MockView), mockView(MockView), mockView(MockView)];
|
||||
let linker = mockDeepLinker();
|
||||
let views = convertToViews(linker, pages);
|
||||
expect(views.length).toEqual(3);
|
||||
expect(views[0].component).toEqual(MockView);
|
||||
expect(views[1].component).toEqual(MockView);
|
||||
expect(views[2].component).toEqual(MockView);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('convertToView', () => {
|
||||
|
||||
it('should return new ViewController instance from page component link config name', () => {
|
||||
let linker = mockDeepLinker({
|
||||
links: [{ component: MockView, name: 'someName' }]
|
||||
});
|
||||
let outputView = convertToView(linker, 'someName', null);
|
||||
expect(outputView.component).toEqual(MockView);
|
||||
});
|
||||
|
||||
it('should return new ViewController instance from page component', () => {
|
||||
let linker = mockDeepLinker();
|
||||
let outputView = convertToView(linker, MockView, null);
|
||||
expect(outputView.component).toEqual(MockView);
|
||||
});
|
||||
|
||||
it('should return existing ViewController instance', () => {
|
||||
let linker = mockDeepLinker();
|
||||
let inputView = new ViewController(MockView);
|
||||
let outputView = convertToView(linker, inputView, null);
|
||||
expect(outputView).toEqual(inputView);
|
||||
});
|
||||
|
||||
it('should return null for null/undefined/number', () => {
|
||||
let linker = mockDeepLinker();
|
||||
let outputView = convertToView(linker, null, null);
|
||||
expect(outputView).toEqual(null);
|
||||
|
||||
outputView = convertToView(linker, undefined, undefined);
|
||||
expect(outputView).toEqual(null);
|
||||
|
||||
outputView = convertToView(linker, 8675309, null);
|
||||
expect(outputView).toEqual(null);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('setZIndex', () => {
|
||||
|
||||
it('should set zIndex 100 when leaving view doesnt have a zIndex', () => {
|
||||
let leavingView = mockView();
|
||||
let enteringView = mockView();
|
||||
|
||||
let nav = mockNavController();
|
||||
mockViews(nav, [leavingView, enteringView]);
|
||||
|
||||
setZIndex(nav, enteringView, leavingView, DIRECTION_FORWARD, mockRenderer());
|
||||
expect(enteringView._zIndex).toEqual(100);
|
||||
});
|
||||
|
||||
it('should set zIndex 100 on first entering view', () => {
|
||||
let enteringView = mockView();
|
||||
let nav = mockNavController();
|
||||
setZIndex(nav, enteringView, null, DIRECTION_FORWARD, mockRenderer());
|
||||
expect(enteringView._zIndex).toEqual(100);
|
||||
});
|
||||
|
||||
it('should set zIndex 101 on second entering view', () => {
|
||||
let leavingView = mockView();
|
||||
leavingView._zIndex = 100;
|
||||
let enteringView = mockView();
|
||||
let nav = mockNavController();
|
||||
setZIndex(nav, enteringView, leavingView, DIRECTION_FORWARD, mockRenderer());
|
||||
expect(enteringView._zIndex).toEqual(101);
|
||||
});
|
||||
|
||||
it('should set zIndex 100 on entering view going back', () => {
|
||||
let leavingView = mockView();
|
||||
leavingView._zIndex = 101;
|
||||
let enteringView = mockView();
|
||||
let nav = mockNavController();
|
||||
setZIndex(nav, enteringView, leavingView, DIRECTION_BACK, mockRenderer());
|
||||
expect(enteringView._zIndex).toEqual(100);
|
||||
});
|
||||
|
||||
it('should set zIndex 9999 on first entering portal view', () => {
|
||||
let enteringView = mockView();
|
||||
let nav = mockNavController();
|
||||
nav._isPortal = true;
|
||||
setZIndex(nav, enteringView, null, DIRECTION_FORWARD, mockRenderer());
|
||||
expect(enteringView._zIndex).toEqual(9999);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
98
src/navigation/test/view-controller.spec.ts
Normal file
98
src/navigation/test/view-controller.spec.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { mockView } from '../../util/mock-providers';
|
||||
|
||||
|
||||
describe('ViewController', () => {
|
||||
|
||||
describe('willEnter', () => {
|
||||
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||
// arrange
|
||||
let viewController = mockView();
|
||||
subscription = viewController.willEnter.subscribe((event) => {
|
||||
// assert
|
||||
expect(event).toEqual(null);
|
||||
done();
|
||||
}, (err: any) => {
|
||||
done();
|
||||
});
|
||||
|
||||
// act
|
||||
viewController._willEnter();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('didEnter', () => {
|
||||
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||
// arrange
|
||||
let viewController = mockView();
|
||||
subscription = viewController.didEnter.subscribe((event) => {
|
||||
// assert
|
||||
expect(event).toEqual(null);
|
||||
done();
|
||||
}, (err: any) => {
|
||||
done();
|
||||
});
|
||||
|
||||
// act
|
||||
viewController._didEnter();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('willLeave', () => {
|
||||
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||
// arrange
|
||||
let viewController = mockView();
|
||||
subscription = viewController.willLeave.subscribe((event) => {
|
||||
// assert
|
||||
expect(event).toEqual(null);
|
||||
done();
|
||||
}, (err: any) => {
|
||||
done();
|
||||
});
|
||||
|
||||
// act
|
||||
viewController._willLeave();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('didLeave', () => {
|
||||
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||
// arrange
|
||||
let viewController = mockView();
|
||||
subscription = viewController.didLeave.subscribe((event) => {
|
||||
// assert
|
||||
expect(event).toEqual(null);
|
||||
done();
|
||||
}, (err: any) => {
|
||||
done();
|
||||
});
|
||||
|
||||
// act
|
||||
viewController._didLeave();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('willUnload', () => {
|
||||
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||
// arrange
|
||||
let viewController = mockView();
|
||||
subscription = viewController.willUnload.subscribe((event) => {
|
||||
expect(event).toEqual(null);
|
||||
done();
|
||||
}, (err: any) => {
|
||||
done();
|
||||
});
|
||||
|
||||
// act
|
||||
viewController._willUnload();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
let subscription: any = null;
|
||||
|
||||
});
|
||||
@@ -1,10 +1,10 @@
|
||||
import { ChangeDetectorRef, ElementRef, EventEmitter, Output, Renderer } from '@angular/core';
|
||||
import { ComponentRef, ElementRef, EventEmitter, Output, Renderer } from '@angular/core';
|
||||
|
||||
import { Footer, Header } from '../toolbar/toolbar';
|
||||
import { isPresent, merge } from '../../util/util';
|
||||
import { Navbar } from '../navbar/navbar';
|
||||
import { NavController } from './nav-controller';
|
||||
import { NavOptions } from './nav-interfaces';
|
||||
import { Footer, Header } from '../components/toolbar/toolbar';
|
||||
import { isPresent, merge } from '../util/util';
|
||||
import { Navbar } from '../components/navbar/navbar';
|
||||
import { NavControllerBase } from './nav-controller-base';
|
||||
import { NavOptions, ViewState } from './nav-util';
|
||||
import { NavParams } from './nav-params';
|
||||
|
||||
|
||||
@@ -28,19 +28,14 @@ import { NavParams } from './nav-params';
|
||||
export class ViewController {
|
||||
private _cntDir: any;
|
||||
private _cntRef: ElementRef;
|
||||
private _tbRefs: ElementRef[] = [];
|
||||
private _hdrDir: Header;
|
||||
private _ftrDir: Footer;
|
||||
private _destroyFn: Function;
|
||||
private _hdAttr: string = null;
|
||||
private _leavingOpts: NavOptions = null;
|
||||
private _loaded: boolean = false;
|
||||
private _nbDir: Navbar;
|
||||
private _onDidDismiss: Function = null;
|
||||
private _onWillDismiss: Function = null;
|
||||
private _pgRef: ElementRef;
|
||||
private _cd: ChangeDetectorRef;
|
||||
protected _nav: NavController;
|
||||
private _hidden: string;
|
||||
private _leavingOpts: NavOptions;
|
||||
private _nb: Navbar;
|
||||
private _onDidDismiss: Function;
|
||||
private _onWillDismiss: Function;
|
||||
private _detached: boolean;
|
||||
|
||||
/**
|
||||
* Observable to be subscribed to when the current component will become active
|
||||
@@ -67,69 +62,75 @@ export class ViewController {
|
||||
didLeave: EventEmitter<any>;
|
||||
|
||||
/**
|
||||
* Observable to be subscribed to when the current component will be destroyed
|
||||
* Observable to be subscribed to when the current component has been destroyed
|
||||
* @returns {Observable} Returns an observable
|
||||
*/
|
||||
willUnload: EventEmitter<any>;
|
||||
|
||||
/**
|
||||
* Observable to be subscribed to when the current component has been destroyed
|
||||
* @returns {Observable} Returns an observable
|
||||
*/
|
||||
didUnload: EventEmitter<any>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
/** @private */
|
||||
data: any;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
/** @private */
|
||||
instance: any;
|
||||
|
||||
/** @private */
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
instance: any = {};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
state: number = 0;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* If this is currently the active view, then set to false
|
||||
* if it does not want the other views to fire their own lifecycles.
|
||||
*/
|
||||
fireOtherLifecycles: boolean = true;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
/** @private */
|
||||
isOverlay: boolean = false;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
zIndex: number;
|
||||
/** @private */
|
||||
_cmp: ComponentRef<any>;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
/** @private */
|
||||
_nav: NavControllerBase;
|
||||
|
||||
/** @private */
|
||||
_zIndex: number;
|
||||
|
||||
/** @private */
|
||||
_state: ViewState;
|
||||
|
||||
/** @private */
|
||||
_cssClass: string;
|
||||
|
||||
/** @private */
|
||||
@Output() private _emitter: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
constructor(public componentType?: any, data?: any) {
|
||||
constructor(public component?: any, data?: any, rootCssClass: string = DEFAULT_CSS_CLASS) {
|
||||
// passed in data could be NavParams, but all we care about is its data object
|
||||
this.data = (data instanceof NavParams ? data.data : (isPresent(data) ? data : {}));
|
||||
|
||||
this._cssClass = rootCssClass;
|
||||
|
||||
this.willEnter = new EventEmitter();
|
||||
this.didEnter = new EventEmitter();
|
||||
this.willLeave = new EventEmitter();
|
||||
this.didLeave = new EventEmitter();
|
||||
this.willUnload = new EventEmitter();
|
||||
this.didUnload = new EventEmitter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
init(componentRef: ComponentRef<any>) {
|
||||
this._cmp = componentRef;
|
||||
this.instance = this.instance || componentRef.instance;
|
||||
this._detached = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_setNav(navCtrl: NavControllerBase) {
|
||||
this._nav = navCtrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_setInstance(instance: any) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,28 +151,28 @@ export class ViewController {
|
||||
* @private
|
||||
* onDismiss(..) has been deprecated. Please use onDidDismiss(..) instead
|
||||
*/
|
||||
private onDismiss(callback: Function) {
|
||||
onDismiss(callback: Function) {
|
||||
// deprecated warning: added beta.11 2016-06-30
|
||||
console.warn('onDismiss(..) has been deprecated. Please use onDidDismiss(..) instead');
|
||||
this.onDidDismiss(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* onDidDismiss
|
||||
*/
|
||||
onDidDismiss(callback: Function) {
|
||||
this._onDidDismiss = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* onWillDismiss
|
||||
*/
|
||||
onWillDismiss(callback: Function) {
|
||||
this._onWillDismiss = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* dismiss
|
||||
*/
|
||||
dismiss(data?: any, role?: any, navOptions: NavOptions = {}) {
|
||||
let options = merge({}, this._leavingOpts, navOptions);
|
||||
@@ -182,13 +183,6 @@ export class ViewController {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setNav(navCtrl: NavController) {
|
||||
this._nav = navCtrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@@ -233,25 +227,11 @@ export class ViewController {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setChangeDetector(cd: ChangeDetectorRef) {
|
||||
this._cd = cd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setInstance(instance: any) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
get name(): string {
|
||||
return this.componentType ? this.componentType['name'] : '';
|
||||
return this.component ? this.component.name : '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,57 +258,68 @@ export class ViewController {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* DOM WRITE
|
||||
*/
|
||||
domShow(shouldShow: boolean, renderer: Renderer) {
|
||||
_domShow(shouldShow: boolean, renderer: Renderer) {
|
||||
// using hidden element attribute to display:none and not render views
|
||||
// renderAttr of '' means the hidden attribute will be added
|
||||
// renderAttr of null means the hidden attribute will be removed
|
||||
// doing checks to make sure we only make an update to the element when needed
|
||||
if (this._pgRef &&
|
||||
(shouldShow && this._hdAttr === '' ||
|
||||
!shouldShow && this._hdAttr !== '')) {
|
||||
|
||||
this._hdAttr = (shouldShow ? null : '');
|
||||
|
||||
renderer.setElementAttribute(this._pgRef.nativeElement, 'hidden', this._hdAttr);
|
||||
// _hidden value of '' means the hidden attribute will be added
|
||||
// _hidden value of null means the hidden attribute will be removed
|
||||
// doing checks to make sure we only update the DOM when actually needed
|
||||
if (this._cmp) {
|
||||
// if it should render, then the hidden attribute should not be on the element
|
||||
if (shouldShow && this._hidden === '' || !shouldShow && this._hidden !== '') {
|
||||
this._hidden = (shouldShow ? null : '');
|
||||
// ******** DOM WRITE ****************
|
||||
renderer.setElementAttribute(this.pageRef().nativeElement, 'hidden', this._hidden);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* DOM WRITE
|
||||
*/
|
||||
setZIndex(zIndex: number, renderer: Renderer) {
|
||||
if (this._pgRef && zIndex !== this.zIndex) {
|
||||
this.zIndex = zIndex;
|
||||
renderer.setElementStyle(this._pgRef.nativeElement, 'z-index', zIndex.toString());
|
||||
_setZIndex(zIndex: number, renderer: Renderer) {
|
||||
if (zIndex !== this._zIndex) {
|
||||
this._zIndex = zIndex;
|
||||
const pageRef = this.pageRef();
|
||||
if (pageRef) {
|
||||
// ******** DOM WRITE ****************
|
||||
renderer.setElementStyle(pageRef.nativeElement, 'z-index', (<any>zIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setPageRef(elementRef: ElementRef) {
|
||||
this._pgRef = elementRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {elementRef} Returns the Page's ElementRef
|
||||
* @returns {ElementRef} Returns the Page's ElementRef.
|
||||
*/
|
||||
pageRef(): ElementRef {
|
||||
return this._pgRef;
|
||||
return this._cmp && this._cmp.location;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setContentRef(elementRef: ElementRef) {
|
||||
_setContent(directive: any) {
|
||||
this._cntDir = directive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {component} Returns the Page's Content component reference.
|
||||
*/
|
||||
getContent() {
|
||||
return this._cntDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_setContentRef(elementRef: ElementRef) {
|
||||
this._cntRef = elementRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {elementRef} Returns the Page's Content ElementRef
|
||||
* @returns {ElementRef} Returns the Content's ElementRef.
|
||||
*/
|
||||
contentRef(): ElementRef {
|
||||
return this._cntRef;
|
||||
@@ -337,28 +328,7 @@ export class ViewController {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setContent(directive: any) {
|
||||
this._cntDir = directive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setToolbarRef(elementRef: ElementRef) {
|
||||
this._tbRefs.push(elementRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
toolbarRefs(): ElementRef[] {
|
||||
return this._tbRefs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setHeader(directive: Header) {
|
||||
_setHeader(directive: Header) {
|
||||
this._hdrDir = directive;
|
||||
}
|
||||
|
||||
@@ -372,7 +342,7 @@ export class ViewController {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setFooter(directive: Footer) {
|
||||
_setFooter(directive: Footer) {
|
||||
this._ftrDir = directive;
|
||||
}
|
||||
|
||||
@@ -385,83 +355,26 @@ export class ViewController {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {component} Returns the Page's Content component reference.
|
||||
*/
|
||||
getContent() {
|
||||
return this._cntDir;
|
||||
_setNavbar(directive: Navbar) {
|
||||
this._nb = directive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setNavbar(directive: Navbar) {
|
||||
this._nbDir = directive;
|
||||
getNavbar(): Navbar {
|
||||
return this._nb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getNavbar() {
|
||||
return this._nbDir;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Find out if the current component has a NavBar or not. Be sure
|
||||
* to wrap this in an `ionViewWillEnter` method in order to make sure
|
||||
* the view has rendered fully.
|
||||
* @returns {boolean} Returns a boolean if this Page has a navbar or not.
|
||||
*/
|
||||
hasNavbar(): boolean {
|
||||
return !!this.getNavbar();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
navbarRef(): ElementRef {
|
||||
let navbar = this.getNavbar();
|
||||
return navbar && navbar.getElementRef();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
titleRef(): ElementRef {
|
||||
let navbar = this.getNavbar();
|
||||
return navbar && navbar.getTitleRef();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
navbarItemRefs(): Array<ElementRef> {
|
||||
let navbar = this.getNavbar();
|
||||
return navbar && navbar.getItemRefs();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
backBtnRef(): ElementRef {
|
||||
let navbar = this.getNavbar();
|
||||
return navbar && navbar.getBackButtonRef();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
backBtnTextRef(): ElementRef {
|
||||
let navbar = this.getNavbar();
|
||||
return navbar && navbar.getBackButtonTextRef();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
navbarBgRef(): ElementRef {
|
||||
let navbar = this.getNavbar();
|
||||
return navbar && navbar.getBackgroundRef();
|
||||
return !!this._nb;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -470,10 +383,7 @@ export class ViewController {
|
||||
* @param {string} backButtonText Set the back button text.
|
||||
*/
|
||||
setBackButtonText(val: string) {
|
||||
let navbar = this.getNavbar();
|
||||
if (navbar) {
|
||||
navbar.setBackButtonText(val);
|
||||
}
|
||||
this._nb && this._nb.setBackButtonText(val);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -482,31 +392,11 @@ export class ViewController {
|
||||
* @param {boolean} Set if this Page's back button should show or not.
|
||||
*/
|
||||
showBackButton(shouldShow: boolean) {
|
||||
let navbar = this.getNavbar();
|
||||
if (navbar) {
|
||||
navbar.hideBackButton = !shouldShow;
|
||||
if (this._nb) {
|
||||
this._nb.hideBackButton = !shouldShow;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
isLoaded(): boolean {
|
||||
return this._loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* The loaded method is used to load any dynamic content/components
|
||||
* into the dom before proceeding with the transition. If a component
|
||||
* needs dynamic component loading, extending ViewController and
|
||||
* overriding this method is a good option
|
||||
* @param {function} done is a callback that must be called when async
|
||||
* loading/actions are completed
|
||||
*/
|
||||
loaded(done: (() => any)) {
|
||||
done();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* The view has loaded. This event only happens once per view being
|
||||
@@ -515,23 +405,31 @@ export class ViewController {
|
||||
* to put your setup code for the view; however, it is not the
|
||||
* recommended method to use when a view becomes active.
|
||||
*/
|
||||
fireLoaded() {
|
||||
this._loaded = true;
|
||||
ctrlFn(this, 'Loaded');
|
||||
_didLoad() {
|
||||
// deprecated warning: added 2016-08-14, beta.12
|
||||
if (this.instance && this.instance.ionViewLoaded) {
|
||||
try {
|
||||
console.warn('ionViewLoaded() has been deprecated. Please rename to ionViewDidLoad()');
|
||||
this.instance.ionViewLoaded();
|
||||
} catch (e) {
|
||||
console.error(this.name + ' iionViewLoaded: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
ctrlFn(this, 'DidLoad');
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* The view is about to enter and become the active view.
|
||||
*/
|
||||
fireWillEnter() {
|
||||
if (this._cd) {
|
||||
_willEnter() {
|
||||
if (this._detached && this._cmp) {
|
||||
// ensure this has been re-attached to the change detector
|
||||
this._cd.reattach();
|
||||
|
||||
// detect changes before we run any user code
|
||||
this._cd.detectChanges();
|
||||
this._cmp.changeDetectorRef.reattach();
|
||||
this._detached = false;
|
||||
}
|
||||
|
||||
this.willEnter.emit(null);
|
||||
ctrlFn(this, 'WillEnter');
|
||||
}
|
||||
@@ -541,9 +439,8 @@ export class ViewController {
|
||||
* The view has fully entered and is now the active view. This
|
||||
* will fire, whether it was the first load or loaded from the cache.
|
||||
*/
|
||||
fireDidEnter() {
|
||||
let navbar = this.getNavbar();
|
||||
navbar && navbar.didEnter();
|
||||
_didEnter() {
|
||||
this._nb && this._nb.didEnter();
|
||||
this.didEnter.emit(null);
|
||||
ctrlFn(this, 'DidEnter');
|
||||
}
|
||||
@@ -552,7 +449,7 @@ export class ViewController {
|
||||
* @private
|
||||
* The view has is about to leave and no longer be the active view.
|
||||
*/
|
||||
fireWillLeave() {
|
||||
_willLeave() {
|
||||
this.willLeave.emit(null);
|
||||
ctrlFn(this, 'WillLeave');
|
||||
}
|
||||
@@ -562,60 +459,87 @@ export class ViewController {
|
||||
* The view has finished leaving and is no longer the active view. This
|
||||
* will fire, whether it is cached or unloaded.
|
||||
*/
|
||||
fireDidLeave() {
|
||||
_didLeave() {
|
||||
this.didLeave.emit(null);
|
||||
ctrlFn(this, 'DidLeave');
|
||||
|
||||
// when this is not the active page
|
||||
// we no longer need to detect changes
|
||||
this._cd && this._cd.detach();
|
||||
if (!this._detached && this._cmp) {
|
||||
this._cmp.changeDetectorRef.detach();
|
||||
this._detached = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* The view is about to be destroyed and have its elements removed.
|
||||
*/
|
||||
fireWillUnload() {
|
||||
_willUnload() {
|
||||
this.willUnload.emit(null);
|
||||
ctrlFn(this, 'WillUnload');
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onDestroy(destroyFn: Function) {
|
||||
this._destroyFn = destroyFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
destroy() {
|
||||
this.didUnload.emit(null);
|
||||
ctrlFn(this, 'DidUnload');
|
||||
|
||||
this._destroyFn && this._destroyFn();
|
||||
this._destroyFn = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface LifeCycleEvent {
|
||||
componentType?: any;
|
||||
}
|
||||
|
||||
function ctrlFn(viewCtrl: ViewController, fnName: string) {
|
||||
if (viewCtrl.instance) {
|
||||
// deprecated warning: added 2016-06-01, beta.8
|
||||
if (viewCtrl.instance['onPage' + fnName]) {
|
||||
// deprecated warning: added 2016-08-14, beta.12
|
||||
if (this.instance && this.instance.ionViewDidUnload) {
|
||||
console.warn('ionViewDidUnload() has been deprecated. Please use ionViewWillUnload() instead');
|
||||
try {
|
||||
console.warn('onPage' + fnName + '() has been deprecated. Please rename to ionView' + fnName + '()');
|
||||
viewCtrl.instance['onPage' + fnName]();
|
||||
this.instance.ionViewDidUnload();
|
||||
} catch (e) {
|
||||
console.error(viewCtrl.name + ' onPage' + fnName + ': ' + e.message);
|
||||
console.error(this.name + ' ionViewDidUnload: ' + e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* DOM WRITE
|
||||
*/
|
||||
_destroy(renderer: Renderer) {
|
||||
if (this._cmp) {
|
||||
if (renderer) {
|
||||
// ensure the element is cleaned up for when the view pool reuses this element
|
||||
// ******** DOM WRITE ****************
|
||||
renderer.setElementAttribute(this._cmp.location.nativeElement, 'class', null);
|
||||
renderer.setElementAttribute(this._cmp.location.nativeElement, 'style', null);
|
||||
}
|
||||
|
||||
// completely destroy this component. boom.
|
||||
this._cmp.destroy();
|
||||
}
|
||||
|
||||
if (this._nav) {
|
||||
// remove it from the nav
|
||||
const index = this._nav.indexOf(this);
|
||||
if (index > -1) {
|
||||
this._nav._views.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
this._nav = this._cmp = this.instance = this._cntDir = this._cntRef = this._hdrDir = this._ftrDir = this._nb = this._onDidDismiss = this._onWillDismiss = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_lifecycleTest(lifecycle: string): boolean | string | Promise<any> {
|
||||
let result: any = true;
|
||||
|
||||
if (this.instance && this.instance['ionViewCan' + lifecycle]) {
|
||||
try {
|
||||
result = this.instance['ionViewCan' + lifecycle]();
|
||||
|
||||
} catch (e) {
|
||||
console.error(`${this.name} ionViewCan${lifecycle} error: ${e}`);
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function ctrlFn(viewCtrl: ViewController, fnName: string) {
|
||||
if (viewCtrl.instance) {
|
||||
// fire off ionView lifecycle instance method
|
||||
if (viewCtrl.instance['ionView' + fnName]) {
|
||||
try {
|
||||
@@ -626,3 +550,10 @@ function ctrlFn(viewCtrl: ViewController, fnName: string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function isViewController(viewCtrl: any) {
|
||||
return !!(viewCtrl && (<ViewController>viewCtrl)._didLoad && (<ViewController>viewCtrl)._willUnload);
|
||||
}
|
||||
|
||||
const DEFAULT_CSS_CLASS = 'ion-page';
|
||||
Reference in New Issue
Block a user