{
// _isPrevented is used to prevent unwanted opening/closing after swiping open/close
// or swiping open the menu while pressing down on the MenuToggle button
if ((shouldOpen && this.isOpen) || this._isPrevented()) {
@@ -428,7 +423,7 @@ export class Menu extends Ion {
this._before();
return new Promise(resolve => {
- this._getType().setOpen(shouldOpen, () => {
+ this._getType().setOpen(shouldOpen, animated, () => {
this._after(shouldOpen);
resolve(this.isOpen);
});
@@ -519,21 +514,6 @@ export class Menu extends Ion {
}
}
- /**
- * @private
- */
- tempDisable(temporarilyDisable: boolean) {
- if (temporarilyDisable) {
- this._prevEnabled = this._isEnabled;
- this._getType().setProgessStep(0);
- this.enable(false);
-
- } else {
- this.enable(this._prevEnabled);
- this._after(false);
- }
- }
-
private _prevent() {
// used to prevent unwanted opening/closing after swiping open/close
// or swiping open the menu while pressing down on the MenuToggle
@@ -617,13 +597,19 @@ export class Menu extends Ion {
return this.backdrop.getNativeElement();
}
+ /**
+ * @private
+ */
+ getMenuController(): MenuController {
+ return this._menuCtrl;
+ }
+
/**
* @private
*/
ngOnDestroy() {
this._menuCtrl.unregister(this);
this._cntGesture && this._cntGesture.destroy();
- this._menuGesture && this._menuGesture.destroy();
this._type && this._type.destroy();
this._resizeUnreg && this._resizeUnreg();
this._cntEle = null;
diff --git a/src/components/menu/test/basic/index.ts b/src/components/menu/test/basic/index.ts
index f54f20fb58..43e823f4df 100644
--- a/src/components/menu/test/basic/index.ts
+++ b/src/components/menu/test/basic/index.ts
@@ -67,6 +67,14 @@ class E2EPage {
});
}
+ openRightMenu() {
+ this.menu.open('right');
+ }
+
+ openLeftMenu() {
+ this.menu.open('left');
+ }
+
onDrag(ev: any) {
console.log('Menu is being dragged', ev);
}
diff --git a/src/components/menu/test/basic/main.html b/src/components/menu/test/basic/main.html
index 3ce5cb6838..aa07376973 100644
--- a/src/components/menu/test/basic/main.html
+++ b/src/components/menu/test/basic/main.html
@@ -14,6 +14,10 @@
{{p.title}}
+
+
@@ -90,6 +94,10 @@
{{p.title}}
+
+
@@ -148,6 +156,6 @@
-
+
diff --git a/src/components/menu/test/basic/page1.html b/src/components/menu/test/basic/page1.html
index 036d3d4b6a..ae6905f827 100644
--- a/src/components/menu/test/basic/page1.html
+++ b/src/components/menu/test/basic/page1.html
@@ -35,9 +35,19 @@
Page 1
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/modal/test/basic/index.ts b/src/components/modal/test/basic/index.ts
index 141c5d54e7..d14b32d029 100644
--- a/src/components/modal/test/basic/index.ts
+++ b/src/components/modal/test/basic/index.ts
@@ -1,12 +1,12 @@
import { Component, Injectable } from '@angular/core';
-import { ActionSheetController, Config, ionicBootstrap, ModalController, NavController, NavParams, PageTransition, Platform, TransitionOptions, ViewController } from '../../../../../src';
+import { ActionSheetController, App, Config, ionicBootstrap, ModalController, NavController, NavParams, PageTransition, Platform, TransitionOptions, ViewController } from '../../../../../src';
@Injectable()
class SomeComponentProvider {
constructor(private config: Config) {
- console.log('SomeComponentProvider constructor')
+ console.log('SomeComponentProvider constructor');
}
getName() {
@@ -17,7 +17,7 @@ class SomeComponentProvider {
@Injectable()
class SomeAppProvider {
constructor(private config: Config) {
- console.log('SomeAppProvider constructor')
+ console.log('SomeAppProvider constructor');
}
getData() {
@@ -84,7 +84,7 @@ class E2EPage {
}
presentModalWithInputs() {
- let modal = this.modalCtrl.create(ModalWithInputs);
+ let modal = this.modalCtrl.create(ModalWithInputs);
modal.onDidDismiss((data: any) => {
console.log('Modal with inputs data:', data);
});
@@ -98,7 +98,7 @@ class E2EPage {
});
}
- presentNavigableModal(){
+ presentNavigableModal() {
this.modalCtrl.create(NavigableModal).present();
}
}
@@ -117,11 +117,11 @@ class E2EPage {
`
})
class NavigableModal {
- constructor(private navController:NavController) {
+ constructor(private nav: NavController) {
}
submit(){
- this.navController.push(NavigableModal2);
+ this.nav.push(NavigableModal2);
}
}
@@ -139,10 +139,10 @@ class NavigableModal {
`
})
class NavigableModal2 {
- constructor(private navController:NavController) {
+ constructor(private navController: NavController) {
}
- submit(){
+ submit() {
this.navController.pop();
}
}
@@ -188,23 +188,23 @@ class ModalPassData {
this.viewCtrl.dismiss(this.data);
}
- ionViewLoaded(){
+ ionViewLoaded() {
console.log('ModalPassData ionViewLoaded fired');
}
- ionViewWillEnter(){
+ ionViewWillEnter() {
console.log('ModalPassData ionViewWillEnter fired');
}
- ionViewDidEnter(){
+ ionViewDidEnter() {
console.log('ModalPassData ionViewDidEnter fired');
}
- ionViewWillLeave(){
+ ionViewWillLeave() {
console.log('ModalPassData ionViewWillLeave fired');
}
- ionViewDidLeave(){
+ ionViewDidLeave() {
console.log('ModalPassData ionViewDidLeave fired');
}
}
@@ -375,10 +375,10 @@ class ContactUs {
})
class ModalFirstPage {
- private items:any[];
- constructor(private nav: NavController, private actionSheetCtrl: ActionSheetController) {
+ private items: any[];
+ constructor(private nav: NavController, private app: App, private actionSheetCtrl: ActionSheetController) {
this.items = [];
- for ( let i = 0; i < 50; i++ ){
+ for ( let i = 0; i < 50; i++ ) {
this.items.push({
value: (i + 1)
});
@@ -387,24 +387,24 @@ class ModalFirstPage {
push() {
let page = ModalSecondPage;
- let params = { id: 8675309, myData: [1,2,3,4] };
+ let params = { id: 8675309, myData: [1, 2, 3, 4] };
this.nav.push(page, params);
}
dismiss() {
- this.nav.rootNav.pop();
+ this.app.getRootNav().pop();
}
- ionViewLoaded(){
+ ionViewLoaded() {
console.log('ModalFirstPage ionViewLoaded fired');
}
- ionViewWillEnter(){
+ ionViewWillEnter() {
console.log('ModalFirstPage ionViewWillEnter fired');
}
- ionViewDidEnter(){
+ ionViewDidEnter() {
console.log('ModalFirstPage ionViewDidEnter fired');
}
@@ -430,8 +430,8 @@ class ModalFirstPage {
// overlays are added and removed from the root navigation
// find the root navigation, and pop this alert
// when the alert is done animating out, then pop off the modal
- this.nav.rootNav.pop().then(() => {
- this.nav.rootNav.pop();
+ this.app.getRootNav().pop().then(() => {
+ this.app.getRootNav().pop();
});
// by default an alert will dismiss itself
@@ -477,15 +477,15 @@ class ModalSecondPage {
console.log('Second page params:', params);
}
- ionViewLoaded(){
+ ionViewLoaded() {
console.log('ModalSecondPage ionViewLoaded');
}
- ionViewWillEnter(){
+ ionViewWillEnter() {
console.log('ModalSecondPage ionViewWillEnter');
}
- ionViewDidEnter(){
+ ionViewDidEnter() {
console.log('ModalSecondPage ionViewDidEnter');
}
}
diff --git a/src/components/nav/nav-controller-base.ts b/src/components/nav/nav-controller-base.ts
new file mode 100644
index 0000000000..5f68c8dafc
--- /dev/null
+++ b/src/components/nav/nav-controller-base.ts
@@ -0,0 +1,1303 @@
+import { ComponentResolver, ElementRef, EventEmitter, NgZone, provide, ReflectiveInjector, Renderer, ViewContainerRef } from '@angular/core';
+
+import { addSelector } from '../../config/bootstrap';
+import { App } from '../app/app';
+import { Config } from '../../config/config';
+import { GestureController } from '../../gestures/gesture-controller';
+import { Ion } from '../ion';
+import { isBlank, isPresent, pascalCaseToDashCase } from '../../util/util';
+import { Keyboard } from '../../util/keyboard';
+import { NavController } from './nav-controller';
+import { NavOptions, DIRECTION_BACK, DIRECTION_FORWARD } from './nav-interfaces';
+import { NavParams } from './nav-params';
+import { SwipeBackGesture } from './swipe-back';
+import { Transition } from '../../transitions/transition';
+import { ViewController } from './view-controller';
+
+
+/**
+ * This class is for internal use only. It is not exported publicly.
+ */
+export class NavControllerBase extends Ion implements NavController {
+ _transIds = 0;
+ _init = false;
+ _isPortal: boolean;
+ _trans: Transition;
+ _sbGesture: SwipeBackGesture;
+ _sbThreshold: number;
+ _viewport: ViewContainerRef;
+ _children: any[] = [];
+ _sbEnabled: boolean;
+ _ids: number = -1;
+ _trnsDelay: any;
+ _views: ViewController[] = [];
+
+ viewDidLoad: EventEmitter;
+ viewWillEnter: EventEmitter;
+ viewDidEnter: EventEmitter;
+ viewWillLeave: EventEmitter;
+ viewDidLeave: EventEmitter;
+ viewWillUnload: EventEmitter;
+ viewDidUnload: EventEmitter;
+
+ id: string;
+ parent: any;
+ config: Config;
+ trnsTime: number = 0;
+
+ constructor(
+ parent: any,
+ public _app: App,
+ config: Config,
+ public _keyboard: Keyboard,
+ elementRef: ElementRef,
+ public _zone: NgZone,
+ public _renderer: Renderer,
+ public _compiler: ComponentResolver,
+ public _gestureCtrl: GestureController
+ ) {
+ super(elementRef);
+
+ this.parent = parent;
+ this.config = config;
+
+ this._trnsDelay = config.get('pageTransitionDelay');
+
+ 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();
+ this.viewDidUnload = new EventEmitter();
+ }
+
+ setViewport(val: ViewContainerRef) {
+ this._viewport = val;
+ }
+
+ setRoot(page: any, params?: any, opts?: NavOptions): Promise {
+ return this.setPages([{page, params}], opts);
+ }
+
+ setPages(pages: Array<{page: any, params?: any}>, opts?: NavOptions): Promise {
+ if (!pages || !pages.length) {
+ return Promise.resolve(false);
+ }
+
+ if (isBlank(opts)) {
+ opts = {};
+ }
+
+ // remove existing views
+ let leavingView = this._remove(0, this._views.length);
+
+ // create view controllers out of the pages and insert the new views
+ let views = pages.map(p => new ViewController(p.page, p.params));
+ let enteringView = this._insert(0, views);
+
+ // if animation wasn't set to true then default it to NOT animate
+ if (opts.animate !== true) {
+ opts.animate = false;
+ }
+
+ // set the nav direction to "back" if it wasn't set
+ opts.direction = opts.direction || DIRECTION_BACK;
+
+ let resolve: any;
+ let promise = new Promise(res => { resolve = res; });
+
+ // start the transition, fire resolve when done...
+ this._transition(enteringView, leavingView, opts, (hasCompleted: boolean) => {
+ // transition has completed!!
+ resolve(hasCompleted);
+ });
+
+ return promise;
+ }
+
+ push(page: any, params?: any, opts?: NavOptions, done?: Function): Promise {
+ return this.insertPages(-1, [{page: page, params: params}], opts, done);
+ }
+
+ /**
+ * DEPRECATED: Please use inject the overlays controller and use the present method on the instance instead.
+ */
+ private present(enteringView: ViewController, opts?: NavOptions): Promise {
+ // 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();
+ }
+
+ insert(insertIndex: number, page: any, params?: any, opts?: NavOptions, done?: Function): Promise {
+ return this.insertPages(insertIndex, [{page: page, params: params}], opts, done);
+ }
+
+ insertPages(insertIndex: number, insertPages: Array<{page: any, params?: any}>, opts?: NavOptions, done?: Function): Promise {
+ let views = insertPages.map(p => new ViewController(p.page, p.params));
+ return this.insertViews(insertIndex, views, opts, done);
+ }
+
+ insertViews(insertIndex: number, insertViews: ViewController[], opts: NavOptions = {}, done?: Function) {
+ let promise: Promise;
+ if (!done) {
+ // only create a promise if a done callback wasn't provided
+ promise = new Promise(res => { done = res; });
+ }
+
+ if (!insertViews || !insertViews.length) {
+ done(false);
+ return promise;
+ }
+
+ if (isBlank(opts)) {
+ opts = {};
+ }
+
+ // insert the new page into the stack
+ // returns the newly created entering view
+ let enteringView = this._insert(insertIndex, insertViews);
+
+ // manually set the new view's id if an id was passed in the options
+ if (isPresent(opts.id)) {
+ enteringView.id = opts.id;
+ }
+
+ // set the nav direction to "forward" if it wasn't set
+ opts.direction = opts.direction || 'forward';
+
+ // set which animation it should use if it wasn't set yet
+ if (!opts.animation) {
+ opts.animation = enteringView.getTransitionName(opts.direction);
+ }
+
+ // it's possible that the newly added view doesn't need to
+ // transition in, but was simply inserted somewhere in the stack
+ // go backwards through the stack and find the first active view
+ // which could be active or one ready to enter
+ for (var i = this._views.length - 1; i >= 0; i--) {
+ if (this._views[i].state === STATE_ACTIVE || this._views[i].state === STATE_INIT_ENTER) {
+ // found the view at the end of the stack that's either
+ // already active or it is about to enter
+
+ if (this._views[i] === enteringView) {
+ // cool, so the last valid view is also our entering view!!
+ // this means we should animate that bad boy in so it's the active view
+ // return a promise and resolve when the transition has completed
+
+ // get the leaving view which the _insert() already set
+ let leavingView = this.getByState(STATE_INIT_LEAVE);
+
+ // start the transition, fire resolve when done...
+ this._transition(enteringView, leavingView, opts, done);
+ return promise;
+ }
+ break;
+ }
+ }
+
+ // the page was not pushed onto the end of the stack
+ // but rather inserted somewhere in the middle or beginning
+ // Since there are views after this new one, don't transition in
+ // auto resolve cuz there was is no need for an animation
+ done(enteringView);
+
+ return promise;
+ }
+
+ _insert(insertIndex: number, insertViews: ViewController[]): ViewController {
+ // when this is done, there should only be at most
+ // 1 STATE_INIT_ENTER and 1 STATE_INIT_LEAVE
+ // there should not be any that are STATE_ACTIVE after this is done
+
+ // 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 (insertIndex < 0 || insertIndex > this._views.length) {
+ insertIndex = this._views.length;
+ }
+
+ // first see if there's an active view
+ let view = this.getActive();
+ if (view) {
+ // there's an active view, set that it's initialized to leave
+ view.state = STATE_INIT_LEAVE;
+
+ } else if (view = this.getByState(STATE_INIT_ENTER)) {
+ // oh no, there's already a transition initalized ready to enter!
+ // but it actually hasn't entered yet at all so lets
+ // just keep it in the array, but not render or animate it in
+ view.state = STATE_INACTIVE;
+ }
+
+ // insert each of the views in the pages array
+ let insertView: ViewController = null;
+
+ insertViews.forEach((view, i) => {
+ insertView = view;
+
+ // create the new entering view
+ view.setNav(this);
+ view.state = STATE_INACTIVE;
+
+ // 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(insertIndex + i, 0, view);
+ });
+
+ if (insertView) {
+ insertView.state = STATE_INIT_ENTER;
+ }
+
+ return insertView;
+ }
+
+ pop(opts?: NavOptions, done?: Function): Promise {
+ // get the index of the active view
+ // which will become the view to be leaving
+ let activeView = this.getByState(STATE_TRANS_ENTER) ||
+ this.getByState(STATE_INIT_ENTER) ||
+ this.getActive();
+
+ return this.remove(this.indexOf(activeView), 1, opts, done);
+ }
+
+ popToRoot(opts?: NavOptions, done?: Function): Promise {
+ return this.popTo(this.first(), opts, done);
+ }
+
+ popTo(view: ViewController, opts?: NavOptions, done?: Function): Promise {
+ let startIndex = this.indexOf(view);
+ if (startIndex < 0) {
+ return Promise.reject('View not found to pop to');
+ }
+
+ let activeView = this.getByState(STATE_TRANS_ENTER) ||
+ this.getByState(STATE_INIT_ENTER) ||
+ this.getActive();
+ let removeCount = this.indexOf(activeView) - startIndex;
+
+ return this.remove(startIndex + 1, removeCount, opts, done);
+ }
+
+ remove(startIndex: number = -1, removeCount: number = 1, opts?: NavOptions, done?: Function): Promise {
+ let promise: Promise;
+
+ if (!done) {
+ promise = new Promise(resolve => { done = resolve; });
+ }
+
+ if (startIndex === -1) {
+ startIndex = (this._views.length - 1);
+
+ } else if (startIndex < 0 || startIndex >= this._views.length) {
+ console.error('index out of range removing view from nav');
+ done(false);
+ return promise;
+ }
+
+ if (isBlank(opts)) {
+ opts = {};
+ }
+
+ // if not set, by default climb up the nav controllers if
+ // there isn't a previous view in this nav controller
+ if (isBlank(opts.climbNav)) {
+ opts.climbNav = true;
+ }
+
+ // default the direction to "back"
+ opts.direction = opts.direction || DIRECTION_BACK;
+
+ // figure out the states of each view in the stack
+ let leavingView = this._remove(startIndex, removeCount);
+
+ if (!leavingView) {
+ let forcedActive = this.getByState(STATE_FORCE_ACTIVE);
+ if (forcedActive) {
+ // this scenario happens when a remove is going on
+ // during a transition
+ if (this._trans) {
+ this._trans.stop();
+ this._trans.destroy();
+ this._trans = null;
+ this._cleanup();
+ }
+
+ done(false);
+ return promise;
+ }
+ }
+
+ if (leavingView) {
+ // there is a view ready to leave, meaning that a transition needs
+ // to happen and the previously active view is going to animate out
+
+ // get the view thats ready to enter
+ let enteringView = this.getByState(STATE_INIT_ENTER);
+
+ if (!enteringView && !this._isPortal) {
+ // oh nos! no entering view to go to!
+ // if there is no previous view that would enter in this nav stack
+ // and the option is set to climb up the nav parent looking
+ // for the next nav we could transition to instead
+ if (opts.climbNav) {
+ let parentNav: NavController = this.parent;
+ while (parentNav) {
+ if (!isTabs(parentNav)) {
+ // Tabs can be a parent, but it is not a collection of views
+ // only we're looking for an actual NavController w/ stack of views
+ leavingView.fireWillLeave();
+ this.viewWillLeave.emit(leavingView);
+ this._app.viewWillLeave.emit(leavingView);
+
+ return parentNav.pop(opts).then((rtnVal: boolean) => {
+ leavingView.fireDidLeave();
+ this.viewDidLeave.emit(leavingView);
+ this._app.viewDidLeave.emit(leavingView);
+ return rtnVal;
+ });
+ }
+ parentNav = parentNav.parent;
+ }
+ }
+
+ // there's no previous view and there's no valid parent nav
+ // to climb to so this shouldn't actually remove the leaving
+ // view because there's nothing that would enter, eww
+ leavingView.state = STATE_ACTIVE;
+ done(false);
+
+ return promise;
+ }
+
+ if (!opts.animation) {
+ opts.animation = leavingView.getTransitionName(opts.direction);
+ }
+
+ // start the transition, fire resolve when done...
+ this._transition(enteringView, leavingView, opts, done);
+
+ return promise;
+ }
+
+ // no need to transition when the active view isn't being removed
+ // there's still an active view after _remove() figured out states
+ // so this means views that were only removed before the active
+ // view, so auto-resolve since no transition needs to happen
+ done(false);
+ return promise;
+ }
+
+ /**
+ * @private
+ */
+ _remove(startIndex: number, removeCount: number): ViewController {
+ // when this is done, there should only be at most
+ // 1 STATE_INIT_ENTER and 1 STATE_INIT_LEAVE
+ // there should not be any that are STATE_ACTIVE after this is done
+ let view: ViewController = null;
+
+ // loop through each view that is set to be removed
+ for (var i = startIndex, ii = removeCount + startIndex; i < ii; i++) {
+ view = this.getByIndex(i);
+ if (!view) break;
+
+ if (view.state === STATE_TRANS_ENTER || view.state === STATE_TRANS_LEAVE) {
+ // oh no!!! this view should be removed, but it's
+ // actively transitioning in at the moment!!
+ // since it's viewable right now, let's just set that
+ // it should be removed after the transition
+ view.state = STATE_REMOVE_AFTER_TRANS;
+
+ } else if (view.state === STATE_INIT_ENTER) {
+ // asked to be removed before it even entered!
+ view.state = STATE_CANCEL_ENTER;
+
+ } else {
+ // if this view is already leaving then no need to immediately
+ // remove it, otherwise set the remove state
+ // this is useful if the view being removed isn't going to
+ // animate out, but just removed from the stack, no transition
+ view.state = STATE_REMOVE;
+ }
+ }
+
+ if (view = this.getByState(STATE_INIT_LEAVE)) {
+ // looks like there's already an active leaving view
+
+ // reassign previous entering view to just be inactive
+ let enteringView = this.getByState(STATE_INIT_ENTER);
+ if (enteringView) {
+ enteringView.state = STATE_INACTIVE;
+ }
+
+ // from the index of the leaving view, go backwards and
+ // find the first view that is inactive
+ for (var i = this.indexOf(view) - 1; i >= 0; i--) {
+ if (this._views[i].state === STATE_INACTIVE) {
+ this._views[i].state = STATE_INIT_ENTER;
+ break;
+ }
+ }
+
+ } else if (view = this.getByState(STATE_TRANS_LEAVE)) {
+ // an active transition is happening, but a new transition
+ // still needs to happen force this view to be the active one
+ view.state = STATE_FORCE_ACTIVE;
+
+ } else if (view = this.getByState(STATE_REMOVE)) {
+ // there is no active transition about to happen
+ // find the first view that is supposed to be removed and
+ // set that it is the init leaving view
+ // the first view to be removed, it should init leave
+ view.state = STATE_INIT_LEAVE;
+ view.fireWillUnload();
+ this.viewWillUnload.emit(view);
+ this._app.viewWillUnload.emit(view);
+
+ // from the index of the leaving view, go backwards and
+ // find the first view that is inactive so it can be the entering
+ for (var i = this.indexOf(view) - 1; i >= 0; i--) {
+ if (this._views[i].state === STATE_INACTIVE) {
+ this._views[i].state = STATE_INIT_ENTER;
+ break;
+ }
+ }
+ }
+
+ // if there is still an active view, then it wasn't one that was
+ // set to be removed, so there actually won't be a transition at all
+ view = this.getActive();
+ if (view) {
+ // the active view remains untouched, so all the removes
+ // must have happened before it, so really no need for transition
+ view = this.getByState(STATE_INIT_ENTER);
+ if (view) {
+ // if it was going to enter, then just make inactive
+ view.state = STATE_INACTIVE;
+ }
+ view = this.getByState(STATE_INIT_LEAVE);
+ if (view) {
+ // this was going to leave, so just remove it completely
+ view.state = STATE_REMOVE;
+ }
+ }
+
+ // remove views that have been set to be removed, but not
+ // apart of any transitions that will eventually happen
+ this._views.filter(v => v.state === STATE_REMOVE).forEach(view => {
+ view.fireWillLeave();
+ view.fireDidLeave();
+ this._views.splice(this.indexOf(view), 1);
+ view.destroy();
+ });
+
+ return this.getByState(STATE_INIT_LEAVE);
+ }
+
+ /**
+ * @private
+ */
+ _transition(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, done: Function) {
+ let transId = ++this._transIds;
+
+ if (enteringView === leavingView) {
+ // if the entering view and leaving view are the same thing don't continue
+ this._transFinish(transId, enteringView, leavingView, null, false, false);
+ done(false);
+ return;
+ }
+
+ if (isBlank(opts)) {
+ opts = {};
+ }
+
+ this._setAnimate(opts);
+
+ if (!leavingView) {
+ // if no leaving view then create a bogus one
+ leavingView = new ViewController();
+ }
+
+ if (!enteringView) {
+ // if no entering view then create a bogus one
+ enteringView = new ViewController();
+ enteringView.fireLoaded();
+ }
+
+ /* Async steps to complete a transition
+ 1. _render: compile the view and render it in the DOM. Load page if it hasn't loaded already. When done call postRender
+ 2. _postRender: Run willEnter/willLeave, then wait a frame (change detection happens), then call beginTransition
+ 3. _beforeTrans: Create the transition's animation, play the animation, wait for it to end
+ 4. _afterTrans: Run didEnter/didLeave, call _transComplete()
+ 5. _transComplete: Cleanup, remove cache views, then call the final callback
+ */
+
+ // begin the multiple async process of transitioning to the entering view
+ this._render(transId, enteringView, leavingView, opts, (hasCompleted: boolean) => {
+ this._transFinish(transId, enteringView, leavingView, opts.direction, false, hasCompleted);
+ done(hasCompleted);
+ });
+ }
+
+ /**
+ * @private
+ */
+ _setAnimate(opts: NavOptions) {
+ if ((this._views.length === 1 && !this._init && !this._isPortal) || this.config.get('animate') === false) {
+ opts.animate = false;
+ }
+ }
+
+ /**
+ * @private
+ */
+ _render(transId: number, enteringView: ViewController, leavingView: ViewController, opts: NavOptions, done: Function) {
+ // compile/load the view into the DOM
+
+ if (enteringView.state === STATE_INACTIVE) {
+ // this entering view is already set to inactive, so this
+ // transition must be canceled, so don't continue
+ return done();
+ }
+
+ enteringView.state = STATE_INIT_ENTER;
+ leavingView.state = STATE_INIT_LEAVE;
+
+ // remember if this nav is already transitioning or not
+ let isAlreadyTransitioning = this.isTransitioning();
+
+ if (enteringView.isLoaded()) {
+ // already compiled this view, do not load again and continue
+ this._postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done);
+
+ } else {
+ // view has not been compiled/loaded yet
+ // continue once the view has finished compiling
+ // DOM WRITE
+ this.setTransitioning(true, 500);
+
+ this.loadPage(enteringView, this._viewport, opts, () => {
+ enteringView.fireLoaded();
+ this.viewDidLoad.emit(enteringView);
+ this._app.viewDidLoad.emit(enteringView);
+
+ this._postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done);
+ });
+ }
+ }
+
+ /**
+ * @private
+ */
+ _postRender(transId: number, enteringView: ViewController, leavingView: ViewController, isAlreadyTransitioning: boolean, opts: NavOptions, done: Function) {
+ // called after _render has completed and the view is compiled/loaded
+
+ if (enteringView.state === STATE_INACTIVE) {
+ // this entering view is already set to inactive, so this
+ // transition must be canceled, so don't continue
+ return done();
+ }
+
+ if (!opts.preload) {
+ // the enteringView will become the active view, and is not being preloaded
+
+ // set the correct zIndex for the entering and leaving views
+ // if there's already another trans_enter happening then
+ // the zIndex for the entering view should go off of that one
+ // DOM WRITE
+ let lastestLeavingView = this.getByState(STATE_TRANS_ENTER) || leavingView;
+ this._setZIndex(enteringView, lastestLeavingView, opts.direction);
+
+ // make sure the entering and leaving views are showing
+ // DOM WRITE
+ if (isAlreadyTransitioning) {
+ // the previous transition was still going when this one started
+ // so to be safe, only update showing the entering/leaving
+ // don't hide the others when they could still be transitioning
+ enteringView.domShow(true, this._renderer);
+ leavingView.domShow(true, this._renderer);
+
+ } else {
+ // there are no other transitions happening but this one
+ // only entering/leaving should show, all others hidden
+ // also if a view is an overlay or the previous view is an
+ // overlay then always show the overlay and the view before it
+ this._views.forEach(view => {
+ view.domShow(this._isPortal || (view === enteringView) || (view === leavingView), this._renderer);
+ });
+ }
+
+ // call each view's lifecycle events
+ if (leavingView.fireOtherLifecycles) {
+ // only fire entering lifecycle if the leaving
+ // view hasn't explicitly set not to
+ enteringView.fireWillEnter();
+ this.viewWillEnter.emit(enteringView);
+ this._app.viewWillEnter.emit(enteringView);
+ }
+
+ if (enteringView.fireOtherLifecycles) {
+ // only fire leaving lifecycle if the entering
+ // view hasn't explicitly set not to
+ leavingView.fireWillLeave();
+ this.viewWillLeave.emit(leavingView);
+ this._app.viewWillLeave.emit(leavingView);
+ }
+
+ } else {
+ // this view is being preloaded, don't call lifecycle events
+ // transition does not need to animate
+ opts.animate = false;
+ }
+
+ this._beforeTrans(enteringView, leavingView, opts, done);
+ }
+
+ /**
+ * @private
+ */
+ _beforeTrans(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, done: Function) {
+ // called after one raf from postRender()
+ // create the transitions animation, play the animation
+ // when the transition ends call wait for it to end
+
+ if (enteringView.state === STATE_INACTIVE || enteringView.state === STATE_CANCEL_ENTER) {
+ // this entering view is already set to inactive or has been canceled
+ // so this transition must not begin, so don't continue
+ return done();
+ }
+
+ enteringView.state = STATE_TRANS_ENTER;
+ leavingView.state = STATE_TRANS_LEAVE;
+
+ // everything during the transition should runOutsideAngular
+ this._zone.runOutsideAngular(() => {
+
+ // init the transition animation
+ let transitionOpts = {
+ animation: opts.animation,
+ direction: opts.direction,
+ duration: opts.duration,
+ easing: opts.easing,
+ renderDelay: opts.transitionDelay || this._trnsDelay,
+ isRTL: this.config.platform.isRTL(),
+ ev: opts.ev,
+ };
+
+ let transAnimation = this._createTrans(enteringView, leavingView, transitionOpts);
+
+ this._trans && this._trans.destroy();
+ this._trans = transAnimation;
+
+ if (opts.animate === false) {
+ // force it to not animate the elements, just apply the "to" styles
+ transAnimation.duration(0);
+ }
+
+ // check if a parent is transitioning and get the time that it ends
+ let parentTransitionEndTime = this.getLongestTrans(Date.now());
+ if (parentTransitionEndTime > 0) {
+ // the parent is already transitioning and has disabled the app
+ // so just update the local transitioning information
+ let duration = parentTransitionEndTime - Date.now();
+ this.setTransitioning(true, duration);
+
+ } else {
+ // this is the only active transition (for now), so disable the app
+ let keyboardDurationPadding = 0;
+ if (this._keyboard.isOpen()) {
+ // add XXms to the duration the app is disabled when the keyboard is open
+ keyboardDurationPadding = 600;
+ }
+ let duration = transAnimation.getDuration() + keyboardDurationPadding;
+ let enableApp = (duration < 64);
+ this._app.setEnabled(enableApp, duration);
+ this.setTransitioning(!enableApp, duration);
+ }
+
+ // create a callback for when the animation is done
+ transAnimation.onFinish((trans: Transition) => {
+ // transition animation has ended
+
+ // destroy the animation and it's element references
+ trans.destroy();
+
+ this._afterTrans(enteringView, leavingView, opts, trans.hasCompleted, done);
+ });
+
+ // 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
+ transAnimation.progressStart();
+
+ } else {
+
+ // this is a normal animation
+ // kick it off and let it play through
+ transAnimation.play();
+ }
+ });
+ }
+
+ /**
+ * @private
+ */
+ _afterTrans(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, hasCompleted: boolean, done: Function) {
+ // transition has completed, update each view's state
+ // place back into the zone, run didEnter/didLeave
+ // call the final callback when done
+
+ // run inside of the zone again
+ this._zone.run(() => {
+
+ if (!opts.preload && hasCompleted) {
+ if (leavingView.fireOtherLifecycles) {
+ // only fire entering lifecycle if the leaving
+ // view hasn't explicitly set not to
+ enteringView.fireDidEnter();
+ this.viewDidEnter.emit(enteringView);
+ this._app.viewDidEnter.emit(enteringView);
+ }
+
+ if (enteringView.fireOtherLifecycles && this._init) {
+ // only fire leaving lifecycle if the entering
+ // view hasn't explicitly set not to
+ // and after the nav has initialized
+ leavingView.fireDidLeave();
+ this.viewDidLeave.emit(leavingView);
+ this._app.viewDidLeave.emit(leavingView);
+ }
+ }
+
+ if (enteringView.state === STATE_INACTIVE) {
+ // this entering view is already set to inactive, so this
+ // transition must be canceled, so don't continue
+ return done(hasCompleted);
+ }
+
+ if (opts.keyboardClose !== false && this._keyboard.isOpen()) {
+ // the keyboard is still open!
+ // no problem, let's just close for them
+ this._keyboard.close();
+ this._keyboard.onClose(() => {
+
+ // keyboard has finished closing, transition complete
+ done(hasCompleted);
+ }, 32);
+
+ } else {
+ // all good, transition complete
+ done(hasCompleted);
+ }
+ });
+ }
+
+ /**
+ * @private
+ */
+ _transFinish(transId: number, enteringView: ViewController, leavingView: ViewController, direction: string, updateUrl: boolean, hasCompleted: boolean) {
+ // a transition has completed, but not sure if it's the last one or not
+ // check if this transition is the most recent one or not
+
+ if (enteringView.state === STATE_CANCEL_ENTER) {
+ // this view was told to leave before it finished entering
+ this.remove(enteringView.index, 1);
+ }
+
+ if (transId === this._transIds) {
+ // ok, good news, there were no other transitions that kicked
+ // off during the time this transition started and ended
+
+ if (hasCompleted) {
+ // this transition has completed as normal
+ // so the entering one is now the active view
+ // and the leaving view is now just inactive
+ if (enteringView.state !== STATE_REMOVE_AFTER_TRANS) {
+ enteringView.state = STATE_ACTIVE;
+ }
+ if (leavingView.state !== STATE_REMOVE_AFTER_TRANS) {
+ leavingView.state = STATE_INACTIVE;
+ }
+
+ // only need to do all this clean up if the transition
+ // completed, otherwise nothing actually changed
+ // destroy all of the views that come after the active view
+ this._cleanup();
+
+ // make sure only this entering view and PREVIOUS view are the
+ // only two views that are not display:none
+ // do not make any changes to the stack's current visibility
+ // if there is an overlay somewhere in the stack
+ leavingView = this.getPrevious(enteringView);
+ if (this._isPortal) {
+ // ensure the entering view is showing
+ enteringView.domShow(true, this._renderer);
+
+ } else {
+ // only possibly hide a view if there are no overlays in the stack
+ this._views.forEach(view => {
+ view.domShow((view === enteringView) || (view === leavingView), this._renderer);
+ });
+ }
+
+ // this check only needs to happen once, which will add the css
+ // class to the nav when it's finished its first transition
+ this._init = true;
+
+ } else {
+ // this transition has not completed, meaning the
+ // entering view did not end up as the active view
+ // this would happen when swipe to go back started
+ // but the user did not complete the swipe and the
+ // what was the active view stayed as the active view
+ leavingView.state = STATE_ACTIVE;
+ enteringView.state = STATE_INACTIVE;
+ }
+
+ // check if there is a parent actively transitioning
+ let transitionEndTime = this.getLongestTrans(Date.now());
+ // if transitionEndTime is greater than 0, there is a parent transition occurring
+ // so delegate enabling the app to the parent. If it <= 0, go ahead and enable the app
+ if (transitionEndTime <= 0) {
+ this._app && this._app.setEnabled(true);
+ }
+
+ // update that this nav is not longer actively transitioning
+ this.setTransitioning(false);
+
+ // see if we should add the swipe back gesture listeners or not
+ this._sbCheck();
+
+ } else {
+ // darn, so this wasn't the most recent transition
+ // so while this one did end, there's another more recent one
+ // still going on. Because a new transition is happening,
+ // then this entering view isn't actually going to be the active
+ // one, so only update the state to active/inactive if the state
+ // wasn't already updated somewhere else during its transition
+ if (enteringView.state === STATE_TRANS_ENTER) {
+ enteringView.state = STATE_INACTIVE;
+ }
+ if (leavingView.state === STATE_TRANS_LEAVE) {
+ leavingView.state = STATE_INACTIVE;
+ }
+ }
+ }
+
+ /**
+ *@private
+ * This method is just a wrapper to the Transition function of same name
+ * to make it easy/possible to mock the method call by overriding the function.
+ * In testing we don't want to actually do the animation, we want to return a stub instead
+ */
+ _createTrans(enteringView: ViewController, leavingView: ViewController, transitionOpts: any): Transition {
+ return Transition.createTransition(enteringView, leavingView, transitionOpts);
+ }
+
+ _cleanup() {
+ // ok, cleanup time!! Destroy all of the views that are
+ // INACTIVE and come after the active view
+ let activeViewIndex = this.indexOf(this.getActive());
+ let destroys = this._views.filter(v => v.state === STATE_REMOVE_AFTER_TRANS);
+
+ for (var i = activeViewIndex + 1; i < this._views.length; i++) {
+ if (this._views[i].state === STATE_INACTIVE) {
+ destroys.push(this._views[i]);
+ }
+ }
+
+ // all pages being destroyed should be removed from the list of
+ // pages and completely removed from the dom
+ destroys.forEach(view => {
+ this._views.splice(this.indexOf(view), 1);
+ view.destroy();
+ this.viewDidUnload.emit(view);
+ this._app.viewDidUnload.emit(view);
+ });
+
+ // if any z-index goes under 0, then reset them all
+ let shouldResetZIndex = this._views.some(v => v.zIndex < 0);
+ if (shouldResetZIndex) {
+ this._views.forEach(view => {
+ view.setZIndex(view.zIndex + INIT_ZINDEX + 1, this._renderer);
+ });
+ }
+ }
+
+ getActiveChildNav(): any {
+ return this._children[this._children.length - 1];
+ }
+
+ /**
+ * @private
+ */
+ registerChildNav(nav: any) {
+ this._children.push(nav);
+ }
+
+ /**
+ * @private
+ */
+ unregisterChildNav(nav: any) {
+ let index = this._children.indexOf(nav);
+ if (index > -1) {
+ this._children.splice(index, 1);
+ }
+ }
+
+ /**
+ * @private
+ */
+ ngOnDestroy() {
+ for (var i = this._views.length - 1; i >= 0; i--) {
+ this._views[i].destroy();
+ }
+ this._views.length = 0;
+
+ if (this.parent && this.parent.unregisterChildNav) {
+ this.parent.unregisterChildNav(this);
+ }
+ }
+
+ /**
+ * @private
+ */
+ loadPage(view: ViewController, viewport: ViewContainerRef, opts: NavOptions, done: Function) {
+ if (!viewport || !view.componentType) {
+ return;
+ }
+
+ // TEMPORARY: automatically set selector w/ dah reflector
+ // TODO: use componentFactory.create once fixed
+ addSelector(view.componentType, 'ion-page');
+
+ this._compiler.resolveComponent(view.componentType).then(componentFactory => {
+
+ if (view.state === STATE_CANCEL_ENTER) {
+ // view may have already been removed from the stack
+ // if so, don't even bother adding it
+ view.destroy();
+ this._views.splice(view.index, 1);
+ return;
+ }
+
+ // add more providers to just this page
+ let componentProviders = ReflectiveInjector.resolve([
+ provide(NavController, {useValue: this}),
+ provide(ViewController, {useValue: view}),
+ provide(NavParams, {useValue: view.getNavParams()})
+ ]);
+
+ let childInjector = ReflectiveInjector.fromResolvedProviders(componentProviders, this._viewport.parentInjector);
+
+ let componentRef = componentFactory.create(childInjector, null, null);
+
+ viewport.insert(componentRef.hostView, viewport.length);
+
+ // a new ComponentRef has been created
+ // set the ComponentRef's instance to its ViewController
+ view.setInstance(componentRef.instance);
+
+ // the component has been loaded, so call the view controller's loaded method to load any dependencies into the dom
+ view.loaded(() => {
+
+ // the ElementRef of the actual ion-page created
+ let pageElementRef = componentRef.location;
+
+ // remember the ChangeDetectorRef for this ViewController
+ view.setChangeDetector(componentRef.changeDetectorRef);
+
+ // remember the ElementRef to the ion-page elementRef that was just created
+ view.setPageRef(pageElementRef);
+
+ // auto-add page css className created from component JS class name
+ let cssClassName = pascalCaseToDashCase(view.componentType.name);
+ this._renderer.setElementClass(pageElementRef.nativeElement, cssClassName, true);
+
+ view.onDestroy(() => {
+ // ensure the element is cleaned up for when the view pool reuses this element
+ this._renderer.setElementAttribute(pageElementRef.nativeElement, 'class', null);
+ this._renderer.setElementAttribute(pageElementRef.nativeElement, 'style', null);
+ componentRef.destroy();
+ });
+
+ // our job is done here
+ done(view);
+ });
+ });
+ }
+
+ /**
+ * @private
+ */
+ swipeBackStart() {
+ // default the direction to "back"
+ let opts: NavOptions = {
+ direction: DIRECTION_BACK,
+ progressAnimation: true
+ };
+
+ // figure out the states of each view in the stack
+ let leavingView = this._remove(this._views.length - 1, 1);
+
+ if (leavingView) {
+ opts.animation = leavingView.getTransitionName(opts.direction);
+
+ // get the view thats ready to enter
+ let enteringView = this.getByState(STATE_INIT_ENTER);
+
+ // start the transition, fire callback when done...
+ this._transition(enteringView, leavingView, opts, (hasCompleted: boolean) => {
+ // swipe back has finished!!
+ console.debug('swipeBack, hasCompleted', hasCompleted);
+ });
+ }
+ }
+
+ /**
+ * @private
+ */
+ swipeBackProgress(stepValue: number) {
+ if (this._trans && this._sbGesture) {
+ // continue to disable the app while actively dragging
+ this._app.setEnabled(false, 4000);
+ this.setTransitioning(true, 4000);
+
+ // set the transition animation's progress
+ this._trans.progressStep(stepValue);
+ }
+ }
+
+ /**
+ * @private
+ */
+ swipeBackEnd(shouldComplete: boolean, currentStepValue: number) {
+ if (this._trans && this._sbGesture) {
+ // the swipe back gesture has ended
+ this._trans.progressEnd(shouldComplete, currentStepValue);
+ }
+ }
+
+ /**
+ * @private
+ */
+ _sbCheck() {
+ if (this._sbEnabled) {
+ // this nav controller can have swipe to go back
+
+ if (!this._sbGesture) {
+ // create the swipe back gesture if we haven't already
+ let 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.isTransitioning() && this._app.isEnabled() && this.canGoBack());
+ }
+
+ canGoBack(): boolean {
+ let activeView = this.getActive();
+ if (activeView) {
+ return activeView.enableBack();
+ }
+ return false;
+ }
+
+ isTransitioning(includeAncestors?: boolean): boolean {
+ let now = Date.now();
+ if (includeAncestors && this.getLongestTrans(now) > 0) {
+ return true;
+ }
+ return (this.trnsTime > now);
+ }
+
+ setTransitioning(isTransitioning: boolean, fallback: number = 700) {
+ this.trnsTime = (isTransitioning ? Date.now() + fallback : 0);
+ }
+
+ getLongestTrans(now: number) {
+ // traverses parents upwards and looks at the time the
+ // transition ends (if it's transitioning) and returns the
+ // value that is the furthest into the future thus giving us
+ // the longest transition duration
+ let parentNav = this.parent;
+ let transitionEndTime = -1;
+ while (parentNav) {
+ if (parentNav.trnsTime > transitionEndTime) {
+ transitionEndTime = parentNav.trnsTime;
+ }
+ parentNav = parentNav.parent;
+ }
+
+ // only check if the transitionTime is greater than the current time once
+ return transitionEndTime > 0 && transitionEndTime > now ? transitionEndTime : 0;
+ }
+
+ getByState(state: number): ViewController {
+ for (var i = this._views.length - 1; i >= 0; i--) {
+ if (this._views[i].state === state) {
+ return this._views[i];
+ }
+ }
+ return null;
+ }
+
+ getByIndex(index: number): ViewController {
+ return (index < this._views.length && index > -1 ? this._views[index] : null);
+ }
+
+ getActive(): ViewController {
+ return this.getByState(STATE_ACTIVE);
+ }
+
+ isActive(view: ViewController): boolean {
+ // returns if the given view is the active view or not
+ return !!(view && view.state === STATE_ACTIVE);
+ }
+
+ getPrevious(view: ViewController): ViewController {
+ // returns the view controller which is before the given view controller.
+ return this.getByIndex(this.indexOf(view) - 1);
+ }
+
+ first(): ViewController {
+ // returns the first view controller in this nav controller's stack.
+ return (this._views.length ? this._views[0] : null);
+ }
+
+ last(): ViewController {
+ // returns the last page in this nav controller's stack.
+ return (this._views.length ? this._views[this._views.length - 1] : null);
+ }
+
+ 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;
+ }
+
+ /**
+ * DEPRECATED: Please use app.getRootNav() instead
+ */
+ private 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
+ * Dismiss all pages which have set the `dismissOnPageChange` property.
+ */
+ dismissPageChangeViews() {
+ this._views.forEach(view => {
+ if (view.data && view.data.dismissOnPageChange) {
+ view.dismiss();
+ }
+ });
+ }
+
+ /**
+ * @private
+ */
+ _setZIndex(enteringView: ViewController, leavingView: ViewController, direction: string) {
+ if (enteringView) {
+ // get the leaving view, which could be in various states
+ if (!leavingView || !leavingView.isLoaded()) {
+ // the leavingView is a mocked view, either we're
+ // actively transitioning or it's the initial load
+
+ var previousView = this.getPrevious(enteringView);
+ if (previousView && previousView.isLoaded()) {
+ // we found a better previous view to reference
+ // use this one instead
+ enteringView.setZIndex(previousView.zIndex + 1, this._renderer);
+
+ } else {
+ // this is the initial view
+ enteringView.setZIndex(this._isPortal ? PORTAL_ZINDEX : INIT_ZINDEX, this._renderer);
+ }
+
+ } else if (direction === DIRECTION_BACK) {
+ // moving back
+ enteringView.setZIndex(leavingView.zIndex - 1, this._renderer);
+
+ } else {
+ // moving forward
+ enteringView.setZIndex(leavingView.zIndex + 1, this._renderer);
+ }
+ }
+ }
+
+}
+
+export const isTabs = (nav: any) => {
+ // Tabs (ion-tabs)
+ return !!nav.getSelected;
+};
+
+export const isTab = (nav: any) => {
+ // Tab (ion-tab)
+ return isPresent(nav._tabId);
+};
+
+export const isNav = function(nav: any) {
+ // Nav (ion-nav), Tab (ion-tab), Portal (ion-portal)
+ return isPresent(nav.push);
+};
+
+
+export const STATE_ACTIVE = 1;
+export const STATE_INACTIVE = 2;
+export const STATE_INIT_ENTER = 3;
+export const STATE_INIT_LEAVE = 4;
+export const STATE_TRANS_ENTER = 5;
+export const STATE_TRANS_LEAVE = 6;
+export const STATE_REMOVE = 7;
+export const STATE_REMOVE_AFTER_TRANS = 8;
+export const STATE_CANCEL_ENTER = 9;
+export const STATE_FORCE_ACTIVE = 10;
+
+const INIT_ZINDEX = 100;
+const PORTAL_ZINDEX = 9999;
+
+let ctrlIds = -1;
\ No newline at end of file
diff --git a/src/components/nav/nav-controller.ts b/src/components/nav/nav-controller.ts
index abe56111dc..604925995b 100644
--- a/src/components/nav/nav-controller.ts
+++ b/src/components/nav/nav-controller.ts
@@ -1,16 +1,11 @@
-import { ComponentResolver, ElementRef, EventEmitter, NgZone, provide, ReflectiveInjector, Renderer, ViewContainerRef } from '@angular/core';
+import { EventEmitter } from '@angular/core';
-import { addSelector } from '../../config/bootstrap';
-import { App } from '../app/app';
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 { MenuController } from '../menu/menu-controller';
import { NavOptions } from './nav-interfaces';
-import { NavParams } from './nav-params';
-import { SwipeBackGesture } from './swipe-back';
-import { Transition } from '../../transitions/transition';
import { ViewController } from './view-controller';
@@ -157,19 +152,7 @@ import { ViewController } from './view-controller';
*
* @see {@link /docs/v2/components#navigation Navigation Component Docs}
*/
-export class NavController extends Ion {
- private _transIds = 0;
- private _init = false;
- private _trans: Transition;
- private _sbGesture: SwipeBackGesture;
- private _sbThreshold: number;
- private _viewport: ViewContainerRef;
- private _children: any[] = [];
-
- protected _sbEnabled: boolean;
- protected _ids: number = -1;
- protected _trnsDelay: any;
- protected _views: ViewController[] = [];
+export abstract class NavController {
/**
* Observable to be subscribed to when a component is loaded.
@@ -219,7 +202,9 @@ export class NavController extends Ion {
id: string;
/**
- * @private
+ * The parent navigation instance. If this is the root nav, then
+ * it'll be `null`. A `Tab` instance's parent is `Tabs`, otherwise
+ * the parent would be another nav, if it's not already the root nav.
*/
parent: any;
@@ -228,55 +213,6 @@ export class NavController extends Ion {
*/
config: Config;
- /**
- * @private
- */
- isPortal: boolean = false;
-
- /**
- * @private
- */
- trnsTime: number = 0;
-
- constructor(
- parent: any,
- protected _app: App,
- config: Config,
- protected _keyboard: Keyboard,
- elementRef: ElementRef,
- protected _zone: NgZone,
- protected _renderer: Renderer,
- protected _compiler: ComponentResolver,
- protected _menuCtrl: MenuController
- ) {
- super(elementRef);
-
- this.parent = parent;
- this.config = config;
-
- this._trnsDelay = config.get('pageTransitionDelay');
-
- this._sbEnabled = config.getBoolean('swipeBackEnabled');
- this._sbThreshold = config.getNumber('swipeBackThreshold', 40);
-
- this.id = (++ctrlIds).toString();
-
- this.viewDidLoad = new EventEmitter();
- this.viewWillEnter = new EventEmitter();
- this.viewDidEnter = new EventEmitter();
- this.viewWillLeave = new EventEmitter();
- this.viewDidLeave = new EventEmitter();
- this.viewWillUnload = new EventEmitter();
- this.viewDidUnload = new EventEmitter();
- }
-
- /**
- * @private
- */
- setViewport(val: ViewContainerRef) {
- this._viewport = val;
- }
-
/**
* Set the root for the current navigation stack.
* @param {Page} page The name of the component you want to push on the navigation stack.
@@ -284,9 +220,7 @@ export class NavController extends Ion {
* @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.
*/
- setRoot(page: any, params?: any, opts?: NavOptions): Promise {
- return this.setPages([{page, params}], opts);
- }
+ abstract setRoot(page: any, params?: any, opts?: NavOptions, done?: Function): Promise;
/**
* Set the views of the current navigation stack and navigate to the
@@ -298,41 +232,7 @@ export class NavController extends Ion {
* @param {object} [opts={}] Nav options to go with this transition.
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
*/
- setPages(pages: Array<{page: any, params?: any}>, opts?: NavOptions): Promise {
- if (!pages || !pages.length) {
- return Promise.resolve(false);
- }
-
- if (isBlank(opts)) {
- opts = {};
- }
-
- // remove existing views
- let leavingView = this._remove(0, this._views.length);
-
- // create view controllers out of the pages and insert the new views
- let views = pages.map(p => new ViewController(p.page, p.params));
- let enteringView = this._insert(0, views);
-
- // if animation wasn't set to true then default it to NOT animate
- if (opts.animate !== true) {
- opts.animate = false;
- }
-
- // set the nav direction to "back" if it wasn't set
- opts.direction = opts.direction || DIRECTION_BACK;
-
- let resolve: any;
- let promise = new Promise(res => { resolve = res; });
-
- // start the transition, fire resolve when done...
- this._transition(enteringView, leavingView, opts, (hasCompleted: boolean) => {
- // transition has completed!!
- resolve(hasCompleted);
- });
-
- return promise;
- }
+ abstract setPages(pages: Array<{page: any, params?: any}>, opts?: NavOptions, done?: Function): Promise;
/**
* Push a new component onto the current navication stack. Pass any aditional information
@@ -343,20 +243,7 @@ export class NavController extends Ion {
* @param {object} [opts={}] Nav options to go with this transition.
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
*/
- push(page: any, params?: any, opts?: NavOptions) {
- return this.insertPages(-1, [{page: page, params: params}], opts);
- }
-
- /**
- * @private
- * DEPRECATED: Please use inject the overlays controller and use the present method on the instance instead.
- */
- private present(enteringView: ViewController, opts?: NavOptions): Promise {
- // 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();
- }
+ abstract push(page: any, params?: any, opts?: NavOptions, done?: Function): Promise;
/**
* Inserts a component into the nav stack at the specified index. This is useful if
@@ -369,137 +256,19 @@ export class NavController extends Ion {
* @param {object} [opts={}] Nav options to go with this transition.
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
*/
- insert(insertIndex: number, page: any, params?: any, opts?: NavOptions): Promise {
- return this.insertPages(insertIndex, [{page: page, params: params}], opts);
- }
+ abstract insert(insertIndex: number, page: any, params?: any, opts?: NavOptions, done?: Function): Promise;
/**
* Inserts an array of components into the nav stack at the specified index.
- * The last component in the array will animate in and become the active component
+ * The last component in the array will become instantiated as a view,
+ * and animate in to become the active view.
*
* @param {number} insertIndex The index where you want to insert the page.
* @param {array<{page: Page, params=: any}>} insertPages An array of objects, each with a `page` and optionally `params` property.
* @param {object} [opts={}] Nav options to go with this transition.
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
*/
- insertPages(insertIndex: number, insertPages: Array<{page: any, params?: any}>, opts?: NavOptions): Promise {
- let views = insertPages.map(p => new ViewController(p.page, p.params));
- return this.insertViews(insertIndex, views, opts);
- }
-
- /**
- * @private
- */
- insertViews(insertIndex: number, insertViews: ViewController[], opts?: NavOptions): Promise {
- if (!insertViews || !insertViews.length) {
- return Promise.reject('invalid pages');
- }
-
- if (isBlank(opts)) {
- opts = {};
- }
-
- // insert the new page into the stack
- // returns the newly created entering view
- let enteringView = this._insert(insertIndex, insertViews);
-
- // set the nav direction to "forward" if it wasn't set
- opts.direction = opts.direction || 'forward';
-
- // set which animation it should use if it wasn't set yet
- if (!opts.animation) {
- opts.animation = enteringView.getTransitionName(opts.direction);
- }
-
- let resolve: any;
- let promise = new Promise(res => { resolve = res; });
-
- // it's possible that the newly added view doesn't need to
- // transition in, but was simply inserted somewhere in the stack
- // go backwards through the stack and find the first active view
- // which could be active or one ready to enter
- for (var i = this._views.length - 1; i >= 0; i--) {
- if (this._views[i].state === STATE_ACTIVE || this._views[i].state === STATE_INIT_ENTER) {
- // found the view at the end of the stack that's either
- // already active or it is about to enter
-
- if (this._views[i] === enteringView) {
- // cool, so the last valid view is also our entering view!!
- // this means we should animate that bad boy in so it's the active view
- // return a promise and resolve when the transition has completed
-
- // get the leaving view which the _insert() already set
- let leavingView = this.getByState(STATE_INIT_LEAVE);
-
- // start the transition, fire resolve when done...
- this._transition(enteringView, leavingView, opts, (hasCompleted: boolean) => {
- // transition has completed!!
- resolve(hasCompleted);
- });
-
- return promise;
- }
- break;
- }
- }
-
- // the page was not pushed onto the end of the stack
- // but rather inserted somewhere in the middle or beginning
- // Since there are views after this new one, don't transition in
- // auto resolve cuz there was is no need for an animation
- return Promise.resolve(enteringView);
- }
-
- /**
- * @private
- */
- private _insert(insertIndex: number, insertViews: Array): ViewController {
- // when this is done, there should only be at most
- // 1 STATE_INIT_ENTER and 1 STATE_INIT_LEAVE
- // there should not be any that are STATE_ACTIVE after this is done
-
- // 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 (insertIndex < 0 || insertIndex > this._views.length) {
- insertIndex = this._views.length;
- }
-
- // first see if there's an active view
- let view = this.getActive();
- if (view) {
- // there's an active view, set that it's initialized to leave
- view.state = STATE_INIT_LEAVE;
-
- } else if (view = this.getByState(STATE_INIT_ENTER)) {
- // oh no, there's already a transition initalized ready to enter!
- // but it actually hasn't entered yet at all so lets
- // just keep it in the array, but not render or animate it in
- view.state = STATE_INACTIVE;
- }
-
- // insert each of the views in the pages array
- let insertView: ViewController = null;
-
- insertViews.forEach((view, i) => {
- insertView = view;
-
- // create the new entering view
- view.setNav(this);
- view.state = STATE_INACTIVE;
-
- // 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(insertIndex + i, 0, view);
- });
-
- if (insertView) {
- insertView.state = STATE_INIT_ENTER;
- }
-
- return insertView;
- }
+ abstract insertPages(insertIndex: number, insertPages: Array<{page: any, params?: any}>, opts?: NavOptions, done?: Function): Promise;
/**
* Call to navigate back from a current component. Similar to `push()`, you
@@ -508,24 +277,7 @@ export class NavController extends Ion {
* @param {object} [opts={}] Nav options to go with this transition.
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
*/
- pop(opts?: NavOptions): Promise {
- // get the index of the active view
- // which will become the view to be leaving
- let activeView = this.getByState(STATE_TRANS_ENTER) ||
- this.getByState(STATE_INIT_ENTER) ||
- this.getActive();
-
- if (isBlank(opts)) {
- opts = {};
- }
-
- // if not set, by default climb up the nav controllers if
- // there isn't a previous view in this nav controller
- if (isBlank(opts.climbNav)) {
- opts.climbNav = true;
- }
- return this.remove(this.indexOf(activeView), 1, opts);
- }
+ abstract pop(opts?: NavOptions, done?: Function): Promise;
/**
* Navigate back to the root of the stack, no matter how far back that is.
@@ -533,9 +285,7 @@ export class NavController extends Ion {
* @param {object} [opts={}] Nav options to go with this transition.
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
*/
- popToRoot(opts?: NavOptions): Promise {
- return this.popTo(this.first(), opts);
- }
+ abstract popToRoot(opts?: NavOptions, done?: Function): Promise;
/**
* Pop to a specific view in the history stack.
@@ -544,19 +294,7 @@ export class NavController extends Ion {
* @param {object} [opts={}] Nav options to go with this transition.
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
*/
- popTo(view: ViewController, opts?: NavOptions): Promise {
- let startIndex = this.indexOf(view);
- if (startIndex < 0) {
- return Promise.reject('View not found to pop to');
- }
-
- let activeView = this.getByState(STATE_TRANS_ENTER) ||
- this.getByState(STATE_INIT_ENTER) ||
- this.getActive();
- let removeCount = this.indexOf(activeView) - startIndex;
-
- return this.remove(startIndex + 1, removeCount, opts);
- }
+ abstract popTo(view: ViewController, opts?: NavOptions, done?: Function): Promise;
/**
* Removes a page from the nav stack at the specified index.
@@ -566,840 +304,68 @@ export class NavController extends Ion {
* @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.
*/
- remove(startIndex: number = -1, removeCount: number = 1, opts?: NavOptions): Promise {
- if (startIndex === -1) {
- startIndex = this._views.length - 1;
-
- } else if (startIndex < 0 || startIndex >= this._views.length) {
- return Promise.reject('remove index out of range');
- }
-
- if (isBlank(opts)) {
- opts = {};
- }
-
- // default the direction to "back"
- opts.direction = opts.direction || DIRECTION_BACK;
-
- // figure out the states of each view in the stack
- let leavingView = this._remove(startIndex, removeCount);
-
- if (!leavingView) {
- let forcedActive = this.getByState(STATE_FORCE_ACTIVE);
- if (forcedActive) {
- // this scenario happens when a remove is going on
- // during a transition
- if (this._trans) {
- this._trans.stop();
- this._trans.destroy();
- this._trans = null;
- this._cleanup();
- }
-
- return Promise.resolve(false);
- }
- }
-
- if (leavingView) {
- // there is a view ready to leave, meaning that a transition needs
- // to happen and the previously active view is going to animate out
-
- // get the view thats ready to enter
- let enteringView = this.getByState(STATE_INIT_ENTER);
-
- if (!enteringView && !this.isPortal) {
- // oh nos! no entering view to go to!
- // if there is no previous view that would enter in this nav stack
- // and the option is set to climb up the nav parent looking
- // for the next nav we could transition to instead
- if (opts.climbNav) {
- let parentNav: NavController = this.parent;
- while (parentNav) {
- if (!parentNav['_tabs']) {
- // Tabs can be a parent, but it is not a collection of views
- // only we're looking for an actual NavController w/ stack of views
- leavingView.fireWillLeave();
- this.viewWillLeave.emit(leavingView);
- this._app.viewWillLeave.emit(leavingView);
-
- return parentNav.pop(opts).then((rtnVal: boolean) => {
- leavingView.fireDidLeave();
- this.viewDidLeave.emit(leavingView);
- this._app.viewDidLeave.emit(leavingView);
- return rtnVal;
- });
- }
- parentNav = parentNav.parent;
- }
- }
-
- // there's no previous view and there's no valid parent nav
- // to climb to so this shouldn't actually remove the leaving
- // view because there's nothing that would enter, eww
- leavingView.state = STATE_ACTIVE;
- return Promise.resolve(false);
- }
-
- let resolve: any;
- let promise = new Promise(res => { resolve = res; });
-
- if (!opts.animation) {
- opts.animation = leavingView.getTransitionName(opts.direction);
- }
-
- // start the transition, fire resolve when done...
- this._transition(enteringView, leavingView, opts, (hasCompleted: boolean) => {
- // transition has completed!!
- resolve(hasCompleted);
- });
-
- return promise;
- }
-
- // no need to transition when the active view isn't being removed
- // there's still an active view after _remove() figured out states
- // so this means views that were only removed before the active
- // view, so auto-resolve since no transition needs to happen
- return Promise.resolve(false);
- }
+ abstract remove(startIndex: number, removeCount?: number, opts?: NavOptions, done?: Function): Promise;
/**
- * @private
+ * @param {number} index The index of the page to get.
+ * @returns {ViewController} Returns the view controller that matches the given index.
*/
- private _remove(startIndex: number, removeCount: number): ViewController {
- // when this is done, there should only be at most
- // 1 STATE_INIT_ENTER and 1 STATE_INIT_LEAVE
- // there should not be any that are STATE_ACTIVE after this is done
- let view: ViewController = null;
-
- // loop through each view that is set to be removed
- for (var i = startIndex, ii = removeCount + startIndex; i < ii; i++) {
- view = this.getByIndex(i);
- if (!view) break;
-
- if (view.state === STATE_TRANS_ENTER || view.state === STATE_TRANS_LEAVE) {
- // oh no!!! this view should be removed, but it's
- // actively transitioning in at the moment!!
- // since it's viewable right now, let's just set that
- // it should be removed after the transition
- view.state = STATE_REMOVE_AFTER_TRANS;
-
- } else if (view.state === STATE_INIT_ENTER) {
- // asked to be removed before it even entered!
- view.state = STATE_CANCEL_ENTER;
-
- } else {
- // if this view is already leaving then no need to immediately
- // remove it, otherwise set the remove state
- // this is useful if the view being removed isn't going to
- // animate out, but just removed from the stack, no transition
- view.state = STATE_REMOVE;
- }
- }
-
- if (view = this.getByState(STATE_INIT_LEAVE)) {
- // looks like there's already an active leaving view
-
- // reassign previous entering view to just be inactive
- let enteringView = this.getByState(STATE_INIT_ENTER);
- if (enteringView) {
- enteringView.state = STATE_INACTIVE;
- }
-
- // from the index of the leaving view, go backwards and
- // find the first view that is inactive
- for (var i = this.indexOf(view) - 1; i >= 0; i--) {
- if (this._views[i].state === STATE_INACTIVE) {
- this._views[i].state = STATE_INIT_ENTER;
- break;
- }
- }
-
- } else if (view = this.getByState(STATE_TRANS_LEAVE)) {
- // an active transition is happening, but a new transition
- // still needs to happen force this view to be the active one
- view.state = STATE_FORCE_ACTIVE;
-
- } else if (view = this.getByState(STATE_REMOVE)) {
- // there is no active transition about to happen
- // find the first view that is supposed to be removed and
- // set that it is the init leaving view
- // the first view to be removed, it should init leave
- view.state = STATE_INIT_LEAVE;
- view.fireWillUnload();
- this.viewWillUnload.emit(view);
- this._app.viewWillUnload.emit(view);
-
- // from the index of the leaving view, go backwards and
- // find the first view that is inactive so it can be the entering
- for (var i = this.indexOf(view) - 1; i >= 0; i--) {
- if (this._views[i].state === STATE_INACTIVE) {
- this._views[i].state = STATE_INIT_ENTER;
- break;
- }
- }
- }
-
- // if there is still an active view, then it wasn't one that was
- // set to be removed, so there actually won't be a transition at all
- view = this.getActive();
- if (view) {
- // the active view remains untouched, so all the removes
- // must have happened before it, so really no need for transition
- view = this.getByState(STATE_INIT_ENTER);
- if (view) {
- // if it was going to enter, then just make inactive
- view.state = STATE_INACTIVE;
- }
- view = this.getByState(STATE_INIT_LEAVE);
- if (view) {
- // this was going to leave, so just remove it completely
- view.state = STATE_REMOVE;
- }
- }
-
- // remove views that have been set to be removed, but not
- // apart of any transitions that will eventually happen
- this._views.filter(v => v.state === STATE_REMOVE).forEach(view => {
- view.fireWillLeave();
- view.fireDidLeave();
- this._views.splice(this.indexOf(view), 1);
- view.destroy();
- });
-
- return this.getByState(STATE_INIT_LEAVE);
- }
+ abstract getByIndex(index: number): ViewController;
/**
- * @private
+ * @returns {ViewController} Returns the active page's view controller.
*/
- private _transition(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, done: Function) {
- let transId = ++this._transIds;
-
- if (enteringView === leavingView) {
- // if the entering view and leaving view are the same thing don't continue
- this._transFinish(transId, enteringView, leavingView, null, false);
- return done(false);
- }
-
- if (isBlank(opts)) {
- opts = {};
- }
-
- this._setAnimate(opts);
-
- if (!leavingView) {
- // if no leaving view then create a bogus one
- leavingView = new ViewController();
- }
-
- if (!enteringView) {
- // if no entering view then create a bogus one
- enteringView = new ViewController();
- enteringView.fireLoaded();
- }
-
- /* Async steps to complete a transition
- 1. _render: compile the view and render it in the DOM. Load page if it hasn't loaded already. When done call postRender
- 2. _postRender: Run willEnter/willLeave, then wait a frame (change detection happens), then call beginTransition
- 3. _beforeTrans: Create the transition's animation, play the animation, wait for it to end
- 4. _afterTrans: Run didEnter/didLeave, call _transComplete()
- 5. _transComplete: Cleanup, remove cache views, then call the final callback
- */
-
- // begin the multiple async process of transitioning to the entering view
- this._render(transId, enteringView, leavingView, opts, (hasCompleted: boolean) => {
- this._transFinish(transId, enteringView, leavingView, opts.direction, hasCompleted);
- done(hasCompleted);
- });
- }
+ abstract getActive(): ViewController;
/**
- * @private
+ * Returns if the given view is the active view or not.
+ * @param {ViewController} view
+ * @returns {boolean}
*/
- private _setAnimate(opts: NavOptions) {
- if ((this._views.length === 1 && !this._init && !this.isPortal) || this.config.get('animate') === false) {
- opts.animate = false;
- }
- }
+ abstract isActive(view: ViewController): boolean;
/**
- * @private
+ * Returns the view controller which is before the given view controller.
+ * @param {ViewController} view
+ * @returns {viewController}
*/
- private _render(transId: number, enteringView: ViewController, leavingView: ViewController, opts: NavOptions, done: Function) {
- // compile/load the view into the DOM
-
- if (enteringView.state === STATE_INACTIVE) {
- // this entering view is already set to inactive, so this
- // transition must be canceled, so don't continue
- return done();
- }
-
- enteringView.state = STATE_INIT_ENTER;
- leavingView.state = STATE_INIT_LEAVE;
-
- // remember if this nav is already transitioning or not
- let isAlreadyTransitioning = this.isTransitioning();
-
- if (enteringView.isLoaded()) {
- // already compiled this view, do not load again and continue
- this._postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done);
-
- } else {
- // view has not been compiled/loaded yet
- // continue once the view has finished compiling
- // DOM WRITE
- this.setTransitioning(true, 500);
-
- this.loadPage(enteringView, this._viewport, opts, () => {
- enteringView.fireLoaded();
- this.viewDidLoad.emit(enteringView);
- this._app.viewDidLoad.emit(enteringView);
-
- this._postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done);
- });
- }
- }
+ abstract getPrevious(view: ViewController): ViewController;
/**
- * @private
+ * Returns the first view controller in this nav controller's stack.
+ * @returns {ViewController}
*/
- private _postRender(transId: number, enteringView: ViewController, leavingView: ViewController, isAlreadyTransitioning: boolean, opts: NavOptions, done: Function) {
- // called after _render has completed and the view is compiled/loaded
-
- if (enteringView.state === STATE_INACTIVE) {
- // this entering view is already set to inactive, so this
- // transition must be canceled, so don't continue
- return done();
- }
-
- if (!opts.preload) {
- // the enteringView will become the active view, and is not being preloaded
-
- // set the correct zIndex for the entering and leaving views
- // if there's already another trans_enter happening then
- // the zIndex for the entering view should go off of that one
- // DOM WRITE
- let lastestLeavingView = this.getByState(STATE_TRANS_ENTER) || leavingView;
- this._setZIndex(enteringView, lastestLeavingView, opts.direction);
-
- // make sure the entering and leaving views are showing
- // DOM WRITE
- if (isAlreadyTransitioning) {
- // the previous transition was still going when this one started
- // so to be safe, only update showing the entering/leaving
- // don't hide the others when they could still be transitioning
- enteringView.domShow(true, this._renderer);
- leavingView.domShow(true, this._renderer);
-
- } else {
- // there are no other transitions happening but this one
- // only entering/leaving should show, all others hidden
- // also if a view is an overlay or the previous view is an
- // overlay then always show the overlay and the view before it
- var view: ViewController;
- var shouldShow: boolean;
-
- for (var i = 0, ii = this._views.length; i < ii; i++) {
- view = this._views[i];
- shouldShow = (view === enteringView) ||
- (view === leavingView) ||
- view.isOverlay ||
- (i < ii - 1 ? this._views[i + 1].isOverlay : false);
- view.domShow(shouldShow, this._renderer);
- }
- }
-
- // call each view's lifecycle events
- if (leavingView.fireOtherLifecycles) {
- // only fire entering lifecycle if the leaving
- // view hasn't explicitly set not to
- enteringView.fireWillEnter();
- this.viewWillEnter.emit(enteringView);
- this._app.viewWillEnter.emit(enteringView);
- }
-
- if (enteringView.fireOtherLifecycles) {
- // only fire leaving lifecycle if the entering
- // view hasn't explicitly set not to
- leavingView.fireWillLeave();
- this.viewWillLeave.emit(leavingView);
- this._app.viewWillLeave.emit(leavingView);
- }
-
- } else {
- // this view is being preloaded, don't call lifecycle events
- // transition does not need to animate
- opts.animate = false;
- }
-
- this._beforeTrans(enteringView, leavingView, opts, done);
- }
+ abstract first(): ViewController;
/**
- * @private
+ * Returns the last page in this nav controller's stack.
+ * @returns {ViewController}
*/
- private _beforeTrans(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, done: Function) {
- // called after one raf from postRender()
- // create the transitions animation, play the animation
- // when the transition ends call wait for it to end
-
- if (enteringView.state === STATE_INACTIVE || enteringView.state === STATE_CANCEL_ENTER) {
- // this entering view is already set to inactive or has been canceled
- // so this transition must not begin, so don't continue
- return done();
- }
-
- enteringView.state = STATE_TRANS_ENTER;
- leavingView.state = STATE_TRANS_LEAVE;
-
- // everything during the transition should runOutsideAngular
- this._zone.runOutsideAngular(() => {
-
- // init the transition animation
- let transitionOpts = {
- animation: opts.animation,
- direction: opts.direction,
- duration: opts.duration,
- easing: opts.easing,
- renderDelay: opts.transitionDelay || this._trnsDelay,
- isRTL: this.config.platform.isRTL(),
- ev: opts.ev,
- };
-
- let transAnimation = this._createTrans(enteringView, leavingView, transitionOpts);
-
- this._trans && this._trans.destroy();
- this._trans = transAnimation;
-
- if (opts.animate === false) {
- // force it to not animate the elements, just apply the "to" styles
- transAnimation.duration(0);
- }
-
- // check if a parent is transitioning and get the time that it ends
- let parentTransitionEndTime = this._getLongestTrans(Date.now());
- if (parentTransitionEndTime > 0) {
- // the parent is already transitioning and has disabled the app
- // so just update the local transitioning information
- let duration = parentTransitionEndTime - Date.now();
- this.setTransitioning(true, duration);
-
- } else {
- // this is the only active transition (for now), so disable the app
- let keyboardDurationPadding = 0;
- if (this._keyboard.isOpen()) {
- // add XXms to the duration the app is disabled when the keyboard is open
- keyboardDurationPadding = 600;
- }
- let duration = transAnimation.getDuration() + keyboardDurationPadding;
- let enableApp = (duration < 64);
- this._app.setEnabled(enableApp, duration);
- this.setTransitioning(!enableApp, duration);
- }
-
- // create a callback for when the animation is done
- transAnimation.onFinish((trans: Transition) => {
- // transition animation has ended
-
- // destroy the animation and it's element references
- trans.destroy();
-
- this._afterTrans(enteringView, leavingView, opts, trans.hasCompleted, done);
- });
-
- // 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
- transAnimation.progressStart();
-
- } else {
-
- // this is a normal animation
- // kick it off and let it play through
- transAnimation.play();
- }
- });
- }
+ abstract last(): ViewController;
/**
- * @private
+ * Returns the index number of the given view controller.
+ * @param {ViewController} view
+ * @returns {number}
*/
- private _afterTrans(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, hasCompleted: boolean, done: Function) {
- // transition has completed, update each view's state
- // place back into the zone, run didEnter/didLeave
- // call the final callback when done
-
- // run inside of the zone again
- this._zone.run(() => {
-
- if (!opts.preload && hasCompleted) {
- if (leavingView.fireOtherLifecycles) {
- // only fire entering lifecycle if the leaving
- // view hasn't explicitly set not to
- enteringView.fireDidEnter();
- this.viewDidEnter.emit(enteringView);
- this._app.viewDidEnter.emit(enteringView);
- }
-
- if (enteringView.fireOtherLifecycles && this._init) {
- // only fire leaving lifecycle if the entering
- // view hasn't explicitly set not to
- // and after the nav has initialized
- leavingView.fireDidLeave();
- this.viewDidLeave.emit(leavingView);
- this._app.viewDidLeave.emit(leavingView);
- }
- }
-
- if (enteringView.state === STATE_INACTIVE) {
- // this entering view is already set to inactive, so this
- // transition must be canceled, so don't continue
- return done(hasCompleted);
- }
-
- if (opts.keyboardClose !== false && this._keyboard.isOpen()) {
- // the keyboard is still open!
- // no problem, let's just close for them
- this._keyboard.close();
- this._keyboard.onClose(() => {
-
- // keyboard has finished closing, transition complete
- done(hasCompleted);
- }, 32);
-
- } else {
- // all good, transition complete
- done(hasCompleted);
- }
- });
- }
+ abstract indexOf(view: ViewController): number;
/**
- * @private
+ * Returns the number of views in this nav controller.
+ * @returns {number} The number of views in this stack, including the current view.
*/
- private _transFinish(transId: number, enteringView: ViewController, leavingView: ViewController, direction: string, hasCompleted: boolean) {
- // a transition has completed, but not sure if it's the last one or not
- // check if this transition is the most recent one or not
-
- if (enteringView.state === STATE_CANCEL_ENTER) {
- // this view was told to leave before it finished entering
- this.remove(enteringView.index, 1);
- }
-
- if (transId === this._transIds) {
- // ok, good news, there were no other transitions that kicked
- // off during the time this transition started and ended
-
- if (hasCompleted) {
- // this transition has completed as normal
- // so the entering one is now the active view
- // and the leaving view is now just inactive
- if (enteringView.state !== STATE_REMOVE_AFTER_TRANS) {
- enteringView.state = STATE_ACTIVE;
- }
- if (leavingView.state !== STATE_REMOVE_AFTER_TRANS) {
- leavingView.state = STATE_INACTIVE;
- }
-
- // only need to do all this clean up if the transition
- // completed, otherwise nothing actually changed
- // destroy all of the views that come after the active view
- this._cleanup();
-
- // make sure only this entering view and PREVIOUS view are the
- // only two views that are not display:none
- // do not make any changes to the stack's current visibility
- // if there is an overlay somewhere in the stack
- leavingView = this.getPrevious(enteringView);
- if (this.hasOverlay()) {
- // ensure the entering view is showing
- enteringView.domShow(true, this._renderer);
-
- } else {
- // only possibly hide a view if there are no overlays in the stack
- this._views.forEach(view => {
- let shouldShow = (view === enteringView) || (view === leavingView);
- view.domShow(shouldShow, this._renderer);
- });
- }
-
- // this check only needs to happen once, which will add the css
- // class to the nav when it's finished its first transition
- this._init = true;
-
- } else {
- // this transition has not completed, meaning the
- // entering view did not end up as the active view
- // this would happen when swipe to go back started
- // but the user did not complete the swipe and the
- // what was the active view stayed as the active view
- leavingView.state = STATE_ACTIVE;
- enteringView.state = STATE_INACTIVE;
- }
-
- // check if there is a parent actively transitioning
- let transitionEndTime = this._getLongestTrans(Date.now());
- // if transitionEndTime is greater than 0, there is a parent transition occurring
- // so delegate enabling the app to the parent. If it <= 0, go ahead and enable the app
- if (transitionEndTime <= 0) {
- this._app && this._app.setEnabled(true);
- }
-
- // update that this nav is not longer actively transitioning
- this.setTransitioning(false);
-
- // see if we should add the swipe back gesture listeners or not
- this._sbCheck();
-
- } else {
- // darn, so this wasn't the most recent transition
- // so while this one did end, there's another more recent one
- // still going on. Because a new transition is happening,
- // then this entering view isn't actually going to be the active
- // one, so only update the state to active/inactive if the state
- // wasn't already updated somewhere else during its transition
- if (enteringView.state === STATE_TRANS_ENTER) {
- enteringView.state = STATE_INACTIVE;
- }
- if (leavingView.state === STATE_TRANS_LEAVE) {
- leavingView.state = STATE_INACTIVE;
- }
- }
- }
+ abstract length(): number;
/**
- *@private
- * This method is just a wrapper to the Transition function of same name
- * to make it easy/possible to mock the method call by overriding the function.
- * In testing we don't want to actually do the animation, we want to return a stub instead
+ * Returns the active child navigation.
*/
- private _createTrans(enteringView: ViewController, leavingView: ViewController, transitionOpts: any) {
- return Transition.createTransition(enteringView, leavingView, transitionOpts);
- }
-
- private _cleanup() {
- // ok, cleanup time!! Destroy all of the views that are
- // INACTIVE and come after the active view
- let activeViewIndex = this.indexOf(this.getActive());
- let destroys = this._views.filter(v => v.state === STATE_REMOVE_AFTER_TRANS);
-
- for (var i = activeViewIndex + 1; i < this._views.length; i++) {
- if (this._views[i].state === STATE_INACTIVE) {
- destroys.push(this._views[i]);
- }
- }
-
- // all pages being destroyed should be removed from the list of
- // pages and completely removed from the dom
- destroys.forEach(view => {
- this._views.splice(this.indexOf(view), 1);
- view.destroy();
- this.viewDidUnload.emit(view);
- this._app.viewDidUnload.emit(view);
- });
-
- // if any z-index goes under 0, then reset them all
- let shouldResetZIndex = this._views.some(v => v.zIndex < 0);
- if (shouldResetZIndex) {
- this._views.forEach(view => {
- view.setZIndex(view.zIndex + INIT_ZINDEX + 1, this._renderer);
- });
- }
- }
+ abstract getActiveChildNav(): any;
/**
- * @private
+ * Returns if the nav controller is actively transitioning or not.
+ * @return {boolean}
*/
- getActiveChildNav(): any {
- return this._children[this._children.length - 1];
- }
-
- /**
- * @private
- */
- registerChildNav(nav: any) {
- this._children.push(nav);
- }
-
- /**
- * @private
- */
- unregisterChildNav(nav: any) {
- let index = this._children.indexOf(nav);
- if (index > -1) {
- this._children.splice(index, 1);
- }
- }
-
- /**
- * @private
- */
- ngOnDestroy() {
- for (var i = this._views.length - 1; i >= 0; i--) {
- this._views[i].destroy();
- }
- this._views.length = 0;
-
- if (this.parent && this.parent.unregisterChildNav) {
- this.parent.unregisterChildNav(this);
- }
- }
-
- /**
- * @private
- */
- loadPage(view: ViewController, viewport: ViewContainerRef, opts: NavOptions, done: Function) {
- if (!viewport || !view.componentType) {
- return;
- }
-
- // TEMPORARY: automatically set selector w/ dah reflector
- // TODO: use componentFactory.create once fixed
- addSelector(view.componentType, 'ion-page');
-
- this._compiler.resolveComponent(view.componentType).then(componentFactory => {
-
- if (view.state === STATE_CANCEL_ENTER) {
- // view may have already been removed from the stack
- // if so, don't even bother adding it
- view.destroy();
- this._views.splice(view.index, 1);
- return;
- }
-
- // add more providers to just this page
- let componentProviders = ReflectiveInjector.resolve([
- provide(NavController, {useValue: this}),
- provide(ViewController, {useValue: view}),
- provide(NavParams, {useValue: view.getNavParams()})
- ]);
-
- let childInjector = ReflectiveInjector.fromResolvedProviders(componentProviders, this._viewport.parentInjector);
-
- let componentRef = componentFactory.create(childInjector, null, null);
-
- viewport.insert(componentRef.hostView, viewport.length);
-
- // a new ComponentRef has been created
- // set the ComponentRef's instance to its ViewController
- view.setInstance(componentRef.instance);
-
- // the component has been loaded, so call the view controller's loaded method to load any dependencies into the dom
- view.loaded(() => {
-
- // the ElementRef of the actual ion-page created
- let pageElementRef = componentRef.location;
-
- // remember the ChangeDetectorRef for this ViewController
- view.setChangeDetector(componentRef.changeDetectorRef);
-
- // remember the ElementRef to the ion-page elementRef that was just created
- view.setPageRef(pageElementRef);
-
- // auto-add page css className created from component JS class name
- let cssClassName = pascalCaseToDashCase(view.componentType.name);
- this._renderer.setElementClass(pageElementRef.nativeElement, cssClassName, true);
-
- view.onDestroy(() => {
- // ensure the element is cleaned up for when the view pool reuses this element
- this._renderer.setElementAttribute(pageElementRef.nativeElement, 'class', null);
- this._renderer.setElementAttribute(pageElementRef.nativeElement, 'style', null);
- componentRef.destroy();
- });
-
- // our job is done here
- done(view);
- });
- });
- }
-
- /**
- * @private
- */
- swipeBackStart() {
- // default the direction to "back"
- let opts: NavOptions = {
- direction: DIRECTION_BACK,
- progressAnimation: true
- };
-
- // figure out the states of each view in the stack
- let leavingView = this._remove(this._views.length - 1, 1);
-
- if (leavingView) {
- opts.animation = leavingView.getTransitionName(opts.direction);
-
- // get the view thats ready to enter
- let enteringView = this.getByState(STATE_INIT_ENTER);
-
- // start the transition, fire callback when done...
- this._transition(enteringView, leavingView, opts, (hasCompleted: boolean) => {
- // swipe back has finished!!
- console.debug('swipeBack, hasCompleted', hasCompleted);
- });
- }
- }
-
- /**
- * @private
- */
- swipeBackProgress(stepValue: number) {
- if (this._trans && this._sbGesture) {
- // continue to disable the app while actively dragging
- this._app.setEnabled(false, 4000);
- this.setTransitioning(true, 4000);
-
- // set the transition animation's progress
- this._trans.progressStep(stepValue);
- }
- }
-
- /**
- * @private
- */
- swipeBackEnd(shouldComplete: boolean, currentStepValue: number) {
- if (this._trans && this._sbGesture) {
- // the swipe back gesture has ended
- this._trans.progressEnd(shouldComplete, currentStepValue);
- }
- }
-
- /**
- * @private
- */
- private _sbCheck() {
- if (this._sbEnabled) {
- // this nav controller can have swipe to go back
-
- if (!this._sbGesture) {
- // create the swipe back gesture if we haven't already
- let opts = {
- edge: 'left',
- threshold: this._sbThreshold
- };
- this._sbGesture = new SwipeBackGesture(this.getNativeElement(), opts, this, this._menuCtrl);
- }
-
- 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();
- }
- }
- }
+ abstract isTransitioning(includeAncestors?: boolean): boolean
/**
* If it's possible to use swipe back or not. If it's not possible
@@ -1408,231 +374,13 @@ export class NavController extends Ion {
* will return `true`.
* @returns {boolean}
*/
- canSwipeBack(): boolean {
- return (this._sbEnabled && !this.isTransitioning() && this._app.isEnabled() && this.canGoBack());
- }
+ abstract canSwipeBack(): boolean;
/**
* Returns `true` if there's a valid previous page that we can pop
* back to. Otherwise returns `false`.
* @returns {boolean}
*/
- canGoBack(): boolean {
- let activeView = this.getActive();
- if (activeView) {
- return activeView.enableBack();
- }
- return false;
- }
+ abstract canGoBack(): boolean;
- /**
- * Returns if the nav controller is actively transitioning or not.
- * @return {boolean}
- */
- isTransitioning(includeAncestors?: boolean): boolean {
- let now = Date.now();
- if (includeAncestors && this._getLongestTrans(now) > 0) {
- return true;
- }
- return (this.trnsTime > now);
- }
-
- /**
- * @private
- */
- setTransitioning(isTransitioning: boolean, fallback: number = 700) {
- this.trnsTime = (isTransitioning ? Date.now() + fallback : 0);
- }
-
- /**
- * @private
- * This method traverses the tree of parents upwards
- * and looks at the time the transition ends (if it's transitioning)
- * and returns the value that is the furthest into the future
- * thus giving us the longest transition duration
- */
- private _getLongestTrans(now: number) {
- let parentNav = this.parent;
- let transitionEndTime = -1;
- while (parentNav) {
- if (parentNav.trnsTime > transitionEndTime) {
- transitionEndTime = parentNav.trnsTime;
- }
- parentNav = parentNav.parent;
- }
- // only check if the transitionTime is greater than the current time once
- return transitionEndTime > 0 && transitionEndTime > now ? transitionEndTime : 0;
- }
-
- /**
- * @private
- */
- hasOverlay(): boolean {
- for (var i = this._views.length - 1; i >= 0; i--) {
- if (this._views[i].isOverlay) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * @private
- */
- getByState(state: number): ViewController {
- for (var i = this._views.length - 1; i >= 0; i--) {
- if (this._views[i].state === state) {
- return this._views[i];
- }
- }
- return null;
- }
-
- /**
- * @param {number} index The index of the page to get.
- * @returns {ViewController} Returns the view controller that matches the given index.
- */
- getByIndex(index: number): ViewController {
- return (index < this._views.length && index > -1 ? this._views[index] : null);
- }
-
- /**
- * @returns {ViewController} Returns the active page's view controller.
- */
- getActive(): ViewController {
- return this.getByState(STATE_ACTIVE);
- }
-
- /**
- * @param {ViewController} view
- * @returns {boolean}
- */
- isActive(view: ViewController): boolean {
- return !!(view && view.state === STATE_ACTIVE);
- }
-
- /**
- * Returns the view controller which is before the given view controller.
- * @param {ViewController} view
- * @returns {viewController}
- */
- getPrevious(view: ViewController): ViewController {
- return this.getByIndex(this.indexOf(view) - 1);
- }
-
- /**
- * Returns the first view controller in this nav controller's stack.
- * @returns {ViewController}
- */
- first(): ViewController {
- return (this._views.length ? this._views[0] : null);
- }
-
- /**
- * Returns the last page in this nav controller's stack.
- * @returns {ViewController}
- */
- last(): ViewController {
- return (this._views.length ? this._views[this._views.length - 1] : null);
- }
-
- /**
- * Returns the index number of the given view controller.
- * @param {ViewController} view
- * @returns {number}
- */
- indexOf(view: ViewController): number {
- return this._views.indexOf(view);
- }
-
- /**
- * Returns the number of views in this nav controller.
- * @returns {number} The number of views in this stack, including the current view.
- */
- length(): number {
- return this._views.length;
- }
-
- /**
- * @private
- */
- isSwipeBackEnabled(): boolean {
- return this._sbEnabled;
- }
-
- /**
- * Returns the root `NavController`.
- * @returns {NavController}
- */
- get rootNav(): NavController {
- let nav = this;
- while (nav.parent) {
- nav = nav.parent;
- }
- return nav;
- }
-
- /**
- * @private
- * Dismiss all pages which have set the `dismissOnPageChange` property.
- */
- dismissPageChangeViews() {
- this._views.forEach(view => {
- if (view.data && view.data.dismissOnPageChange) {
- view.dismiss();
- }
- });
- }
-
- /**
- * @private
- */
- private _setZIndex(enteringView: ViewController, leavingView: ViewController, direction: string) {
- if (enteringView) {
- // get the leaving view, which could be in various states
- if (!leavingView || !leavingView.isLoaded()) {
- // the leavingView is a mocked view, either we're
- // actively transitioning or it's the initial load
-
- var previousView = this.getPrevious(enteringView);
- if (previousView && previousView.isLoaded()) {
- // we found a better previous view to reference
- // use this one instead
- enteringView.setZIndex(previousView.zIndex + 1, this._renderer);
-
- } else {
- // this is the initial view
- enteringView.setZIndex(this.isPortal ? PORTAL_ZINDEX : INIT_ZINDEX, this._renderer);
- }
-
- } else if (direction === DIRECTION_BACK) {
- // moving back
- enteringView.setZIndex(leavingView.zIndex - 1, this._renderer);
-
- } else {
- // moving forward
- enteringView.setZIndex(leavingView.zIndex + 1, this._renderer);
- }
- }
- }
-
-}
-
-const STATE_ACTIVE = 1;
-const STATE_INACTIVE = 2;
-const STATE_INIT_ENTER = 3;
-const STATE_INIT_LEAVE = 4;
-const STATE_TRANS_ENTER = 5;
-const STATE_TRANS_LEAVE = 6;
-const STATE_REMOVE = 7;
-const STATE_REMOVE_AFTER_TRANS = 8;
-const STATE_CANCEL_ENTER = 9;
-const STATE_FORCE_ACTIVE = 10;
-
-export const DIRECTION_BACK = 'back';
-export const DIRECTION_FORWARD = 'forward';
-
-const INIT_ZINDEX = 100;
-const PORTAL_ZINDEX = 9999;
-
-let ctrlIds = -1;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/components/nav/nav-interfaces.ts b/src/components/nav/nav-interfaces.ts
index 6337470c28..55f2278053 100644
--- a/src/components/nav/nav-interfaces.ts
+++ b/src/components/nav/nav-interfaces.ts
@@ -13,3 +13,6 @@ export interface NavOptions {
climbNav?: boolean;
ev?: any;
}
+
+export const DIRECTION_BACK = 'back';
+export const DIRECTION_FORWARD = 'forward';
diff --git a/src/components/nav/nav-pop.ts b/src/components/nav/nav-pop.ts
index 6a3bbed73b..3ed4e37076 100644
--- a/src/components/nav/nav-pop.ts
+++ b/src/components/nav/nav-pop.ts
@@ -1,11 +1,12 @@
-import { Directive, Optional } from '@angular/core';
+import { Directive, HostListener, Input, Optional } from '@angular/core';
import { NavController } from './nav-controller';
-
+import { noop } from '../../util/util';
/**
* @name NavPop
* @description
- * Directive for declaratively pop the current page off from the navigation stack.
+ * Directive to declaratively pop the current page off from the
+ * navigation stack.
*
* @usage
* ```html
@@ -22,11 +23,7 @@ import { NavController } from './nav-controller';
* @see {@link ../NavPush NavPush API Docs}
*/
@Directive({
- selector: '[nav-pop]',
- host: {
- '(click)': 'onClick()',
- 'role': 'link'
- }
+ selector: '[navPop]'
})
export class NavPop {
@@ -36,10 +33,15 @@ export class NavPop {
}
}
- /**
- * @private
- */
- onClick() {
- this._nav && this._nav.pop();
+ @HostListener('click')
+ onClick(): boolean {
+ // If no target, or if target is _self, prevent default browser behavior
+ if (this._nav) {
+ this._nav.pop(null, noop);
+ return false;
+ }
+
+ return true;
}
+
}
diff --git a/src/components/nav/nav-portal.ts b/src/components/nav/nav-portal.ts
index d7bf91b1fd..38ee8a4b6b 100644
--- a/src/components/nav/nav-portal.ts
+++ b/src/components/nav/nav-portal.ts
@@ -2,9 +2,9 @@ import { ComponentResolver, Directive, ElementRef, forwardRef, Inject, NgZone, O
import { App } from '../app/app';
import { Config } from '../../config/config';
+import { GestureController } from '../../gestures/gesture-controller';
import { Keyboard } from '../../util/keyboard';
-import { MenuController } from '../menu/menu-controller';
-import { NavController } from '../nav/nav-controller';
+import { NavControllerBase } from '../nav/nav-controller-base';
/**
* @private
@@ -12,7 +12,7 @@ import { NavController } from '../nav/nav-controller';
@Directive({
selector: '[nav-portal]'
})
-export class NavPortal extends NavController {
+export class NavPortal extends NavControllerBase {
constructor(
@Inject(forwardRef(() => App)) app: App,
config: Config,
@@ -21,11 +21,11 @@ export class NavPortal extends NavController {
zone: NgZone,
renderer: Renderer,
compiler: ComponentResolver,
- menuCtrl: MenuController,
+ gestureCtrl: GestureController,
viewPort: ViewContainerRef
) {
- super(null, app, config, keyboard, elementRef, zone, renderer, compiler, menuCtrl);
- this.isPortal = true;
+ super(null, app, config, keyboard, elementRef, zone, renderer, compiler, gestureCtrl);
+ this._isPortal = true;
this.setViewport(viewPort);
app.setPortal(this);
diff --git a/src/components/nav/nav-push.ts b/src/components/nav/nav-push.ts
index 8437077aa2..c22414b9d0 100644
--- a/src/components/nav/nav-push.ts
+++ b/src/components/nav/nav-push.ts
@@ -1,28 +1,35 @@
-import { Directive, Input, Optional } from '@angular/core';
+import { Directive, HostListener, Input, Optional } from '@angular/core';
import { NavController } from './nav-controller';
+import { noop } from '../../util/util';
/**
* @name NavPush
* @description
- * Directive for declaratively linking to a new page instead of using
- * {@link ../NavController/#push NavController.push}. Similar to ui-router's `ui-sref`.
+ * Directive to declaratively push a new page to the current nav
+ * stack.
*
* @usage
* ```html
*
* ```
- * To specify parameters you can use array syntax or the `nav-params` property:
+ *
+ * To specify parameters you can use array syntax or the `navParams`
+ * property:
+ *
* ```html
- *
+ *
* ```
- * Where `pushPage` and `params` are specified in your component, and `pushPage`
- * contains a reference to a [@Page component](../../../config/Page/):
+ *
+ * Where `pushPage` and `params` are specified in your component,
+ * and `pushPage` contains a reference to a
+ * [@Page component](../../../config/Page/):
*
* ```ts
- * import {LoginPage} from 'login';
+ * import { LoginPage } from './login';
+ *
* @Component({
- * template: ``
+ * template: ``
* })
* class MyPage {
* constructor(){
@@ -32,61 +39,42 @@ import { NavController } from './nav-controller';
* }
* ```
*
- * ### Alternate syntax
- * You can also use syntax similar to Angular2's router, passing an array to
- * NavPush:
- * ```html
- *
- * ```
* @demo /docs/v2/demos/navigation/
* @see {@link /docs/v2/components#navigation Navigation Component Docs}
* @see {@link ../NavPop NavPop API Docs}
+ *
*/
@Directive({
- selector: '[navPush]',
- host: {
- '(click)': 'onClick()',
- 'role': 'link'
- }
+ selector: '[navPush]'
})
export class NavPush {
/**
- * @input {Page} the page you want to push
- */
- @Input() navPush: any;
-
- /**
- * @input {any} Any parameters you want to pass along
- */
- @Input() navParams: any;
-
- constructor(
- @Optional() private _nav: NavController
- ) {
- if (!_nav) {
- console.error('nav-push must be within a NavController');
- }
- }
-
- /**
- * @private
+ * @input {Page} The Page to push onto the Nav.
*/
- onClick() {
- let destination: any, params: any;
+ @Input() navPush: any[]|string;
- if (this.navPush instanceof Array) {
- if (this.navPush.length > 2) {
- throw 'Too many [navPush] arguments, expects [View, { params }]';
- }
- destination = this.navPush[0];
- params = this.navPush[1] || this.navParams;
+ /**
+ * @input {any} Parameters to pass to the page.
+ */
+ @Input() navParams: {[k: string]: any};
- } else {
- destination = this.navPush;
- params = this.navParams;
+
+ constructor(@Optional() private _nav: NavController) {
+ if (!_nav) {
+ console.error('navPush must be within a NavController');
+ }
+ }
+
+ @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);
+ return false;
}
- this._nav && this._nav.push(destination, params);
+ return true;
}
+
}
diff --git a/src/components/nav/nav.ts b/src/components/nav/nav.ts
index c49123f595..1b6ad476a4 100644
--- a/src/components/nav/nav.ts
+++ b/src/components/nav/nav.ts
@@ -3,9 +3,9 @@ import { AfterViewInit, Component, ComponentResolver, ElementRef, Input, Optiona
import { App } from '../app/app';
import { Config } from '../../config/config';
import { Keyboard } from '../../util/keyboard';
+import { GestureController } from '../../gestures/gesture-controller';
import { isTrueProperty } from '../../util/util';
-import { MenuController } from '../menu/menu-controller';
-import { NavController } from './nav-controller';
+import { NavControllerBase } from './nav-controller-base';
import { ViewController } from './view-controller';
/**
@@ -114,13 +114,13 @@ import { ViewController } from './view-controller';
`,
encapsulation: ViewEncapsulation.None,
})
-export class Nav extends NavController implements AfterViewInit {
+export class Nav extends NavControllerBase implements AfterViewInit {
private _root: any;
private _hasInit: boolean = false;
constructor(
@Optional() viewCtrl: ViewController,
- @Optional() parent: NavController,
+ @Optional() parent: NavControllerBase,
app: App,
config: Config,
keyboard: Keyboard,
@@ -128,9 +128,9 @@ export class Nav extends NavController implements AfterViewInit {
zone: NgZone,
renderer: Renderer,
compiler: ComponentResolver,
- menuCtrl: MenuController
+ gestureCtrl: GestureController
) {
- super(parent, app, config, keyboard, elementRef, zone, renderer, compiler, menuCtrl);
+ super(parent, app, config, keyboard, elementRef, zone, renderer, compiler, gestureCtrl);
if (viewCtrl) {
// an ion-nav can also act as an ion-page within a parent ion-nav
@@ -164,9 +164,6 @@ export class Nav extends NavController implements AfterViewInit {
this._hasInit = true;
if (this._root) {
- if (typeof this._root !== 'function') {
- throw 'The [root] property in must be given a reference to a component class from within the constructor.';
- }
this.push(this._root);
}
}
diff --git a/src/components/nav/swipe-back.ts b/src/components/nav/swipe-back.ts
index 04ab090627..ebd34cfda0 100644
--- a/src/components/nav/swipe-back.ts
+++ b/src/components/nav/swipe-back.ts
@@ -1,6 +1,7 @@
import { assign } from '../../util/util';
+import { GestureController, GestureDelegate, GesturePriority } from '../../gestures/gesture-controller';
import { MenuController } from '../menu/menu-controller';
-import { NavController } from './nav-controller';
+import { NavControllerBase } from './nav-controller-base';
import { SlideData } from '../../gestures/slide-gesture';
import { SlideEdgeGesture } from '../../gestures/slide-edge-gesture';
@@ -10,36 +11,32 @@ export class SwipeBackGesture extends SlideEdgeGesture {
constructor(
element: HTMLElement,
options: any,
- private _nav: NavController,
- private _menuCtrl: MenuController
+ private _nav: NavControllerBase,
+ gestureCtlr: GestureController
) {
super(element, assign({
direction: 'x',
- maxEdgeStart: 75
+ maxEdgeStart: 75,
+ gesture: gestureCtlr.create('goback-swipe', {
+ priority: GesturePriority.GoBackSwipe,
+ })
}, options));
}
- canStart(ev: any) {
+ canStart(ev: any): boolean {
// the gesture swipe angle must be mainly horizontal and the
// gesture distance would be relatively short for a swipe back
// and swipe back must be possible on this nav controller
- if (ev.angle > -40 &&
- ev.angle < 40 &&
- ev.distance < 50 &&
- this._nav.canSwipeBack()) {
- // passed the tests, now see if the super says it's cool or not
- return super.canStart(ev);
- }
-
- // nerp, not today
- return false;
+ return (
+ this._nav.canSwipeBack() &&
+ super.canStart(ev)
+ );
}
- onSlideBeforeStart(slideData: SlideData, ev: any) {
- console.debug('swipeBack, onSlideBeforeStart', ev.srcEvent.type);
- this._nav.swipeBackStart();
- this._menuCtrl.tempDisable(true);
+ onSlideBeforeStart(slideData: SlideData, ev: any) {
+ console.debug('swipeBack, onSlideBeforeStart', ev.type);
+ this._nav.swipeBackStart();
}
onSlide(slide: SlideData) {
@@ -49,15 +46,10 @@ export class SwipeBackGesture extends SlideEdgeGesture {
}
onSlideEnd(slide: SlideData, ev: any) {
- let shouldComplete = (Math.abs(ev.velocityX) > 0.2 || Math.abs(slide.delta) > Math.abs(slide.max) * 0.5);
-
+ let shouldComplete = (Math.abs(slide.velocity) > 0.2 || Math.abs(slide.delta) > Math.abs(slide.max) * 0.5);
let currentStepValue = (slide.distance / slide.max);
console.debug('swipeBack, onSlideEnd, shouldComplete', shouldComplete, 'currentStepValue', currentStepValue);
-
this._nav.swipeBackEnd(shouldComplete, currentStepValue);
-
- this._menuCtrl.tempDisable(false);
}
-
}
diff --git a/src/components/nav/test/basic/index.ts b/src/components/nav/test/basic/index.ts
index 2b675d2808..12b1645f14 100644
--- a/src/components/nav/test/basic/index.ts
+++ b/src/components/nav/test/basic/index.ts
@@ -1,6 +1,6 @@
import { Component, ViewChild } from '@angular/core';
import { NavController, AlertController, Content } from '../../../../../src';
-import { ionicBootstrap } from '../../../../../src';
+import { ionicBootstrap, App } from '../../../../../src';
import { NavParams, ViewController } from '../../../../../src';;
@@ -148,6 +148,7 @@ class FirstPage {
class FullPage {
constructor(
private nav: NavController,
+ private app: App,
private alertCtrl: AlertController,
private params: NavParams
) {}
@@ -184,8 +185,8 @@ class FullPage {
// overlays are added and removed from the root navigation
// ensure you using the root navigation, and pop this alert
// when the alert is done animating out, then pop off the active page
- this.nav.rootNav.pop().then(() => {
- this.nav.rootNav.pop();
+ this.app.getRootNav().pop().then(() => {
+ this.app.getRootNav().pop();
});
// by default an alert will dismiss itself
diff --git a/src/components/nav/test/child-navs/index.ts b/src/components/nav/test/child-navs/index.ts
index ae12253bbb..f1af093062 100644
--- a/src/components/nav/test/child-navs/index.ts
+++ b/src/components/nav/test/child-navs/index.ts
@@ -1,5 +1,5 @@
-import {Component} from '@angular/core';
-import {ionicBootstrap, NavController} from '../../../../../src';
+import { Component} from '@angular/core';
+import { ionicBootstrap, NavController } from '../../../../../src';
@Component({
template: ``,
@@ -29,11 +29,11 @@ ionicBootstrap(E2EApp);
})
class LandingPage{
- constructor(private _navController: NavController){
+ constructor(private nav: NavController){
}
goToPage(){
- this._navController.push(FirstPage);
+ this.nav.push(FirstPage);
}
}
diff --git a/src/components/nav/test/insert-views/index.ts b/src/components/nav/test/insert-views/index.ts
index e2a85e4c04..e0ba9b1356 100644
--- a/src/components/nav/test/insert-views/index.ts
+++ b/src/components/nav/test/insert-views/index.ts
@@ -1,5 +1,5 @@
-import {Component} from '@angular/core';
-import {ionicBootstrap, NavController} from '../../../../../src';
+import { Component} from '@angular/core';
+import { ionicBootstrap, NavController } from '../../../../../src';
@Component({
diff --git a/src/components/nav/test/memory/index.ts b/src/components/nav/test/memory/index.ts
index d0af5d4555..232f982b91 100644
--- a/src/components/nav/test/memory/index.ts
+++ b/src/components/nav/test/memory/index.ts
@@ -1,5 +1,5 @@
-import {Component} from '@angular/core';
-import {ionicBootstrap, NavController} from '../../../../../src';
+import { Component} from '@angular/core';
+import { ionicBootstrap, NavController } from '../../../../../src';
let delay = 100;
@@ -16,7 +16,7 @@ let count = 0;
`
})
class Page1 {
- tmr;
+ tmr: number;
constructor(private nav: NavController) {}
@@ -50,7 +50,7 @@ class Page1 {
`
})
class Page2 {
- tmr;
+ tmr: number;
constructor(private nav: NavController) {}
diff --git a/src/components/nav/test/nav-controller.spec.ts b/src/components/nav/test/nav-controller.spec.ts
index 213a1238e0..d609791eba 100644
--- a/src/components/nav/test/nav-controller.spec.ts
+++ b/src/components/nav/test/nav-controller.spec.ts
@@ -1,1739 +1,1616 @@
-import { NavController, Tabs, NavOptions, Config, ViewController, App, Platform } from '../../../../src';
+import { Renderer } from '@angular/core';
+import { App, Config, Form, Keyboard, MenuController, NavOptions, Platform, Tabs, ViewController } from '../../../../src';
+import { NavControllerBase } from '../../../../src/components/nav/nav-controller-base';
+import { STATE_ACTIVE, STATE_INACTIVE, STATE_INIT_ENTER, STATE_INIT_LEAVE, STATE_TRANS_ENTER, STATE_TRANS_LEAVE, STATE_REMOVE, STATE_REMOVE_AFTER_TRANS, STATE_CANCEL_ENTER, STATE_FORCE_ACTIVE } from '../../../../src/components/nav/nav-controller-base';
+import { mockNavController, mockElementRef, mockTransition } from '../../../../src/util/mock-providers';
export function run() {
- describe('NavController', () => {
- describe('pop', () => {
+describe('NavController', () => {
- it('should do nothing if its the first view in the stack', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_ACTIVE;
- nav.views = [view1];
+ describe('pop', () => {
- expect(nav.length()).toBe(1);
+ it('should do nothing if its the first view in the stack', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_ACTIVE;
+ nav._views = [view1];
- nav.pop();
+ expect(nav.length()).toBe(1);
- expect(nav.length()).toBe(1);
- expect(nav.getByIndex(0).state).toBe(STATE_ACTIVE);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- });
+ nav.pop();
+ expect(nav.length()).toBe(1);
+ expect(nav.getByIndex(0).state).toBe(STATE_ACTIVE);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
});
- describe('popToRoot', () => {
-
- it('should go back to root', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INACTIVE;
- let view2 = new ViewController(Page2);
- view2.state = STATE_INACTIVE;
- let view3 = new ViewController(Page3);
- view3.state = STATE_INACTIVE;
- let view4 = new ViewController(Page4);
- view4.state = STATE_ACTIVE;
- nav.views = [view1, view2, view3, view4];
-
- nav.popToRoot();
- expect(nav.length()).toBe(2);
- expect(nav.getByIndex(0).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_INIT_LEAVE);
- expect(nav.getByIndex(1).componentType).toBe(Page4);
-
- expect(view2.state).toBe(STATE_REMOVE);
- expect(view3.state).toBe(STATE_REMOVE);
- });
-
- });
-
- describe('popTo', () => {
-
- it('should go back two views', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INACTIVE;
- let view2 = new ViewController(Page2);
- view2.state = STATE_INACTIVE;
- let view3 = new ViewController(Page3);
- view3.state = STATE_INACTIVE;
- let view4 = new ViewController(Page4);
- view4.state = STATE_ACTIVE;
- nav.views = [view1, view2, view3, view4];
-
- nav.popTo(view2);
-
- expect(nav.length()).toBe(3);
- expect(nav.getByIndex(0).state).toBe(STATE_INACTIVE);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(1).componentType).toBe(Page2);
- expect(view3.state).toBe(STATE_REMOVE);
- expect(nav.getByIndex(2).state).toBe(STATE_INIT_LEAVE);
- expect(nav.getByIndex(2).componentType).toBe(Page4);
- });
-
- });
-
- describe('remove', () => {
-
- it('should create opts if passed in arg is undefined or null', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INACTIVE;
- let view2 = new ViewController(Page2);
- view2.state = STATE_ACTIVE;
- nav.views = [view1, view2];
-
- nav.remove(1, 1, null);
- });
-
- });
-
- describe('_remove', () => {
-
- it('should reassign activily transitioning leave that isnt getting removed, to become force active', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INACTIVE;
- let view2 = new ViewController(Page2);
- view2.state = STATE_TRANS_LEAVE;
- let view3 = new ViewController(Page3);
- view3.state = STATE_TRANS_ENTER;
- nav.views = [view1, view2, view3];
-
- nav._remove(2, 1);
-
- expect(nav.getByIndex(0).state).toBe(STATE_INACTIVE);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_FORCE_ACTIVE);
- expect(nav.getByIndex(1).componentType).toBe(Page2);
- expect(nav.getByIndex(2).state).toBe(STATE_REMOVE_AFTER_TRANS);
- expect(nav.getByIndex(2).componentType).toBe(Page3);
- });
-
- it('should reassign activily transitioning views that should be removed to STATE_REMOVE_AFTER_TRANS', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INACTIVE;
- let view2 = new ViewController(Page2);
- view2.state = STATE_TRANS_ENTER;
- let view3 = new ViewController(Page3);
- view3.state = STATE_TRANS_LEAVE;
- nav.views = [view1, view2, view3];
-
- nav._remove(1, 2);
- expect(nav.getByIndex(0).state).toBe(STATE_INACTIVE);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_REMOVE_AFTER_TRANS);
- expect(nav.getByIndex(1).componentType).toBe(Page2);
- expect(nav.getByIndex(2).state).toBe(STATE_REMOVE_AFTER_TRANS);
- expect(nav.getByIndex(2).componentType).toBe(Page3);
- });
-
- it('should keep same init leave, but set previous init enter to inactive', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INACTIVE;
- let view2 = new ViewController(Page2);
- view2.state = STATE_INIT_ENTER;
- let view3 = new ViewController(Page3);
- view3.state = STATE_INIT_LEAVE;
- nav.views = [view1, view2, view3];
-
- nav._remove(1, 1);
- expect(nav.length()).toBe(3);
- expect(view1.state).toBe(STATE_INIT_ENTER);
- expect(view2.state).toBe(STATE_CANCEL_ENTER);
- expect(view3.state).toBe(STATE_INIT_LEAVE);
-
- expect(nav.getByIndex(0).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_CANCEL_ENTER);
- expect(nav.getByIndex(1).componentType).toBe(Page2);
- expect(nav.getByIndex(2).state).toBe(STATE_INIT_LEAVE);
- expect(nav.getByIndex(2).componentType).toBe(Page3);
- });
-
- it('should set to pop the active and enter the previous', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INACTIVE;
- let view2 = new ViewController(Page2);
- view2.state = STATE_ACTIVE;
- nav.views = [view1, view2];
-
- nav._remove(1, 1);
- expect(view1.state).toBe(STATE_INIT_ENTER);
- expect(view2.state).toBe(STATE_INIT_LEAVE);
- });
-
- it('should set to remove 2 views before active one, active stays the same', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INACTIVE;
- let view2 = new ViewController(Page2);
- view2.state = STATE_INACTIVE;
- let view3 = new ViewController(Page3);
- view3.state = STATE_INACTIVE;
- let view4 = new ViewController(Page4);
- view4.state = STATE_INACTIVE;
- let view5 = new ViewController(Page5);
- view5.state = STATE_ACTIVE;
- nav.views = [view1, view2, view3, view4, view5];
-
- nav._remove(2, 2);
- expect(nav.length()).toBe(3);
- expect(view1.state).toBe(STATE_INACTIVE);
- expect(view2.state).toBe(STATE_INACTIVE);
- expect(view3.state).toBe(STATE_REMOVE);
- expect(view4.state).toBe(STATE_REMOVE);
- expect(view5.state).toBe(STATE_ACTIVE);
-
- expect(nav.getByIndex(0).state).toBe(STATE_INACTIVE);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_INACTIVE);
- expect(nav.getByIndex(1).componentType).toBe(Page2);
- expect(nav.getByIndex(2).state).toBe(STATE_ACTIVE);
- expect(nav.getByIndex(2).componentType).toBe(Page5);
- });
-
- it('should set to remove all views other than the first', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INACTIVE;
- let view2 = new ViewController(Page2);
- view2.state = STATE_INACTIVE;
- let view3 = new ViewController(Page3);
- view3.state = STATE_INACTIVE;
- let view4 = new ViewController(Page4);
- view4.state = STATE_ACTIVE;
- nav.views = [view1, view2, view3, view4];
-
- nav._remove(1, 9999);
- expect(nav.length()).toBe(2);
- expect(view1.state).toBe(STATE_INIT_ENTER);
- expect(view2.state).toBe(STATE_REMOVE);
- expect(view3.state).toBe(STATE_REMOVE);
- expect(view4.state).toBe(STATE_INIT_LEAVE);
-
- expect(nav.getByIndex(0).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_INIT_LEAVE);
- expect(nav.getByIndex(1).componentType).toBe(Page4);
- });
-
- it('should set to remove 3 views and enter the first inactive one, remove includes active one', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INACTIVE;
- let view2 = new ViewController(Page2);
- view2.state = STATE_INACTIVE;
- let view3 = new ViewController(Page3);
- view3.state = STATE_INACTIVE;
- let view4 = new ViewController(Page4);
- view4.state = STATE_ACTIVE;
- nav.views = [view1, view2, view3, view4];
-
- nav._remove(1, 3);
- expect(nav.length()).toBe(2);
- expect(view1.state).toBe(STATE_INIT_ENTER);
- expect(view2.state).toBe(STATE_REMOVE);
- expect(view3.state).toBe(STATE_REMOVE);
- expect(view4.state).toBe(STATE_INIT_LEAVE);
-
- expect(nav.getByIndex(0).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_INIT_LEAVE);
- expect(nav.getByIndex(1).componentType).toBe(Page4);
- });
-
- it('should set to remove the active and enter the previous', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INACTIVE;
- let view2 = new ViewController(Page2);
- view2.state = STATE_ACTIVE;
- nav.views = [view1, view2];
-
- nav._remove(1, 1);
- expect(view1.state).toBe(STATE_INIT_ENTER);
- expect(view2.state).toBe(STATE_INIT_LEAVE);
- });
-
- it('should set to remove the only view in the stack', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_ACTIVE;
- nav.views = [view1];
-
- nav._remove(0, 1);
- expect(nav.getByIndex(0).state).toBe(STATE_INIT_LEAVE);
- });
-
- it('should call willLeave/didLeave/destroy on views with STATE_REMOVE', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INACTIVE;
- let view2 = new ViewController(Page2);
- view2.state = STATE_INACTIVE;
- let view3 = new ViewController(Page3);
- view3.state = STATE_INACTIVE;
- let view4 = new ViewController(Page4);
- view4.state = STATE_ACTIVE;
- nav.views = [view1, view2, view3, view4];
-
- spyOn(view1, 'fireWillLeave');
- spyOn(view1, 'fireDidLeave');
- spyOn(view1, 'destroy');
-
- spyOn(view2, 'fireWillLeave');
- spyOn(view2, 'fireDidLeave');
- spyOn(view2, 'destroy');
-
- spyOn(view3, 'fireWillLeave');
- spyOn(view3, 'fireDidLeave');
- spyOn(view3, 'destroy');
-
- spyOn(view4, 'fireWillLeave');
- spyOn(view4, 'fireDidLeave');
- spyOn(view4, 'destroy');
-
- nav._remove(1, 3);
- expect(nav.length()).toBe(2);
- expect(view1.state).toBe(STATE_INIT_ENTER);
- expect(view2.state).toBe(STATE_REMOVE);
- expect(view3.state).toBe(STATE_REMOVE);
- expect(view4.state).toBe(STATE_INIT_LEAVE);
-
- expect(view1.fireWillLeave).not.toHaveBeenCalled();
- expect(view1.fireDidLeave).not.toHaveBeenCalled();
- expect(view1.destroy).not.toHaveBeenCalled();
-
- expect(view2.fireWillLeave).toHaveBeenCalled();
- expect(view2.fireDidLeave).toHaveBeenCalled();
- expect(view2.destroy).toHaveBeenCalled();
-
- expect(view3.fireWillLeave).toHaveBeenCalled();
- expect(view3.fireDidLeave).toHaveBeenCalled();
- expect(view3.destroy).toHaveBeenCalled();
-
- expect(view4.fireWillLeave).not.toHaveBeenCalled();
- expect(view4.fireDidLeave).not.toHaveBeenCalled();
- expect(view4.destroy).not.toHaveBeenCalled();
- });
- });
-
- 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).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_ACTIVE);
- expect(nav.getByIndex(1).componentType).toBe(Page2);
- expect(nav.getByIndex(2).state).toBe(STATE_TRANS_ENTER);
- expect(nav.getByIndex(2).componentType).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.destroy).not.toHaveBeenCalled();
- expect(view2.destroy).toHaveBeenCalled();
- expect(view3.destroy).toHaveBeenCalled();
- });
-
- it('should reset zIndexes if their is a negative zindex', () => {
- let view1 = new ViewController(Page1);
- view1.setPageRef( getElementRef() );
- view1.state = STATE_INACTIVE;
- view1.zIndex = -1;
-
- let view2 = new ViewController(Page2);
- view2.setPageRef( getElementRef() );
- view2.state = STATE_INACTIVE;
- view2.zIndex = 0;
-
- let view3 = new ViewController(Page3);
- view3.setPageRef( getElementRef() );
- 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);
- });
- });
-
- describe('_postRender', () => {
- it('should immediately call done when enteringView state is inactive', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INACTIVE;
- var wasCalled = false;
- var done = () => {
- wasCalled = true;
- };
- nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
-
- nav._postRender(1, view1, null, false, null, done);
-
- expect(wasCalled).toBe(true);
- });
-
- it('should call willEnter on entering view', () => {
- let enteringView = new ViewController(Page1);
- let leavingView = new ViewController(Page2);
- var navOptions: NavOptions = {};
- var done = () => {};
- nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
-
- spyOn(enteringView, 'fireWillEnter');
-
- nav._postRender(1, enteringView, leavingView, false, navOptions, done);
-
- expect(enteringView.fireWillEnter).toHaveBeenCalled();
- });
-
- it('should not call willEnter on entering view when it is being preloaded', () => {
- let enteringView = new ViewController(Page1);
- let leavingView = new ViewController(Page2);
- var navOptions: NavOptions = {
- preload: true
- };
- var done = () => {};
- nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
-
- spyOn(enteringView, 'fireWillEnter');
-
- nav._postRender(1, enteringView, leavingView, false, navOptions, done);
-
- expect(enteringView.fireWillEnter).not.toHaveBeenCalled();
- });
-
- it('should call willLeave on leaving view', () => {
- let enteringView = new ViewController(Page1);
- let leavingView = new ViewController(Page2);
- var navOptions: NavOptions = {};
- var done = () => {};
- nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
-
- spyOn(leavingView, 'fireWillLeave');
-
- nav._postRender(1, enteringView, leavingView, false, navOptions, done);
-
- expect(leavingView.fireWillLeave).toHaveBeenCalled();
- });
-
- it('should not call willEnter when the leaving view has fireOtherLifecycles not true', () => {
- let enteringView = new ViewController(Page1);
- let leavingView = new ViewController(Page2);
- var navOptions: NavOptions = {};
- var done = () => {};
- nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
-
- spyOn(enteringView, 'fireWillEnter');
- spyOn(leavingView, 'fireWillLeave');
-
- leavingView.fireOtherLifecycles = false;
-
- nav._postRender(1, enteringView, leavingView, false, navOptions, done);
-
- expect(enteringView.fireWillEnter).not.toHaveBeenCalled();
- expect(leavingView.fireWillLeave).toHaveBeenCalled();
- });
-
- it('should not call willLeave when the entering view has fireOtherLifecycles not true', () => {
- let enteringView = new ViewController(Page1);
- let leavingView = new ViewController(Page2);
- var navOptions: NavOptions = {};
- var done = () => {};
- nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
-
- spyOn(enteringView, 'fireWillEnter');
- spyOn(leavingView, 'fireWillLeave');
-
- enteringView.fireOtherLifecycles = false;
-
- nav._postRender(1, enteringView, leavingView, false, navOptions, done);
-
- expect(enteringView.fireWillEnter).toHaveBeenCalled();
- expect(leavingView.fireWillLeave).not.toHaveBeenCalled();
- });
-
- it('should not call willLeave on leaving view when it is being preloaded', () => {
- let enteringView = new ViewController(Page1);
- let leavingView = new ViewController(Page2);
- var navOptions: NavOptions = {
- preload: true
- };
- var done = () => {};
- nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
-
- spyOn(leavingView, 'fireWillLeave');
-
- nav._postRender(1, enteringView, leavingView, false, navOptions, done);
-
- expect(leavingView.fireWillLeave).not.toHaveBeenCalled();
- });
-
- it('should set animate false when preloading', () => {
- let enteringView = new ViewController(Page1);
- let leavingView = new ViewController(Page2);
- var navOptions: NavOptions = {
- preload: true
- };
- var done = () => {};
- nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
-
- nav._postRender(1, enteringView, leavingView, false, navOptions, done);
-
- expect(navOptions.animate).toBe(false);
- });
-
- it('should set domShow true when isAlreadyTransitioning', () => {
- let enteringView = new ViewController(Page1);
- let leavingView = new ViewController(Page2);
- let isAlreadyTransitioning = true;
- var navOptions: NavOptions = {};
- var done = () => {};
- nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
- nav._renderer = null;
-
- spyOn(enteringView, 'domShow');
- spyOn(leavingView, 'domShow');
-
- nav._postRender(1, enteringView, leavingView, isAlreadyTransitioning, navOptions, done);
-
- expect(enteringView.domShow).toHaveBeenCalledWith(true, nav._renderer);
- expect(leavingView.domShow).toHaveBeenCalledWith(true, nav._renderer);
- });
-
- it('should set domShow true when isAlreadyTransitioning false for the entering/leaving views', () => {
- let view1 = new ViewController(Page1);
- let view2 = new ViewController(Page2);
- let view3 = new ViewController(Page3);
- let isAlreadyTransitioning = false;
- var navOptions: NavOptions = {};
- var done = () => {};
- nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
- nav._renderer = null;
- nav.views = [view1, view2, view3];
-
- spyOn(view1, 'domShow');
- spyOn(view2, 'domShow');
- spyOn(view3, 'domShow');
-
- nav._postRender(1, view3, view2, isAlreadyTransitioning, navOptions, done);
-
- expect(view1.domShow).toHaveBeenCalledWith(false, nav._renderer);
- expect(view2.domShow).toHaveBeenCalledWith(true, nav._renderer);
- expect(view3.domShow).toHaveBeenCalledWith(true, nav._renderer);
- });
-
- it('should set domShow true when isAlreadyTransitioning false for views when a view has isOverlay=true', () => {
- let view1 = new ViewController(Page1);
- let view2 = new ViewController(Page2);
- let view3 = new ViewController(Page3);
- let view4 = new ViewController(Page4);
- let isAlreadyTransitioning = false;
- var navOptions: NavOptions = {};
- var done = () => {};
- nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
- nav._renderer = null;
- nav.views = [view1, view2, view3, view4];
-
- view3.isOverlay = true;
-
- spyOn(view1, 'domShow');
- spyOn(view2, 'domShow');
- spyOn(view3, 'domShow');
- spyOn(view4, 'domShow');
-
- nav._postRender(1, view4, view3, isAlreadyTransitioning, navOptions, done);
-
- expect(view1.domShow).toHaveBeenCalledWith(false, nav._renderer);
- expect(view2.domShow).toHaveBeenCalledWith(true, nav._renderer);
- expect(view3.domShow).toHaveBeenCalledWith(true, nav._renderer);
- expect(view4.domShow).toHaveBeenCalledWith(true, nav._renderer);
- });
-
- });
-
- describe('_setZIndex', () => {
-
- it('should set zIndex off of the previous view to the entering view is loaded and the leavingView is not loaded', () => {
- let leavingView = new ViewController();
- leavingView.zIndex = 100;
- leavingView._loaded = true;
- let enteringView = new ViewController();
- enteringView.setPageRef({});
-
- nav.views = [leavingView, enteringView];
-
- nav._setZIndex(enteringView, leavingView, 'forward');
- expect(enteringView.zIndex).toEqual(101);
- });
-
- it('should set zIndex 100 when leaving view is not loaded', () => {
- let leavingView = new ViewController();
- leavingView._loaded = false;
- let enteringView = new ViewController();
- enteringView.setPageRef({});
-
- nav.views = [leavingView, enteringView];
-
- nav._setZIndex(enteringView, leavingView, 'forward');
- expect(enteringView.zIndex).toEqual(100);
- });
-
- it('should set zIndex 100 on first entering view', () => {
- let enteringView = new ViewController();
- enteringView.setPageRef({});
- nav._setZIndex(enteringView, null, 'forward');
- expect(enteringView.zIndex).toEqual(100);
- });
-
- it('should set zIndex 1 on second entering view', () => {
- let leavingView = new ViewController();
- leavingView.zIndex = 0;
- leavingView._loaded = true;
- let enteringView = new ViewController();
- enteringView.setPageRef({});
- nav._setZIndex(enteringView, leavingView, 'forward');
- expect(enteringView.zIndex).toEqual(1);
- });
-
- it('should set zIndex 0 on entering view going back', () => {
- let leavingView = new ViewController();
- leavingView.zIndex = 1;
- leavingView._loaded = true;
- let enteringView = new ViewController();
- enteringView.setPageRef({});
- nav._setZIndex(enteringView, leavingView, 'back');
- expect(enteringView.zIndex).toEqual(0);
- });
-
- it('should set zIndex 9999 on first entering portal view', () => {
- let enteringView = new ViewController();
- enteringView.setPageRef({});
- nav.isPortal = true;
- nav._setZIndex(enteringView, null, 'forward');
- expect(enteringView.zIndex).toEqual(9999);
- });
-
- it('should set zIndex 10000 on second entering portal view', () => {
- let leavingView = new ViewController();
- leavingView.zIndex = 9999;
- leavingView._loaded = true;
- let enteringView = new ViewController();
- enteringView.setPageRef({});
- nav._portal = null;
- nav._setZIndex(enteringView, leavingView, 'forward');
- expect(enteringView.zIndex).toEqual(10000);
- });
-
- it('should set zIndex 9999 on entering portal view going back', () => {
- let leavingView = new ViewController();
- leavingView.zIndex = 10000;
- leavingView._loaded = true;
- let enteringView = new ViewController();
- enteringView.setPageRef({});
- nav._portal = null;
- nav._setZIndex(enteringView, leavingView, 'back');
- expect(enteringView.zIndex).toEqual(9999);
- });
-
- });
-
- describe('_setAnimate', () => {
-
- it('should be unchanged when the nav is a portal', () => {
- nav.views = [new ViewController()];
- nav._init = false;
- nav.isPortal = true;
- let opts: NavOptions = {};
- nav._setAnimate(opts);
- expect(opts.animate).toBeUndefined();
- });
-
- it('should not animate when theres only 1 view, and nav hasnt initialized yet', () => {
- nav.views = [new ViewController()];
- nav._init = false;
- let opts: NavOptions = {};
- nav._setAnimate(opts);
- expect(opts.animate).toEqual(false);
- });
-
- it('should be unchanged when theres only 1 view, and nav has already initialized', () => {
- nav.views = [new ViewController()];
- nav._init = true;
- let opts: NavOptions = {};
- nav._setAnimate(opts);
- expect(opts.animate).toBeUndefined();
- });
-
- it('should not animate with config animate = false, and has initialized', () => {
- config.set('animate', false);
- nav._init = true;
- let opts: NavOptions = {};
- nav._setAnimate(opts);
- expect(opts.animate).toEqual(false);
- });
-
- it('should not animate with config animate = false, and has not initialized', () => {
- config.set('animate', false);
- nav._init = false;
- let opts: NavOptions = {};
- nav._setAnimate(opts);
- expect(opts.animate).toEqual(false);
- });
-
- });
-
- describe('_afterTrans', () => {
-
- it('should call didEnter/didLeave', () => {
- let enteringView = new ViewController();
- let leavingView = new ViewController();
- let navOpts: NavOptions = {};
- let hasCompleted = true;
- let doneCalled = false;
- let done = () => {doneCalled = true;}
-
- spyOn(enteringView, 'fireDidEnter');
- spyOn(leavingView, 'fireDidLeave');
-
- nav._init = true;
- nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
-
- expect(enteringView.fireDidEnter).toHaveBeenCalled();
- expect(leavingView.fireDidLeave).toHaveBeenCalled();
- expect(doneCalled).toBe(true);
- });
-
- it('should not call didEnter/didLeave when preloaded', () => {
- let enteringView = new ViewController();
- let leavingView = new ViewController();
- let navOpts: NavOptions = {
- preload: true
- };
- let hasCompleted = true;
- let doneCalled = false;
- let done = () => {doneCalled = true;}
-
- spyOn(enteringView, 'fireDidEnter');
- spyOn(leavingView, 'fireDidLeave');
-
- nav._init = true;
- nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
-
- expect(enteringView.fireDidEnter).not.toHaveBeenCalled();
- expect(leavingView.fireDidLeave).not.toHaveBeenCalled();
- expect(doneCalled).toBe(true);
- });
-
- it('should not call didLeave when enteringView set fireOtherLifecycles to false', () => {
- let enteringView = new ViewController();
- let leavingView = new ViewController();
- let navOpts: NavOptions = {};
- let hasCompleted = true;
- let doneCalled = false;
- let done = () => {doneCalled = true;}
-
- enteringView.fireOtherLifecycles = false;
-
- spyOn(enteringView, 'fireDidEnter');
- spyOn(leavingView, 'fireDidLeave');
-
- nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
-
- expect(enteringView.fireDidEnter).toHaveBeenCalled();
- expect(leavingView.fireDidLeave).not.toHaveBeenCalled();
- expect(doneCalled).toBe(true);
- });
-
- it('should not call didEnter when leavingView set fireOtherLifecycles to false', () => {
- let enteringView = new ViewController();
- let leavingView = new ViewController();
- let navOpts: NavOptions = {};
- let hasCompleted = true;
- let doneCalled = false;
- let done = () => {doneCalled = true;}
-
- leavingView.fireOtherLifecycles = false;
-
- spyOn(enteringView, 'fireDidEnter');
- spyOn(leavingView, 'fireDidLeave');
-
- nav._init = true;
- nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
-
- expect(enteringView.fireDidEnter).not.toHaveBeenCalled();
- expect(leavingView.fireDidLeave).toHaveBeenCalled();
- expect(doneCalled).toBe(true);
- });
-
- it('should not call didEnter/didLeave when not hasCompleted', () => {
- let enteringView = new ViewController();
- let leavingView = new ViewController();
- let navOpts: NavOptions = {};
- let hasCompleted = false;
- let doneCalled = false;
- let done = () => {doneCalled = true;}
-
- spyOn(enteringView, 'fireDidEnter');
- spyOn(leavingView, 'fireDidLeave');
-
- nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
-
- expect(enteringView.fireDidEnter).not.toHaveBeenCalled();
- expect(leavingView.fireDidLeave).not.toHaveBeenCalled();
- expect(doneCalled).toBe(true);
- });
-
- });
-
- describe('_transFinish', () => {
-
- it('should remove entering view if it was already set to cancel', () => {
- let enteringView = new ViewController(Page1);
- let leavingView = new ViewController(Page2);
- enteringView.state = STATE_CANCEL_ENTER;
-
- spyOn(nav, 'remove');
-
- nav._transFinish(1, enteringView, leavingView, 'forward', true);
-
- expect(nav.remove).toHaveBeenCalled();
- expect(enteringView.state).toBe(STATE_CANCEL_ENTER);
- });
-
- it('should not entering/leaving state, after transition that isnt the most recent, and state already changed', () => {
- let enteringView = new ViewController(Page1);
- enteringView.state = 'somethingelse';
- let leavingView = new ViewController(Page2);
- leavingView.state = 'somethingelse';
-
- nav._transIds = 2;
-
- nav._transFinish(1, enteringView, leavingView, 'forward', true);
-
- expect(enteringView.state).toBe('somethingelse');
- expect(leavingView.state).toBe('somethingelse');
- });
-
- it('should set entering/leaving to inactive, after transition that isnt the most recent', () => {
- let enteringView = new ViewController(Page1);
- enteringView.state = STATE_TRANS_ENTER;
- let leavingView = new ViewController(Page2);
- leavingView.state = STATE_TRANS_LEAVE;
-
- nav._transIds = 2;
-
- nav._transFinish(1, enteringView, leavingView, 'forward', true);
-
- expect(enteringView.state).toBe(STATE_INACTIVE);
- expect(leavingView.state).toBe(STATE_INACTIVE);
- });
-
- it('should set entering active, leaving inactive, after transition', () => {
- let enteringView = new ViewController(Page1);
- enteringView.state = STATE_TRANS_ENTER;
- let leavingView = new ViewController(Page2);
- leavingView.state = STATE_TRANS_LEAVE;
-
- nav._transIds = 1;
-
- nav._transFinish(1, enteringView, leavingView, 'forward', true);
-
- expect(enteringView.state).toBe(STATE_ACTIVE);
- expect(leavingView.state).toBe(STATE_INACTIVE);
- });
-
- it('should set entering inactive, leaving active, after transition has not completed', () => {
- let enteringView = new ViewController(Page1);
- enteringView.state = STATE_TRANS_ENTER;
- let leavingView = new ViewController(Page2);
- leavingView.state = STATE_TRANS_LEAVE;
-
- nav._transIds = 1;
-
- nav._transFinish(1, enteringView, leavingView, 'back', false);
-
- expect(enteringView.state).toBe(STATE_INACTIVE);
- expect(leavingView.state).toBe(STATE_ACTIVE);
- });
-
- it('should run cleanup when most recent transition and has completed', () => {
- let enteringView = new ViewController(Page1);
- enteringView.state = STATE_TRANS_ENTER;
- let leavingView = new ViewController(Page2);
- leavingView.state = STATE_TRANS_LEAVE;
- let hasCompleted = true;
-
- spyOn(nav, '_cleanup');
-
- nav._transIds = 1;
-
- nav._transFinish(1, enteringView, leavingView, 'back', hasCompleted);
-
- expect(nav._cleanup).toHaveBeenCalled();
- });
-
- it('should not run cleanup when most not recent transition', () => {
- let enteringView = new ViewController(Page1);
- enteringView.state = STATE_TRANS_ENTER;
- let leavingView = new ViewController(Page2);
- leavingView.state = STATE_TRANS_LEAVE;
- let hasCompleted = true;
-
- spyOn(nav, '_cleanup');
-
- nav._transIds = 1;
-
- nav._transFinish(2, enteringView, leavingView, 'back', hasCompleted);
-
- expect(nav._cleanup).not.toHaveBeenCalled();
- });
-
- it('should not run cleanup when it hasnt completed transition, but is the most recent', () => {
- let enteringView = new ViewController(Page1);
- enteringView.state = STATE_TRANS_ENTER;
- let leavingView = new ViewController(Page2);
- leavingView.state = STATE_TRANS_LEAVE;
- let hasCompleted = false;
-
- spyOn(nav, '_cleanup');
-
- nav._transIds = 1;
-
- nav._transFinish(1, enteringView, leavingView, 'back', hasCompleted);
-
- expect(nav._cleanup).not.toHaveBeenCalled();
- });
-
- it('should set transitioning is over when most recent transition finishes', () => {
- let enteringView = new ViewController(Page1);
- enteringView.state = STATE_TRANS_ENTER;
- let leavingView = new ViewController(Page2);
- leavingView.state = STATE_TRANS_LEAVE;
- let hasCompleted = true;
-
- spyOn(nav, 'setTransitioning');
-
- nav._transIds = 1;
-
- nav._transFinish(1, enteringView, leavingView, 'back', hasCompleted);
-
- expect(nav.setTransitioning).toHaveBeenCalledWith(false);
- });
-
- it('should set transitioning is not over if its not the most recent transition', () => {
- let enteringView = new ViewController(Page1);
- enteringView.state = STATE_TRANS_ENTER;
- let leavingView = new ViewController(Page2);
- leavingView.state = STATE_TRANS_LEAVE;
- let hasCompleted = true;
-
- spyOn(nav, 'setTransitioning');
-
- nav._transIds = 2;
-
- nav._transFinish(1, enteringView, leavingView, 'back', hasCompleted);
-
- expect(nav.setTransitioning).not.toHaveBeenCalled();
- });
-
- it('should set not run domShow when when any view in the stack has isOverlay=true', () => {
- let view1 = new ViewController(Page1);
- let view2 = new ViewController(Page2);
- let view3 = new ViewController(Page3);
- let view4 = new ViewController(Page4);
- let hasCompleted = true;
- nav.views = [view1, view2, view3, view4];
-
- view1.isOverlay = true;
-
- nav._transIds = 1;
-
- spyOn(view1, 'domShow');
- spyOn(view2, 'domShow');
- spyOn(view3, 'domShow');
- spyOn(view4, 'domShow');
-
- nav._transFinish(1, view4, view3, 'forward', hasCompleted);
-
- expect(view1.domShow).not.toHaveBeenCalled();
- expect(view2.domShow).not.toHaveBeenCalled();
- expect(view3.domShow).not.toHaveBeenCalled();
- expect(view4.domShow).toHaveBeenCalled();
- });
-
- it('should re-enable the app when transition time <= 0', () => {
- // arrange
- let enteringView = new ViewController(Page1);
- enteringView.state = 'somethingelse';
- let leavingView = new ViewController(Page2);
- leavingView.state = 'somethingelse';
- nav._transIds = 1;
- nav._app = {
- setEnabled: () => {}
- };
-
- spyOn(nav._app, 'setEnabled');
- spyOn(nav, 'setTransitioning');
-
- // act
- nav._transFinish(nav._transIds, enteringView, leavingView, 'forward', true);
-
- // assert
- expect(nav._app.setEnabled).toHaveBeenCalledWith(true);
- expect(nav.setTransitioning).toHaveBeenCalledWith(false);
- });
-
- it('should not re-enable app when transition time > 0', () => {
- // arrange
- let enteringView = new ViewController(Page1);
- enteringView.state = 'somethingelse';
- let leavingView = new ViewController(Page2);
- leavingView.state = 'somethingelse';
- nav._transIds = 1;
- nav._app = {
- setEnabled: () => {}
- };
-
- spyOn(nav._app, 'setEnabled');
- spyOn(nav, 'setTransitioning');
-
- nav._getLongestTrans = () => { return 50 };
-
- // act
- nav._transFinish(nav._transIds, enteringView, leavingView, 'forward', true);
-
- // assert
- expect(nav._app.setEnabled).not.toHaveBeenCalled();
- expect(nav.setTransitioning).toHaveBeenCalledWith(false);
- });
-
- });
-
- describe('_insert', () => {
-
- it('should push page when previous transition is still actively transitioning', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_TRANS_ENTER;
- let view2 = new ViewController(Page2);
- view2.state = STATE_TRANS_LEAVE;
- nav.views = [view1, view2];
-
- let view3 = new ViewController(Page3);
- nav._insert(-1, [view3]);
-
- expect(nav.getByIndex(0).state).toBe(STATE_TRANS_ENTER);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_TRANS_LEAVE);
- expect(nav.getByIndex(1).componentType).toBe(Page2);
- expect(nav.getByIndex(2).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(2).componentType).toBe(Page3);
- });
-
- it('should push page when previous transition views init, but havent transitioned yet', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INIT_LEAVE;
- let view2 = new ViewController(Page2);
- view2.state = STATE_INIT_ENTER;
- nav.views = [view1, view2];
-
- let view3 = new ViewController(Page3);
- nav._insert(-1, [view3]);
-
- expect(nav.getByIndex(0).state).toBe(STATE_INIT_LEAVE);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_INACTIVE);
- expect(nav.getByIndex(1).componentType).toBe(Page2);
- expect(nav.getByIndex(2).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(2).componentType).toBe(Page3);
- });
-
- it('should insert multiple pages, back to back, with a starting active page', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_ACTIVE;
- nav.views = [view1];
-
- let view2 = new ViewController(Page2);
- nav._insert(-1, [view2]);
-
- expect(nav.getByIndex(0).state).toBe(STATE_INIT_LEAVE);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(1).componentType).toBe(Page2);
-
- let view3 = new ViewController(Page3);
- nav._insert(-1, [view3]);
-
- expect(nav.getByIndex(0).state).toBe(STATE_INIT_LEAVE);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_INACTIVE);
- expect(nav.getByIndex(1).componentType).toBe(Page2);
- expect(nav.getByIndex(2).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(2).componentType).toBe(Page3);
- });
-
- it('should insert multiple pages, back to back, no starting active page', () => {
- let view1 = new ViewController(Page1);
- nav._insert(-1, [view1]);
- expect(nav.getByIndex(0).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
-
- let view2 = new ViewController(Page2);
- nav._insert(-1, [view2]);
-
- expect(nav.getByIndex(0).state).toBe(STATE_INACTIVE);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(1).componentType).toBe(Page2);
-
- let view3 = new ViewController(Page3);
- nav._insert(1, [view3]);
-
- expect(nav.getByIndex(0).state).toBe(STATE_INACTIVE);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(1).componentType).toBe(Page3);
- expect(nav.getByIndex(2).state).toBe(STATE_INACTIVE);
- expect(nav.getByIndex(2).componentType).toBe(Page2);
- });
-
- it('should push a page, and abort previous init', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INIT_LEAVE;
- let view2 = new ViewController(Page2);
- view2.state = STATE_INIT_ENTER;
- nav.views = [view1, view2];
-
- let view3 = new ViewController(Page3);
- nav._insert(-1, [view3]);
- expect(nav.length()).toBe(3);
-
- expect(nav.getByIndex(0).state).toBe(STATE_INIT_LEAVE);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_INACTIVE);
- expect(nav.getByIndex(1).componentType).toBe(Page2);
- expect(nav.getByIndex(2).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(2).componentType).toBe(Page3);
- });
-
- it('should insert a page between the first and second', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INACTIVE;
- let view2 = new ViewController(Page2);
- view2.state = STATE_ACTIVE;
- nav.views = [view1, view2];
-
- let view3 = new ViewController(Page3);
- nav._insert(1, [view3]);
- expect(nav.length()).toBe(3);
-
- expect(nav.getByIndex(0).state).toBe(STATE_INACTIVE);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(1).componentType).toBe(Page3);
- expect(nav.getByIndex(2).state).toBe(STATE_INIT_LEAVE);
- expect(nav.getByIndex(2).componentType).toBe(Page2);
- });
-
- it('should insert a page before the first', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_ACTIVE;
- nav.views = [view1];
-
- let view2 = new ViewController(Page2);
- nav._insert(0, [view2]);
- expect(nav.getByIndex(0).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(0).componentType).toBe(Page2);
- expect(nav.getByIndex(1).state).toBe(STATE_INIT_LEAVE);
- expect(nav.getByIndex(1).componentType).toBe(Page1);
- });
-
- it('should insert 3 pages', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_ACTIVE;
- nav.views = [view1];
-
- let insertViews = [
- new ViewController(Page2),
- new ViewController(Page3),
- new ViewController(Page4)
- ];
- nav._insert(-1, insertViews);
- expect(nav.length()).toBe(4);
- expect(nav.getByIndex(0).state).toBe(STATE_INIT_LEAVE);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_INACTIVE);
- expect(nav.getByIndex(1).componentType).toBe(Page2);
- expect(nav.getByIndex(2).state).toBe(STATE_INACTIVE);
- expect(nav.getByIndex(2).componentType).toBe(Page3);
- expect(nav.getByIndex(3).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(3).componentType).toBe(Page4);
- });
-
- it('should push the second page', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_ACTIVE;
- nav.views = [view1];
-
- let view2 = new ViewController(Page2)
- nav._insert(-1, [view2]);
- expect(nav.length()).toBe(2);
- expect(nav.getByIndex(0).state).toBe(STATE_INIT_LEAVE);
- expect(nav.getByIndex(0).componentType).toBe(Page1);
- expect(nav.getByIndex(1).state).toBe(STATE_INIT_ENTER);
- expect(nav.getByIndex(1).componentType).toBe(Page2);
- });
-
- it('should push the first page, using a number greater than the length', () => {
- let view1 = new ViewController(Page1)
- nav._insert(8675309, [view1]);
-
- expect(nav.length()).toBe(1);
- expect(nav.getByIndex(0).state).toBe(STATE_INIT_ENTER);
- });
-
- it('should push the first page, using -1', () => {
- let view1 = new ViewController(Page1)
- nav._insert(-1, [view1]);
-
- expect(nav.getByIndex(0).id).toBeDefined();
- expect(nav.length()).toBe(1);
- expect(nav.getByIndex(0).state).toBe(STATE_INIT_ENTER);
- });
-
- });
-
- describe('getActive', () => {
- it('should getActive()', () => {
- expect(nav.getActive()).toBe(null);
- let view1 = new ViewController(Page1);
- view1.state = STATE_INIT_ENTER;
- nav.views = [view1];
- expect(nav.getActive()).toBe(null);
- view1.state = STATE_ACTIVE;
- expect(nav.getActive()).toBe(view1);
- });
- });
-
- describe('getByState', () => {
- it('should getByState()', () => {
- expect(nav.getByState(null)).toBe(null);
-
- let view1 = new ViewController(Page1);
- view1.state = STATE_INIT_ENTER;
- let view2 = new ViewController(Page2);
- view2.state = STATE_INIT_ENTER;
- nav.views = [view1, view2];
-
- expect(nav.getByState('whatever')).toBe(null);
- expect(nav.getByState(STATE_INIT_ENTER)).toBe(view2);
-
- view2.state = STATE_INACTIVE;
- expect(nav.getByState(STATE_INIT_ENTER)).toBe(view1);
-
- view2.state = STATE_ACTIVE;
- expect(nav.getActive()).toBe(view2);
- });
- });
-
- describe('getPrevious', () => {
- it('should getPrevious()', () => {
- expect(nav.getPrevious(null)).toBe(null);
-
- let view1 = new ViewController(Page1);
- let view2 = new ViewController(Page2);
- nav.views = [view1, view2];
-
- expect(nav.getPrevious(view1)).toBe(null);
- expect(nav.getPrevious(view2)).toBe(view1);
- });
- });
-
- describe('first', () => {
- it('should get first()', () => {
- expect(nav.first()).toBe(null);
- let view1 = new ViewController(Page1);
- view1.setNav(nav);
- let view2 = new ViewController(Page2);
- view2.setNav(nav);
- nav.views = [view1];
-
- expect(nav.first()).toBe(view1);
- expect(view1.isFirst()).toBe(true);
-
- nav.views = [view1, view2];
- expect(nav.first()).toBe(view1);
- expect(view1.isFirst()).toBe(true);
- expect(view2.isFirst()).toBe(false);
- });
- });
-
- describe('last', () => {
- it('should get last()', () => {
- expect(nav.last()).toBe(null);
- let view1 = new ViewController(Page1);
- view1.setNav(nav);
- let view2 = new ViewController(Page2);
- view2.setNav(nav);
- nav.views = [view1];
-
- expect(nav.last()).toBe(view1);
- expect(view1.isLast()).toBe(true);
-
- nav.views = [view1, view2];
- expect(nav.last()).toBe(view2);
- expect(view1.isLast()).toBe(false);
- expect(view2.isLast()).toBe(true);
- });
- });
-
- describe('indexOf', () => {
- it('should get indexOf()', () => {
- let view1 = new ViewController(Page1);
- let view2 = new ViewController(Page2);
-
- expect(nav.length()).toBe(0);
- expect(nav.indexOf(view1)).toBe(-1);
-
- nav.views = [view1, view2];
- expect(nav.indexOf(view1)).toBe(0);
- expect(nav.indexOf(view2)).toBe(1);
- expect(nav.length()).toBe(2);
- });
- });
-
- describe('getByIndex', () => {
- it('should get getByIndex()', () => {
- expect(nav.getByIndex(-99)).toBe(null);
- expect(nav.getByIndex(99)).toBe(null);
-
- let view1 = new ViewController(Page1);
- let view2 = new ViewController(Page2);
- nav.views = [view1, view2];
-
- expect(nav.getByIndex(-1)).toBe(null);
- expect(nav.getByIndex(0)).toBe(view1);
- expect(nav.getByIndex(1)).toBe(view2);
- expect(nav.getByIndex(2)).toBe(null);
- });
- });
-
- /* private method */
- describe('_beforeTrans', () => {
-
- it('shouldnt disable app on short transition', () => {
- // arrange
- let executeAssertions = () => {
- // assertions triggerd by callbacks
- expect(app.setEnabled).toHaveBeenCalledWith(true, 50);
- expect(nav.setTransitioning).toHaveBeenCalledWith(false, 50);
- };
- let mockTransition = {
- play: () => {
- executeAssertions();
- },
- getDuration: () => { return 50},
- onFinish: () => {}
- };
- nav._createTrans = () => {
- return mockTransition;
- };
- nav.config = {
- platform : {
- isRTL: () => {}
- }
- };
- let app = {
- setEnabled: () => {}
- };
- nav._app = app;
-
- spyOn(app, 'setEnabled');
- spyOn(nav, 'setTransitioning');
-
- let view1 = new ViewController(Page1);
- let view2 = new ViewController(Page2);
-
- // act
- nav._beforeTrans(view1, view2, {}, () => {});
- });
-
- it('should disable app on longer transition', () => {
- // arrange
- let executeAssertions = () => {
- // assertions triggerd by callbacks
- expect(app.setEnabled).toHaveBeenCalledWith(false, 200);
- expect(nav.setTransitioning).toHaveBeenCalledWith(true, 200);
- };
- let mockTransition = {
- play: () => {
- executeAssertions();
- },
- getDuration: () => { return 200},
- onFinish: () => {}
- };
- nav._createTrans = () => {
- return mockTransition;
- };
- nav.config = {
- platform : {
- isRTL: () => {}
- }
- };
- let app = {
- setEnabled: () => {}
- };
- nav._app = app;
-
- spyOn(app, 'setEnabled');
- spyOn(nav, 'setTransitioning');
-
- let view1 = new ViewController(Page1);
- let view2 = new ViewController(Page2);
-
- // act
- nav._beforeTrans(view1, view2, {}, () => {});
- });
-
- it('should disable app w/ padding when keyboard is open', () => {
- // arrange
- let executeAssertions = () => {
- // assertions triggerd by callbacks
- expect(app.setEnabled.calls.mostRecent().args[0]).toEqual(false);
- expect(app.setEnabled.calls.mostRecent().args[1]).toBeGreaterThan(200);
-
- expect(nav.setTransitioning.calls.mostRecent().args[0]).toEqual(true);
- expect(nav.setTransitioning.calls.mostRecent().args[1]).toBeGreaterThan(200);
- };
- let mockTransition = {
- play: () => {
- executeAssertions();
- },
- getDuration: () => { return 200},
- onFinish: () => {}
- };
- nav._createTrans = () => {
- return mockTransition;
- };
- nav.config = {
- platform : {
- isRTL: () => {}
- }
- };
- let app = {
- setEnabled: () => {}
- };
- nav._app = app;
- nav._keyboard = {
- isOpen: () => true
- };
-
- spyOn(app, 'setEnabled');
- spyOn(nav, 'setTransitioning');
-
- let view1 = new ViewController(Page1);
- let view2 = new ViewController(Page2);
-
- // act
- nav._beforeTrans(view1, view2, {}, () => {});
- });
-
- it('shouldnt update app enabled when parent transition is occurring', () => {
- // arrange
- let executeAssertions = () => {
- // assertions triggerd by callbacks
- expect(app.setEnabled).not.toHaveBeenCalled();
- expect(nav.setTransitioning.calls.mostRecent().args[0]).toEqual(true);
- };
- let mockTransition = {
- play: () => {
- executeAssertions();
- },
- getDuration: () => { return 200},
- onFinish: () => {}
- };
- nav._createTrans = () => {
- return mockTransition;
- };
- nav.config = {
- platform : {
- isRTL: () => {}
- }
- };
- let app = {
- setEnabled: () => {}
- };
- nav._app = app;
-
- spyOn(app, 'setEnabled');
- spyOn(nav, 'setTransitioning');
-
- nav._getLongestTrans = () => { return Date.now() + 100 };
-
- let view1 = new ViewController(Page1);
- let view2 = new ViewController(Page2);
-
- // act
- nav._beforeTrans(view1, view2, {}, () => {});
- });
-
- it('should not begin transition when entering stated is inactive', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_INACTIVE;
-
- let wasDoneCalled = false;
- let done = () => {
- wasDoneCalled = true;
- };
-
- nav._beforeTrans(view1, null, {}, done);
-
- expect(wasDoneCalled).toEqual(true);
- expect(view1.state).toEqual(STATE_INACTIVE);
- });
-
- it('should not begin transition when entering state is canceled', () => {
- let view1 = new ViewController(Page1);
- view1.state = STATE_CANCEL_ENTER;
-
- let wasDoneCalled = false;
- let done = () => {
- wasDoneCalled = true;
- };
-
- nav._beforeTrans(view1, null, {}, done);
-
- expect(wasDoneCalled).toEqual(true);
- expect(view1.state).toEqual(STATE_CANCEL_ENTER);
- });
- });
-
- /* private method */
- describe('_getLongestTrans', () => {
- it('should return 0 when transition end time is less than 0', () => {
- // arrange
- nav.parent = null;
- // act
- let returnedValue = nav._getLongestTrans(Date.now());
- // asssert
- expect(returnedValue).toEqual(0);
- });
-
- it('should return 0 when transition end time is less than now', () => {
- // arrange
- nav.parent = {
- trnsTime: Date.now() - 5
- };
- // act
- let returnedValue = nav._getLongestTrans(Date.now());
- // asssert
- expect(returnedValue).toEqual(0);
- });
-
- it('should return 0 when parent transition time not set', () => {
- // arrange
- nav.parent = {
- trnsTime: undefined
- };
- // act
- let returnedValue = nav._getLongestTrans(Date.now());
- // asssert
- expect(returnedValue).toEqual(0);
- });
-
- it('should return transitionEndTime when transition end time is greater than now', () => {
- // arrange
- let expectedReturnValue = Date.now() + 100;
- nav.parent = {
- trnsTime: expectedReturnValue
- };
- // act
- let returnedValue = nav._getLongestTrans(Date.now());
- // asssert
- expect(returnedValue).toEqual(expectedReturnValue);
- });
-
- it('should return the greatest end of transition time if found on first parent', () => {
- // arrange
- let expectedReturnValue = Date.now() + 100;
- let firstParent = {
- trnsTime: expectedReturnValue
- };
- let secondParent = {
- trnsTime: Date.now() + 50
- };
- let thirdParent = {
- trnsTime: Date.now()
- };
- let fourthParent = {
- trnsTime: Date.now() + 20
- };
- firstParent.parent = secondParent;
- secondParent.parent = thirdParent;
- thirdParent.parent = fourthParent;
- nav.parent = firstParent;
- // act
- let returnedValue = nav._getLongestTrans(Date.now());
- // asssert
- expect(returnedValue).toEqual(expectedReturnValue);
- });
-
- it('should return the greatest end of transition time if found on middle parent', () => {
- // arrange
- let expectedReturnValue = Date.now() + 100;
- let firstParent = {
- trnsTime: Date.now()
- };
- let secondParent = {
- trnsTime: Date.now() + 50
- };
- let thirdParent = {
- trnsTime: expectedReturnValue
- };
- let fourthParent = {
- trnsTime: Date.now() + 20
- };
- firstParent.parent = secondParent;
- secondParent.parent = thirdParent;
- thirdParent.parent = fourthParent;
- nav.parent = firstParent;
- // act
- let returnedValue = nav._getLongestTrans(Date.now());
- // asssert
- expect(returnedValue).toEqual(expectedReturnValue);
- });
-
- it('should return the greatest end of transition time if found on last parent', () => {
- // arrange
- let expectedReturnValue = Date.now() + 100;
- let firstParent = {
- trnsTime: Date.now()
- };
- let secondParent = {
- trnsTime: Date.now() + 50
- };
- let thirdParent = {
- trnsTime: Date.now() + 20
- };
- let fourthParent = {
- trnsTime: expectedReturnValue
- };
- firstParent.parent = secondParent;
- secondParent.parent = thirdParent;
- thirdParent.parent = fourthParent;
- nav.parent = firstParent;
- // act
- let returnedValue = nav._getLongestTrans(Date.now());
- // asssert
- expect(returnedValue).toEqual(expectedReturnValue);
- });
- });
-
- // setup stuff
- let nav: MockNavController;
- let config = new Config();
- let platform = new Platform();
-
- class Page1 {}
- class Page2 {}
- class Page3 {}
- class Page4 {}
- class Page5 {}
-
- beforeEach(() => {
- nav = mockNav();
- });
-
- function mockNav(): MockNavController {
- let elementRef = getElementRef();
-
- let app = new App(config, platform);
- let nav = new MockNavController(null, app, config, null, elementRef, null, null, null);
-
- nav._keyboard = {
- isOpen: function() {
- return false;
- }
- };
- nav._zone = {
- run: function(cb) {
- cb();
- },
- runOutsideAngular: function(cb) {
- cb();
- }
- };
- nav._renderer = {
- setElementAttribute: function(){},
- setElementClass: function(){},
- setElementStyle: function(){}
- };
-
- return nav;
- }
-
- function getElementRef() {
- return {
- nativeElement: document.createElement('div')
- }
- }
-
});
+
+ describe('popToRoot', () => {
+
+ it('should go back to root', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INACTIVE;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_INACTIVE;
+ let view3 = new ViewController(Page3);
+ view3.state = STATE_INACTIVE;
+ let view4 = new ViewController(Page4);
+ view4.state = STATE_ACTIVE;
+ nav._views = [view1, view2, view3, view4];
+
+ nav.popToRoot();
+ expect(nav.length()).toBe(2);
+ expect(nav.getByIndex(0).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_INIT_LEAVE);
+ expect(nav.getByIndex(1).componentType).toBe(Page4);
+
+ expect(view2.state).toBe(STATE_REMOVE);
+ expect(view3.state).toBe(STATE_REMOVE);
+ });
+
+ });
+
+ describe('popTo', () => {
+
+ it('should go back two views', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INACTIVE;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_INACTIVE;
+ let view3 = new ViewController(Page3);
+ view3.state = STATE_INACTIVE;
+ let view4 = new ViewController(Page4);
+ view4.state = STATE_ACTIVE;
+ nav._views = [view1, view2, view3, view4];
+
+ nav.popTo(view2);
+
+ expect(nav.length()).toBe(3);
+ expect(nav.getByIndex(0).state).toBe(STATE_INACTIVE);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(1).componentType).toBe(Page2);
+ expect(view3.state).toBe(STATE_REMOVE);
+ expect(nav.getByIndex(2).state).toBe(STATE_INIT_LEAVE);
+ expect(nav.getByIndex(2).componentType).toBe(Page4);
+ });
+
+ });
+
+ describe('remove', () => {
+
+ it('should create opts if passed in arg is undefined or null', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INACTIVE;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_ACTIVE;
+ nav._views = [view1, view2];
+
+ nav.remove(1, 1, null);
+ });
+
+ });
+
+ describe('_remove', () => {
+
+ it('should reassign activily transitioning leave that isnt getting removed, to become force active', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INACTIVE;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_TRANS_LEAVE;
+ let view3 = new ViewController(Page3);
+ view3.state = STATE_TRANS_ENTER;
+ nav._views = [view1, view2, view3];
+
+ nav._remove(2, 1);
+
+ expect(nav.getByIndex(0).state).toBe(STATE_INACTIVE);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_FORCE_ACTIVE);
+ expect(nav.getByIndex(1).componentType).toBe(Page2);
+ expect(nav.getByIndex(2).state).toBe(STATE_REMOVE_AFTER_TRANS);
+ expect(nav.getByIndex(2).componentType).toBe(Page3);
+ });
+
+ it('should reassign activily transitioning views that should be removed to STATE_REMOVE_AFTER_TRANS', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INACTIVE;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_TRANS_ENTER;
+ let view3 = new ViewController(Page3);
+ view3.state = STATE_TRANS_LEAVE;
+ nav._views = [view1, view2, view3];
+
+ nav._remove(1, 2);
+ expect(nav.getByIndex(0).state).toBe(STATE_INACTIVE);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_REMOVE_AFTER_TRANS);
+ expect(nav.getByIndex(1).componentType).toBe(Page2);
+ expect(nav.getByIndex(2).state).toBe(STATE_REMOVE_AFTER_TRANS);
+ expect(nav.getByIndex(2).componentType).toBe(Page3);
+ });
+
+ it('should keep same init leave, but set previous init enter to inactive', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INACTIVE;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_INIT_ENTER;
+ let view3 = new ViewController(Page3);
+ view3.state = STATE_INIT_LEAVE;
+ nav._views = [view1, view2, view3];
+
+ nav._remove(1, 1);
+ expect(nav.length()).toBe(3);
+ expect(view1.state).toBe(STATE_INIT_ENTER);
+ expect(view2.state).toBe(STATE_CANCEL_ENTER);
+ expect(view3.state).toBe(STATE_INIT_LEAVE);
+
+ expect(nav.getByIndex(0).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_CANCEL_ENTER);
+ expect(nav.getByIndex(1).componentType).toBe(Page2);
+ expect(nav.getByIndex(2).state).toBe(STATE_INIT_LEAVE);
+ expect(nav.getByIndex(2).componentType).toBe(Page3);
+ });
+
+ it('should set to pop the active and enter the previous', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INACTIVE;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_ACTIVE;
+ nav._views = [view1, view2];
+
+ nav._remove(1, 1);
+ expect(view1.state).toBe(STATE_INIT_ENTER);
+ expect(view2.state).toBe(STATE_INIT_LEAVE);
+ });
+
+ it('should set to remove 2 views before active one, active stays the same', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INACTIVE;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_INACTIVE;
+ let view3 = new ViewController(Page3);
+ view3.state = STATE_INACTIVE;
+ let view4 = new ViewController(Page4);
+ view4.state = STATE_INACTIVE;
+ let view5 = new ViewController(Page5);
+ view5.state = STATE_ACTIVE;
+ nav._views = [view1, view2, view3, view4, view5];
+
+ nav._remove(2, 2);
+ expect(nav.length()).toBe(3);
+ expect(view1.state).toBe(STATE_INACTIVE);
+ expect(view2.state).toBe(STATE_INACTIVE);
+ expect(view3.state).toBe(STATE_REMOVE);
+ expect(view4.state).toBe(STATE_REMOVE);
+ expect(view5.state).toBe(STATE_ACTIVE);
+
+ expect(nav.getByIndex(0).state).toBe(STATE_INACTIVE);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_INACTIVE);
+ expect(nav.getByIndex(1).componentType).toBe(Page2);
+ expect(nav.getByIndex(2).state).toBe(STATE_ACTIVE);
+ expect(nav.getByIndex(2).componentType).toBe(Page5);
+ });
+
+ it('should set to remove all views other than the first', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INACTIVE;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_INACTIVE;
+ let view3 = new ViewController(Page3);
+ view3.state = STATE_INACTIVE;
+ let view4 = new ViewController(Page4);
+ view4.state = STATE_ACTIVE;
+ nav._views = [view1, view2, view3, view4];
+
+ nav._remove(1, 9999);
+ expect(nav.length()).toBe(2);
+ expect(view1.state).toBe(STATE_INIT_ENTER);
+ expect(view2.state).toBe(STATE_REMOVE);
+ expect(view3.state).toBe(STATE_REMOVE);
+ expect(view4.state).toBe(STATE_INIT_LEAVE);
+
+ expect(nav.getByIndex(0).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_INIT_LEAVE);
+ expect(nav.getByIndex(1).componentType).toBe(Page4);
+ });
+
+ it('should set to remove 3 views and enter the first inactive one, remove includes active one', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INACTIVE;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_INACTIVE;
+ let view3 = new ViewController(Page3);
+ view3.state = STATE_INACTIVE;
+ let view4 = new ViewController(Page4);
+ view4.state = STATE_ACTIVE;
+ nav._views = [view1, view2, view3, view4];
+
+ nav._remove(1, 3);
+ expect(nav.length()).toBe(2);
+ expect(view1.state).toBe(STATE_INIT_ENTER);
+ expect(view2.state).toBe(STATE_REMOVE);
+ expect(view3.state).toBe(STATE_REMOVE);
+ expect(view4.state).toBe(STATE_INIT_LEAVE);
+
+ expect(nav.getByIndex(0).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_INIT_LEAVE);
+ expect(nav.getByIndex(1).componentType).toBe(Page4);
+ });
+
+ it('should set to remove the active and enter the previous', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INACTIVE;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_ACTIVE;
+ nav._views = [view1, view2];
+
+ nav._remove(1, 1);
+ expect(view1.state).toBe(STATE_INIT_ENTER);
+ expect(view2.state).toBe(STATE_INIT_LEAVE);
+ });
+
+ it('should set to remove the only view in the stack', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_ACTIVE;
+ nav._views = [view1];
+
+ nav._remove(0, 1);
+ expect(nav.getByIndex(0).state).toBe(STATE_INIT_LEAVE);
+ });
+
+ it('should call willLeave/didLeave/destroy on views with STATE_REMOVE', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INACTIVE;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_INACTIVE;
+ let view3 = new ViewController(Page3);
+ view3.state = STATE_INACTIVE;
+ let view4 = new ViewController(Page4);
+ view4.state = STATE_ACTIVE;
+ nav._views = [view1, view2, view3, view4];
+
+ spyOn(view1, 'fireWillLeave');
+ spyOn(view1, 'fireDidLeave');
+ spyOn(view1, 'destroy');
+
+ spyOn(view2, 'fireWillLeave');
+ spyOn(view2, 'fireDidLeave');
+ spyOn(view2, 'destroy');
+
+ spyOn(view3, 'fireWillLeave');
+ spyOn(view3, 'fireDidLeave');
+ spyOn(view3, 'destroy');
+
+ spyOn(view4, 'fireWillLeave');
+ spyOn(view4, 'fireDidLeave');
+ spyOn(view4, 'destroy');
+
+ nav._remove(1, 3);
+ expect(nav.length()).toBe(2);
+ expect(view1.state).toBe(STATE_INIT_ENTER);
+ expect(view2.state).toBe(STATE_REMOVE);
+ expect(view3.state).toBe(STATE_REMOVE);
+ expect(view4.state).toBe(STATE_INIT_LEAVE);
+
+ expect(view1.fireWillLeave).not.toHaveBeenCalled();
+ expect(view1.fireDidLeave).not.toHaveBeenCalled();
+ expect(view1.destroy).not.toHaveBeenCalled();
+
+ expect(view2.fireWillLeave).toHaveBeenCalled();
+ expect(view2.fireDidLeave).toHaveBeenCalled();
+ expect(view2.destroy).toHaveBeenCalled();
+
+ expect(view3.fireWillLeave).toHaveBeenCalled();
+ expect(view3.fireDidLeave).toHaveBeenCalled();
+ expect(view3.destroy).toHaveBeenCalled();
+
+ expect(view4.fireWillLeave).not.toHaveBeenCalled();
+ expect(view4.fireDidLeave).not.toHaveBeenCalled();
+ expect(view4.destroy).not.toHaveBeenCalled();
+ });
+ });
+
+ 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).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_ACTIVE);
+ expect(nav.getByIndex(1).componentType).toBe(Page2);
+ expect(nav.getByIndex(2).state).toBe(STATE_TRANS_ENTER);
+ expect(nav.getByIndex(2).componentType).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.destroy).not.toHaveBeenCalled();
+ expect(view2.destroy).toHaveBeenCalled();
+ expect(view3.destroy).toHaveBeenCalled();
+ });
+
+ it('should reset zIndexes if their is a negative zindex', () => {
+ let view1 = new ViewController(Page1);
+ view1.setPageRef( mockElementRef() );
+ view1.state = STATE_INACTIVE;
+ view1.zIndex = -1;
+
+ let view2 = new ViewController(Page2);
+ view2.setPageRef( mockElementRef() );
+ view2.state = STATE_INACTIVE;
+ view2.zIndex = 0;
+
+ let view3 = new ViewController(Page3);
+ view3.setPageRef( 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);
+ });
+ });
+
+ describe('_postRender', () => {
+ it('should immediately call done when enteringView state is inactive', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INACTIVE;
+ var wasCalled = false;
+ var done = () => {
+ wasCalled = true;
+ };
+ nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
+
+ nav._postRender(1, view1, null, false, null, done);
+
+ expect(wasCalled).toBe(true);
+ });
+
+ it('should call willEnter on entering view', () => {
+ let enteringView = new ViewController(Page1);
+ let leavingView = new ViewController(Page2);
+ var navOptions: NavOptions = {};
+ var done = () => {};
+ nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
+
+ spyOn(enteringView, 'fireWillEnter');
+
+ nav._postRender(1, enteringView, leavingView, false, navOptions, done);
+
+ expect(enteringView.fireWillEnter).toHaveBeenCalled();
+ });
+
+ it('should not call willEnter on entering view when it is being preloaded', () => {
+ let enteringView = new ViewController(Page1);
+ let leavingView = new ViewController(Page2);
+ var navOptions: NavOptions = {
+ preload: true
+ };
+ var done = () => {};
+ nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
+
+ spyOn(enteringView, 'fireWillEnter');
+
+ nav._postRender(1, enteringView, leavingView, false, navOptions, done);
+
+ expect(enteringView.fireWillEnter).not.toHaveBeenCalled();
+ });
+
+ it('should call willLeave on leaving view', () => {
+ let enteringView = new ViewController(Page1);
+ let leavingView = new ViewController(Page2);
+ var navOptions: NavOptions = {};
+ var done = () => {};
+ nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
+
+ spyOn(leavingView, 'fireWillLeave');
+
+ nav._postRender(1, enteringView, leavingView, false, navOptions, done);
+
+ expect(leavingView.fireWillLeave).toHaveBeenCalled();
+ });
+
+ it('should not call willEnter when the leaving view has fireOtherLifecycles not true', () => {
+ let enteringView = new ViewController(Page1);
+ let leavingView = new ViewController(Page2);
+ var navOptions: NavOptions = {};
+ var done = () => {};
+ nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
+
+ spyOn(enteringView, 'fireWillEnter');
+ spyOn(leavingView, 'fireWillLeave');
+
+ leavingView.fireOtherLifecycles = false;
+
+ nav._postRender(1, enteringView, leavingView, false, navOptions, done);
+
+ expect(enteringView.fireWillEnter).not.toHaveBeenCalled();
+ expect(leavingView.fireWillLeave).toHaveBeenCalled();
+ });
+
+ it('should not call willLeave when the entering view has fireOtherLifecycles not true', () => {
+ let enteringView = new ViewController(Page1);
+ let leavingView = new ViewController(Page2);
+ var navOptions: NavOptions = {};
+ var done = () => {};
+ nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
+
+ spyOn(enteringView, 'fireWillEnter');
+ spyOn(leavingView, 'fireWillLeave');
+
+ enteringView.fireOtherLifecycles = false;
+
+ nav._postRender(1, enteringView, leavingView, false, navOptions, done);
+
+ expect(enteringView.fireWillEnter).toHaveBeenCalled();
+ expect(leavingView.fireWillLeave).not.toHaveBeenCalled();
+ });
+
+ it('should not call willLeave on leaving view when it is being preloaded', () => {
+ let enteringView = new ViewController(Page1);
+ let leavingView = new ViewController(Page2);
+ var navOptions: NavOptions = {
+ preload: true
+ };
+ var done = () => {};
+ nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
+
+ spyOn(leavingView, 'fireWillLeave');
+
+ nav._postRender(1, enteringView, leavingView, false, navOptions, done);
+
+ expect(leavingView.fireWillLeave).not.toHaveBeenCalled();
+ });
+
+ it('should set animate false when preloading', () => {
+ let enteringView = new ViewController(Page1);
+ let leavingView = new ViewController(Page2);
+ var navOptions: NavOptions = {
+ preload: true
+ };
+ var done = () => {};
+ nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
+
+ nav._postRender(1, enteringView, leavingView, false, navOptions, done);
+
+ expect(navOptions.animate).toBe(false);
+ });
+
+ it('should set domShow true when isAlreadyTransitioning', () => {
+ let enteringView = new ViewController(Page1);
+ let leavingView = new ViewController(Page2);
+ let isAlreadyTransitioning = true;
+ var navOptions: NavOptions = {};
+ var done = () => {};
+ nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
+ nav._renderer = null;
+
+ spyOn(enteringView, 'domShow');
+ spyOn(leavingView, 'domShow');
+
+ nav._postRender(1, enteringView, leavingView, isAlreadyTransitioning, navOptions, done);
+
+ expect(enteringView.domShow).toHaveBeenCalledWith(true, nav._renderer);
+ expect(leavingView.domShow).toHaveBeenCalledWith(true, nav._renderer);
+ });
+
+ it('should set domShow true when isAlreadyTransitioning false for the entering/leaving views', () => {
+ let view1 = new ViewController(Page1);
+ let view2 = new ViewController(Page2);
+ let view3 = new ViewController(Page3);
+ let isAlreadyTransitioning = false;
+ var navOptions: NavOptions = {};
+ var done = () => {};
+ nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
+ nav._renderer = null;
+ nav._views = [view1, view2, view3];
+
+ spyOn(view1, 'domShow');
+ spyOn(view2, 'domShow');
+ spyOn(view3, 'domShow');
+
+ nav._postRender(1, view3, view2, isAlreadyTransitioning, navOptions, done);
+
+ expect(view1.domShow).toHaveBeenCalledWith(false, nav._renderer);
+ expect(view2.domShow).toHaveBeenCalledWith(true, nav._renderer);
+ expect(view3.domShow).toHaveBeenCalledWith(true, nav._renderer);
+ });
+
+ it('should set domShow true when isAlreadyTransitioning false for views when nav is a portal', () => {
+ let view1 = new ViewController(Page1);
+ let view2 = new ViewController(Page2);
+ let view3 = new ViewController(Page3);
+ let view4 = new ViewController(Page4);
+ let isAlreadyTransitioning = false;
+ var navOptions: NavOptions = {};
+ var done = () => {};
+ nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
+ nav._renderer = null;
+ nav._views = [view1, view2, view3, view4];
+
+ nav._isPortal = true
+
+ spyOn(view1, 'domShow');
+ spyOn(view2, 'domShow');
+ spyOn(view3, 'domShow');
+ spyOn(view4, 'domShow');
+
+ nav._postRender(1, view4, view3, isAlreadyTransitioning, navOptions, done);
+
+ expect(view1.domShow).toHaveBeenCalledWith(true, nav._renderer);
+ expect(view2.domShow).toHaveBeenCalledWith(true, nav._renderer);
+ expect(view3.domShow).toHaveBeenCalledWith(true, nav._renderer);
+ expect(view4.domShow).toHaveBeenCalledWith(true, nav._renderer);
+ });
+
+ });
+
+ describe('_setZIndex', () => {
+
+ it('should set zIndex off of the previous view to the entering view is loaded and the leavingView is not loaded', () => {
+ let leavingView = new ViewController();
+ leavingView.zIndex = 100;
+ leavingView.fireLoaded();
+ let enteringView = new ViewController();
+ enteringView.setPageRef(mockElementRef());
+
+ nav._views = [leavingView, enteringView];
+
+ nav._setZIndex(enteringView, leavingView, 'forward');
+ expect(enteringView.zIndex).toEqual(101);
+ });
+
+ it('should set zIndex 100 when leaving view is not loaded', () => {
+ let leavingView = new ViewController();
+ let enteringView = new ViewController();
+ enteringView.setPageRef(mockElementRef());
+
+ nav._views = [leavingView, enteringView];
+
+ nav._setZIndex(enteringView, leavingView, 'forward');
+ expect(enteringView.zIndex).toEqual(100);
+ });
+
+ it('should set zIndex 100 on first entering view', () => {
+ let enteringView = new ViewController();
+ enteringView.setPageRef(mockElementRef());
+ nav._setZIndex(enteringView, null, 'forward');
+ expect(enteringView.zIndex).toEqual(100);
+ });
+
+ it('should set zIndex 1 on second entering view', () => {
+ let leavingView = new ViewController();
+ leavingView.zIndex = 0;
+ leavingView.fireLoaded();
+ let enteringView = new ViewController();
+ enteringView.setPageRef(mockElementRef());
+ nav._setZIndex(enteringView, leavingView, 'forward');
+ expect(enteringView.zIndex).toEqual(1);
+ });
+
+ it('should set zIndex 0 on entering view going back', () => {
+ let leavingView = new ViewController();
+ leavingView.zIndex = 1;
+ leavingView.fireLoaded();
+ let enteringView = new ViewController();
+ enteringView.setPageRef(mockElementRef());
+ nav._setZIndex(enteringView, leavingView, 'back');
+ expect(enteringView.zIndex).toEqual(0);
+ });
+
+ it('should set zIndex 9999 on first entering portal view', () => {
+ let enteringView = new ViewController();
+ enteringView.setPageRef(mockElementRef());
+ nav._isPortal = true;
+ nav._setZIndex(enteringView, null, 'forward');
+ expect(enteringView.zIndex).toEqual(9999);
+ });
+
+ it('should set zIndex 10000 on second entering portal view', () => {
+ let leavingView = new ViewController();
+ leavingView.zIndex = 9999;
+ leavingView.fireLoaded();
+ let enteringView = new ViewController();
+ enteringView.setPageRef(mockElementRef());
+ nav._isPortal = true;
+ nav._setZIndex(enteringView, leavingView, 'forward');
+ expect(enteringView.zIndex).toEqual(10000);
+ });
+
+ it('should set zIndex 9999 on entering portal view going back', () => {
+ let leavingView = new ViewController();
+ leavingView.zIndex = 10000;
+ leavingView.fireLoaded();
+ let enteringView = new ViewController();
+ enteringView.setPageRef(mockElementRef());
+ nav._isPortal = true;
+ nav._setZIndex(enteringView, leavingView, 'back');
+ expect(enteringView.zIndex).toEqual(9999);
+ });
+
+ });
+
+ describe('_setAnimate', () => {
+
+ it('should be unchanged when the nav is a portal', () => {
+ nav._views = [new ViewController()];
+ nav._init = false;
+ nav._isPortal = true;
+ let opts: NavOptions = {};
+ nav._setAnimate(opts);
+ expect(opts.animate).toBeUndefined();
+ });
+
+ it('should not animate when theres only 1 view, and nav hasnt initialized yet', () => {
+ nav._views = [new ViewController()];
+ nav._init = false;
+ let opts: NavOptions = {};
+ nav._setAnimate(opts);
+ expect(opts.animate).toEqual(false);
+ });
+
+ it('should be unchanged when theres only 1 view, and nav has already initialized', () => {
+ nav._views = [new ViewController()];
+ nav._init = true;
+ let opts: NavOptions = {};
+ nav._setAnimate(opts);
+ expect(opts.animate).toBeUndefined();
+ });
+
+ it('should not animate with config animate = false, and has initialized', () => {
+ nav.config.set('animate', false);
+ nav._init = true;
+ let opts: NavOptions = {};
+ nav._setAnimate(opts);
+ expect(opts.animate).toEqual(false);
+ });
+
+ it('should not animate with config animate = false, and has not initialized', () => {
+ nav.config.set('animate', false);
+ nav._init = false;
+ let opts: NavOptions = {};
+ nav._setAnimate(opts);
+ expect(opts.animate).toEqual(false);
+ });
+
+ });
+
+ describe('_afterTrans', () => {
+
+ it('should call didEnter/didLeave', () => {
+ let enteringView = new ViewController();
+ let leavingView = new ViewController();
+ let navOpts: NavOptions = {};
+ let hasCompleted = true;
+ let doneCalled = false;
+ let done = () => {doneCalled = true;}
+
+ spyOn(enteringView, 'fireDidEnter');
+ spyOn(leavingView, 'fireDidLeave');
+
+ nav._init = true;
+ nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
+
+ expect(enteringView.fireDidEnter).toHaveBeenCalled();
+ expect(leavingView.fireDidLeave).toHaveBeenCalled();
+ expect(doneCalled).toBe(true);
+ });
+
+ it('should not call didEnter/didLeave when preloaded', () => {
+ let enteringView = new ViewController();
+ let leavingView = new ViewController();
+ let navOpts: NavOptions = {
+ preload: true
+ };
+ let hasCompleted = true;
+ let doneCalled = false;
+ let done = () => {doneCalled = true;}
+
+ spyOn(enteringView, 'fireDidEnter');
+ spyOn(leavingView, 'fireDidLeave');
+
+ nav._init = true;
+ nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
+
+ expect(enteringView.fireDidEnter).not.toHaveBeenCalled();
+ expect(leavingView.fireDidLeave).not.toHaveBeenCalled();
+ expect(doneCalled).toBe(true);
+ });
+
+ it('should not call didLeave when enteringView set fireOtherLifecycles to false', () => {
+ let enteringView = new ViewController();
+ let leavingView = new ViewController();
+ let navOpts: NavOptions = {};
+ let hasCompleted = true;
+ let doneCalled = false;
+ let done = () => {doneCalled = true;}
+
+ enteringView.fireOtherLifecycles = false;
+
+ spyOn(enteringView, 'fireDidEnter');
+ spyOn(leavingView, 'fireDidLeave');
+
+ nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
+
+ expect(enteringView.fireDidEnter).toHaveBeenCalled();
+ expect(leavingView.fireDidLeave).not.toHaveBeenCalled();
+ expect(doneCalled).toBe(true);
+ });
+
+ it('should not call didEnter when leavingView set fireOtherLifecycles to false', () => {
+ let enteringView = new ViewController();
+ let leavingView = new ViewController();
+ let navOpts: NavOptions = {};
+ let hasCompleted = true;
+ let doneCalled = false;
+ let done = () => {doneCalled = true;}
+
+ leavingView.fireOtherLifecycles = false;
+
+ spyOn(enteringView, 'fireDidEnter');
+ spyOn(leavingView, 'fireDidLeave');
+
+ nav._init = true;
+ nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
+
+ expect(enteringView.fireDidEnter).not.toHaveBeenCalled();
+ expect(leavingView.fireDidLeave).toHaveBeenCalled();
+ expect(doneCalled).toBe(true);
+ });
+
+ it('should not call didEnter/didLeave when not hasCompleted', () => {
+ let enteringView = new ViewController();
+ let leavingView = new ViewController();
+ let navOpts: NavOptions = {};
+ let hasCompleted = false;
+ let doneCalled = false;
+ let done = () => {doneCalled = true;}
+
+ spyOn(enteringView, 'fireDidEnter');
+ spyOn(leavingView, 'fireDidLeave');
+
+ nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
+
+ expect(enteringView.fireDidEnter).not.toHaveBeenCalled();
+ expect(leavingView.fireDidLeave).not.toHaveBeenCalled();
+ expect(doneCalled).toBe(true);
+ });
+
+ });
+
+ describe('_transFinish', () => {
+
+ it('should remove entering view if it was already set to cancel', () => {
+ let enteringView = new ViewController(Page1);
+ let leavingView = new ViewController(Page2);
+ enteringView.state = STATE_CANCEL_ENTER;
+ let direction = 'foward';
+ let updateUrl = false;
+ let hasCompleted = true;
+
+ spyOn(nav, 'remove');
+
+ nav._transFinish(1, enteringView, leavingView, direction, updateUrl, hasCompleted);
+
+ expect(nav.remove).toHaveBeenCalled();
+ expect(enteringView.state).toBe(STATE_CANCEL_ENTER);
+ });
+
+ it('should not entering/leaving state, after transition that isnt the most recent, and state already changed', () => {
+ let enteringView = new ViewController(Page1);
+ enteringView.state = 234234;
+ let leavingView = new ViewController(Page2);
+ leavingView.state = 234234;
+ let direction = 'foward';
+ let updateUrl = false;
+ let hasCompleted = true;
+
+ nav._transIds = 2;
+
+ nav._transFinish(1, enteringView, leavingView, direction, updateUrl, hasCompleted);
+
+ expect(enteringView.state).toBe(234234);
+ expect(leavingView.state).toBe(234234);
+ });
+
+ it('should set entering/leaving to inactive, after transition that isnt the most recent', () => {
+ let enteringView = new ViewController(Page1);
+ enteringView.state = STATE_TRANS_ENTER;
+ let leavingView = new ViewController(Page2);
+ leavingView.state = STATE_TRANS_LEAVE;
+ let direction = 'foward';
+ let updateUrl = false;
+ let hasCompleted = true;
+
+ nav._transIds = 2;
+
+ nav._transFinish(1, enteringView, leavingView, direction, updateUrl, hasCompleted);
+
+ expect(enteringView.state).toBe(STATE_INACTIVE);
+ expect(leavingView.state).toBe(STATE_INACTIVE);
+ });
+
+ it('should set entering active, leaving inactive, after transition', () => {
+ let enteringView = new ViewController(Page1);
+ enteringView.state = STATE_TRANS_ENTER;
+ let leavingView = new ViewController(Page2);
+ leavingView.state = STATE_TRANS_LEAVE;
+ let direction = 'foward';
+ let updateUrl = false;
+ let hasCompleted = true;
+
+ nav._transIds = 1;
+
+ nav._transFinish(1, enteringView, leavingView, direction, updateUrl, hasCompleted);
+
+ expect(enteringView.state).toBe(STATE_ACTIVE);
+ expect(leavingView.state).toBe(STATE_INACTIVE);
+ });
+
+ it('should set entering inactive, leaving active, after transition has not completed', () => {
+ let enteringView = new ViewController(Page1);
+ enteringView.state = STATE_TRANS_ENTER;
+ let leavingView = new ViewController(Page2);
+ leavingView.state = STATE_TRANS_LEAVE;
+ let direction = 'back';
+ let updateUrl = false;
+ let hasCompleted = false;
+
+ nav._transIds = 1;
+
+ nav._transFinish(1, enteringView, leavingView, direction, updateUrl, hasCompleted);
+
+ expect(enteringView.state).toBe(STATE_INACTIVE);
+ expect(leavingView.state).toBe(STATE_ACTIVE);
+ });
+
+ it('should run cleanup when most recent transition and has completed', () => {
+ let enteringView = new ViewController(Page1);
+ enteringView.state = STATE_TRANS_ENTER;
+ let leavingView = new ViewController(Page2);
+ leavingView.state = STATE_TRANS_LEAVE;
+ let direction = 'back';
+ let updateUrl = false;
+ let hasCompleted = true;
+
+ spyOn(nav, '_cleanup');
+
+ nav._transIds = 1;
+
+ nav._transFinish(1, enteringView, leavingView, direction, updateUrl, hasCompleted);
+
+ expect(nav._cleanup).toHaveBeenCalled();
+ });
+
+ it('should not run cleanup when most not recent transition', () => {
+ let enteringView = new ViewController(Page1);
+ enteringView.state = STATE_TRANS_ENTER;
+ let leavingView = new ViewController(Page2);
+ leavingView.state = STATE_TRANS_LEAVE;
+ let direction = 'back';
+ let updateUrl = false;
+ let hasCompleted = true;
+
+ spyOn(nav, '_cleanup');
+
+ nav._transIds = 1;
+
+ nav._transFinish(2, enteringView, leavingView, direction, updateUrl, hasCompleted);
+
+ expect(nav._cleanup).not.toHaveBeenCalled();
+ });
+
+ it('should not run cleanup when it hasnt completed transition, but is the most recent', () => {
+ let enteringView = new ViewController(Page1);
+ enteringView.state = STATE_TRANS_ENTER;
+ let leavingView = new ViewController(Page2);
+ leavingView.state = STATE_TRANS_LEAVE;
+ let direction = 'back';
+ let updateUrl = false;
+ let hasCompleted = false;
+
+ spyOn(nav, '_cleanup');
+
+ nav._transIds = 1;
+
+ nav._transFinish(1, enteringView, leavingView, direction, updateUrl, hasCompleted);
+
+ expect(nav._cleanup).not.toHaveBeenCalled();
+ });
+
+ it('should set transitioning is over when most recent transition finishes', () => {
+ let enteringView = new ViewController(Page1);
+ enteringView.state = STATE_TRANS_ENTER;
+ let leavingView = new ViewController(Page2);
+ leavingView.state = STATE_TRANS_LEAVE;
+ let direction = 'back';
+ let updateUrl = false;
+ let hasCompleted = true;
+
+ spyOn(nav, 'setTransitioning');
+
+ nav._transIds = 1;
+
+ nav._transFinish(1, enteringView, leavingView, direction, updateUrl, hasCompleted);
+
+ expect(nav.setTransitioning).toHaveBeenCalledWith(false);
+ });
+
+ it('should set transitioning is not over if its not the most recent transition', () => {
+ let enteringView = new ViewController(Page1);
+ enteringView.state = STATE_TRANS_ENTER;
+ let leavingView = new ViewController(Page2);
+ leavingView.state = STATE_TRANS_LEAVE;
+ let direction = 'back';
+ let updateUrl = false;
+ let hasCompleted = true;
+
+ spyOn(nav, 'setTransitioning');
+
+ nav._transIds = 2;
+
+ nav._transFinish(1, enteringView, leavingView, direction, updateUrl, hasCompleted);
+
+ expect(nav.setTransitioning).not.toHaveBeenCalled();
+ });
+
+ it('should re-enable the app when transition time <= 0', () => {
+ // arrange
+ let enteringView = new ViewController(Page1);
+ enteringView.state = 234234;
+ let leavingView = new ViewController(Page2);
+ leavingView.state = 234234;
+ nav._transIds = 1;
+
+ let direction = 'forward';
+ let updateUrl = false;
+ let hasCompleted = true;
+
+ spyOn(nav._app, 'setEnabled');
+ spyOn(nav, 'setTransitioning');
+
+ // act
+ nav._transFinish(nav._transIds, enteringView, leavingView, direction, updateUrl, hasCompleted);
+
+ // assert
+ expect(nav._app.setEnabled).toHaveBeenCalledWith(true);
+ expect(nav.setTransitioning).toHaveBeenCalledWith(false);
+ });
+
+ it('should not re-enable app when transition time > 0', () => {
+ // arrange
+ let enteringView = new ViewController(Page1);
+ enteringView.state = 235234;
+ let leavingView = new ViewController(Page2);
+ leavingView.state = 235234;
+ nav._transIds = 1;
+
+ let direction = 'forward';
+ let updateUrl = false;
+ let hasCompleted = true;
+
+ spyOn(nav._app, 'setEnabled');
+ spyOn(nav, 'setTransitioning');
+
+ nav.getLongestTrans = () => { return 50 };
+
+ // act
+ nav._transFinish(nav._transIds, enteringView, leavingView, direction, updateUrl, hasCompleted);
+
+ // assert
+ expect(nav._app.setEnabled).not.toHaveBeenCalled();
+ expect(nav.setTransitioning).toHaveBeenCalledWith(false);
+ });
+
+ });
+
+ describe('_insert', () => {
+
+ it('should push page when previous transition is still actively transitioning', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_TRANS_ENTER;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_TRANS_LEAVE;
+ nav._views = [view1, view2];
+
+ let view3 = new ViewController(Page3);
+ nav._insert(-1, [view3]);
+
+ expect(nav.getByIndex(0).state).toBe(STATE_TRANS_ENTER);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_TRANS_LEAVE);
+ expect(nav.getByIndex(1).componentType).toBe(Page2);
+ expect(nav.getByIndex(2).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(2).componentType).toBe(Page3);
+ });
+
+ it('should push page when previous transition views init, but havent transitioned yet', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INIT_LEAVE;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_INIT_ENTER;
+ nav._views = [view1, view2];
+
+ let view3 = new ViewController(Page3);
+ nav._insert(-1, [view3]);
+
+ expect(nav.getByIndex(0).state).toBe(STATE_INIT_LEAVE);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_INACTIVE);
+ expect(nav.getByIndex(1).componentType).toBe(Page2);
+ expect(nav.getByIndex(2).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(2).componentType).toBe(Page3);
+ });
+
+ it('should insert multiple pages, back to back, with a starting active page', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_ACTIVE;
+ nav._views = [view1];
+
+ let view2 = new ViewController(Page2);
+ nav._insert(-1, [view2]);
+
+ expect(nav.getByIndex(0).state).toBe(STATE_INIT_LEAVE);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(1).componentType).toBe(Page2);
+
+ let view3 = new ViewController(Page3);
+ nav._insert(-1, [view3]);
+
+ expect(nav.getByIndex(0).state).toBe(STATE_INIT_LEAVE);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_INACTIVE);
+ expect(nav.getByIndex(1).componentType).toBe(Page2);
+ expect(nav.getByIndex(2).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(2).componentType).toBe(Page3);
+ });
+
+ it('should insert multiple pages, back to back, no starting active page', () => {
+ let view1 = new ViewController(Page1);
+ nav._insert(-1, [view1]);
+ expect(nav.getByIndex(0).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+
+ let view2 = new ViewController(Page2);
+ nav._insert(-1, [view2]);
+
+ expect(nav.getByIndex(0).state).toBe(STATE_INACTIVE);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(1).componentType).toBe(Page2);
+
+ let view3 = new ViewController(Page3);
+ nav._insert(1, [view3]);
+
+ expect(nav.getByIndex(0).state).toBe(STATE_INACTIVE);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(1).componentType).toBe(Page3);
+ expect(nav.getByIndex(2).state).toBe(STATE_INACTIVE);
+ expect(nav.getByIndex(2).componentType).toBe(Page2);
+ });
+
+ it('should push a page, and abort previous init', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INIT_LEAVE;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_INIT_ENTER;
+ nav._views = [view1, view2];
+
+ let view3 = new ViewController(Page3);
+ nav._insert(-1, [view3]);
+ expect(nav.length()).toBe(3);
+
+ expect(nav.getByIndex(0).state).toBe(STATE_INIT_LEAVE);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_INACTIVE);
+ expect(nav.getByIndex(1).componentType).toBe(Page2);
+ expect(nav.getByIndex(2).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(2).componentType).toBe(Page3);
+ });
+
+ it('should insert a page between the first and second', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INACTIVE;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_ACTIVE;
+ nav._views = [view1, view2];
+
+ let view3 = new ViewController(Page3);
+ nav._insert(1, [view3]);
+ expect(nav.length()).toBe(3);
+
+ expect(nav.getByIndex(0).state).toBe(STATE_INACTIVE);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(1).componentType).toBe(Page3);
+ expect(nav.getByIndex(2).state).toBe(STATE_INIT_LEAVE);
+ expect(nav.getByIndex(2).componentType).toBe(Page2);
+ });
+
+ it('should insert a page before the first', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_ACTIVE;
+ nav._views = [view1];
+
+ let view2 = new ViewController(Page2);
+ nav._insert(0, [view2]);
+ expect(nav.getByIndex(0).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(0).componentType).toBe(Page2);
+ expect(nav.getByIndex(1).state).toBe(STATE_INIT_LEAVE);
+ expect(nav.getByIndex(1).componentType).toBe(Page1);
+ });
+
+ it('should insert 3 pages', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_ACTIVE;
+ nav._views = [view1];
+
+ let insertViews = [
+ new ViewController(Page2),
+ new ViewController(Page3),
+ new ViewController(Page4)
+ ];
+ nav._insert(-1, insertViews);
+ expect(nav.length()).toBe(4);
+ expect(nav.getByIndex(0).state).toBe(STATE_INIT_LEAVE);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_INACTIVE);
+ expect(nav.getByIndex(1).componentType).toBe(Page2);
+ expect(nav.getByIndex(2).state).toBe(STATE_INACTIVE);
+ expect(nav.getByIndex(2).componentType).toBe(Page3);
+ expect(nav.getByIndex(3).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(3).componentType).toBe(Page4);
+ });
+
+ it('should push the second page', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_ACTIVE;
+ nav._views = [view1];
+
+ let view2 = new ViewController(Page2)
+ nav._insert(-1, [view2]);
+ expect(nav.length()).toBe(2);
+ expect(nav.getByIndex(0).state).toBe(STATE_INIT_LEAVE);
+ expect(nav.getByIndex(0).componentType).toBe(Page1);
+ expect(nav.getByIndex(1).state).toBe(STATE_INIT_ENTER);
+ expect(nav.getByIndex(1).componentType).toBe(Page2);
+ });
+
+ it('should push the first page, using a number greater than the length', () => {
+ let view1 = new ViewController(Page1)
+ nav._insert(8675309, [view1]);
+
+ expect(nav.length()).toBe(1);
+ expect(nav.getByIndex(0).state).toBe(STATE_INIT_ENTER);
+ });
+
+ it('should push the first page, using -1', () => {
+ let view1 = new ViewController(Page1)
+ nav._insert(-1, [view1]);
+
+ expect(nav.getByIndex(0).id).toBeDefined();
+ expect(nav.length()).toBe(1);
+ expect(nav.getByIndex(0).state).toBe(STATE_INIT_ENTER);
+ });
+
+ });
+
+ describe('getActive', () => {
+ it('should getActive()', () => {
+ expect(nav.getActive()).toBe(null);
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INIT_ENTER;
+ nav._views = [view1];
+ expect(nav.getActive()).toBe(null);
+ view1.state = STATE_ACTIVE;
+ expect(nav.getActive()).toBe(view1);
+ });
+ });
+
+ describe('getByState', () => {
+ it('should getByState()', () => {
+ expect(nav.getByState(null)).toBe(null);
+
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INIT_ENTER;
+ let view2 = new ViewController(Page2);
+ view2.state = STATE_INIT_ENTER;
+ nav._views = [view1, view2];
+
+ expect(nav.getByState(8675309)).toBe(null);
+ expect(nav.getByState(STATE_INIT_ENTER)).toBe(view2);
+
+ view2.state = STATE_INACTIVE;
+ expect(nav.getByState(STATE_INIT_ENTER)).toBe(view1);
+
+ view2.state = STATE_ACTIVE;
+ expect(nav.getActive()).toBe(view2);
+ });
+ });
+
+ describe('getPrevious', () => {
+ it('should getPrevious()', () => {
+ expect(nav.getPrevious(null)).toBe(null);
+
+ let view1 = new ViewController(Page1);
+ let view2 = new ViewController(Page2);
+ nav._views = [view1, view2];
+
+ expect(nav.getPrevious(view1)).toBe(null);
+ expect(nav.getPrevious(view2)).toBe(view1);
+ });
+ });
+
+ describe('first', () => {
+ it('should get first()', () => {
+ expect(nav.first()).toBe(null);
+ let view1 = new ViewController(Page1);
+ view1.setNav(nav);
+ let view2 = new ViewController(Page2);
+ view2.setNav(nav);
+ nav._views = [view1];
+
+ expect(nav.first()).toBe(view1);
+ expect(view1.isFirst()).toBe(true);
+
+ nav._views = [view1, view2];
+ expect(nav.first()).toBe(view1);
+ expect(view1.isFirst()).toBe(true);
+ expect(view2.isFirst()).toBe(false);
+ });
+ });
+
+ describe('last', () => {
+ it('should get last()', () => {
+ expect(nav.last()).toBe(null);
+ let view1 = new ViewController(Page1);
+ view1.setNav(nav);
+ let view2 = new ViewController(Page2);
+ view2.setNav(nav);
+ nav._views = [view1];
+
+ expect(nav.last()).toBe(view1);
+ expect(view1.isLast()).toBe(true);
+
+ nav._views = [view1, view2];
+ expect(nav.last()).toBe(view2);
+ expect(view1.isLast()).toBe(false);
+ expect(view2.isLast()).toBe(true);
+ });
+ });
+
+ describe('indexOf', () => {
+ it('should get indexOf()', () => {
+ let view1 = new ViewController(Page1);
+ let view2 = new ViewController(Page2);
+
+ expect(nav.length()).toBe(0);
+ expect(nav.indexOf(view1)).toBe(-1);
+
+ nav._views = [view1, view2];
+ expect(nav.indexOf(view1)).toBe(0);
+ expect(nav.indexOf(view2)).toBe(1);
+ expect(nav.length()).toBe(2);
+ });
+ });
+
+ describe('getByIndex', () => {
+ it('should get getByIndex()', () => {
+ expect(nav.getByIndex(-99)).toBe(null);
+ expect(nav.getByIndex(99)).toBe(null);
+
+ let view1 = new ViewController(Page1);
+ let view2 = new ViewController(Page2);
+ nav._views = [view1, view2];
+
+ expect(nav.getByIndex(-1)).toBe(null);
+ expect(nav.getByIndex(0)).toBe(view1);
+ expect(nav.getByIndex(1)).toBe(view2);
+ expect(nav.getByIndex(2)).toBe(null);
+ });
+ });
+
+ /* private method */
+ describe('_beforeTrans', () => {
+
+ it('shouldnt disable app on short transition', () => {
+ // arrange
+ let executeAssertions = () => {
+ // assertions triggerd by callbacks
+ expect(nav._app.setEnabled).toHaveBeenCalledWith(true, 50);
+ expect(nav.setTransitioning).toHaveBeenCalledWith(false, 50);
+ };
+ nav._createTrans = mockTransition(executeAssertions, 50);
+
+ spyOn(nav._app, 'setEnabled');
+ spyOn(nav, 'setTransitioning');
+
+ let view1 = new ViewController(Page1);
+ let view2 = new ViewController(Page2);
+
+ // act
+ nav._beforeTrans(view1, view2, {}, () => {});
+ });
+
+ it('should disable app on longer transition', () => {
+ // arrange
+ let executeAssertions = () => {
+ // assertions triggerd by callbacks
+ expect(nav._app.setEnabled).toHaveBeenCalledWith(false, 200);
+ expect(nav.setTransitioning).toHaveBeenCalledWith(true, 200);
+ };
+
+ nav._createTrans = mockTransition(executeAssertions, 200);
+
+ spyOn(nav._app, 'setEnabled');
+ spyOn(nav, 'setTransitioning');
+
+ let view1 = new ViewController(Page1);
+ let view2 = new ViewController(Page2);
+
+ // act
+ nav._beforeTrans(view1, view2, {}, () => {});
+ });
+
+ it('should disable app w/ padding when keyboard is open', () => {
+ // arrange
+ let executeAssertions = () => {
+ // assertions triggerd by callbacks
+ expect(nav._app.setEnabled.calls.mostRecent().args[0]).toEqual(false);
+ expect(nav._app.setEnabled.calls.mostRecent().args[1]).toBeGreaterThan(200);
+
+ expect(nav.setTransitioning.calls.mostRecent().args[0]).toEqual(true);
+ expect(nav.setTransitioning.calls.mostRecent().args[1]).toBeGreaterThan(200);
+ };
+
+ nav._createTrans = mockTransition(executeAssertions, 200);
+
+ nav._keyboard.isOpen = () => true;
+
+ spyOn(nav._app, 'setEnabled');
+ spyOn(nav, 'setTransitioning');
+
+ let view1 = new ViewController(Page1);
+ let view2 = new ViewController(Page2);
+
+ // act
+ nav._beforeTrans(view1, view2, {}, () => {});
+ });
+
+ it('shouldnt update app enabled when parent transition is occurring', () => {
+ // arrange
+ let executeAssertions = () => {
+ // assertions triggerd by callbacks
+ expect(nav._app.setEnabled).not.toHaveBeenCalled();
+ expect(nav.setTransitioning.calls.mostRecent().args[0]).toEqual(true);
+ };
+
+ nav._createTrans = mockTransition(executeAssertions, 200);
+
+ spyOn(nav._app, 'setEnabled');
+ spyOn(nav, 'setTransitioning');
+
+ nav.getLongestTrans = () => { return Date.now() + 100 };
+
+ let view1 = new ViewController(Page1);
+ let view2 = new ViewController(Page2);
+
+ // act
+ nav._beforeTrans(view1, view2, {}, () => {});
+ });
+
+ it('should not begin transition when entering stated is inactive', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_INACTIVE;
+
+ let wasDoneCalled = false;
+ let done = () => {
+ wasDoneCalled = true;
+ };
+
+ nav._beforeTrans(view1, null, {}, done);
+
+ expect(wasDoneCalled).toEqual(true);
+ expect(view1.state).toEqual(STATE_INACTIVE);
+ });
+
+ it('should not begin transition when entering state is canceled', () => {
+ let view1 = new ViewController(Page1);
+ view1.state = STATE_CANCEL_ENTER;
+
+ let wasDoneCalled = false;
+ let done = () => {
+ wasDoneCalled = true;
+ };
+
+ nav._beforeTrans(view1, null, {}, done);
+
+ expect(wasDoneCalled).toEqual(true);
+ expect(view1.state).toEqual(STATE_CANCEL_ENTER);
+ });
+ });
+
+ /* private method */
+ describe('getLongestTrans', () => {
+ it('should return 0 when transition end time is less than 0', () => {
+ // arrange
+ nav.parent = null;
+ // act
+ let returnedValue = nav.getLongestTrans(Date.now());
+ // asssert
+ expect(returnedValue).toEqual(0);
+ });
+
+ it('should return 0 when transition end time is less than now', () => {
+ // arrange
+ nav.parent = {
+ trnsTime: Date.now() - 5
+ };
+ // act
+ let returnedValue = nav.getLongestTrans(Date.now());
+ // asssert
+ expect(returnedValue).toEqual(0);
+ });
+
+ it('should return 0 when parent transition time not set', () => {
+ // arrange
+ nav.parent = {
+ trnsTime: undefined
+ };
+ // act
+ let returnedValue = nav.getLongestTrans(Date.now());
+ // asssert
+ expect(returnedValue).toEqual(0);
+ });
+
+ it('should return transitionEndTime when transition end time is greater than now', () => {
+ // arrange
+ let expectedReturnValue = Date.now() + 100;
+ nav.parent = {
+ trnsTime: expectedReturnValue
+ };
+ // act
+ let returnedValue = nav.getLongestTrans(Date.now());
+ // asssert
+ expect(returnedValue).toEqual(expectedReturnValue);
+ });
+
+ it('should return the greatest end of transition time if found on first parent', () => {
+ // arrange
+ let expectedReturnValue = Date.now() + 100;
+ let firstParent: any = {
+ trnsTime: expectedReturnValue
+ };
+ let secondParent: any = {
+ trnsTime: Date.now() + 50
+ };
+ let thirdParent: any = {
+ trnsTime: Date.now()
+ };
+ let fourthParent: any = {
+ trnsTime: Date.now() + 20
+ };
+ firstParent.parent = secondParent;
+ secondParent.parent = thirdParent;
+ thirdParent.parent = fourthParent;
+ nav.parent = firstParent;
+ // act
+ let returnedValue = nav.getLongestTrans(Date.now());
+ // asssert
+ expect(returnedValue).toEqual(expectedReturnValue);
+ });
+
+ it('should return the greatest end of transition time if found on middle parent', () => {
+ // arrange
+ let expectedReturnValue = Date.now() + 100;
+ let firstParent: any = {
+ trnsTime: Date.now()
+ };
+ let secondParent: any = {
+ trnsTime: Date.now() + 50
+ };
+ let thirdParent: any = {
+ trnsTime: expectedReturnValue
+ };
+ let fourthParent = {
+ trnsTime: Date.now() + 20
+ };
+ firstParent.parent = secondParent;
+ secondParent.parent = thirdParent;
+ thirdParent.parent = fourthParent;
+ nav.parent = firstParent;
+ // act
+ let returnedValue = nav.getLongestTrans(Date.now());
+ // asssert
+ expect(returnedValue).toEqual(expectedReturnValue);
+ });
+
+ it('should return the greatest end of transition time if found on last parent', () => {
+ // arrange
+ let expectedReturnValue = Date.now() + 100;
+ let firstParent: any = {
+ trnsTime: Date.now()
+ };
+ let secondParent: any = {
+ trnsTime: Date.now() + 50
+ };
+ let thirdParent: any = {
+ trnsTime: Date.now() + 20
+ };
+ let fourthParent = {
+ trnsTime: expectedReturnValue
+ };
+ firstParent.parent = secondParent;
+ secondParent.parent = thirdParent;
+ thirdParent.parent = fourthParent;
+ nav.parent = firstParent;
+ // act
+ let returnedValue = nav.getLongestTrans(Date.now());
+ // asssert
+ expect(returnedValue).toEqual(expectedReturnValue);
+ });
+ });
+
+ // setup stuff
+ let nav: NavControllerBase;
+
+ class Page1 {}
+ class Page2 {}
+ class Page3 {}
+ class Page4 {}
+ class Page5 {}
+
+ beforeEach(() => {
+ nav = mockNavController();
+ });
+
+});
}
-
-class MockNavController extends NavController {
-
- get views(): ViewController[] {
- return this._views;
- }
- set views(views: ViewController[]) {
- this._views = views;
- }
-
-}
-
-
-const STATE_ACTIVE = 1;
-const STATE_INACTIVE = 2;
-const STATE_INIT_ENTER = 3;
-const STATE_INIT_LEAVE = 4;
-const STATE_TRANS_ENTER = 5;
-const STATE_TRANS_LEAVE = 6;
-const STATE_REMOVE = 7;
-const STATE_REMOVE_AFTER_TRANS = 8;
-const STATE_CANCEL_ENTER = 9;
-const STATE_FORCE_ACTIVE = 10;
diff --git a/src/components/picker/picker-component.ts b/src/components/picker/picker-component.ts
index b3a2ad7da8..b3f95ef404 100644
--- a/src/components/picker/picker-component.ts
+++ b/src/components/picker/picker-component.ts
@@ -24,7 +24,7 @@ import { ViewController } from '../nav/view-controller';
template: `
{{col.prefix}}
-
@@ -74,11 +74,12 @@ export class PickerColumnCmp {
this.setSelected(this.col.selectedIndex, 0);
// Listening for pointer events
- this.events.pointerEventsRef(this.elementRef,
- (ev: any) => this.pointerStart(ev),
- (ev: any) => this.pointerMove(ev),
- (ev: any) => this.pointerEnd(ev)
- );
+ this.events.pointerEvents({
+ elementRef: this.elementRef,
+ pointerDown: this.pointerStart.bind(this),
+ pointerMove: this.pointerMove.bind(this),
+ pointerUp: this.pointerEnd.bind(this)
+ });
}
ngOnDestroy() {
diff --git a/src/components/range/range.ts b/src/components/range/range.ts
index afc89f0abe..ddb9e91169 100644
--- a/src/components/range/range.ts
+++ b/src/components/range/range.ts
@@ -364,11 +364,12 @@ export class Range implements AfterViewInit, ControlValueAccessor, OnDestroy {
this._renderer.setElementStyle(this._bar.nativeElement, 'right', barR);
// add touchstart/mousedown listeners
- this._events.pointerEventsRef(this._slider,
- this.pointerDown.bind(this),
- this.pointerMove.bind(this),
- this.pointerUp.bind(this));
-
+ this._events.pointerEvents({
+ elementRef: this._slider,
+ pointerDown: this.pointerDown.bind(this),
+ pointerMove: this.pointerMove.bind(this),
+ pointerUp: this.pointerUp.bind(this)
+ });
this.createTicks();
}
@@ -430,18 +431,12 @@ export class Range implements AfterViewInit, ControlValueAccessor, OnDestroy {
ev.preventDefault();
ev.stopPropagation();
- if (this._start !== null && this._active !== null) {
- // only use pointer move if it's a valid pointer
- // and we already have start coordinates
+ // update the ratio for the active knob
+ this.updateKnob(pointerCoord(ev), this._rect);
- // update the ratio for the active knob
- this.updateKnob(pointerCoord(ev), this._rect);
-
- // update the active knob's position
- this._active.position();
- this._pressed = this._active.pressed = true;
-
- }
+ // update the active knob's position
+ this._active.position();
+ this._pressed = this._active.pressed = true;
}
/**
diff --git a/src/components/refresher/refresher.ts b/src/components/refresher/refresher.ts
index eda4e1c406..7acebde8c2 100644
--- a/src/components/refresher/refresher.ts
+++ b/src/components/refresher/refresher.ts
@@ -2,6 +2,7 @@ import { Directive, EventEmitter, Host, Input, Output, NgZone } from '@angular/c
import { Content } from '../content/content';
import { CSS, pointerCoord } from '../../util/dom';
+import { GestureController, GestureDelegate, GesturePriority } from '../../gestures/gesture-controller';
import { isTrueProperty } from '../../util/util';
import { PointerEvents, UIEventManager } from '../../util/ui-event-manager';
@@ -98,6 +99,7 @@ export class Refresher {
private _didStart: boolean;
private _lastCheck: number = 0;
private _isEnabled: boolean = true;
+ private _gesture: GestureDelegate;
private _events: UIEventManager = new UIEventManager(false);
private _pointerEvents: PointerEvents;
private _top: string = '';
@@ -196,8 +198,11 @@ export class Refresher {
@Output() ionStart: EventEmitter = new EventEmitter();
- constructor(@Host() private _content: Content, private _zone: NgZone) {
+ constructor(@Host() private _content: Content, private _zone: NgZone, gestureCtrl: GestureController) {
_content.addCssClass('has-refresher');
+ this._gesture = gestureCtrl.create('refresher', {
+ priority: GesturePriority.Refresher,
+ });
}
private _onStart(ev: TouchEvent): any {
@@ -216,6 +221,10 @@ export class Refresher {
return false;
}
+ if (!this._gesture.canStart()) {
+ return false;
+ }
+
let coord = pointerCoord(ev);
console.debug('Pull-to-refresh, onStart', ev.type, 'y:', coord.y);
@@ -228,7 +237,7 @@ export class Refresher {
this.startY = this.currentY = coord.y;
this.progress = 0;
- this.state = STATE_PULLING;
+ this.state = STATE_INACTIVE;
return true;
}
@@ -242,6 +251,10 @@ export class Refresher {
return 1;
}
+ if (!this._gesture.canStart()) {
+ return 0;
+ }
+
// do nothing if it's actively refreshing
// or it's in the process of closing
// or this was never a startY
@@ -462,10 +475,12 @@ export class Refresher {
this._events.unlistenAll();
this._pointerEvents = null;
if (shouldListen) {
- this._pointerEvents = this._events.pointerEvents(this._content.getScrollElement(),
- this._onStart.bind(this),
- this._onMove.bind(this),
- this._onEnd.bind(this));
+ this._pointerEvents = this._events.pointerEvents({
+ element: this._content.getScrollElement(),
+ pointerDown: this._onStart.bind(this),
+ pointerMove: this._onMove.bind(this),
+ pointerUp: this._onEnd.bind(this)
+ });
}
}
@@ -482,6 +497,7 @@ export class Refresher {
* @private
*/
ngOnDestroy() {
+ this._gesture.destroy();
this._setListeners(false);
}
diff --git a/src/components/refresher/test/refresher.spec.ts b/src/components/refresher/test/refresher.spec.ts
index 6daf15f543..8ddb21a1e8 100644
--- a/src/components/refresher/test/refresher.spec.ts
+++ b/src/components/refresher/test/refresher.spec.ts
@@ -1,4 +1,4 @@
-import {Refresher, Content, Config, Ion} from '../../../../src';
+import { Refresher, Content, Config, GestureController, Ion } from '../../../../src';
export function run() {
@@ -218,17 +218,19 @@ describe('Refresher', () => {
let refresher: Refresher;
let content: Content;
let contentElementRef;
+ let gestureController: GestureController;
let zone = {
- run: function(cb) {cb()},
- runOutsideAngular: function(cb) {cb()}
+ run: function (cb) { cb(); },
+ runOutsideAngular: function (cb) { cb(); }
};
beforeEach(() => {
contentElementRef = mockElementRef();
- content = new Content(contentElementRef, config, null, null, null);
+ gestureController = new GestureController();
+ content = new Content(contentElementRef, config, null, null, zone, null, null);
content._scrollEle = document.createElement('scroll-content');
- refresher = new Refresher(content, zone, mockElementRef());
+ refresher = new Refresher(content, zone, gestureController);
});
function touchEv(y: number) {
diff --git a/src/components/slides/test/intro/index.ts b/src/components/slides/test/intro/index.ts
index 7913afa37a..8b3b6fa4af 100644
--- a/src/components/slides/test/intro/index.ts
+++ b/src/components/slides/test/intro/index.ts
@@ -1,5 +1,5 @@
-import {Component} from '@angular/core';
-import {ionicBootstrap, NavController} from '../../../../../src';
+import { Component} from '@angular/core';
+import { ionicBootstrap, NavController } from '../../../../../src';
@Component({
@@ -8,7 +8,7 @@ import {ionicBootstrap, NavController} from '../../../../../src';
class IntroPage {
continueText: string = "Skip";
startingIndex: number = 1;
- mySlideOptions;
+ mySlideOptions: any;
showSlide: boolean = true;
constructor(private nav: NavController) {
@@ -20,16 +20,16 @@ class IntroPage {
};
}
- onSlideChanged(slider) {
- console.log("Slide changed", slider);
+ onSlideChanged(slider: any) {
+ console.log('Slide changed', slider);
}
- onSlideChangeStart(slider) {
- console.log("Slide change start", slider);
- slider.isEnd ? this.continueText = "Continue" : this.continueText = "Skip";
+ onSlideChangeStart(slider: any) {
+ console.log('Slide change start', slider);
+ slider.isEnd ? this.continueText = 'Continue' : this.continueText = "Skip";
}
- onSlideMove(slider) {
+ onSlideMove(slider: any) {
console.log("Slide move", slider);
}
diff --git a/src/components/slides/test/loop/index.ts b/src/components/slides/test/loop/index.ts
index ef3480cd2d..8b95e2cd42 100644
--- a/src/components/slides/test/loop/index.ts
+++ b/src/components/slides/test/loop/index.ts
@@ -14,16 +14,16 @@ class E2EApp {
constructor() {
this.slides = [
{
- name: "Slide 1",
- class: "yellow"
+ name: 'Slide 1',
+ class: 'yellow'
},
{
- name: "Slide 2",
- class: "red"
+ name: 'Slide 2',
+ class: 'red'
},
{
- name: "Slide 3",
- class: "blue"
+ name: 'Slide 3',
+ class: 'blue'
}
];
diff --git a/src/components/tabs/tab.ts b/src/components/tabs/tab.ts
index a91aeb9166..e04ab0a3aa 100644
--- a/src/components/tabs/tab.ts
+++ b/src/components/tabs/tab.ts
@@ -1,11 +1,11 @@
-import { ChangeDetectorRef, Component, ComponentResolver, ElementRef, EventEmitter, forwardRef, Input, Inject, NgZone, Output, Renderer, ViewChild, ViewEncapsulation, ViewContainerRef } from '@angular/core';
+import { ChangeDetectorRef, Component, ComponentResolver, ElementRef, EventEmitter, forwardRef, Input, Inject, NgZone, Optional, Output, Renderer, ViewChild, ViewEncapsulation, ViewContainerRef } from '@angular/core';
import { App } from '../app/app';
import { Config } from '../../config/config';
+import { GestureController } from '../../gestures/gesture-controller';
import { isTrueProperty} from '../../util/util';
import { Keyboard} from '../../util/keyboard';
-import { MenuController } from '../menu/menu-controller';
-import { NavController } from '../nav/nav-controller';
+import { NavControllerBase } from '../nav/nav-controller-base';
import { NavOptions} from '../nav/nav-interfaces';
import { TabButton} from './tab-button';
import { Tabs} from './tabs';
@@ -128,7 +128,7 @@ import { ViewController} from '../nav/view-controller';
template: '',
encapsulation: ViewEncapsulation.None,
})
-export class Tab extends NavController {
+export class Tab extends NavControllerBase {
private _isInitial: boolean;
private _isEnabled: boolean = true;
private _isShown: boolean = true;
@@ -229,17 +229,13 @@ export class Tab extends NavController {
renderer: Renderer,
compiler: ComponentResolver,
private _cd: ChangeDetectorRef,
- menuCtrl: MenuController
+ gestureCtrl: GestureController
) {
// A Tab is a NavController for its child pages
- super(parent, app, config, keyboard, elementRef, zone, renderer, compiler, menuCtrl);
+ super(parent, app, config, keyboard, elementRef, zone, renderer, compiler, gestureCtrl);
parent.add(this);
- if (parent.rootNav) {
- this._sbEnabled = parent.rootNav.isSwipeBackEnabled();
- }
-
this._tabId = 'tabpanel-' + this.id;
this._btnId = 'tab-' + this.id;
}
@@ -264,7 +260,7 @@ export class Tab extends NavController {
*/
load(opts: NavOptions, done?: Function) {
if (!this._loaded && this.root) {
- this.push(this.root, this.rootParams, opts).then(() => {
+ this.push(this.root, this.rootParams, opts, () => {
done(true);
});
this._loaded = true;
@@ -294,20 +290,7 @@ export class Tab extends NavController {
* @private
*/
loadPage(viewCtrl: ViewController, viewport: ViewContainerRef, opts: NavOptions, done: Function) {
- let 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);
- }
- }
done();
});
}
diff --git a/src/components/tabs/tabs.md.scss b/src/components/tabs/tabs.md.scss
index 1e2007b216..695e7c0cde 100644
--- a/src/components/tabs/tabs.md.scss
+++ b/src/components/tabs/tabs.md.scss
@@ -12,7 +12,8 @@ $tabbar-md-item-icon-size: 2.4rem !default;
$tabbar-md-item-height: 4.8rem !default;
$tab-button-md-active-color: $toolbar-md-active-color !default;
-$tab-button-md-inactive-color: rgba($toolbar-md-inactive-color, .7) !default;
+$tab-button-md-inactive-opacity: .7 !default;
+$tab-button-md-inactive-color: rgba($toolbar-md-inactive-color, $tab-button-md-inactive-opacity) !default;
ion-tabbar {
@@ -103,7 +104,7 @@ tab-highlight {
background-color: $color-base;
.tab-button {
- color: $color-contrast;
+ color: rgba($color-contrast, $tab-button-md-inactive-opacity);
}
.tab-button:hover:not(.disable-hover),
diff --git a/src/components/tabs/tabs.ts b/src/components/tabs/tabs.ts
index 50b129c124..dffb4b3542 100644
--- a/src/components/tabs/tabs.ts
+++ b/src/components/tabs/tabs.ts
@@ -7,9 +7,11 @@ import { Config } from '../../config/config';
import { Content } from '../content/content';
import { Icon } from '../icon/icon';
import { Ion } from '../ion';
-import { isBlank, isTrueProperty } from '../../util/util';
+import { isBlank, isPresent, isTrueProperty } from '../../util/util';
import { nativeRaf } from '../../util/dom';
-import { NavController, DIRECTION_FORWARD } from '../nav/nav-controller';
+import { NavController } from '../nav/nav-controller';
+import { NavControllerBase } from '../nav/nav-controller-base';
+import { NavOptions, DIRECTION_FORWARD } from '../nav/nav-interfaces';
import { Platform } from '../../platform/platform';
import { Tab } from './tab';
import { TabButton } from './tab-button';
@@ -148,7 +150,6 @@ import { ViewController } from '../nav/view-controller';
-
`,
directives: [Badge, Icon, NgClass, NgFor, NgIf, TabButton, TabHighlight],
encapsulation: ViewEncapsulation.None,
@@ -165,18 +166,13 @@ export class Tabs extends Ion {
/**
* @private
*/
- id: number;
+ id: string;
/**
* @private
*/
selectHistory: string[] = [];
- /**
- * @private
- */
- subPages: boolean;
-
/**
* @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.
*/
@@ -225,12 +221,7 @@ export class Tabs extends Ion {
/**
* @private
*/
- @ViewChild('portal', {read: ViewContainerRef}) portal: ViewContainerRef;
-
- /**
- * @private
- */
- parent: NavController;
+ parent: NavControllerBase;
constructor(
@Optional() parent: NavController,
@@ -243,16 +234,14 @@ export class Tabs extends Ion {
) {
super(_elementRef);
- this.parent = parent;
- this.id = ++tabIds;
+ this.parent = parent;
+ this.id = 't' + (++tabIds);
this._sbPadding = _config.getBoolean('statusbarPadding');
- this.subPages = _config.getBoolean('tabsHideOnSubPages');
this._useHighlight = _config.getBoolean('tabsHighlight');
// TODO deprecated 07-07-2016 beta.11
if (_config.get('tabSubPages') !== null) {
- console.warn('Config option "tabSubPages" has been deprecated. Please use "tabsHideOnSubPages" instead.');
- this.subPages = _config.getBoolean('tabSubPages');
+ console.warn('Config option "tabSubPages" has been deprecated. The Material Design spec now supports Bottom Navigation: https://material.google.com/components/bottom-navigation.html');
}
// TODO deprecated 07-07-2016 beta.11
@@ -261,9 +250,9 @@ export class Tabs extends Ion {
this._useHighlight = _config.getBoolean('tabbarHighlight');
}
- if (parent) {
+ if (this.parent) {
// this Tabs has a parent Nav
- parent.registerChildNav(this);
+ this.parent.registerChildNav(this);
} else if (this._app) {
// this is the root navcontroller for the entire app
@@ -333,41 +322,32 @@ export class Tabs extends Ion {
* @private
*/
initTabs() {
- // first check if preloadTab is set as an input @Input, then check the config
- let preloadTabs = (isBlank(this.preloadTabs) ? this._config.getBoolean('preloadTabs') : isTrueProperty(this.preloadTabs));
+ // 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));
- // get the selected index
- let selectedIndex = this.selectedIndex ? parseInt(this.selectedIndex, 10) : 0;
-
- // ensure the selectedIndex isn't a hidden or disabled tab
- // also find the first available index incase we need it later
- let availableIndex = -1;
- this._tabs.forEach((tab, index) => {
- if (tab.enabled && tab.show && availableIndex < 0) {
- // we know this tab index is safe to show
- availableIndex = index;
- }
-
- if (index === selectedIndex && (!tab.enabled || !tab.show)) {
- // the selectedIndex is not safe to show
- selectedIndex = -1;
- }
- });
-
- if (selectedIndex < 0) {
- // the selected index wasn't safe to show
- // instead use an available index found to be safe to show
- selectedIndex = availableIndex;
+ // get the selectedIndex and ensure it isn't hidden or disabled
+ let selectedTab = this._tabs.find((t, i) => i === selectedIndex && t.enabled && t.show);
+ if (!selectedTab) {
+ // wasn't able to select the tab they wanted
+ // try to find the first tab that's available
+ selectedTab = this._tabs.find(t => t.enabled && t.show);
}
- this._tabs.forEach((tab, index) => {
- if (index === selectedIndex) {
- this.select(tab);
+ if (selectedTab) {
+ // we found a tab to select
+ this.select(selectedTab);
+ }
- } else if (preloadTabs) {
- tab.preload(1000 * index);
- }
- });
+ // 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);
+ });
+ }
}
/**
@@ -392,31 +372,33 @@ export class Tabs extends Ion {
/**
* @param {number|Tab} tabOrIndex Index, or the Tab instance, of the tab to select.
*/
- select(tabOrIndex: number | Tab) {
+ select(tabOrIndex: number | Tab, opts: NavOptions = {}, done?: Function): Promise {
+ let promise: Promise;
+ if (!done) {
+ promise = new Promise(res => { done = res; });
+ }
+
let selectedTab: Tab = (typeof tabOrIndex === 'number' ? this.getByIndex(tabOrIndex) : tabOrIndex);
if (isBlank(selectedTab)) {
- return;
+ return Promise.resolve();
}
let deselectedTab = this.getSelected();
-
if (selectedTab === deselectedTab) {
// no change
- return this._touchActive(selectedTab);
+ this._touchActive(selectedTab);
+ return Promise.resolve();
}
-
console.debug(`Tabs, select: ${selectedTab.id}`);
- let opts = {
- animate: false
- };
-
let deselectedPage: ViewController;
if (deselectedTab) {
deselectedPage = deselectedTab.getActive();
deselectedPage && deselectedPage.fireWillLeave();
}
+ opts.animate = false;
+
let selectedPage = selectedTab.getActive();
selectedPage && selectedPage.fireWillEnter();
@@ -464,7 +446,11 @@ export class Tabs extends Ion {
});
}
}
+
+ done();
});
+
+ return promise;
}
/**
@@ -526,6 +512,13 @@ export class Tabs extends Ion {
return this._tabs.indexOf(tab);
}
+ /**
+ * @private
+ */
+ length(): number {
+ return this._tabs.length;
+ }
+
/**
* @private
* "Touch" the active tab, going back to the root view of the tab
@@ -561,20 +554,6 @@ export class Tabs extends Ion {
return Promise.resolve();
}
- /**
- * @private
- * Returns the root NavController. Returns `null` if Tabs is not
- * within a NavController.
- * @returns {NavController}
- */
- get rootNav(): NavController {
- let nav = this.parent;
- while (nav && nav.parent) {
- nav = nav.parent;
- }
- return nav;
- }
-
/**
* @private
* DOM WRITE
diff --git a/src/components/tabs/tabs.wp.scss b/src/components/tabs/tabs.wp.scss
index c33a5aac3c..0cbc0a291a 100644
--- a/src/components/tabs/tabs.wp.scss
+++ b/src/components/tabs/tabs.wp.scss
@@ -12,7 +12,8 @@ $tabbar-wp-item-icon-size: 2.4rem !default;
$tabbar-wp-item-height: 4.8rem !default;
$tab-button-wp-active-color: $toolbar-wp-active-color !default;
-$tab-button-wp-inactive-color: rgba($toolbar-wp-inactive-color, .7) !default;
+$tab-button-wp-inactive-opacity: .7 !default;
+$tab-button-wp-inactive-color: rgba($toolbar-wp-inactive-color, $tab-button-wp-inactive-opacity) !default;
$tab-button-wp-background-activated: rgba(0, 0, 0, .1) !default;
@@ -96,7 +97,7 @@ ion-tabbar {
background-color: $color-base;
.tab-button {
- color: $color-contrast;
+ color: rgba($color-contrast, $tab-button-wp-inactive-opacity);
}
.tab-button:hover:not(.disable-hover),
diff --git a/src/components/tabs/test/advanced/index.ts b/src/components/tabs/test/advanced/index.ts
index 84a24078c8..072831de21 100644
--- a/src/components/tabs/test/advanced/index.ts
+++ b/src/components/tabs/test/advanced/index.ts
@@ -1,6 +1,6 @@
import { Component, ViewChild } from '@angular/core';
-import { ionicBootstrap, NavController, NavParams, ModalController, ViewController, Tabs, Tab } from '../../../../../src';
+import { App, ionicBootstrap, NavController, NavParams, ModalController, ViewController, Tabs, Tab } from '../../../../../src';
@Component({
@@ -92,7 +92,7 @@ class TabsPage {
class Tab1Page1 {
userId: string;
- constructor(private nav: NavController, private tabs: Tabs, private params: NavParams) {
+ constructor(private nav: NavController, private app: App, private tabs: Tabs, private params: NavParams) {
this.userId = params.get('userId');
}
@@ -102,7 +102,7 @@ class Tab1Page1 {
goBack() {
console.log('go back begin');
- this.nav.pop().then((val) => {
+ this.nav.pop().then((val: any) => {
console.log('go back completed', val);
});;
}
@@ -112,7 +112,7 @@ class Tab1Page1 {
}
logout() {
- this.nav.rootNav.setRoot(SignIn, null, { animate: true, direction: 'back' });
+ this.app.getRootNav().setRoot(SignIn, null, { animate: true, direction: 'back' });
}
ionViewWillEnter() {
@@ -323,10 +323,8 @@ class Tab3Page1 {
@Component({
- template: ''
+ template: ''
})
-class E2EApp {
- root = SignIn;
-}
+class E2EApp {}
ionicBootstrap(E2EApp);
diff --git a/src/components/tabs/test/basic/index.ts b/src/components/tabs/test/basic/index.ts
index 03428939db..ded7adb258 100644
--- a/src/components/tabs/test/basic/index.ts
+++ b/src/components/tabs/test/basic/index.ts
@@ -1,5 +1,5 @@
-import {Component} from '@angular/core';
-import {ionicBootstrap, NavController, App, AlertController, ModalController, ViewController, Tab, Tabs} from '../../../../../src';
+import { Component} from '@angular/core';
+import { ionicBootstrap, App, AlertController, ModalController, ViewController, Tab, Tabs } from '../../../../../src';
//
// Modal
diff --git a/src/components/tabs/test/child-navs/index.ts b/src/components/tabs/test/child-navs/index.ts
index d24c27f87a..ae9239bf0b 100644
--- a/src/components/tabs/test/child-navs/index.ts
+++ b/src/components/tabs/test/child-navs/index.ts
@@ -1,5 +1,5 @@
-import {Component} from '@angular/core';
-import {ionicBootstrap, NavController, App, Alert, Modal, ViewController, Tab, Tabs} from '../../../../../src';
+import { Component } from '@angular/core';
+import { ionicBootstrap, NavController, App, Alert, Modal, ViewController, Tab, Tabs } from '../../../../../src';
//
diff --git a/src/components/tabs/test/tabs.spec.ts b/src/components/tabs/test/tabs.spec.ts
index 025ed668fe..219bb84ca1 100644
--- a/src/components/tabs/test/tabs.spec.ts
+++ b/src/components/tabs/test/tabs.spec.ts
@@ -1,10 +1,89 @@
-import {Component} from '@angular/core';
-import {App, Nav, Tabs, Tab, NavOptions, Config, ViewController, Platform} from '../../../../src';
+import { Component } from '@angular/core';
+import { App, Config, Nav, NavOptions, Platform, Tab, Tabs, ViewController } from '../../../../src';
+import { mockTab, mockTabs } from '../../../../src/util/mock-providers';
export function run() {
describe('Tabs', () => {
+ describe('initTabs', () => {
+
+ it('should preload all tabs', () => {
+ var tabs = mockTabs();
+ var tab0 = mockTab(tabs);
+ var tab1 = mockTab(tabs);
+ tab0.root = SomePage;
+ tab1.root = SomePage;
+
+ tab0.preload = () => {};
+ tab1.preload = () => {};
+
+ spyOn(tab0, 'preload');
+ spyOn(tab1, 'preload');
+
+ tabs.preloadTabs = true;
+
+ tabs.initTabs();
+
+ expect(tab0.isSelected).toEqual(true);
+ expect(tab1.isSelected).toEqual(false);
+
+ expect(tab0.preload).not.toHaveBeenCalled();
+ expect(tab1.preload).toHaveBeenCalled();
+ });
+
+ it('should not select a hidden or disabled tab', () => {
+ var tabs = mockTabs();
+ var tab0 = mockTab(tabs);
+ var tab1 = mockTab(tabs);
+ tab0.root = SomePage;
+ tab1.root = SomePage;
+
+ tab1.enabled = false;
+ tab1.show = false;
+
+ tabs.selectedIndex = '1';
+ tabs.initTabs();
+
+ expect(tab0.isSelected).toEqual(true);
+ expect(tab1.isSelected).toEqual(false);
+ });
+
+ it('should select the second tab from selectedIndex input', () => {
+ var tabs = mockTabs();
+ var tab0 = mockTab(tabs);
+ var tab1 = mockTab(tabs);
+ tab0.root = SomePage;
+ tab1.root = SomePage;
+
+ tabs.selectedIndex = '1';
+ tabs.initTabs();
+
+ expect(tab0.isSelected).toEqual(false);
+ expect(tab1.isSelected).toEqual(true);
+ });
+
+ it('should select the first tab by default', () => {
+ var tabs = mockTabs();
+ var tab0 = mockTab(tabs);
+ var tab1 = mockTab(tabs);
+ tab0.root = SomePage;
+ tab1.root = SomePage;
+
+ spyOn(tab0, 'preload');
+ spyOn(tab1, 'preload');
+
+ tabs.initTabs();
+
+ expect(tab0.isSelected).toEqual(true);
+ expect(tab1.isSelected).toEqual(false);
+
+ expect(tab0.preload).not.toHaveBeenCalled();
+ expect(tab1.preload).not.toHaveBeenCalled();
+ });
+
+ });
+
describe('previousTab', () => {
it('should find the previous tab when there has been 3 selections', () => {
@@ -12,9 +91,6 @@ describe('Tabs', () => {
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
var tab2 = mockTab(tabs);
- tabs.add(tab0);
- tabs.add(tab1);
- tabs.add(tab2);
tab0.root = SomePage;
tab1.root = SomePage;
tab2.root = SomePage;
@@ -36,8 +112,6 @@ describe('Tabs', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
- tabs.add(tab0);
- tabs.add(tab1);
tab0.root = SomePage;
tab1.root = SomePage;
@@ -56,8 +130,6 @@ describe('Tabs', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
- tabs.add(tab0);
- tabs.add(tab1);
tab0.root = SomePage;
tab1.root = SomePage;
@@ -87,8 +159,6 @@ describe('Tabs', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
- tabs.add(tab0);
- tabs.add(tab1);
tab0.root = SomePage;
tab1.root = SomePage;
@@ -103,12 +173,11 @@ describe('Tabs', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
- tabs.add(tab0);
- tabs.add(tab1);
tab0.root = SomePage;
tab1.root = SomePage;
+ expect(tabs.length()).toEqual(2);
expect(tab0.isSelected).toBeUndefined();
expect(tab1.isSelected).toBeUndefined();
@@ -118,16 +187,6 @@ describe('Tabs', () => {
expect(tab1.isSelected).toEqual(false);
});
- it('should not select an invalid tab index', () => {
- var tabs = mockTabs();
- var tab0 = mockTab(tabs);
- var tab1 = mockTab(tabs);
- tabs.add(tab0);
- tabs.add(tab1);
-
- expect(tabs.select(22)).toBeUndefined();
- });
-
});
describe('getByIndex', () => {
@@ -137,8 +196,6 @@ describe('Tabs', () => {
var tab0 = mockTab(tabs);
tab0.setRoot({});
var tab1 = mockTab(tabs);
- tabs.add(tab0);
- tabs.add(tab1);
expect(tabs.getIndex(tab0)).toEqual(0);
expect(tabs.getIndex(tab1)).toEqual(1);
@@ -152,8 +209,6 @@ describe('Tabs', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
- tabs.add(tab0);
- tabs.add(tab1);
tab1.setSelected(true);
@@ -164,48 +219,15 @@ describe('Tabs', () => {
var tabs = mockTabs();
var tab0 = mockTab(tabs);
var tab1 = mockTab(tabs);
- tabs.add(tab0);
- tabs.add(tab1);
expect(tabs.getSelected()).toEqual(null);
});
});
- var app: App;
- var config: Config;
- var platform: Platform;
- var _cd: any;
-
- function mockNav(): Nav {
- return new Nav(null, null, null, config, null, null, null, null, null);
- }
-
- function mockTabs(): Tabs {
- return new Tabs(null, null, null, config, null, null, null);
- }
-
- function mockTab(parentTabs: Tabs): Tab {
- var tab = new Tab(parentTabs, app, config, null, null, null, null, null, _cd);
- tab.load = function(opts: any, cb: Function) {
- cb();
- };
- return tab;
- }
-
@Component({})
class SomePage {}
- beforeEach(() => {
- config = new Config();
- platform = new Platform();
- app = new App(config, platform);
- _cd = {
- reattach: function(){},
- detach: function(){}
- };
- });
-
});
diff --git a/src/components/toast/toast-component.ts b/src/components/toast/toast-component.ts
index 34852212a4..150a4babad 100644
--- a/src/components/toast/toast-component.ts
+++ b/src/components/toast/toast-component.ts
@@ -4,7 +4,6 @@ import { NgIf } from '@angular/common';
import { Animation } from '../../animations/animation';
import { Config } from '../../config/config';
import { isPresent } from '../../util/util';
-import { NavController } from '../nav/nav-controller';
import { NavParams } from '../nav/nav-params';
import { Transition, TransitionOptions } from '../../transitions/transition';
import { ViewController } from '../nav/view-controller';
@@ -44,7 +43,6 @@ export class ToastCmp implements AfterViewInit {
private id: number;
constructor(
- private _nav: NavController,
private _viewCtrl: ViewController,
private _config: Config,
private _elementRef: ElementRef,
diff --git a/src/components/toggle/toggle.ts b/src/components/toggle/toggle.ts
index 90926cd57c..30ceff83ed 100644
--- a/src/components/toggle/toggle.ts
+++ b/src/components/toggle/toggle.ts
@@ -242,11 +242,12 @@ export class Toggle implements AfterContentInit, ControlValueAccessor, OnDestroy
*/
ngAfterContentInit() {
this._init = true;
- this._events.pointerEventsRef(this._elementRef,
- (ev: any) => this.pointerDown(ev),
- (ev: any) => this.pointerMove(ev),
- (ev: any) => this.pointerUp(ev)
- );
+ this._events.pointerEvents({
+ elementRef: this._elementRef,
+ pointerDown: this.pointerDown.bind(this),
+ pointerMove: this.pointerMove.bind(this),
+ pointerUp: this.pointerUp.bind(this)
+ });
}
/**
diff --git a/src/config/config.ts b/src/config/config.ts
index e34b64464f..81f7d374ce 100644
--- a/src/config/config.ts
+++ b/src/config/config.ts
@@ -103,7 +103,6 @@ import {isObject, isDefined, isFunction, isArray} from '../util/util';
* | `tabsHighlight` | `boolean` | Whether to show a highlight line under the tab when it is selected. |
* | `tabsLayout` | `string` | The layout to use for all tabs. Available options: `"icon-top"`, `"icon-left"`, `"icon-right"`, `"icon-bottom"`, `"icon-hide"`, `"title-hide"`. |
* | `tabsPlacement` | `string` | The position of the tabs relative to the content. Available options: `"top"`, `"bottom"` |
- * | `tabsHideOnSubPages` | `boolean` | Whether to hide the tabs on child pages or not. If `true` it will not show the tabs on child pages. |
* | `toastEnter` | `string` | The name of the transition to use while a toast is presented. |
* | `toastLeave` | `string` | The name of the transition to use while a toast is dismissed. |
*
diff --git a/src/config/modes.ts b/src/config/modes.ts
index 4e61d2694e..3e0a631377 100644
--- a/src/config/modes.ts
+++ b/src/config/modes.ts
@@ -39,7 +39,6 @@ Config.setModeConfig('ios', {
tabsHighlight: false,
tabsPlacement: 'bottom',
- tabsHideOnSubPages: false,
toastEnter: 'toast-slide-in',
toastLeave: 'toast-slide-out',
@@ -82,8 +81,7 @@ Config.setModeConfig('md', {
spinner: 'crescent',
tabsHighlight: true,
- tabsPlacement: 'top',
- tabsHideOnSubPages: true,
+ tabsPlacement: 'bottom',
toastEnter: 'toast-md-slide-in',
toastLeave: 'toast-md-slide-out',
@@ -127,7 +125,6 @@ Config.setModeConfig('wp', {
tabsHighlight: false,
tabsPlacement: 'top',
- tabsHideOnSubPages: true,
toastEnter: 'toast-wp-slide-in',
toastLeave: 'toast-wp-slide-out',
diff --git a/src/config/providers.ts b/src/config/providers.ts
index 9ed2c31303..4b4ca71f24 100644
--- a/src/config/providers.ts
+++ b/src/config/providers.ts
@@ -10,6 +10,7 @@ import { closest, nativeTimeout } from '../util/dom';
import { Events } from '../util/events';
import { FeatureDetect } from '../util/feature-detect';
import { Form } from '../util/form';
+import { GestureController } from '../gestures/gesture-controller';
import { IONIC_DIRECTIVES } from './directives';
import { isPresent } from '../util/util';
import { Keyboard } from '../util/keyboard';
@@ -64,6 +65,7 @@ export function ionicProviders(customProviders?: Array, config?: any): any[
provide(Events, {useValue: events}),
provide(FeatureDetect, {useValue: featureDetect}),
Form,
+ GestureController,
HTTP_PROVIDERS,
Keyboard,
LoadingController,
diff --git a/src/config/test/config.spec.ts b/src/config/test/config.spec.ts
index 3e75db43f2..b734ad2ec2 100644
--- a/src/config/test/config.spec.ts
+++ b/src/config/test/config.spec.ts
@@ -2,12 +2,14 @@ import {Config, Platform, ionicProviders} from '../../../src';
export function run() {
+describe('Config', () => {
+
it('should set activator setting to none for old Android Browser on a linux device', () => {
let config = new Config();
let platform = new Platform();
platform.setUserAgent('Mozilla/5.0 (Linux; U; Android 4.2.2; nl-nl; GT-I9505 Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30');
platform.setNavigatorPlatform('linux');
- platform.load(null);
+ platform.load();
config.setPlatform(platform);
expect(config.get('activator')).toEqual('none');
@@ -18,7 +20,7 @@ export function run() {
let platform = new Platform();
platform.setUserAgent('Mozilla/5.0 (Linux; U; Android 4.2.2; nl-nl; GT-I9505 Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30');
platform.setNavigatorPlatform('MacIntel');
- platform.load(null);
+ platform.load();
config.setPlatform(platform);
expect(config.get('activator')).toEqual('ripple');
@@ -29,7 +31,7 @@ export function run() {
let platform = new Platform();
platform.setUserAgent('Mozilla/5.0 (Linux; Android 4.2.2; GT-I9505 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1650.59 Mobile Safari/537.36');
platform.setNavigatorPlatform('linux');
- platform.load(null);
+ platform.load();
config.setPlatform(platform);
expect(config.get('activator')).toEqual('none');
@@ -40,7 +42,7 @@ export function run() {
let platform = new Platform();
platform.setUserAgent('Mozilla/5.0 (Linux; Android 4.2.2; GT-I9505 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1650.59 Mobile Safari/537.36');
platform.setNavigatorPlatform('linux');
- platform.load(null);
+ platform.load();
config.setPlatform(platform);
expect(config.get('activator')).toEqual('ripple');
@@ -51,7 +53,7 @@ export function run() {
let platform = new Platform();
platform.setUserAgent('Mozilla/5.0 (Android 5.0; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0');
platform.setNavigatorPlatform('linux');
- platform.load(null);
+ platform.load();
config.setPlatform(platform);
expect(config.get('activator')).toEqual('ripple');
@@ -62,7 +64,7 @@ export function run() {
let platform = new Platform();
platform.setUserAgent('Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0');
platform.setNavigatorPlatform('linux');
- platform.load(null);
+ platform.load();
config.setPlatform(platform);
expect(config.get('activator')).toEqual('none');
@@ -105,7 +107,7 @@ export function run() {
config.setPlatform(platform);
expect(config.get('mode')).toEqual('md');
- expect(config.get('tabsPlacement')).toEqual('top');
+ expect(config.get('tabsHighlight')).toEqual(true);
});
it('should override mode settings from platforms setting', () => {
@@ -120,7 +122,7 @@ export function run() {
config.setPlatform(platform);
expect(config.get('mode')).toEqual('md');
- expect(config.get('tabsPlacement')).toEqual('top');
+ expect(config.get('tabsHighlight')).toEqual(true);
});
it('should get boolean value from querystring', () => {
@@ -259,7 +261,7 @@ export function run() {
let platform = new Platform(['android']);
config.setPlatform(platform);
- expect(config.get('tabsPlacement')).toEqual('top');
+ expect(config.get('tabsHighlight')).toEqual(true);
});
it('should get setting from ios mode', () => {
@@ -562,4 +564,6 @@ export function run() {
expect(config.settings()).toEqual({});
});
+});
+
}
diff --git a/src/decorators/page.ts b/src/decorators/page.ts
index 6e84f7b6f4..41a36a6813 100644
--- a/src/decorators/page.ts
+++ b/src/decorators/page.ts
@@ -38,7 +38,6 @@ export function Page(config: PageMetadata) {
config.selector = 'ion-page';
config.host = config.host || {};
config.host['[hidden]'] = '_hidden';
- config.host['[class.tab-subpage]'] = '_tabSubPage';
var annotations = _reflect.getMetadata('annotations', cls) || [];
annotations.push(new Component(config));
_reflect.defineMetadata('annotations', annotations, cls);
diff --git a/src/gestures/drag-gesture.ts b/src/gestures/drag-gesture.ts
index d94d5b18d0..f1ea44d07c 100644
--- a/src/gestures/drag-gesture.ts
+++ b/src/gestures/drag-gesture.ts
@@ -1,42 +1,142 @@
-import {Gesture} from './gesture';
-import {defaults} from '../util';
+
+import { defaults } from '../util';
+import { GestureDelegate } from '../gestures/gesture-controller';
+import { PointerEvents, UIEventManager } from '../util/ui-event-manager';
+import { PanRecognizer } from './recognizers';
+import { pointerCoord, Coordinates } from '../util/dom';
/**
* @private
*/
+export interface PanGestureConfig {
+ threshold?: number;
+ maxAngle?: number;
+ direction?: 'x' | 'y';
+ gesture?: GestureDelegate;
+}
-export class DragGesture extends Gesture {
- public dragging: boolean;
+/**
+ * @private
+ */
+export class PanGesture {
+ private dragging: boolean;
+ private events: UIEventManager = new UIEventManager(false);
+ private pointerEvents: PointerEvents;
+ private detector: PanRecognizer;
+ private started: boolean = false;
+ private captured: boolean = false;
+ public isListening: boolean = false;
+ protected gestute: GestureDelegate;
+ protected direction: string;
- constructor(element: HTMLElement, opts = {}) {
- defaults(opts, {});
- super(element, opts);
+ constructor(private element: HTMLElement, opts: PanGestureConfig = {}) {
+ defaults(opts, {
+ threshold: 20,
+ maxAngle: 40,
+ direction: 'x'
+ });
+ this.gestute = opts.gesture;
+ this.direction = opts.direction;
+ this.detector = new PanRecognizer(opts.direction, opts.threshold, opts.maxAngle);
}
listen() {
- super.listen();
-
- this.on('panstart', (ev: UIEvent) => {
- if (this.onDragStart(ev) !== false) {
- this.dragging = true;
- }
- });
-
- this.on('panmove', (ev: UIEvent) => {
- if (!this.dragging) return;
- if (this.onDrag(ev) === false) {
- this.dragging = false;
- }
- });
-
- this.on('panend', (ev: UIEvent) => {
- if (!this.dragging) return;
- this.onDragEnd(ev);
- this.dragging = false;
- });
+ if (!this.isListening) {
+ this.pointerEvents = this.events.pointerEvents({
+ element: this.element,
+ pointerDown: this.pointerDown.bind(this),
+ pointerMove: this.pointerMove.bind(this),
+ pointerUp: this.pointerUp.bind(this),
+ });
+ this.isListening = true;
+ }
}
- onDrag(ev: any): boolean { return true; }
- onDragStart(ev: any): boolean { return true; }
- onDragEnd(ev: any): void {}
+ unlisten() {
+ this.gestute && this.gestute.release();
+ this.events.unlistenAll();
+ this.isListening = false;
+ }
+
+ destroy() {
+ this.gestute && this.gestute.destroy();
+ this.unlisten();
+ this.element = null;
+ }
+
+ pointerDown(ev: any): boolean {
+ if (this.started) {
+ return;
+ }
+ if (!this.canStart(ev)) {
+ return false;
+ }
+ if (this.gestute) {
+ // Release fallback
+ this.gestute.release();
+ // Start gesture
+ if (!this.gestute.start()) {
+ return false;
+ }
+ }
+
+ let coord = pointerCoord(ev);
+ this.detector.start(coord);
+ this.started = true;
+ this.captured = false;
+ return true;
+ }
+
+ pointerMove(ev: any) {
+ if (!this.started) {
+ return;
+ }
+ if (this.captured) {
+ this.onDragMove(ev);
+ return;
+ }
+ let coord = pointerCoord(ev);
+ if (this.detector.detect(coord)) {
+
+ if (this.detector.pan() !== 0 && this.canCapture(ev) &&
+ (!this.gestute || this.gestute.capture())) {
+ this.onDragStart(ev);
+ this.captured = true;
+ return;
+ }
+
+ // Detection/capturing was not successful, aborting!
+ this.started = false;
+ this.captured = false;
+ this.pointerEvents.stop();
+ this.notCaptured(ev);
+ }
+ }
+
+ pointerUp(ev: any) {
+ if (!this.started) {
+ return;
+ }
+ this.gestute && this.gestute.release();
+
+ if (this.captured) {
+ this.onDragEnd(ev);
+ } else {
+ this.notCaptured(ev);
+ }
+ this.captured = false;
+ this.started = false;
+ }
+
+ getNativeElement(): HTMLElement {
+ return this.element;
+ }
+
+ // Implemented in a subclass
+ canStart(ev: any): boolean { return true; }
+ canCapture(ev: any): boolean { return true; }
+ onDragStart(ev: any) { }
+ onDragMove(ev: any) { }
+ onDragEnd(ev: any) { }
+ notCaptured(ev: any) { }
}
diff --git a/src/gestures/gesture-controller.ts b/src/gestures/gesture-controller.ts
new file mode 100644
index 0000000000..f768dc252e
--- /dev/null
+++ b/src/gestures/gesture-controller.ts
@@ -0,0 +1,226 @@
+
+import { forwardRef, Inject, Injectable } from '@angular/core';
+import { App } from '../components/app/app';
+
+export const enum GesturePriority {
+ Minimun = -10000,
+ VeryLow = -20,
+ Low = -10,
+ Normal = 0,
+ High = 10,
+ VeryHigh = 20,
+
+ SlidingItem = Low,
+ MenuSwipe = High,
+ GoBackSwipe = VeryHigh,
+ Refresher = Normal,
+}
+
+export const enum DisableScroll {
+ Never,
+ DuringCapture,
+ Always,
+}
+
+export interface GestureOptions {
+ disable?: string[];
+ disableScroll?: DisableScroll;
+ priority?: number;
+}
+
+@Injectable()
+export class GestureController {
+ private id: number = 1;
+ private requestedStart: { [eventId: number]: number } = {};
+ private disabledGestures: { [eventName: string]: Set } = {};
+ private disabledScroll: Set = new Set();
+ private capturedID: number = null;
+
+ constructor(@Inject(forwardRef(() => App)) private _app: App) { }
+
+ create(name: string, opts: GestureOptions = {}): GestureDelegate {
+ return new GestureDelegate(name, this.newID(), this, opts);
+ }
+
+ newID(): number {
+ let id = this.id; this.id++;
+ return id;
+ }
+
+ start(gestureName: string, id: number, priority: number): boolean {
+ if (!this.canStart(gestureName)) {
+ delete this.requestedStart[id];
+ return false;
+ }
+
+ this.requestedStart[id] = priority;
+ return true;
+ }
+
+ capture(gestureName: string, id: number, priority: number): boolean {
+ if (!this.start(gestureName, id, priority)) {
+ return false;
+ }
+ let requestedStart = this.requestedStart;
+ let maxPriority = GesturePriority.Minimun;
+ for (let gestureID in requestedStart) {
+ maxPriority = Math.max(maxPriority, requestedStart[gestureID]);
+ }
+
+ if (maxPriority === priority) {
+ this.capturedID = id;
+ this.requestedStart = {};
+ return true;
+ }
+ delete requestedStart[id];
+ console.debug(`${gestureName} can not start because it is has lower priority`);
+ return false;
+ }
+
+ release(id: number) {
+ delete this.requestedStart[id];
+ if (this.capturedID && id === this.capturedID) {
+ this.capturedID = null;
+ }
+ }
+
+ disableGesture(gestureName: string, id: number) {
+ let set = this.disabledGestures[gestureName];
+ if (!set) {
+ set = new Set();
+ this.disabledGestures[gestureName] = set;
+ }
+ set.add(id);
+ }
+
+ enableGesture(gestureName: string, id: number) {
+ let set = this.disabledGestures[gestureName];
+ if (set) {
+ set.delete(id);
+ }
+ }
+
+ disableScroll(id: number) {
+ let isEnabled = !this.isScrollDisabled();
+ this.disabledScroll.add(id);
+ if (this._app && isEnabled && this.isScrollDisabled()) {
+ console.debug('GestureController: Disabling scrolling');
+ this._app.setScrollDisabled(true);
+ }
+ }
+
+ enableScroll(id: number) {
+ let isDisabled = this.isScrollDisabled();
+ this.disabledScroll.delete(id);
+ if (this._app && isDisabled && !this.isScrollDisabled()) {
+ console.debug('GestureController: Enabling scrolling');
+ this._app.setScrollDisabled(false);
+ }
+ }
+
+ canStart(gestureName: string): boolean {
+ if (this.capturedID) {
+ // a gesture already captured
+ return false;
+ }
+
+ if (this.isDisabled(gestureName)) {
+ return false;
+ }
+ return true;
+ }
+
+ isCaptured(): boolean {
+ return !!this.capturedID;
+ }
+
+ isScrollDisabled(): boolean {
+ return this.disabledScroll.size > 0;
+ }
+
+ isDisabled(gestureName: string): boolean {
+ let disabled = this.disabledGestures[gestureName];
+ if (disabled && disabled.size > 0) {
+ return true;
+ }
+ return false;
+ }
+
+}
+
+export class GestureDelegate {
+ private disable: string[];
+ private disableScroll: DisableScroll;
+ public priority: number = 0;
+
+ constructor(
+ private name: string,
+ private id: number,
+ private controller: GestureController,
+ opts: GestureOptions
+ ) {
+ this.disable = opts.disable || [];
+ this.disableScroll = opts.disableScroll || DisableScroll.Never;
+ this.priority = opts.priority || 0;
+
+ // Disable gestures
+ for (let gestureName of this.disable) {
+ controller.disableGesture(gestureName, id);
+ }
+
+ // Disable scrolling (always)
+ if (this.disableScroll === DisableScroll.Always) {
+ controller.disableScroll(id);
+ }
+ }
+
+ canStart(): boolean {
+ if (!this.controller) {
+ return false;
+ }
+ return this.controller.canStart(this.name);
+ }
+
+ start(): boolean {
+ if (!this.controller) {
+ return false;
+ }
+ return this.controller.start(this.name, this.id, this.priority);
+ }
+
+ capture(): boolean {
+ if (!this.controller) {
+ return false;
+ }
+ let captured = this.controller.capture(this.name, this.id, this.priority);
+ if (captured && this.disableScroll === DisableScroll.DuringCapture) {
+ this.controller.disableScroll(this.id);
+ }
+ return captured;
+ }
+
+ release() {
+ if (!this.controller) {
+ return;
+ }
+ this.controller.release(this.id);
+ if (this.disableScroll === DisableScroll.DuringCapture) {
+ this.controller.enableScroll(this.id);
+ }
+ }
+
+ destroy() {
+ if (!this.controller) {
+ return;
+ }
+ this.release();
+
+ for (let disabled of this.disable) {
+ this.controller.enableGesture(disabled, this.id);
+ }
+ if (this.disableScroll === DisableScroll.Always) {
+ this.controller.enableScroll(this.id);
+ }
+ this.controller = null;
+ }
+}
\ No newline at end of file
diff --git a/src/gestures/recognizers.ts b/src/gestures/recognizers.ts
new file mode 100644
index 0000000000..ad63faaebe
--- /dev/null
+++ b/src/gestures/recognizers.ts
@@ -0,0 +1,58 @@
+import { pointerCoord, Coordinates } from '../util/dom';
+
+export class PanRecognizer {
+ private startCoord: Coordinates;
+ private dirty: boolean = false;
+ private threshold: number;
+ private maxCosine: number;
+ private _angle: any = 0;
+ private _isPan: number = 0;
+
+ constructor(private direction: string, threshold: number, maxAngle: number) {
+ let radians = maxAngle * (Math.PI / 180);
+ this.maxCosine = Math.cos(radians);
+ this.threshold = threshold * threshold;
+ }
+
+ start(coord: Coordinates) {
+ this.startCoord = coord;
+ this._angle = 0;
+ this._isPan = 0;
+ this.dirty = true;
+ }
+
+ detect(coord: Coordinates): boolean {
+ if (!this.dirty) {
+ return false;
+ }
+ let deltaX = (coord.x - this.startCoord.x);
+ let deltaY = (coord.y - this.startCoord.y);
+ let distance = deltaX * deltaX + deltaY * deltaY;
+ if (distance >= this.threshold) {
+ let angle = Math.atan2(deltaY, deltaX);
+ let cosine = (this.direction === 'y')
+ ? Math.sin(angle)
+ : Math.cos(angle);
+
+ this._angle = angle;
+ if (cosine > this.maxCosine) {
+ this._isPan = 1;
+ } else if (cosine < -this.maxCosine) {
+ this._isPan = -1;
+ } else {
+ this._isPan = 0;
+ }
+ this.dirty = false;
+ return true;
+ }
+ return false;
+ }
+
+ angle(): any {
+ return this._angle;
+ }
+
+ pan(): number {
+ return this._isPan;
+ }
+}
diff --git a/src/gestures/simulator.ts b/src/gestures/simulator.ts
new file mode 100644
index 0000000000..dc304bcf20
--- /dev/null
+++ b/src/gestures/simulator.ts
@@ -0,0 +1,163 @@
+import { pointerCoord, Coordinates } from '../util/dom';
+
+interface Point {
+ coord: Coordinates;
+ duration: number;
+}
+
+export class Simulate {
+ private index: number = 0;
+ private points: Point[] = [];
+ public timedelta: number = 1 / 60;
+
+ public static from(x: any, y?: number): Simulate {
+ let s = new Simulate();
+ return s.start(x, y);
+ }
+
+ reset(): Simulate {
+ this.index = 0;
+ return this;
+ }
+
+ start(x: any, y?: number): Simulate {
+ this.points = [];
+ return this.to(x, y);
+ }
+
+ to(x: any, y?: number): Simulate {
+ this.newPoint(parseCoordinates(x, y), 1);
+ return this;
+ }
+
+ delta(x: any, y?: number): Simulate {
+ let newPoint = parseCoordinates(x, y);
+ let prevCoord = this.getLastPoint().coord;
+ newPoint.x += prevCoord.x;
+ newPoint.y += prevCoord.y;
+
+ this.newPoint(newPoint, 1);
+ return this;
+ }
+
+ deltaPolar(angle: number, distance: number): Simulate {
+ angle *= Math.PI / 180;
+ let prevCoord = this.getLastPoint().coord;
+ let coord = {
+ x: prevCoord.x + (Math.cos(angle) * distance),
+ y: prevCoord.y + (Math.sin(angle) * distance)
+ };
+ this.newPoint(coord, 1);
+ return this;
+ }
+
+ toPolar(angle: number, distance: number): Simulate {
+ angle *= Math.PI / 180;
+ let coord = {
+ x: Math.cos(angle) * distance,
+ y: Math.sin(angle) * distance
+ };
+ this.newPoint(coord, 1);
+ return this;
+ }
+
+ duration(duration: number): Simulate {
+ this.getLastPoint().duration = duration;
+ return this;
+ }
+
+ velocity(vel: number): Simulate {
+ let p1 = this.getLastPoint();
+ let p2 = this.getPreviousPoint();
+ let d = distance(p1, p2);
+ return this.duration(d / vel);
+ }
+
+ swipeRight(maxAngle: number, distance: number): Simulate {
+ // x------>
+ let angle = randomAngle(maxAngle);
+ return this.deltaPolar(angle, distance);
+ }
+
+ swipeLeft(maxAngle: number, distance: number): Simulate {
+ // <------x
+ let angle = randomAngle(maxAngle) + 180;
+ return this.deltaPolar(angle, distance);
+ }
+
+ swipeTop(maxAngle: number, distance: number): Simulate {
+ let angle = randomAngle(maxAngle) + 90;
+ return this.deltaPolar(angle, distance);
+ }
+
+ swipeBottom(maxAngle: number, distance: number): Simulate {
+ let angle = randomAngle(maxAngle) - 90;
+ return this.deltaPolar(angle, distance);
+ }
+
+ run(callback: Function) {
+ let points = this.points;
+ let len = points.length - 1;
+ let i = 0;
+ for (; i < len; i++) {
+ var p1 = points[i].coord;
+ var p2 = points[i + 1].coord;
+ var duration = points[i + 1].duration;
+ var vectorX = p2.x - p1.x;
+ var vectorY = p2.y - p1.y;
+ var nuSteps = Math.ceil(duration / this.timedelta);
+ vectorX /= nuSteps;
+ vectorY /= nuSteps;
+ for (let j = 0; j < nuSteps; j++) {
+ callback({
+ x: p1.x + vectorX * j,
+ y: p1.y + vectorY * j
+ });
+ }
+ }
+ this.index = i;
+
+ return this;
+ }
+
+
+ private newPoint(coord: Coordinates, duration: number) {
+ this.points.push({
+ coord: coord,
+ duration: duration,
+ });
+ }
+
+ private getLastPoint(): Point {
+ let len = this.points.length;
+ if (len > 0) {
+ return this.points[len - 1];
+ }
+ throw new Error('can not call point');
+ }
+
+ private getPreviousPoint(): Point {
+ let len = this.points.length;
+ if (len > 1) {
+ return this.points[len - 2];
+ }
+ throw new Error('can not call point');
+ }
+}
+
+function randomAngle(maxAngle: number): number {
+ return (Math.random() * maxAngle * 2) - maxAngle;
+}
+
+function distance(a: Coordinates, b: Coordinates): number {
+ let deltaX = a.x - b.x;
+ let deltaY = a.y - a.y;
+ return Math.hypot(deltaX, deltaY);
+}
+
+function parseCoordinates(coord: Coordinates | number, y?: number): Coordinates {
+ if (typeof coord === 'number') {
+ return { x: coord, y: y };
+ }
+ return coord;
+}
\ No newline at end of file
diff --git a/src/gestures/slide-edge-gesture.ts b/src/gestures/slide-edge-gesture.ts
index 74b5d18567..d40425f06e 100644
--- a/src/gestures/slide-edge-gesture.ts
+++ b/src/gestures/slide-edge-gesture.ts
@@ -1,6 +1,6 @@
-import {SlideGesture} from './slide-gesture';
-import {defaults} from '../util/util';
-import {windowDimensions} from '../util/dom';
+import { SlideGesture } from './slide-gesture';
+import { defaults } from '../util/util';
+import { pointerCoord, windowDimensions } from '../util/dom';
/**
* @private
@@ -22,8 +22,9 @@ export class SlideEdgeGesture extends SlideGesture {
}
canStart(ev: any): boolean {
+ let coord = pointerCoord(ev);
this._d = this.getContainerDimensions();
- return this.edges.every(edge => this._checkEdge(edge, ev.center));
+ return this.edges.every(edge => this._checkEdge(edge, coord));
}
getContainerDimensions() {
diff --git a/src/gestures/slide-gesture.ts b/src/gestures/slide-gesture.ts
index dca0d0f19f..421ed1e050 100644
--- a/src/gestures/slide-gesture.ts
+++ b/src/gestures/slide-gesture.ts
@@ -1,16 +1,15 @@
-import {DragGesture} from './drag-gesture';
-import {clamp} from '../util';
-
+import { PanGesture } from './drag-gesture';
+import { clamp } from '../util';
+import { pointerCoord } from '../util/dom';
/**
* @private
*/
-export class SlideGesture extends DragGesture {
+export class SlideGesture extends PanGesture {
public slide: SlideData = null;
constructor(element: HTMLElement, opts = {}) {
super(element, opts);
- this.element = element;
}
/*
@@ -20,7 +19,7 @@ export class SlideGesture extends DragGesture {
getSlideBoundaries(slide: SlideData, ev: any) {
return {
min: 0,
- max: this.element.offsetWidth
+ max: this.getNativeElement().offsetWidth
};
}
@@ -33,48 +32,43 @@ export class SlideGesture extends DragGesture {
return 0;
}
- canStart(ev: any): boolean {
- return true;
- }
-
- onDragStart(ev: any): boolean {
- if (!this.canStart(ev)) {
- return false;
- }
-
+ onDragStart(ev: any) {
this.slide = {};
this.onSlideBeforeStart(this.slide, ev);
- var {min, max} = this.getSlideBoundaries(this.slide, ev);
+ let {min, max} = this.getSlideBoundaries(this.slide, ev);
+ let coord = pointerCoord(ev);
this.slide.min = min;
this.slide.max = max;
this.slide.elementStartPos = this.getElementStartPos(this.slide, ev);
- this.slide.pointerStartPos = ev.center[this.direction];
+ this.slide.pos = this.slide.pointerStartPos = coord[this.direction];
+ this.slide.timestamp = Date.now();
this.slide.started = true;
+ this.slide.velocity = 0;
this.onSlideStart(this.slide, ev);
-
- return true;
}
- onDrag(ev: any): boolean {
- if (!this.slide || !this.slide.started) {
- return false;
- }
+ onDragMove(ev: any) {
+ let coord = pointerCoord(ev);
+ let newPos = coord[this.direction];
+ let newTimestamp = Date.now();
+ let velocity = (newPos - this.slide.pos) / (newTimestamp - this.slide.timestamp);
- this.slide.pos = ev.center[this.direction];
+ this.slide.pos = newPos;
+ this.slide.timestamp = newTimestamp;
this.slide.distance = clamp(
this.slide.min,
- this.slide.pos - this.slide.pointerStartPos + this.slide.elementStartPos,
+ newPos - this.slide.pointerStartPos + this.slide.elementStartPos,
this.slide.max
);
- this.slide.delta = this.slide.pos - this.slide.pointerStartPos;
+ this.slide.velocity = velocity;
+ this.slide.delta = newPos - this.slide.pointerStartPos;
this.onSlide(this.slide, ev);
return true;
}
onDragEnd(ev: any) {
- if (!this.slide || !this.slide.started) return;
this.onSlideEnd(this.slide, ev);
this.slide = null;
}
@@ -85,6 +79,9 @@ export class SlideGesture extends DragGesture {
onSlideEnd(slide?: SlideData, ev?: any): void {}
}
+/**
+ * @private
+ */
export interface SlideData {
min?: number;
max?: number;
@@ -92,6 +89,8 @@ export interface SlideData {
delta?: number;
started?: boolean;
pos?: any;
+ timestamp?: number;
pointerStartPos?: number;
elementStartPos?: number;
+ velocity?: number;
}
diff --git a/src/gestures/test/gesture-controller.spec.ts b/src/gestures/test/gesture-controller.spec.ts
new file mode 100644
index 0000000000..b930d1a95b
--- /dev/null
+++ b/src/gestures/test/gesture-controller.spec.ts
@@ -0,0 +1,314 @@
+import { GestureController, DisableScroll } from '../../../src';
+
+export function run() {
+
+ it('should create an instance of GestureController', () => {
+ let c = new GestureController(null);
+ expect(c.isCaptured()).toEqual(false);
+ expect(c.isScrollDisabled()).toEqual(false);
+ });
+
+ it('should test scrolling enable/disable stack', () => {
+ let c = new GestureController(null);
+ c.enableScroll(1);
+ expect(c.isScrollDisabled()).toEqual(false);
+
+ c.disableScroll(1);
+ expect(c.isScrollDisabled()).toEqual(true);
+ c.disableScroll(1);
+ c.disableScroll(1);
+ expect(c.isScrollDisabled()).toEqual(true);
+
+ c.enableScroll(1);
+ expect(c.isScrollDisabled()).toEqual(false);
+
+ for (var i = 0; i < 100; i++) {
+ for (var j = 0; j < 100; j++) {
+ c.disableScroll(j);
+ }
+ }
+
+ for (var i = 0; i < 100; i++) {
+ expect(c.isScrollDisabled()).toEqual(true);
+ c.enableScroll(50 - i);
+ c.enableScroll(i);
+ }
+ expect(c.isScrollDisabled()).toEqual(false);
+ });
+
+ it('should test gesture enable/disable stack', () => {
+ let c = new GestureController(null);
+ c.enableGesture('swipe', 1);
+ expect(c.isDisabled('swipe')).toEqual(false);
+
+ c.disableGesture('swipe', 1);
+ expect(c.isDisabled('swipe')).toEqual(true);
+ c.disableGesture('swipe', 1);
+ c.disableGesture('swipe', 1);
+ expect(c.isDisabled('swipe')).toEqual(true);
+
+ c.enableGesture('swipe', 1);
+ expect(c.isDisabled('swipe')).toEqual(false);
+
+ // Disabling gestures multiple times
+ for (var gestureName = 0; gestureName < 10; gestureName++) {
+ for (var i = 0; i < 50; i++) {
+ for (var j = 0; j < 50; j++) {
+ c.disableGesture(gestureName.toString(), j);
+ }
+ }
+ }
+
+ for (var gestureName = 0; gestureName < 10; gestureName++) {
+ for (var i = 0; i < 49; i++) {
+ c.enableGesture(gestureName.toString(), i);
+ }
+ expect(c.isDisabled(gestureName.toString())).toEqual(true);
+ c.enableGesture(gestureName.toString(), 49);
+ expect(c.isDisabled(gestureName.toString())).toEqual(false);
+ }
+ });
+
+
+ it('should test if canStart', () => {
+ let c = new GestureController(null);
+ expect(c.canStart('event')).toEqual(true);
+ expect(c.canStart('event1')).toEqual(true);
+ expect(c.canStart('event')).toEqual(true);
+ expect(c['requestedStart']).toEqual({});
+ expect(c.isCaptured()).toEqual(false);
+ });
+
+
+
+ it('should initialize a delegate without options', () => {
+ let c = new GestureController(null);
+ let g = c.create('event');
+ expect(g['name']).toEqual('event');
+ expect(g.priority).toEqual(0);
+ expect(g['disable']).toEqual([]);
+ expect(g['disableScroll']).toEqual(DisableScroll.Never);
+ expect(g['controller']).toEqual(c);
+ expect(g['id']).toEqual(1);
+
+ let g2 = c.create('event2');
+ expect(g2['id']).toEqual(2);
+ });
+
+
+ it('should initialize a delegate with options', () => {
+ let c = new GestureController(null);
+ let g = c.create('swipe', {
+ priority: -123,
+ disableScroll: DisableScroll.DuringCapture,
+ disable: ['event2']
+ });
+ expect(g['name']).toEqual('swipe');
+ expect(g.priority).toEqual(-123);
+ expect(g['disable']).toEqual(['event2']);
+ expect(g['disableScroll']).toEqual(DisableScroll.DuringCapture);
+ expect(g['controller']).toEqual(c);
+ expect(g['id']).toEqual(1);
+ });
+
+ it('should test if several gestures can be started', () => {
+ let c = new GestureController(null);
+ let g1 = c.create('swipe');
+ let g2 = c.create('swipe1', {priority: 3});
+ let g3 = c.create('swipe2', {priority: 4});
+
+ for (var i = 0; i < 10; i++) {
+ expect(g1.start()).toEqual(true);
+ expect(g2.start()).toEqual(true);
+ expect(g3.start()).toEqual(true);
+ }
+ expect(c['requestedStart']).toEqual({
+ 1: 0,
+ 2: 3,
+ 3: 4
+ });
+
+ g1.release();
+ g1.release();
+
+ expect(c['requestedStart']).toEqual({
+ 2: 3,
+ 3: 4
+ });
+ expect(g1.start()).toEqual(true);
+ expect(g2.start()).toEqual(true);
+ g3.destroy();
+
+ expect(c['requestedStart']).toEqual({
+ 1: 0,
+ 2: 3,
+ });
+ });
+
+
+ it('should test if several gestures try to capture at the same time', () => {
+ let c = new GestureController(null);
+ let g1 = c.create('swipe1');
+ let g2 = c.create('swipe2', { priority: 2 });
+ let g3 = c.create('swipe3', { priority: 3 });
+ let g4 = c.create('swipe4', { priority: 4 });
+ let g5 = c.create('swipe5', { priority: 5 });
+
+ // Low priority capture() returns false
+ expect(g2.start()).toEqual(true);
+ expect(g3.start()).toEqual(true);
+ expect(g1.capture()).toEqual(false);
+ expect(c['requestedStart']).toEqual({
+ 2: 2,
+ 3: 3
+ });
+
+ // Low priority start() + capture() returns false
+ expect(g2.capture()).toEqual(false);
+ expect(c['requestedStart']).toEqual({
+ 3: 3
+ });
+
+ // Higher priority capture() return true
+ expect(g4.capture()).toEqual(true);
+ expect(c.isScrollDisabled()).toEqual(false);
+ expect(c.isCaptured()).toEqual(true);
+ expect(c['requestedStart']).toEqual({});
+
+ // Higher priority can not capture because it is already capture
+ expect(g5.capture()).toEqual(false);
+ expect(g5.canStart()).toEqual(false);
+ expect(g5.start()).toEqual(false);
+ expect(c['requestedStart']).toEqual({});
+
+ // Only captured gesture can release
+ g1.release();
+ g2.release();
+ g3.release();
+ g5.release();
+ expect(c.isCaptured()).toEqual(true);
+
+ // G4 releases
+ g4.release();
+ expect(c.isCaptured()).toEqual(false);
+
+ // Once it was release, any gesture can capture
+ expect(g1.start()).toEqual(true);
+ expect(g1.capture()).toEqual(true);
+ });
+
+
+ it('should destroy correctly', () => {
+ let c = new GestureController(null);
+ let g = c.create('swipe', {
+ priority: 123,
+ disableScroll: DisableScroll.Always,
+ disable: ['event2']
+ });
+ expect(c.isScrollDisabled()).toEqual(true);
+
+ // Capturing
+ expect(g.capture()).toEqual(true);
+ expect(c.isCaptured()).toEqual(true);
+ expect(g.capture()).toEqual(false);
+ expect(c.isScrollDisabled()).toEqual(true);
+
+ // Releasing
+ g.release();
+ expect(c.isCaptured()).toEqual(false);
+ expect(c.isScrollDisabled()).toEqual(true);
+ expect(g.capture()).toEqual(true);
+ expect(c.isCaptured()).toEqual(true);
+
+ // Destroying
+ g.destroy();
+ expect(c.isCaptured()).toEqual(false);
+ expect(g['controller']).toBeNull();
+
+ // it should return false and not crash
+ expect(g.start()).toEqual(false);
+ expect(g.capture()).toEqual(false);
+ g.release();
+ });
+
+
+ it('should disable some events', () => {
+ let c = new GestureController(null);
+
+ let goback = c.create('goback');
+ expect(goback.canStart()).toEqual(true);
+
+ let g2 = c.create('goback2');
+ expect(g2.canStart()).toEqual(true);
+
+ let g3 = c.create('swipe', {
+ disable: ['range', 'goback', 'something']
+ });
+
+ let g4 = c.create('swipe2', {
+ disable: ['range']
+ });
+
+ // it should be noop
+ g3.release();
+
+ // goback is disabled
+ expect(c.isDisabled('range')).toEqual(true);
+ expect(c.isDisabled('goback')).toEqual(true);
+ expect(c.isDisabled('something')).toEqual(true);
+ expect(c.isDisabled('goback2')).toEqual(false);
+ expect(goback.canStart()).toEqual(false);
+ expect(goback.start()).toEqual(false);
+ expect(goback.capture()).toEqual(false);
+ expect(g3.canStart()).toEqual(true);
+
+ // Once g3 is destroyed, goback and something should be enabled
+ g3.destroy();
+ expect(c.isDisabled('range')).toEqual(true);
+ expect(c.isDisabled('goback')).toEqual(false);
+ expect(c.isDisabled('something')).toEqual(false);
+ expect(g3.canStart()).toEqual(false);
+
+ // Once g4 is destroyed, range is also enabled
+ g4.destroy();
+ expect(c.isDisabled('range')).toEqual(false);
+ expect(g4.canStart()).toEqual(false);
+ });
+
+ it('should disable scrolling on capture', () => {
+ let c = new GestureController(null);
+ let g = c.create('goback', {
+ disableScroll: DisableScroll.DuringCapture,
+ });
+ let g1 = c.create('swipe');
+
+ g.start();
+ expect(c.isScrollDisabled()).toEqual(false);
+
+ g1.capture();
+ g.capture();
+ expect(c.isScrollDisabled()).toEqual(false);
+
+ g1.release();
+ expect(c.isScrollDisabled()).toEqual(false);
+
+ g.capture();
+ expect(c.isScrollDisabled()).toEqual(true);
+
+ let g2 = c.create('swipe2', {
+ disableScroll: DisableScroll.Always,
+ });
+ g.release();
+ expect(c.isScrollDisabled()).toEqual(true);
+
+ g2.destroy();
+ expect(c.isScrollDisabled()).toEqual(false);
+
+ g.capture();
+ expect(c.isScrollDisabled()).toEqual(true);
+
+ g.destroy();
+ expect(c.isScrollDisabled()).toEqual(false);
+ });
+
+}
diff --git a/src/gestures/test/recognizers.spec.ts b/src/gestures/test/recognizers.spec.ts
new file mode 100644
index 0000000000..cd3a47bcd6
--- /dev/null
+++ b/src/gestures/test/recognizers.spec.ts
@@ -0,0 +1,206 @@
+
+import { PanRecognizer } from '../../../src/gestures/recognizers';
+import { Simulate } from '../../../src/gestures/simulator';
+
+export function run() {
+
+ it('should not fire if it did not start', () => {
+ let p = new PanRecognizer('x', 2, 2);
+ expect(p.pan()).toEqual(0);
+
+ Simulate.from(0, 0).to(99, 0).run((coord: Coordinates) => {
+ expect(p.detect(coord)).toEqual(false);
+ });
+ });
+
+ it('should reset', () => {
+ let p = new PanRecognizer('x', 2, 2);
+
+ p.start({ x: 0, y: 0 });
+ expect(p.pan()).toEqual(0);
+
+ Simulate.from(0, 0).to(10, 0).run((coord: Coordinates) => {
+ p.detect(coord);
+ });
+ expect(p.pan()).toEqual(1);
+
+ p.start({ x: 0, y: 0 });
+ expect(p.pan()).toEqual(0);
+
+ Simulate.from(0, 0).to(-10, 0).run((coord: Coordinates) => {
+ p.detect(coord);
+ });
+ expect(p.pan()).toEqual(-1);
+ });
+
+ it('should fire with large threshold', () => {
+ let detected = false;
+ let p = new PanRecognizer('x', 100, 40);
+ p.start({ x: 0, y: 0 });
+
+ Simulate
+ .from(0, 0).to(99, 0)
+ // Since threshold is 100, it should not fire yet
+ .run((coord: Coordinates) => expect(p.detect(coord)).toEqual(false))
+
+ // Now it should fire
+ .delta(2, 0)
+ .run((coord: Coordinates) => {
+ if (p.detect(coord)) {
+ // it should detect a horizontal pan
+ expect(p.pan()).toEqual(1);
+ detected = true;
+ }
+ })
+
+ // It should not detect again
+ .delta(20, 0)
+ .to(0, 0)
+ .to(102, 0)
+ .run((coord: Coordinates) => expect(p.detect(coord)).toEqual(false));
+
+ expect(detected).toEqual(true);
+ });
+
+ it('should detect swipe left', () => {
+ let p = new PanRecognizer('x', 20, 20);
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(19, 21).delta(-30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+ expect(p.pan()).toEqual(1);
+
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(-19, 21).delta(-30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+ expect(p.pan()).toEqual(1);
+ });
+
+ it('should detect swipe right', () => {
+ let p = new PanRecognizer('x', 20, 20);
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(180 - 19, 21).delta(30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+ expect(p.pan()).toEqual(-1);
+
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(180 + 19, 21).delta(30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+ expect(p.pan()).toEqual(-1);
+ });
+
+ it('should NOT detect swipe left', () => {
+ let p = new PanRecognizer('x', 20, 20);
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(21, 21).delta(-30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+ expect(p.pan()).toEqual(0);
+
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(-21, 21).delta(-30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+ expect(p.pan()).toEqual(0);
+ });
+
+ it('should NOT detect swipe right', () => {
+ let p = new PanRecognizer('x', 20, 20);
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(180 - 21, 21).delta(30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+ expect(p.pan()).toEqual(0);
+
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(180 + 21, 21).delta(30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+ expect(p.pan()).toEqual(0);
+ });
+
+
+
+ it('should detect swipe top', () => {
+ let p = new PanRecognizer('y', 20, 20);
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(90 - 19, 21).delta(-30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+ expect(p.pan()).toEqual(1);
+
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(90 + 19, 21).delta(-30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+ expect(p.pan()).toEqual(1);
+ });
+
+ it('should detect swipe bottom', () => {
+ let p = new PanRecognizer('y', 20, 20);
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(-90 + 19, 21).delta(30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+ expect(p.pan()).toEqual(-1);
+
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(-90 - 19, 21).delta(30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+ expect(p.pan()).toEqual(-1);
+ });
+
+ it('should NOT detect swipe top', () => {
+ let p = new PanRecognizer('y', 20, 20);
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(90 - 21, 21).delta(-30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+ expect(p.pan()).toEqual(0);
+
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(90 + 21, 21).delta(-30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+ expect(p.pan()).toEqual(0);
+ });
+
+ it('should NOT detect swipe bottom', () => {
+ let p = new PanRecognizer('y', 20, 20);
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(-90 + 21, 21).delta(30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+ expect(p.pan()).toEqual(0);
+
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(-90 - 21, 21).delta(30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+ expect(p.pan()).toEqual(0);
+ });
+
+ it('should NOT confuse between pan Y and X', () => {
+ let p = new PanRecognizer('x', 20, 20);
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).deltaPolar(90, 21).delta(30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+
+ expect(p.pan()).toEqual(0);
+ });
+
+ it('should NOT confuse between pan X and Y', () => {
+ let p = new PanRecognizer('y', 20, 20);
+ p.start({ x: 0, y: 0 });
+ Simulate
+ .from(0, 0).delta(30, 0)
+ .run((coord: Coordinates) => p.detect(coord));
+
+ expect(p.pan()).toEqual(0);
+ });
+}
diff --git a/src/index.ts b/src/index.ts
index f0aa6922e3..5043478fb8 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -12,6 +12,7 @@ export * from './gestures/drag-gesture';
export * from './gestures/gesture';
export * from './gestures/slide-edge-gesture';
export * from './gestures/slide-gesture';
+export * from './gestures/gesture-controller';
export * from './platform/platform';
export * from './platform/storage';
diff --git a/src/platform/cordova.ios.scss b/src/platform/cordova.ios.scss
index aa2a1945ea..9e0609fae9 100644
--- a/src/platform/cordova.ios.scss
+++ b/src/platform/cordova.ios.scss
@@ -10,7 +10,6 @@ $cordova-ios-statusbar-padding-modal-max-width: $cordova-statusbar-paddi
ion-nav > ion-page,
ion-nav > ion-page > ion-header,
ion-tab > ion-page > ion-header,
-ion-tabs > ion-page.tab-subpage > ion-header,
ion-menu {
@include toolbar-statusbar-padding($toolbar-ios-height, $content-ios-padding);
@include toolbar-title-statusbar-padding($toolbar-ios-height, $content-ios-padding);
diff --git a/src/platform/cordova.md.scss b/src/platform/cordova.md.scss
index 4897a4e278..cb8dc1d351 100644
--- a/src/platform/cordova.md.scss
+++ b/src/platform/cordova.md.scss
@@ -10,7 +10,6 @@ $cordova-md-statusbar-padding-modal-max-width: $cordova-statusbar-paddin
ion-nav > ion-page,
ion-nav > ion-page > ion-header,
ion-tab > ion-page > ion-header,
-ion-tabs > ion-page.tab-subpage > ion-header,
ion-menu {
@include toolbar-statusbar-padding($toolbar-md-height, $content-md-padding);
}
diff --git a/src/platform/cordova.wp.scss b/src/platform/cordova.wp.scss
index 230a426fb9..b45a38f227 100644
--- a/src/platform/cordova.wp.scss
+++ b/src/platform/cordova.wp.scss
@@ -10,7 +10,6 @@ $cordova-wp-statusbar-padding-modal-max-width: $cordova-statusbar-paddin
ion-nav > ion-page,
ion-nav > ion-page > ion-header,
ion-tab > ion-page > ion-header,
-ion-tabs > ion-page.tab-subpage > ion-header,
ion-menu {
@include toolbar-statusbar-padding($toolbar-wp-height, $content-wp-padding);
}
diff --git a/src/util/dom.ts b/src/util/dom.ts
index 5a6c61d396..9a97f5cd50 100644
--- a/src/util/dom.ts
+++ b/src/util/dom.ts
@@ -190,8 +190,10 @@ export function pointerCoord(ev: any): Coordinates {
}
export function hasPointerMoved(threshold: number, startCoord: Coordinates, endCoord: Coordinates) {
- return startCoord && endCoord &&
- (Math.abs(startCoord.x - endCoord.x) > threshold || Math.abs(startCoord.y - endCoord.y) > threshold);
+ let deltaX = (startCoord.x - endCoord.x);
+ let deltaY = (startCoord.y - endCoord.y);
+ let distance = deltaX * deltaX + deltaY * deltaY;
+ return distance > (threshold * threshold);
}
export function isActive(ele: HTMLElement) {
diff --git a/src/util/mock-providers.ts b/src/util/mock-providers.ts
new file mode 100644
index 0000000000..acdb194a79
--- /dev/null
+++ b/src/util/mock-providers.ts
@@ -0,0 +1,171 @@
+import { ChangeDetectorRef, ElementRef, NgZone, Renderer } from '@angular/core';
+import { Location } from '@angular/common';
+
+import { App, Config, Form, GestureController, Keyboard, MenuController, NavOptions, Platform, Tab, Tabs, Transition, ViewController } from '../../src';
+import { NavControllerBase } from '../../src/components/nav/nav-controller-base';
+
+
+export const mockConfig = function(config?: any) {
+ return new Config(config);
+};
+
+export const mockPlatform = function(platforms?: string[]) {
+ return new Platform(platforms);
+};
+
+export const mockApp = function(config?: Config, platform?: Platform) {
+ config = config || mockConfig();
+ platform = platform || mockPlatform();
+ return new App(config, platform);
+};
+
+export const mockZone = function(): NgZone {
+ let zone: any = {
+ run: function(cb: any) {
+ cb();
+ },
+ runOutsideAngular: function(cb: any) {
+ cb();
+ }
+ };
+ return zone;
+};
+
+export const mockChangeDetectorRef = function(): ChangeDetectorRef {
+ let cd: any = {
+ reattach: () => {},
+ detach: () => {}
+ };
+ return cd;
+};
+
+export const mockElementRef = function(): ElementRef {
+ return {
+ nativeElement: document.createElement('div')
+ };
+};
+
+export const mockRenderer = function(): Renderer {
+ let renderer: any = {
+ setElementAttribute: () => {},
+ setElementClass: () => {},
+ setElementStyle: () => {}
+ };
+ return renderer;
+};
+
+export const mockLocation = function(): Location {
+ let location: any = {
+ path: () => { return ''; },
+ subscribe: () => {},
+ go: () => {},
+ back: () => {}
+ };
+ return location;
+};
+
+export const mockTransition = function(playCallback: Function, duration: number) {
+ return function _createTrans(enteringView: ViewController, leavingView: ViewController, transitionOpts: any): Transition {
+ let transition: any = {
+ play: () => {
+ playCallback();
+ },
+ getDuration: () => { return duration; },
+ onFinish: () => {}
+ };
+ return transition;
+ };
+};
+
+export const mockNavController = function(): NavControllerBase {
+ let platform = mockPlatform();
+
+ let config = mockConfig();
+ config.setPlatform(platform);
+
+ let app = mockApp(config, platform);
+
+ let form = new Form();
+
+ let zone = mockZone();
+
+ let keyboard = new Keyboard(config, form, zone);
+
+ let elementRef = mockElementRef();
+
+ let renderer = mockRenderer();
+
+ let compiler: any = null;
+
+ let gestureCtrl = new GestureController(app);
+
+ let location = mockLocation();
+
+ return new NavControllerBase(
+ null,
+ app,
+ config,
+ keyboard,
+ elementRef,
+ zone,
+ renderer,
+ compiler,
+ gestureCtrl
+ );
+};
+
+export const mockTab = function(parentTabs: Tabs): Tab {
+ let platform = mockPlatform();
+
+ let config = mockConfig();
+ config.setPlatform(platform);
+
+ let app = (parentTabs)._app || mockApp(config, platform);
+
+ let form = new Form();
+
+ let zone = mockZone();
+
+ let keyboard = new Keyboard(config, form, zone);
+
+ let elementRef = mockElementRef();
+
+ let renderer = mockRenderer();
+
+ let changeDetectorRef = mockChangeDetectorRef();
+
+ let compiler: any = null;
+
+ let gestureCtrl = new GestureController(app);
+
+ let location = mockLocation();
+
+ let tab = new Tab(
+ parentTabs,
+ app,
+ config,
+ keyboard,
+ elementRef,
+ zone,
+ renderer,
+ compiler,
+ changeDetectorRef,
+ gestureCtrl
+ );
+
+ tab.load = (opts: any, cb: Function) => {
+ cb();
+ };
+
+ return tab;
+};
+
+export const mockTabs = function(app?: App): Tabs {
+ let config = mockConfig();
+ let platform = mockPlatform();
+ app = app || mockApp(config, platform);
+ let elementRef = mockElementRef();
+ let renderer = mockRenderer();
+
+ return new Tabs(null, null, app, config, elementRef, platform, renderer);
+};
diff --git a/src/util/ui-event-manager.ts b/src/util/ui-event-manager.ts
index 457f7ebc98..03116c7879 100644
--- a/src/util/ui-event-manager.ts
+++ b/src/util/ui-event-manager.ts
@@ -1,7 +1,14 @@
import {ElementRef} from '@angular/core';
-
-
+export interface PointerEventsConfig {
+ element?: HTMLElement;
+ elementRef?: ElementRef;
+ pointerDown: (ev: any) => boolean;
+ pointerMove: (ev: any) => void;
+ pointerUp: (ev: any) => void;
+ nativeOptions?: any;
+ zone?: boolean;
+}
/**
* @private
@@ -10,11 +17,15 @@ export class PointerEvents {
private rmTouchStart: Function = null;
private rmTouchMove: Function = null;
private rmTouchEnd: Function = null;
+ private rmTouchCancel: Function = null;
private rmMouseStart: Function = null;
private rmMouseMove: Function = null;
private rmMouseUp: Function = null;
+ private bindTouchEnd: Function;
+ private bindMouseUp: Function;
+
private lastTouchEvent: number = 0;
mouseWait: number = 2 * 1000;
@@ -24,10 +35,14 @@ export class PointerEvents {
private pointerMove: any,
private pointerUp: any,
private zone: boolean,
- private option: any) {
+ private option: any
+ ) {
- this.rmTouchStart = listenEvent(ele, 'touchstart', zone, option, (ev: any) => this.handleTouchStart(ev));
- this.rmMouseStart = listenEvent(ele, 'mousedown', zone, option, (ev: any) => this.handleMouseDown(ev));
+ this.bindTouchEnd = this.handleTouchEnd.bind(this);
+ this.bindMouseUp = this.handleMouseUp.bind(this);
+
+ this.rmTouchStart = listenEvent(ele, 'touchstart', zone, option, this.handleTouchStart.bind(this));
+ this.rmMouseStart = listenEvent(ele, 'mousedown', zone, option, this.handleMouseDown.bind(this));
}
private handleTouchStart(ev: any) {
@@ -39,7 +54,10 @@ export class PointerEvents {
this.rmTouchMove = listenEvent(this.ele, 'touchmove', this.zone, this.option, this.pointerMove);
}
if (!this.rmTouchEnd) {
- this.rmTouchEnd = listenEvent(this.ele, 'touchend', this.zone, this.option, (ev: any) => this.handleTouchEnd(ev));
+ this.rmTouchEnd = listenEvent(this.ele, 'touchend', this.zone, this.option, this.bindTouchEnd);
+ }
+ if (!this.rmTouchCancel) {
+ this.rmTouchCancel = listenEvent(this.ele, 'touchcancel', this.zone, this.option, this.bindTouchEnd);
}
}
@@ -55,40 +73,43 @@ export class PointerEvents {
this.rmMouseMove = listenEvent(window, 'mousemove', this.zone, this.option, this.pointerMove);
}
if (!this.rmMouseUp) {
- this.rmMouseUp = listenEvent(window, 'mouseup', this.zone, this.option, (ev: any) => this.handleMouseUp(ev));
+ this.rmMouseUp = listenEvent(window, 'mouseup', this.zone, this.option, this.bindMouseUp);
}
}
private handleTouchEnd(ev: any) {
- this.rmTouchMove && this.rmTouchMove();
- this.rmTouchMove = null;
- this.rmTouchEnd && this.rmTouchEnd();
- this.rmTouchEnd = null;
-
+ this.stopTouch();
this.pointerUp(ev);
}
private handleMouseUp(ev: any) {
- this.rmMouseMove && this.rmMouseMove();
- this.rmMouseMove = null;
- this.rmMouseUp && this.rmMouseUp();
- this.rmMouseUp = null;
-
+ this.stopMouse();
this.pointerUp(ev);
}
- stop() {
+ private stopTouch() {
this.rmTouchMove && this.rmTouchMove();
this.rmTouchEnd && this.rmTouchEnd();
+ this.rmTouchCancel && this.rmTouchCancel();
+
this.rmTouchMove = null;
this.rmTouchEnd = null;
+ this.rmTouchCancel = null;
+ }
+ private stopMouse() {
this.rmMouseMove && this.rmMouseMove();
this.rmMouseUp && this.rmMouseUp();
+
this.rmMouseMove = null;
this.rmMouseUp = null;
}
+ stop() {
+ this.stopTouch();
+ this.stopMouse();
+ }
+
destroy() {
this.rmTouchStart && this.rmTouchStart();
this.rmTouchStart = null;
@@ -120,21 +141,26 @@ export class UIEventManager {
return this.listen(ref.nativeElement, eventName, callback, option);
}
- pointerEventsRef(ref: ElementRef, pointerStart: any, pointerMove: any, pointerEnd: any, option?: any): PointerEvents {
- return this.pointerEvents(ref.nativeElement, pointerStart, pointerMove, pointerEnd, option);
- }
-
- pointerEvents(element: any, pointerDown: any, pointerMove: any, pointerUp: any, option: any = false): PointerEvents {
+ pointerEvents(config: PointerEventsConfig): PointerEvents {
+ let element = config.element;
if (!element) {
+ element = config.elementRef.nativeElement;
+ }
+
+ if (!element || !config.pointerDown || !config.pointerMove || !config.pointerUp) {
+ console.error('PointerEvents config is invalid');
return;
}
+ let zone = config.zone || this.zoneWrapped;
+ let options = config.nativeOptions || false;
+
let submanager = new PointerEvents(
element,
- pointerDown,
- pointerMove,
- pointerUp,
- this.zoneWrapped,
- option);
+ config.pointerDown,
+ config.pointerMove,
+ config.pointerUp,
+ zone,
+ options);
let removeFunc = () => submanager.destroy();
this.events.push(removeFunc);
diff --git a/src/util/util.ts b/src/util/util.ts
index 459b618e7c..84000a4047 100644
--- a/src/util/util.ts
+++ b/src/util/util.ts
@@ -1,4 +1,6 @@
+export function noop() {}
+
/**
* Given a min and max, restrict the given number
* to the range.
diff --git a/tooling/generators/provider/provider.tmpl.js b/tooling/generators/provider/provider.tmpl.js
index f105d19921..909c5343a1 100644
--- a/tooling/generators/provider/provider.tmpl.js
+++ b/tooling/generators/provider/provider.tmpl.js
@@ -16,29 +16,7 @@ export class <%= jsClassName %> {
constructor(http) {
this.http = http;
- this.data = null;
}
- load() {
- if (this.data) {
- // already loaded data
- return Promise.resolve(this.data);
- }
-
- // don't have the data yet
- return new Promise(resolve => {
- // We're using Angular Http provider to request the data,
- // then on the response it'll map the JSON data to a parsed JS object.
- // Next we process the data and resolve the promise with the new data.
- this.http.get('path/to/data.json')
- .map(res => res.json())
- .subscribe(data => {
- // we've got back the raw data, now generate the core schedule data
- // and save the data for later reference
- this.data = data;
- resolve(this.data);
- });
- });
- }
}
diff --git a/tooling/generators/provider/provider.tmpl.ts b/tooling/generators/provider/provider.tmpl.ts
index 3c68415f65..ac8e859c0c 100644
--- a/tooling/generators/provider/provider.tmpl.ts
+++ b/tooling/generators/provider/provider.tmpl.ts
@@ -10,32 +10,8 @@ import 'rxjs/add/operator/map';
*/
@Injectable()
export class <%= jsClassName %> {
- data: any;
- constructor(private http: Http) {
- this.data = null;
- }
+ constructor(private http: Http) {}
- load() {
- if (this.data) {
- // already loaded data
- return Promise.resolve(this.data);
- }
-
- // don't have the data yet
- return new Promise(resolve => {
- // We're using Angular Http provider to request the data,
- // then on the response it'll map the JSON data to a parsed JS object.
- // Next we process the data and resolve the promise with the new data.
- this.http.get('path/to/data.json')
- .map(res => res.json())
- .subscribe(data => {
- // we've got back the raw data, now generate the core schedule data
- // and save the data for later reference
- this.data = data;
- resolve(this.data);
- });
- });
- }
}