mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(tabs): have tabs behavior match nav when navigating back/forth via the url
This commit is contained in:
@@ -19,7 +19,6 @@ import { IonicPage, NavController, NavParams } from '../../../../../../..';
|
||||
<div>
|
||||
Name: {{paramTwo}}
|
||||
</div>
|
||||
<button ion-button (click)="goToNext()">Next</button>
|
||||
</ion-content>
|
||||
`
|
||||
})
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ion-split-pane>
|
||||
<ion-nav [root]="rootOne"></ion-nav>
|
||||
<ion-nav [root]="rootTwo" main #content></ion-nav>
|
||||
|
||||
</ion-split-pane>
|
||||
`
|
||||
})
|
||||
export class AppComponent {
|
||||
rootOne = 'NestedNavOnePageOne';
|
||||
rootTwo = 'NestedNavTwoPageOne';
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { IonicApp, IonicModule } from '../../../../..';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
IonicModule.forRoot(AppComponent, { swipeBackEnabled: true, preloadModules: true }),
|
||||
],
|
||||
bootstrap: [IonicApp]
|
||||
})
|
||||
export class AppModule {}
|
||||
5
src/components/nav/test/advanced-nested-navs/app/main.ts
Normal file
5
src/components/nav/test/advanced-nested-navs/app/main.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
@@ -0,0 +1,9 @@
|
||||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title>Page One</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<h2>Page One</h2>
|
||||
<button ion-button (click)="goToPageTwo()">Go to Page Two</button>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from '../../../../../../..';
|
||||
import { FirstPage } from './first-page';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
IonicPageModule.forChild(FirstPage)
|
||||
],
|
||||
declarations: [
|
||||
FirstPage
|
||||
]
|
||||
})
|
||||
export class FirstPageModule { }
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { IonicPage, NavController, } from '../../../../../../..';
|
||||
|
||||
@IonicPage()
|
||||
@Component({
|
||||
templateUrl: 'first-page.html'
|
||||
})
|
||||
export class FirstPage {
|
||||
constructor(public nav: NavController) {
|
||||
}
|
||||
|
||||
goToPageTwo() {
|
||||
this.nav.push('SecondPage', { userId: '123', name: 'Michael Scott'});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from '../../../../../../..';
|
||||
import { NestedNavOnePageOne } from './nested-nav-one-page-one';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
IonicPageModule.forChild(NestedNavOnePageOne)
|
||||
],
|
||||
declarations: [
|
||||
NestedNavOnePageOne
|
||||
]
|
||||
})
|
||||
export class NestedNavOnePageOneModule { }
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { IonicPage, NavController, } from '../../../../../../..';
|
||||
|
||||
@IonicPage()
|
||||
@Component({
|
||||
template: `
|
||||
<ion-nav [root]="root"></ion-nav>
|
||||
`
|
||||
})
|
||||
export class NestedNavOnePageOne {
|
||||
|
||||
root: string = 'NestedNavOnePageTwo';
|
||||
constructor(public nav: NavController) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from '../../../../../../..';
|
||||
import { NestedNavOnePageTwo } from './nested-nav-one-page-two';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
IonicPageModule.forChild(NestedNavOnePageTwo)
|
||||
],
|
||||
declarations: [
|
||||
NestedNavOnePageTwo
|
||||
]
|
||||
})
|
||||
export class NestedNavOnePageTwoModule { }
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { IonicPage, NavController, } from '../../../../../../..';
|
||||
|
||||
@IonicPage()
|
||||
@Component({
|
||||
template: `
|
||||
<ion-nav [root]="root"></ion-nav>
|
||||
`
|
||||
})
|
||||
export class NestedNavOnePageTwo {
|
||||
|
||||
root: string = 'FirstPage';
|
||||
constructor(public nav: NavController) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title>Page Two</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<h2>Page Two</h2>
|
||||
<div>
|
||||
User ID: {{userId}}
|
||||
</div>
|
||||
<div>
|
||||
Name {{name}}
|
||||
</div>
|
||||
<button ion-button (click)="goToNextPage()">Go to Next</button>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from '../../../../../../..';
|
||||
import { SecondPage } from './second-page';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
IonicPageModule.forChild(SecondPage)
|
||||
],
|
||||
declarations: [
|
||||
SecondPage
|
||||
]
|
||||
})
|
||||
export class SecondPageModule { }
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { IonicPage, NavController, NavParams } from '../../../../../../..';
|
||||
|
||||
@IonicPage({
|
||||
segment: 'pageTwo/user/:userId/name/:name'
|
||||
})
|
||||
@Component({
|
||||
templateUrl: 'second-page.html'
|
||||
})
|
||||
export class SecondPage {
|
||||
|
||||
userId: string;
|
||||
name: string;
|
||||
constructor(public nav: NavController, public params: NavParams) {
|
||||
this.userId = this.params.data.userId;
|
||||
this.name = this.params.data.name;
|
||||
}
|
||||
|
||||
goToNextPage() {
|
||||
this.nav.push('ThirdPage', { paramOne: 'mono', paramTwo: 'stereo'});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title>Page Three</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
Page Three
|
||||
<div>
|
||||
Param One: {{paramOne}}
|
||||
</div>
|
||||
<div>
|
||||
Param Two: {{paramTwo}}
|
||||
</div>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from '../../../../../../..';
|
||||
import { ThirdPage } from './third-page';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
IonicPageModule.forChild(ThirdPage)
|
||||
],
|
||||
declarations: [
|
||||
ThirdPage
|
||||
]
|
||||
})
|
||||
export class ThirdPageModule { }
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { IonicPage, NavController, NavParams} from '../../../../../../..';
|
||||
|
||||
@IonicPage({
|
||||
segment: 'thirdPage/paramOne/:paramOne/paramTwo/:paramTwo'
|
||||
})
|
||||
@Component({
|
||||
templateUrl: 'third-page.html'
|
||||
})
|
||||
export class ThirdPage {
|
||||
paramOne: string;
|
||||
paramTwo: string;
|
||||
constructor(public nav: NavController, public params: NavParams) {
|
||||
this.paramOne = params.data.paramOne;
|
||||
this.paramTwo = params.data.paramTwo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title>Page Five</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<h2>Page Five</h2>
|
||||
<div>
|
||||
User ID: {{userId}}
|
||||
</div>
|
||||
<div>
|
||||
Name {{name}}
|
||||
</div>
|
||||
<button ion-button (click)="goToNextPage()">Go to Next</button>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from '../../../../../../..';
|
||||
import { FifthPage } from './fifth-page';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
IonicPageModule.forChild(FifthPage)
|
||||
],
|
||||
declarations: [
|
||||
FifthPage
|
||||
]
|
||||
})
|
||||
export class FifthPageModule { }
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { IonicPage, NavController, NavParams } from '../../../../../../..';
|
||||
|
||||
@IonicPage({
|
||||
segment: 'pageFive/user/:userId/name/:name'
|
||||
})
|
||||
@Component({
|
||||
templateUrl: 'fifth-page.html'
|
||||
})
|
||||
export class FifthPage {
|
||||
|
||||
userId: string;
|
||||
name: string;
|
||||
constructor(public nav: NavController, public params: NavParams) {
|
||||
this.userId = this.params.data.userId;
|
||||
this.name = this.params.data.name;
|
||||
}
|
||||
|
||||
goToNextPage() {
|
||||
this.nav.push('SixthPage', { paramOne: 'Tobey', paramTwo: 'Holly'});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title>Page Four</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<h2>Page Four</h2>
|
||||
<button ion-button (click)="goToPageTwo()">Next</button>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from '../../../../../../..';
|
||||
import { FourthPage } from './fourth-page';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
IonicPageModule.forChild(FourthPage)
|
||||
],
|
||||
declarations: [
|
||||
FourthPage
|
||||
]
|
||||
})
|
||||
export class FourthPageModule { }
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { IonicPage, NavController, } from '../../../../../../..';
|
||||
|
||||
@IonicPage()
|
||||
@Component({
|
||||
templateUrl: 'fourth-page.html'
|
||||
})
|
||||
export class FourthPage {
|
||||
constructor(public nav: NavController) {
|
||||
}
|
||||
|
||||
goToPageTwo() {
|
||||
this.nav.push('FifthPage', { userId: '567', name: 'Pamela Beasley'});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from '../../../../../../..';
|
||||
import { NestedNavTwoPageOne } from './nested-nav-two-page-one';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
IonicPageModule.forChild(NestedNavTwoPageOne)
|
||||
],
|
||||
declarations: [
|
||||
NestedNavTwoPageOne
|
||||
]
|
||||
})
|
||||
export class NestedNavTwoPageOneModule { }
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { IonicPage, NavController, } from '../../../../../../..';
|
||||
|
||||
@IonicPage()
|
||||
@Component({
|
||||
template: `
|
||||
<ion-nav [root]="root"></ion-nav>
|
||||
`
|
||||
})
|
||||
export class NestedNavTwoPageOne {
|
||||
|
||||
root: string = 'NestedNavTwoPageTwo';
|
||||
constructor(public nav: NavController) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from '../../../../../../..';
|
||||
import { NestedNavTwoPageTwo } from './nested-nav-two-page-two';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
IonicPageModule.forChild(NestedNavTwoPageTwo)
|
||||
],
|
||||
declarations: [
|
||||
NestedNavTwoPageTwo
|
||||
]
|
||||
})
|
||||
export class NestedNavTwoPageTwoModule { }
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { IonicPage, NavController, } from '../../../../../../..';
|
||||
|
||||
@IonicPage()
|
||||
@Component({
|
||||
template: `
|
||||
<ion-nav [root]="root"></ion-nav>
|
||||
`
|
||||
})
|
||||
export class NestedNavTwoPageTwo {
|
||||
|
||||
root: string = 'FourthPage';
|
||||
constructor(public nav: NavController) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title>Page Six</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
Page Six
|
||||
<div>
|
||||
Param One: {{paramOne}}
|
||||
</div>
|
||||
<div>
|
||||
Param Two: {{paramTwo}}
|
||||
</div>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from '../../../../../../..';
|
||||
import { SixthPage } from './sixth-page';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
IonicPageModule.forChild(SixthPage)
|
||||
],
|
||||
declarations: [
|
||||
SixthPage
|
||||
]
|
||||
})
|
||||
export class SixthPageModule { }
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { IonicPage, NavController, NavParams} from '../../../../../../..';
|
||||
|
||||
@IonicPage({
|
||||
segment: 'sixthPage/paramOne/:paramOne/paramTwo/:paramTwo'
|
||||
})
|
||||
@Component({
|
||||
templateUrl: 'sixth-page.html'
|
||||
})
|
||||
export class SixthPage {
|
||||
|
||||
paramOne: string;
|
||||
paramTwo: string;
|
||||
constructor(public nav: NavController, public params: NavParams) {
|
||||
this.paramOne = params.data.paramOne;
|
||||
this.paramTwo = params.data.paramTwo;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { IonicPage, } from '../../../../../..';
|
||||
@IonicPage()
|
||||
@Component({
|
||||
template: `
|
||||
<ion-tabs>
|
||||
<ion-tabs name="app">
|
||||
<ion-tab tabIcon="heart" [root]="tab1" tabTitle="Taco Burrito Enchilada"></ion-tab>
|
||||
<ion-tab tabIcon="star" [root]="tab2"></ion-tab>
|
||||
</ion-tabs>
|
||||
|
||||
@@ -307,14 +307,33 @@ export class Tab extends NavControllerBase implements ITab {
|
||||
load(opts: NavOptions, done?: () => void) {
|
||||
if (this._lazyRootFromUrl || (!this._loaded && this.root)) {
|
||||
this.setElementClass('show-tab', true);
|
||||
if (this._lazyRootFromUrl) {
|
||||
this.push(this._lazyRootFromUrl, this._lazyRootFromUrlData, opts, done);
|
||||
this._lazyRootFromUrl = null;
|
||||
this._lazyRootFromUrlData = null;
|
||||
} else {
|
||||
this.push(this.root, this.rootParams, opts, done);
|
||||
// okay, first thing we need to do if check if the view already exists
|
||||
const nameToUse = this._lazyRootFromUrl ? this._lazyRootFromUrl : this.root;
|
||||
const dataToUse = this._lazyRootFromUrlData ? this._lazyRootFromUrlData : this.rootParams;
|
||||
const numViews = this.length() - 1;
|
||||
for (let i = numViews; i >= 0; i--) {
|
||||
const viewController = this.getByIndex(i);
|
||||
if (viewController && (viewController.id === nameToUse || viewController.component === nameToUse)) {
|
||||
if (i === numViews) {
|
||||
// this is the last view in the stack and it's the same
|
||||
// as the segment so there's no change needed
|
||||
return done();
|
||||
} else {
|
||||
// it's not the exact view as the end
|
||||
// let's have this nav go back to this exact view
|
||||
return this.popTo(viewController, {
|
||||
animate: false,
|
||||
updateUrl: false,
|
||||
}, done);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.push(nameToUse, dataToUse, opts, done);
|
||||
|
||||
|
||||
this._lazyRootFromUrl = null;
|
||||
this._lazyRootFromUrlData = null;
|
||||
this._loaded = true;
|
||||
|
||||
} else {
|
||||
@@ -324,7 +343,7 @@ export class Tab extends NavControllerBase implements ITab {
|
||||
this._dom.read(() => {
|
||||
this.resize();
|
||||
});
|
||||
done();
|
||||
return done();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
91
src/components/tabs/test/tab.spec.ts
Normal file
91
src/components/tabs/test/tab.spec.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { mockTab, mockTabs, mockView, mockViews } from '../../../util/mock-providers';
|
||||
|
||||
describe('tab', () => {
|
||||
describe('load', () => {
|
||||
it('should measure and refresh the tabs', () => {
|
||||
// TODO - this test is super leaky but I cant come up with a better way short term
|
||||
const tabs = mockTabs();
|
||||
const tab = mockTab(tabs, false);
|
||||
const spy = jasmine.createSpy('done');
|
||||
|
||||
spyOn(tab, 'push');
|
||||
spyOn(tab, 'popTo');
|
||||
tab.load({}, spy);
|
||||
|
||||
expect(tab.push).not.toHaveBeenCalled();
|
||||
expect(tab.popTo).not.toHaveBeenCalled();
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reuse the view if its the top view in the stack', () => {
|
||||
const tabs = mockTabs();
|
||||
const tab = mockTab(tabs, false);
|
||||
const spy = jasmine.createSpy('done');
|
||||
|
||||
const mockViewOne = mockView('one');
|
||||
const mockViewTwo = mockView('two');
|
||||
mockViews(tab, [mockViewOne, mockViewTwo]);
|
||||
|
||||
tab._lazyRootFromUrl = 'someValue';
|
||||
tab._lazyRootFromUrlData = { };
|
||||
|
||||
mockViewTwo.id = tab._lazyRootFromUrl;
|
||||
|
||||
spyOn(tab, 'push');
|
||||
spyOn(tab, 'popTo');
|
||||
|
||||
tab.load({}, spy);
|
||||
|
||||
expect(tab.push).not.toHaveBeenCalled();
|
||||
expect(tab.popTo).not.toHaveBeenCalled();
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should pop back to a previous view if ', () => {
|
||||
const tabs = mockTabs();
|
||||
const tab = mockTab(tabs, false);
|
||||
const spy = jasmine.createSpy('done');
|
||||
|
||||
const mockViewOne = mockView('one');
|
||||
const mockViewTwo = mockView('two');
|
||||
mockViews(tab, [mockViewOne, mockViewTwo]);
|
||||
|
||||
tab._lazyRootFromUrl = 'someValue';
|
||||
tab._lazyRootFromUrlData = { };
|
||||
|
||||
mockViewOne.id = tab._lazyRootFromUrl;
|
||||
|
||||
spyOn(tab, 'push');
|
||||
spyOn(tab, 'popTo');
|
||||
|
||||
tab.load({}, spy);
|
||||
|
||||
expect(tab.push).not.toHaveBeenCalled();
|
||||
expect(tab.popTo).toHaveBeenCalled();
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should push the view if it doesnt exist already', () => {
|
||||
const tabs = mockTabs();
|
||||
const tab = mockTab(tabs, false);
|
||||
const spy = jasmine.createSpy('done');
|
||||
|
||||
const mockViewOne = mockView('one');
|
||||
const mockViewTwo = mockView('two');
|
||||
mockViews(tab, [mockViewOne, mockViewTwo]);
|
||||
|
||||
tab._lazyRootFromUrl = 'someValue';
|
||||
tab._lazyRootFromUrlData = { };
|
||||
|
||||
spyOn(tab, 'push');
|
||||
spyOn(tab, 'popTo');
|
||||
|
||||
tab.load({}, spy);
|
||||
|
||||
expect(tab.push).toHaveBeenCalled();
|
||||
expect(tab.popTo).not.toHaveBeenCalled();
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
@@ -470,7 +470,7 @@ export function mockOverlayPortal(app: App, config: Config, plt: MockPlatform):
|
||||
);
|
||||
}
|
||||
|
||||
export function mockTab(parentTabs: Tabs): Tab {
|
||||
export function mockTab(parentTabs: Tabs, overrideLoad: boolean = true): Tab {
|
||||
let platform = mockPlatform();
|
||||
let config = mockConfig(null, '/', platform);
|
||||
let app = (<any>parentTabs)._app || mockApp(config, platform);
|
||||
@@ -500,9 +500,11 @@ export function mockTab(parentTabs: Tabs): Tab {
|
||||
null
|
||||
);
|
||||
|
||||
tab.load = (opts: any, cb: Function) => {
|
||||
cb();
|
||||
};
|
||||
if (overrideLoad) {
|
||||
tab.load = (opts: any, cb: Function) => {
|
||||
cb();
|
||||
};
|
||||
}
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user