From d3fa29fe95d6eccfe61ba577af57c7719cea89bf Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Mon, 4 Apr 2016 23:38:58 -0400 Subject: [PATCH] feat(loading): add internal stack for the loading service this allows the user to push multiple loading indicators on top of each other without interfering with the page stack, as well as navigation between pages behind the loading indicator. references #5426 --- ionic/components/loading/loading.ts | 2 ++ ionic/components/nav/nav-controller.ts | 32 +++++++++++++++---- ionic/components/nav/nav-portal.ts | 30 +++++++++++++++++ ionic/components/nav/nav.ts | 12 +++++-- .../nav/test/nav-controller.spec.ts | 2 ++ ionic/components/nav/view-controller.ts | 5 +++ 6 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 ionic/components/nav/nav-portal.ts diff --git a/ionic/components/loading/loading.ts b/ionic/components/loading/loading.ts index 2024d35654..7c7f39f134 100644 --- a/ionic/components/loading/loading.ts +++ b/ionic/components/loading/loading.ts @@ -99,10 +99,12 @@ export class Loading extends ViewController { constructor(opts: LoadingOptions = {}) { opts.showBackdrop = isPresent(opts.showBackdrop) ? !!opts.showBackdrop : true; + opts.dismissOnPageChange = isPresent(opts.dismissOnPageChange) ? !!opts.dismissOnPageChange : false; super(LoadingCmp, opts); this.viewType = 'loading'; this.isOverlay = true; + this.usePortal = true; // by default, loading indicators should not fire lifecycle events of other views // for example, when an loading indicators enters, the current active view should diff --git a/ionic/components/nav/nav-controller.ts b/ionic/components/nav/nav-controller.ts index 4e3477006b..e91ebaadb3 100644 --- a/ionic/components/nav/nav-controller.ts +++ b/ionic/components/nav/nav-controller.ts @@ -8,6 +8,7 @@ import {Keyboard} from '../../util/keyboard'; import {NavParams} from './nav-params'; import {NavRouter} from './nav-router'; import {pascalCaseToDashCase, isTrueProperty, isBlank} from '../../util/util'; +import {Portal} from './nav-portal'; import {raf} from '../../util/dom'; import {SwipeBackGesture} from './swipe-back'; import {Transition} from '../../transitions/transition'; @@ -109,6 +110,7 @@ export class NavController extends Ion { private _trans: Transition; private _sbGesture: SwipeBackGesture; private _sbThreshold: number; + private _portal: Portal; protected _sbEnabled: boolean; protected _ids: number = -1; @@ -170,6 +172,10 @@ export class NavController extends Ion { provide(NavController, {useValue: this}) ]); } + + setPortal(val: Portal) { + this._portal = val; + } /** * Set the root for the current navigation stack @@ -412,7 +418,7 @@ export class NavController extends Ion { if (rootNav['_tabs']) { // TODO: must have until this goes in // https://github.com/angular/angular/issues/5481 - console.error('A parent is required for ActionSheet/Alert/Modal'); + console.error('A parent is required for ActionSheet/Alert/Modal/Loading'); return; } @@ -433,7 +439,12 @@ export class NavController extends Ion { keyboardClose: false, direction: 'back', animation: enteringView.getTransitionName('back') - }); + }); + + if (enteringView.usePortal && this._portal) { + this._portal.present(enteringView); + return; + } // start the transition return rootNav._insertViews(-1, [enteringView], opts); @@ -534,7 +545,7 @@ export class NavController extends Ion { 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 its the active 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 @@ -730,8 +741,8 @@ export class NavController extends Ion { // get the view thats ready to enter let enteringView = this.getByState(STATE_INIT_ENTER); - if (!enteringView) { - // oh knows! no entering view to go to! + if (!enteringView && this._portal) { + // 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 @@ -1248,6 +1259,14 @@ export class NavController extends Ion { // see if we should add the swipe back gesture listeners or not this._sbCheck(); + + if (this._portal) { + this._portal._views.forEach(view => { + if (view.data && view.data.dismissOnPageChange) { + view.dismiss(); + } + }); + } } else { // darn, so this wasn't the most recent transition @@ -1640,7 +1659,7 @@ export class NavController extends Ion { } else { // this is the initial view - enteringView.setZIndex(INIT_ZINDEX, this._renderer); + enteringView.setZIndex(this._portal ? INIT_ZINDEX : PORTAL_ZINDEX, this._renderer); } } else if (direction === 'back') { @@ -1680,5 +1699,6 @@ const STATE_REMOVE = 'remove'; const STATE_REMOVE_AFTER_TRANS = 'remove_after_trans'; const STATE_FORCE_ACTIVE = 'force_active'; const INIT_ZINDEX = 100; +const PORTAL_ZINDEX = 9999; let ctrlIds = -1; diff --git a/ionic/components/nav/nav-portal.ts b/ionic/components/nav/nav-portal.ts new file mode 100644 index 0000000000..6e6786ab4a --- /dev/null +++ b/ionic/components/nav/nav-portal.ts @@ -0,0 +1,30 @@ +import {Directive, ElementRef, Input, Optional, NgZone, Compiler, AppViewManager, Renderer, Type, ContentChild} from 'angular2/core'; + +import {IonicApp} from '../app/app'; +import {Config} from '../../config/config'; +import {Keyboard} from '../../util/keyboard'; +import {NavController} from './nav-controller'; +import {ViewController} from './view-controller'; + +/** + * @private + */ +@Directive({ + selector: '[portal]' +}) +export class Portal extends NavController { + constructor( + @Optional() hostNavCtrl: NavController, + @Optional() viewCtrl: ViewController, + app: IonicApp, + config: Config, + keyboard: Keyboard, + elementRef: ElementRef, + compiler: Compiler, + viewManager: AppViewManager, + zone: NgZone, + renderer: Renderer + ) { + super(hostNavCtrl, app, config, keyboard, elementRef, null, compiler, viewManager, zone, renderer); + } +} diff --git a/ionic/components/nav/nav.ts b/ionic/components/nav/nav.ts index 86ceb92549..b808f57dcb 100644 --- a/ionic/components/nav/nav.ts +++ b/ionic/components/nav/nav.ts @@ -1,10 +1,11 @@ -import {Component, ElementRef, Input, Optional, NgZone, Compiler, AppViewManager, Renderer, Type} from 'angular2/core'; +import {Component, ElementRef, Input, Optional, NgZone, Compiler, AppViewManager, Renderer, Type, ViewChild} from 'angular2/core'; import {IonicApp} from '../app/app'; import {Config} from '../../config/config'; import {Keyboard} from '../../util/keyboard'; import {isTrueProperty} from '../../util/util'; import {NavController} from './nav-controller'; +import {Portal} from './nav-portal'; import {ViewController} from './view-controller'; /** @@ -104,7 +105,8 @@ import {ViewController} from './view-controller'; */ @Component({ selector: 'ion-nav', - template: '
' + template: '
', + directives: [Portal] }) export class Nav extends NavController { private _root: Type; @@ -172,5 +174,9 @@ export class Nav extends NavController { this.push(this._root); } } - + + @ViewChild(Portal) + private set _navPortal(val: Portal) { + this.setPortal(val); + } } diff --git a/ionic/components/nav/test/nav-controller.spec.ts b/ionic/components/nav/test/nav-controller.spec.ts index 7a960b1288..7c7bcbb0ce 100644 --- a/ionic/components/nav/test/nav-controller.spec.ts +++ b/ionic/components/nav/test/nav-controller.spec.ts @@ -1236,6 +1236,8 @@ export function run() { setElementClass: function(){}, setElementStyle: function(){} }; + + nav._portal = new NavController(null, null, config, null, elementRef, null, null, null, null, null); return nav; } diff --git a/ionic/components/nav/view-controller.ts b/ionic/components/nav/view-controller.ts index b974b69fde..30b1f11e7a 100644 --- a/ionic/components/nav/view-controller.ts +++ b/ionic/components/nav/view-controller.ts @@ -77,6 +77,11 @@ export class ViewController { */ isOverlay: boolean = false; + /** + * @private + */ + usePortal: boolean = false; + /** * @private */