From b9eec24c88aeb467f4eba01f120f6862dc1ae717 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Fri, 19 Feb 2016 21:43:24 -0600 Subject: [PATCH] fix(tabs): pop tab page to parent nav Closes #5196 --- ionic/components/nav/nav-controller.ts | 58 +++++++++++++++++-- ionic/components/nav/nav-router.ts | 42 +++++++------- .../nav/test/nav-controller.spec.ts | 31 +++++++++- ionic/components/tabs/tab.ts | 7 ++- ionic/components/tabs/tabs.ts | 2 +- ionic/components/tabs/test/advanced/index.ts | 47 +++++++++++++++ 6 files changed, 152 insertions(+), 35 deletions(-) diff --git a/ionic/components/nav/nav-controller.ts b/ionic/components/nav/nav-controller.ts index 5797e09d55..8cb9dc328a 100644 --- a/ionic/components/nav/nav-controller.ts +++ b/ionic/components/nav/nav-controller.ts @@ -7,7 +7,7 @@ import {IonicApp} from '../app/app'; import {Keyboard} from '../../util/keyboard'; import {NavParams} from './nav-params'; import {NavRouter} from './nav-router'; -import {pascalCaseToDashCase, isTrueProperty} from '../../util/util'; +import {pascalCaseToDashCase, isTrueProperty, isUndefined} from '../../util/util'; import {raf} from '../../util/dom'; import {SwipeBackGesture} from './swipe-back'; import {Transition} from '../../transitions/transition'; @@ -119,7 +119,7 @@ export class NavController extends Ion { /** * @private */ - id: number; + id: string; /** * @private @@ -163,7 +163,7 @@ export class NavController extends Ion { this._sbEnabled = config.getBoolean('swipeBackEnabled') || false; this._sbThreshold = config.get('swipeBackThreshold') || 40; - this.id = ++ctrlIds; + this.id = (++ctrlIds).toString(); // build a new injector for child ViewControllers to use this.providers = Injector.resolve([ @@ -622,6 +622,12 @@ export class NavController extends Ion { let activeView = this.getByState(STATE_TRANS_ENTER) || this.getByState(STATE_INIT_ENTER) || this.getActive(); + + // if not set, by default climb up the nav controllers if + // there isn't a previous view in this nav controller + if (isUndefined(opts.climbNav)) { + opts.climbNav = true; + } return this.remove(this.indexOf(activeView), 1, opts); } @@ -717,6 +723,39 @@ export class NavController extends Ion { 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) { + // oh knows! 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.willLeave(); + + return parentNav.pop(opts).then((rtnVal: boolean) => { + leavingView.didLeave(); + 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; let promise = new Promise(res => { resolve = res; }); @@ -724,9 +763,6 @@ export class NavController extends Ion { opts.animation = leavingView.getTransitionName(opts.direction); } - // get the view thats ready to enter - let enteringView = this.getByState(STATE_INIT_ENTER); - // start the transition, fire resolve when done... this._transition(enteringView, leavingView, opts, (hasCompleted: boolean) => { // transition has completed!! @@ -1226,6 +1262,13 @@ export class NavController extends Ion { } + ngOnDestroy() { + for (var i = this._views.length - 1; i >= 0; i--) { + this._views[i].destroy(); + } + this._views = []; + } + /** * @private */ @@ -1266,6 +1309,8 @@ export class NavController extends Ion { if (!hostViewRef.destroyed && index !== -1) { viewContainer.remove(index); } + + view.setInstance(null); }); // a new ComponentRef has been created @@ -1575,6 +1620,7 @@ export interface NavOptions { transitionDelay?: number; postLoad?: Function; progressAnimation?: boolean; + climbNav?: boolean; } const STATE_ACTIVE = 'active'; diff --git a/ionic/components/nav/nav-router.ts b/ionic/components/nav/nav-router.ts index d93d438c1c..f71ba4cf15 100644 --- a/ionic/components/nav/nav-router.ts +++ b/ionic/components/nav/nav-router.ts @@ -16,7 +16,7 @@ import {ViewController} from './view-controller'; selector: 'ion-nav' }) export class NavRouter extends RouterOutlet { - private _activeViewId; + private _lastUrl: string; constructor( _elementRef: ElementRef, @@ -33,11 +33,6 @@ export class NavRouter extends RouterOutlet { _nav.registerRouter(this); } - /** - * @private - * TODO - * @param {ComponentInstruction} instruction TODO - */ activate(nextInstruction: ComponentInstruction): Promise { var previousInstruction = this['_currentInstruction']; this['_currentInstruction'] = nextInstruction; @@ -45,11 +40,17 @@ export class NavRouter extends RouterOutlet { var childRouter = this['_parentRouter'].childRouter(componentType); // prevent double navigations to the same view - var lastView = this._nav.last(); - if (this._nav.isTransitioning() || lastView && lastView.componentType === componentType && lastView.data === nextInstruction.params) { - return Promise.resolve(); + let instruction = new ResolvedInstruction(nextInstruction, null, null); + let url: string; + if (instruction) { + url = instruction.toRootUrl(); + if (url === this._lastUrl) { + return Promise.resolve(); + } } + console.debug('NavRouter, activate:', componentType.name, 'url:', url); + // tell the NavController which componentType, and it's params, to navigate to return this._nav.push(componentType, nextInstruction.params); } @@ -58,19 +59,13 @@ export class NavRouter extends RouterOutlet { return Promise.resolve(); } - /** - * Called by Ionic after a transition has completed. - * @param {string} direction The direction of the state change - * @param {ViewController} viewCtrl The entering ViewController - */ stateChange(direction: string, viewCtrl: ViewController) { // stateChange is called by Ionic's NavController // type could be "push" or "pop" // viewCtrl is Ionic's ViewController class, which has the properties "componentType" and "params" // only do an update if there's an actual view change - if (!viewCtrl || this._activeViewId === viewCtrl.id) return; - this._activeViewId = viewCtrl.id; + if (!viewCtrl) return; // get the best PathRecognizer for this view's componentType let pathRecognizer = this.getPathRecognizerByComponent(viewCtrl.componentType); @@ -81,16 +76,19 @@ export class NavRouter extends RouterOutlet { // create a ResolvedInstruction from the componentInstruction let instruction = new ResolvedInstruction(componentInstruction, null, null); + if (instruction) { + let url = instruction.toRootUrl(); + if (url === this._lastUrl) return; - this['_parentRouter'].navigateByInstruction(instruction); + this._lastUrl = url; + + this['_parentRouter'].navigateByInstruction(instruction); + + console.debug('NavRouter, stateChange, name:', viewCtrl.name, 'id:', viewCtrl.id, 'url:', url); + } } } - /** - * TODO - * @param {TODO} componentType TODO - * @returns {TODO} TODO - */ getPathRecognizerByComponent(componentType) { // given a componentType, figure out the best PathRecognizer to use let rules = this['_parentRouter'].registry._rules; diff --git a/ionic/components/nav/test/nav-controller.spec.ts b/ionic/components/nav/test/nav-controller.spec.ts index c62dc7159f..d4ec11ec3c 100644 --- a/ionic/components/nav/test/nav-controller.spec.ts +++ b/ionic/components/nav/test/nav-controller.spec.ts @@ -1,8 +1,26 @@ -import {NavController, NavOptions, Config, ViewController} from '../../../../ionic/ionic'; +import {NavController, Tabs, NavOptions, Config, ViewController} from '../../../../ionic/ionic'; export function run() { describe('NavController', () => { + describe('pop', () => { + + it('should do nothing if its the first view in the stack', () => { + let view1 = new ViewController(Page1); + view1.state = STATE_ACTIVE; + nav._views = [view1]; + + expect(nav.length()).toBe(1); + + 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', () => { @@ -1022,10 +1040,15 @@ export function run() { class Page5 {} beforeEach(() => { + nav = mockNav(); + }); + + function mockNav() { let elementRef = { nativeElement: document.createElement('div') }; - nav = new NavController(null, null, config, null, elementRef, null, null, null, null, null); + + let nav = new NavController(null, null, config, null, elementRef, null, null, null, null, null); nav._keyboard = { isOpen: function() { @@ -1045,7 +1068,9 @@ export function run() { setElementClass: function(){}, setElementStyle: function(){} }; - }); + + return nav; + } }); } diff --git a/ionic/components/tabs/tab.ts b/ionic/components/tabs/tab.ts index 30a96dd1d1..81f278c46a 100644 --- a/ionic/components/tabs/tab.ts +++ b/ionic/components/tabs/tab.ts @@ -92,7 +92,7 @@ export class Tab extends NavController { private _panelId: string; private _btnId: string; private _loaded: boolean; - private _loadTimer: any; + private _loadTmr: number; /** * @private @@ -181,7 +181,7 @@ export class Tab extends NavController { * @private */ preload(wait: number) { - this._loadTimer = setTimeout(() => { + this._loadTmr = setTimeout(() => { if (!this._loaded) { console.debug('Tabs, preload', this.id); this.load({ @@ -247,7 +247,8 @@ export class Tab extends NavController { * @private */ ngOnDestroy() { - clearTimeout(this._loadTimer); + clearTimeout(this._loadTmr); + super.ngOnDestroy(); } } diff --git a/ionic/components/tabs/tabs.ts b/ionic/components/tabs/tabs.ts index ae315ca945..f9321583c9 100644 --- a/ionic/components/tabs/tabs.ts +++ b/ionic/components/tabs/tabs.ts @@ -209,7 +209,7 @@ export class Tabs extends Ion { /** * @private */ - add(tab) { + add(tab: Tab) { tab.id = this.id + '-' + (++this._ids); this._tabs.push(tab); } diff --git a/ionic/components/tabs/test/advanced/index.ts b/ionic/components/tabs/test/advanced/index.ts index 8804d53556..3f83548238 100644 --- a/ionic/components/tabs/test/advanced/index.ts +++ b/ionic/components/tabs/test/advanced/index.ts @@ -49,6 +49,14 @@ class SignIn { }) class ChatPage { constructor(private viewCtrl: ViewController) {} + + onPageDidLoad() { + console.log('ChatPage, onPageDidLoad'); + } + + onPageDidUnload() { + console.log('ChatPage, onPageDidUnload'); + } } @@ -98,6 +106,10 @@ class TabsPage { onPageDidLeave() { console.log('TabsPage, onPageDidLeave'); } + + onPageDidUnload() { + console.log('TabsPage, onPageDidUnload'); + } } @@ -113,6 +125,7 @@ class TabsPage { '

' + '

' + '

' + + '

' + '

UserId: {{userId}}

' + '' + '' + @@ -129,6 +142,13 @@ class Tab1Page1 { this.nav.push(Tab1Page2) } + goBack() { + console.log('go back begin'); + this.nav.pop().then((val) => { + console.log('go back completed', val); + });; + } + favoritesTab() { this.tabs.select(1); } @@ -152,6 +172,10 @@ class Tab1Page1 { onPageDidLeave() { console.log('Tab1Page1, onPageDidLeave'); } + + onPageDidUnload() { + console.log('Tab1Page1, onPageDidUnload'); + } } @@ -189,6 +213,10 @@ class Tab1Page2 { onPageDidLeave() { console.log('Tab1Page2, onPageDidLeave'); } + + onPageDidUnload() { + console.log('Tab1Page2, onPageDidUnload'); + } } @@ -221,6 +249,10 @@ class Tab1Page3 { onPageDidLeave() { console.log('Tab1Page3, onPageDidLeave'); } + + onPageDidUnload() { + console.log('Tab1Page3, onPageDidUnload'); + } } @@ -261,6 +293,10 @@ class Tab2Page1 { onPageDidLeave() { console.log('Tab2Page1, onPageDidLeave'); } + + onPageDidUnload() { + console.log('Tab2Page1, onPageDidUnload'); + } } @@ -298,6 +334,10 @@ class Tab2Page2 { onPageDidLeave() { console.log('Tab2Page2, onPageDidLeave'); } + + onPageDidUnload() { + console.log('Tab2Page2, onPageDidUnload'); + } } @@ -330,6 +370,10 @@ class Tab2Page3 { onPageDidLeave() { console.log('Tab2Page3, onPageDidLeave'); } + + onPageDidUnload() { + console.log('Tab2Page3, onPageDidUnload'); + } } @@ -362,6 +406,9 @@ class Tab3Page1 { console.log('Tab3Page1, onPageDidLeave'); } + onPageDidUnload() { + console.log('Tab3Page1, onPageDidUnload'); + } }