Skip to content

Commit c8954d8

Browse files
committed
fix(nav): ionViewCanLeave does not break navigation
fixes #8408
1 parent e8e518a commit c8954d8

File tree

6 files changed

+209
-135
lines changed

6 files changed

+209
-135
lines changed

src/navigation/nav-controller-base.ts

Lines changed: 129 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { AnimationOptions } from '../animations/animation';
44
import { App } from '../components/app/app';
55
import { Config } from '../config/config';
66
import { convertToView, convertToViews, NavOptions, DIRECTION_BACK, DIRECTION_FORWARD, INIT_ZINDEX,
7-
TransitionResolveFn, TransitionRejectFn, TransitionInstruction, ViewState } from './nav-util';
7+
TransitionResolveFn, TransitionInstruction, ViewState } from './nav-util';
88
import { setZIndex } from './nav-util';
99
import { DeepLinker } from './deep-linker';
1010
import { GestureController } from '../gestures/gesture-controller';
@@ -254,28 +254,40 @@ export class NavControllerBase extends Ion implements NavController {
254254

255255
// there is no transition happening right now
256256
// get the next instruction
257-
const ti = this._queue.shift();
257+
const ti = this._nextTI();
258258
if (!ti) {
259-
this.setTransitioning(false);
260259
return false;
261260
}
262261

263-
this.setTransitioning(true, ACTIVE_TRANSITION_MAX_TIME);
264-
const viewsLength = this._views.length;
265-
const activeView = this.getActive();
262+
// Get entering and leaving views
263+
const leavingView = this.getActive();
264+
const enteringView = this._getEnteringView(ti, leavingView);
266265

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

271-
const opts = ti.opts || {};
272-
const resolve = ti.resolve;
273-
const reject = ti.reject;
274-
let insertViews = ti.insertViews;
275-
ti.resolve = ti.reject = ti.opts = ti.insertViews = null;
273+
// Only test canLeave/canEnter if there is transition
274+
let requiresTransition = (ti.enteringRequiresTransition || ti.leavingRequiresTransition) && enteringView !== leavingView;
275+
if (requiresTransition) {
276+
// views have been initialized, now let's test
277+
// to see if the transition is even allowed or not
278+
return this._viewTest(enteringView, leavingView, ti);
279+
280+
} else {
281+
return this._postViewInit(enteringView, leavingView, ti, ti.resolve);
282+
}
283+
}
276284

277-
let enteringRequiresTransition = false;
278-
let leavingRequiresTransition = false;
285+
_nextTI(): TransitionInstruction {
286+
const ti = this._queue.shift();
287+
if (!ti) {
288+
return null;
289+
}
290+
const viewsLength = this._views.length;
279291

280292
if (isPresent(ti.removeStart)) {
281293
if (ti.removeStart < 0) {
@@ -284,39 +296,59 @@ export class NavControllerBase extends Ion implements NavController {
284296
if (ti.removeCount < 0) {
285297
ti.removeCount = (viewsLength - ti.removeStart);
286298
}
287-
288-
leavingRequiresTransition = (ti.removeStart + ti.removeCount === viewsLength);
289-
290-
for (var i = 0; i < ti.removeCount; i++) {
291-
destroyQueue.push(this._views[i + ti.removeStart]);
292-
}
293-
294-
for (var i = viewsLength - 1; i >= 0; i--) {
295-
var view = this._views[i];
296-
if (destroyQueue.indexOf(view) < 0 && view !== leavingView) {
297-
enteringView = view;
298-
break;
299-
}
300-
}
301-
302-
// default the direction to "back"
303-
opts.direction = opts.direction || DIRECTION_BACK;
299+
ti.leavingRequiresTransition = ((ti.removeStart + ti.removeCount) === viewsLength);
304300
}
305301

306-
if (insertViews) {
302+
if (ti.insertViews) {
307303
// allow -1 to be passed in to auto push it on the end
308304
// and clean up the index if it's larger then the size of the stack
309305
if (ti.insertStart < 0 || ti.insertStart > viewsLength) {
310306
ti.insertStart = viewsLength;
311307
}
308+
ti.enteringRequiresTransition = (ti.insertStart === viewsLength);
309+
}
310+
return ti;
311+
}
312312

313-
// only requires a transition if it's going at the end
314-
enteringRequiresTransition = (ti.insertStart === viewsLength);
315-
313+
_getEnteringView(ti: TransitionInstruction, leavingView: ViewController): ViewController {
314+
const insertViews = ti.insertViews;
315+
if (insertViews) {
316316
// grab the very last view of the views to be inserted
317317
// and initialize it as the new entering view
318-
enteringView = insertViews[insertViews.length - 1];
318+
return insertViews[insertViews.length - 1];
319+
}
319320

321+
const removeStart = ti.removeStart;
322+
if (isPresent(removeStart)) {
323+
let views = this._views;
324+
const removeEnd = removeStart + ti.removeCount;
325+
for (var i = views.length - 1; i >= 0; i--) {
326+
var view = views[i];
327+
if ((i < removeStart || i >= removeEnd) && view !== leavingView) {
328+
return view;
329+
}
330+
}
331+
}
332+
return null;
333+
}
334+
335+
_postViewInit(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction, resolve: TransitionResolveFn): boolean {
336+
const opts = ti.opts || {};
337+
338+
const insertViews = ti.insertViews;
339+
const removeStart = ti.removeStart;
340+
341+
let destroyQueue: ViewController[] = [];
342+
343+
if (isPresent(removeStart)) {
344+
for (var i = 0; i < ti.removeCount; i++) {
345+
destroyQueue.push(this._views[i + removeStart]);
346+
}
347+
// default the direction to "back"
348+
opts.direction = opts.direction || DIRECTION_BACK;
349+
}
350+
351+
if (insertViews) {
320352
// manually set the new view's id if an id was passed in the options
321353
if (isPresent(opts.id)) {
322354
enteringView.id = opts.id;
@@ -345,7 +377,7 @@ export class NavControllerBase extends Ion implements NavController {
345377
}
346378
}
347379

348-
if (enteringRequiresTransition) {
380+
if (ti.enteringRequiresTransition) {
349381
// default to forward if not already set
350382
opts.direction = opts.direction || DIRECTION_FORWARD;
351383
}
@@ -380,7 +412,7 @@ export class NavControllerBase extends Ion implements NavController {
380412
}
381413
destroyQueue.length = 0;
382414

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

393425
// huzzah! let us transition these views
394-
this._transition(enteringView, leavingView, opts, resolve, reject);
426+
this._transition(enteringView, leavingView, opts, resolve);
395427

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

406-
_transition(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, resolve: TransitionResolveFn, reject: TransitionRejectFn): void {
407-
// figure out if this transition is the root one or a
408-
// child of a parent nav that has the root transition
409-
this._trnsId = this._trnsCtrl.getRootTrnsId(this);
410-
if (this._trnsId === null) {
411-
// this is the root transition, meaning all child navs and their views
412-
// should be added as a child transition to this one
413-
this._trnsId = this._trnsCtrl.nextId();
414-
}
415-
416-
// create the transition options
417-
const animationOpts: AnimationOptions = {
418-
animation: opts.animation,
419-
direction: opts.direction,
420-
duration: (opts.animate === false ? 0 : opts.duration),
421-
easing: opts.easing,
422-
isRTL: this.config.platform.isRTL(),
423-
ev: opts.ev,
424-
};
425-
426-
// create the transition animation from the TransitionController
427-
// this will either create the root transition, or add it as a child transition
428-
const trns = this._trnsCtrl.get(this._trnsId, enteringView, leavingView, animationOpts);
429-
430-
// ensure any swipeback transitions are cleared out
431-
this._sbTrns && this._sbTrns.destroy();
432-
433-
if (trns.parent) {
434-
// this is important for later to know if there
435-
// are any more child tests to check for
436-
trns.parent.hasChildTrns = true;
437-
438-
} else {
439-
// this is the root transition
440-
if (opts.progressAnimation) {
441-
this._sbTrns = trns;
442-
}
443-
}
444-
445-
trns.registerStart(() => {
446-
this._trnsStart(trns, enteringView, leavingView, opts, resolve);
447-
if (trns.parent) {
448-
trns.parent.start();
449-
}
450-
});
451-
452-
if (enteringView && isBlank(enteringView._state)) {
453-
// render the entering view, and all child navs and views
454-
// ******** DOM WRITE ****************
455-
this._viewInit(trns, enteringView, opts);
456-
}
457-
458-
// views have been initialized, now let's test
459-
// to see if the transition is even allowed or not
460-
const shouldContinue = this._viewTest(trns, enteringView, leavingView, opts, resolve, reject);
461-
if (shouldContinue) {
462-
// synchronous and all tests passed! let's continue
463-
this._postViewInit(trns, enteringView, leavingView, opts, resolve);
464-
}
465-
}
466-
467438
/**
468439
* DOM WRITE
469440
*/
470-
_viewInit(trns: Transition, enteringView: ViewController, opts: NavOptions) {
441+
_viewInit(enteringView: ViewController) {
471442
// entering view has not been initialized yet
472443
const componentProviders = ReflectiveInjector.resolve([
473444
{ provide: NavController, useValue: this },
@@ -482,8 +453,10 @@ export class NavControllerBase extends Ion implements NavController {
482453
enteringView._state = ViewState.INITIALIZED;
483454
}
484455

485-
_viewTest(trns: Transition, enteringView: ViewController, leavingView: ViewController, opts: NavOptions, resolve: TransitionResolveFn, reject: TransitionRejectFn): boolean {
456+
_viewTest(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction): boolean {
486457
const promises: Promise<any>[] = [];
458+
const reject = ti.reject;
459+
const resolve = ti.resolve;
487460

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

496469
} else {
497470
// synchronous reject
498-
reject((leavingTestResult !== false ? leavingTestResult : `ionViewCanLeave rejected`), trns);
471+
reject((leavingTestResult !== false ? leavingTestResult : `ionViewCanLeave rejected`));
499472
return false;
500473
}
501474
}
@@ -511,7 +484,7 @@ export class NavControllerBase extends Ion implements NavController {
511484

512485
} else {
513486
// synchronous reject
514-
reject((enteringTestResult !== false ? enteringTestResult : `ionViewCanEnter rejected`), trns);
487+
reject((enteringTestResult !== false ? enteringTestResult : `ionViewCanEnter rejected`));
515488
return false;
516489
}
517490
}
@@ -521,26 +494,67 @@ export class NavControllerBase extends Ion implements NavController {
521494
// darn, async promises, gotta wait for them to resolve
522495
Promise.all(promises).then(() => {
523496
// all promises resolved! let's continue
524-
this._postViewInit(trns, enteringView, leavingView, opts, resolve);
525-
526-
}, (rejectReason: any) => {
527-
// darn, one of the promises was rejected!!
528-
reject(rejectReason, trns);
497+
this._postViewInit(enteringView, leavingView, ti, resolve);
529498

530499
}).catch((rejectReason) => {
531-
// idk, who knows
532-
reject(rejectReason, trns);
500+
reject(rejectReason);
533501
});
534502

535503
return false;
536504
}
537505

538506
// synchronous and all tests passed! let's move on already
507+
this._postViewInit(enteringView, leavingView, ti, resolve);
508+
539509
return true;
540510
}
541511

542-
_postViewInit(trns: Transition, enteringView: ViewController, leavingView: ViewController, opts: NavOptions, resolve: TransitionResolveFn): void {
543-
// passed both the enter and leave tests
512+
_transition(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, resolve: TransitionResolveFn): void {
513+
// figure out if this transition is the root one or a
514+
// child of a parent nav that has the root transition
515+
this._trnsId = this._trnsCtrl.getRootTrnsId(this);
516+
if (this._trnsId === null) {
517+
// this is the root transition, meaning all child navs and their views
518+
// should be added as a child transition to this one
519+
this._trnsId = this._trnsCtrl.nextId();
520+
}
521+
522+
// create the transition options
523+
const animationOpts: AnimationOptions = {
524+
animation: opts.animation,
525+
direction: opts.direction,
526+
duration: (opts.animate === false ? 0 : opts.duration),
527+
easing: opts.easing,
528+
isRTL: this.config.platform.isRTL(),
529+
ev: opts.ev,
530+
};
531+
532+
// create the transition animation from the TransitionController
533+
// this will either create the root transition, or add it as a child transition
534+
const trns = this._trnsCtrl.get(this._trnsId, enteringView, leavingView, animationOpts);
535+
536+
// ensure any swipeback transitions are cleared out
537+
this._sbTrns && this._sbTrns.destroy();
538+
539+
if (trns.parent) {
540+
// this is important for later to know if there
541+
// are any more child tests to check for
542+
trns.parent.hasChildTrns = true;
543+
544+
} else {
545+
// this is the root transition
546+
if (opts.progressAnimation) {
547+
this._sbTrns = trns;
548+
}
549+
}
550+
551+
trns.registerStart(() => {
552+
this._trnsStart(trns, enteringView, leavingView, opts, resolve);
553+
if (trns.parent) {
554+
trns.parent.start();
555+
}
556+
});
557+
544558
if (enteringView && enteringView._state === ViewState.INITIALIZED) {
545559
// render the entering component in the DOM
546560
// this would also render new child navs/views

src/navigation/nav-util.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ export interface TransitionInstruction {
170170
removeCount?: number;
171171
resolve?: TransitionResolveFn;
172172
reject?: TransitionRejectFn;
173+
leavingRequiresTransition?: boolean;
174+
enteringRequiresTransition?: boolean;
173175
}
174176

175177
export enum ViewState {

0 commit comments

Comments
 (0)