Skip to content

Commit

Permalink
fix(nav): ionViewCanLeave does not break navigation
Browse files Browse the repository at this point in the history
fixes #8408
  • Loading branch information
manucorporat committed Oct 10, 2016
1 parent e8e518a commit c8954d8
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 135 deletions.
244 changes: 129 additions & 115 deletions src/navigation/nav-controller-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AnimationOptions } from '../animations/animation';
import { App } from '../components/app/app';
import { Config } from '../config/config';
import { convertToView, convertToViews, NavOptions, DIRECTION_BACK, DIRECTION_FORWARD, INIT_ZINDEX,
TransitionResolveFn, TransitionRejectFn, TransitionInstruction, ViewState } from './nav-util';
TransitionResolveFn, TransitionInstruction, ViewState } from './nav-util';
import { setZIndex } from './nav-util';
import { DeepLinker } from './deep-linker';
import { GestureController } from '../gestures/gesture-controller';
Expand Down Expand Up @@ -254,28 +254,40 @@ export class NavControllerBase extends Ion implements NavController {

// there is no transition happening right now
// get the next instruction
const ti = this._queue.shift();
const ti = this._nextTI();
if (!ti) {
this.setTransitioning(false);
return false;
}

this.setTransitioning(true, ACTIVE_TRANSITION_MAX_TIME);
const viewsLength = this._views.length;
const activeView = this.getActive();
// Get entering and leaving views
const leavingView = this.getActive();
const enteringView = this._getEnteringView(ti, leavingView);

let enteringView: ViewController;
let leavingView: ViewController = activeView;
const destroyQueue: ViewController[] = [];
// Initialize enteringView
if (enteringView && isBlank(enteringView._state)) {
// render the entering view, and all child navs and views
// ******** DOM WRITE ****************
this._viewInit(enteringView);
}

const opts = ti.opts || {};
const resolve = ti.resolve;
const reject = ti.reject;
let insertViews = ti.insertViews;
ti.resolve = ti.reject = ti.opts = ti.insertViews = null;
// Only test canLeave/canEnter if there is transition
let requiresTransition = (ti.enteringRequiresTransition || ti.leavingRequiresTransition) && enteringView !== leavingView;
if (requiresTransition) {
// views have been initialized, now let's test
// to see if the transition is even allowed or not
return this._viewTest(enteringView, leavingView, ti);

} else {
return this._postViewInit(enteringView, leavingView, ti, ti.resolve);
}
}

let enteringRequiresTransition = false;
let leavingRequiresTransition = false;
_nextTI(): TransitionInstruction {
const ti = this._queue.shift();
if (!ti) {
return null;
}
const viewsLength = this._views.length;

if (isPresent(ti.removeStart)) {
if (ti.removeStart < 0) {
Expand All @@ -284,39 +296,59 @@ export class NavControllerBase extends Ion implements NavController {
if (ti.removeCount < 0) {
ti.removeCount = (viewsLength - ti.removeStart);
}

leavingRequiresTransition = (ti.removeStart + ti.removeCount === viewsLength);

for (var i = 0; i < ti.removeCount; i++) {
destroyQueue.push(this._views[i + ti.removeStart]);
}

for (var i = viewsLength - 1; i >= 0; i--) {
var view = this._views[i];
if (destroyQueue.indexOf(view) < 0 && view !== leavingView) {
enteringView = view;
break;
}
}

// default the direction to "back"
opts.direction = opts.direction || DIRECTION_BACK;
ti.leavingRequiresTransition = ((ti.removeStart + ti.removeCount) === viewsLength);
}

if (insertViews) {
if (ti.insertViews) {
// allow -1 to be passed in to auto push it on the end
// and clean up the index if it's larger then the size of the stack
if (ti.insertStart < 0 || ti.insertStart > viewsLength) {
ti.insertStart = viewsLength;
}
ti.enteringRequiresTransition = (ti.insertStart === viewsLength);
}
return ti;
}

// only requires a transition if it's going at the end
enteringRequiresTransition = (ti.insertStart === viewsLength);

_getEnteringView(ti: TransitionInstruction, leavingView: ViewController): ViewController {
const insertViews = ti.insertViews;
if (insertViews) {
// grab the very last view of the views to be inserted
// and initialize it as the new entering view
enteringView = insertViews[insertViews.length - 1];
return insertViews[insertViews.length - 1];
}

const removeStart = ti.removeStart;
if (isPresent(removeStart)) {
let views = this._views;
const removeEnd = removeStart + ti.removeCount;
for (var i = views.length - 1; i >= 0; i--) {
var view = views[i];
if ((i < removeStart || i >= removeEnd) && view !== leavingView) {
return view;
}
}
}
return null;
}

_postViewInit(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction, resolve: TransitionResolveFn): boolean {
const opts = ti.opts || {};

const insertViews = ti.insertViews;
const removeStart = ti.removeStart;

let destroyQueue: ViewController[] = [];

if (isPresent(removeStart)) {
for (var i = 0; i < ti.removeCount; i++) {
destroyQueue.push(this._views[i + removeStart]);
}
// default the direction to "back"
opts.direction = opts.direction || DIRECTION_BACK;
}

if (insertViews) {
// manually set the new view's id if an id was passed in the options
if (isPresent(opts.id)) {
enteringView.id = opts.id;
Expand Down Expand Up @@ -345,7 +377,7 @@ export class NavControllerBase extends Ion implements NavController {
}
}

if (enteringRequiresTransition) {
if (ti.enteringRequiresTransition) {
// default to forward if not already set
opts.direction = opts.direction || DIRECTION_FORWARD;
}
Expand Down Expand Up @@ -380,7 +412,7 @@ export class NavControllerBase extends Ion implements NavController {
}
destroyQueue.length = 0;

if (enteringRequiresTransition || leavingRequiresTransition && enteringView !== leavingView) {
if (ti.enteringRequiresTransition || ti.leavingRequiresTransition && enteringView !== leavingView) {
// set which animation it should use if it wasn't set yet
if (!opts.animation) {
if (isPresent(ti.removeStart)) {
Expand All @@ -391,7 +423,7 @@ export class NavControllerBase extends Ion implements NavController {
}

// huzzah! let us transition these views
this._transition(enteringView, leavingView, opts, resolve, reject);
this._transition(enteringView, leavingView, opts, resolve);

} else {
// they're inserting/removing the views somewhere in the middle or
Expand All @@ -403,71 +435,10 @@ export class NavControllerBase extends Ion implements NavController {
return true;
}

_transition(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, resolve: TransitionResolveFn, reject: TransitionRejectFn): void {
// figure out if this transition is the root one or a
// child of a parent nav that has the root transition
this._trnsId = this._trnsCtrl.getRootTrnsId(this);
if (this._trnsId === null) {
// this is the root transition, meaning all child navs and their views
// should be added as a child transition to this one
this._trnsId = this._trnsCtrl.nextId();
}

// create the transition options
const animationOpts: AnimationOptions = {
animation: opts.animation,
direction: opts.direction,
duration: (opts.animate === false ? 0 : opts.duration),
easing: opts.easing,
isRTL: this.config.platform.isRTL(),
ev: opts.ev,
};

// create the transition animation from the TransitionController
// this will either create the root transition, or add it as a child transition
const trns = this._trnsCtrl.get(this._trnsId, enteringView, leavingView, animationOpts);

// ensure any swipeback transitions are cleared out
this._sbTrns && this._sbTrns.destroy();

if (trns.parent) {
// this is important for later to know if there
// are any more child tests to check for
trns.parent.hasChildTrns = true;

} else {
// this is the root transition
if (opts.progressAnimation) {
this._sbTrns = trns;
}
}

trns.registerStart(() => {
this._trnsStart(trns, enteringView, leavingView, opts, resolve);
if (trns.parent) {
trns.parent.start();
}
});

if (enteringView && isBlank(enteringView._state)) {
// render the entering view, and all child navs and views
// ******** DOM WRITE ****************
this._viewInit(trns, enteringView, opts);
}

// views have been initialized, now let's test
// to see if the transition is even allowed or not
const shouldContinue = this._viewTest(trns, enteringView, leavingView, opts, resolve, reject);
if (shouldContinue) {
// synchronous and all tests passed! let's continue
this._postViewInit(trns, enteringView, leavingView, opts, resolve);
}
}

/**
* DOM WRITE
*/
_viewInit(trns: Transition, enteringView: ViewController, opts: NavOptions) {
_viewInit(enteringView: ViewController) {
// entering view has not been initialized yet
const componentProviders = ReflectiveInjector.resolve([
{ provide: NavController, useValue: this },
Expand All @@ -482,8 +453,10 @@ export class NavControllerBase extends Ion implements NavController {
enteringView._state = ViewState.INITIALIZED;
}

_viewTest(trns: Transition, enteringView: ViewController, leavingView: ViewController, opts: NavOptions, resolve: TransitionResolveFn, reject: TransitionRejectFn): boolean {
_viewTest(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction): boolean {
const promises: Promise<any>[] = [];
const reject = ti.reject;
const resolve = ti.resolve;

if (leavingView) {
const leavingTestResult = leavingView._lifecycleTest('Leave');
Expand All @@ -495,7 +468,7 @@ export class NavControllerBase extends Ion implements NavController {

} else {
// synchronous reject
reject((leavingTestResult !== false ? leavingTestResult : `ionViewCanLeave rejected`), trns);
reject((leavingTestResult !== false ? leavingTestResult : `ionViewCanLeave rejected`));
return false;
}
}
Expand All @@ -511,7 +484,7 @@ export class NavControllerBase extends Ion implements NavController {

} else {
// synchronous reject
reject((enteringTestResult !== false ? enteringTestResult : `ionViewCanEnter rejected`), trns);
reject((enteringTestResult !== false ? enteringTestResult : `ionViewCanEnter rejected`));
return false;
}
}
Expand All @@ -521,26 +494,67 @@ export class NavControllerBase extends Ion implements NavController {
// darn, async promises, gotta wait for them to resolve
Promise.all(promises).then(() => {
// all promises resolved! let's continue
this._postViewInit(trns, enteringView, leavingView, opts, resolve);

}, (rejectReason: any) => {
// darn, one of the promises was rejected!!
reject(rejectReason, trns);
this._postViewInit(enteringView, leavingView, ti, resolve);

}).catch((rejectReason) => {
// idk, who knows
reject(rejectReason, trns);
reject(rejectReason);
});

return false;
}

// synchronous and all tests passed! let's move on already
this._postViewInit(enteringView, leavingView, ti, resolve);

return true;
}

_postViewInit(trns: Transition, enteringView: ViewController, leavingView: ViewController, opts: NavOptions, resolve: TransitionResolveFn): void {
// passed both the enter and leave tests
_transition(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, resolve: TransitionResolveFn): void {
// figure out if this transition is the root one or a
// child of a parent nav that has the root transition
this._trnsId = this._trnsCtrl.getRootTrnsId(this);
if (this._trnsId === null) {
// this is the root transition, meaning all child navs and their views
// should be added as a child transition to this one
this._trnsId = this._trnsCtrl.nextId();
}

// create the transition options
const animationOpts: AnimationOptions = {
animation: opts.animation,
direction: opts.direction,
duration: (opts.animate === false ? 0 : opts.duration),
easing: opts.easing,
isRTL: this.config.platform.isRTL(),
ev: opts.ev,
};

// create the transition animation from the TransitionController
// this will either create the root transition, or add it as a child transition
const trns = this._trnsCtrl.get(this._trnsId, enteringView, leavingView, animationOpts);

// ensure any swipeback transitions are cleared out
this._sbTrns && this._sbTrns.destroy();

if (trns.parent) {
// this is important for later to know if there
// are any more child tests to check for
trns.parent.hasChildTrns = true;

} else {
// this is the root transition
if (opts.progressAnimation) {
this._sbTrns = trns;
}
}

trns.registerStart(() => {
this._trnsStart(trns, enteringView, leavingView, opts, resolve);
if (trns.parent) {
trns.parent.start();
}
});

if (enteringView && enteringView._state === ViewState.INITIALIZED) {
// render the entering component in the DOM
// this would also render new child navs/views
Expand Down
2 changes: 2 additions & 0 deletions src/navigation/nav-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ export interface TransitionInstruction {
removeCount?: number;
resolve?: TransitionResolveFn;
reject?: TransitionRejectFn;
leavingRequiresTransition?: boolean;
enteringRequiresTransition?: boolean;
}

export enum ViewState {
Expand Down
Loading

0 comments on commit c8954d8

Please sign in to comment.