diff --git a/src/components/app/app.ts b/src/components/app/app.ts index 733e8cf1e8..9cdfddd293 100644 --- a/src/components/app/app.ts +++ b/src/components/app/app.ts @@ -419,8 +419,31 @@ export class App { } } + getNavByIdOrName(id: string) { + const navs = Array.from(this._rootNavs.values()); + for (const navContainer of navs) { + const match = getNavByIdOrName(navContainer, id); + if (match) { + return match; + } + } + return null; + } + } +export function getNavByIdOrName(nav: NavigationContainer, id: string): NavigationContainer { + if (nav.id === id || nav.name === id) { + return nav; + } + for (const child of nav.getAllChildNavs()) { + const tmp = getNavByIdOrName(child, id); + if (tmp) { + return tmp; + } + } + return null; +} function getPoppableNav(nav: NavControllerBase): NavControllerBase { if (!nav) { @@ -455,6 +478,7 @@ export function findTopNavs(nav: NavigationContainer): NavigationContainer[] { return containers; } + const SKIP_BLURRING = ['INPUT', 'TEXTAREA', 'ION-INPUT', 'ION-TEXTAREA']; const ACTIVE_SCROLLING_TIME = 100; const CLICK_BLOCK_BUFFER_IN_MILLIS = 64; diff --git a/src/components/app/test/app.spec.ts b/src/components/app/test/app.spec.ts index 02d5116d52..c84afbc404 100644 --- a/src/components/app/test/app.spec.ts +++ b/src/components/app/test/app.spec.ts @@ -767,6 +767,149 @@ describe('App', () => { }); }); + describe('getNavByIdOrName', () => { + it('should return a basic root nav', () => { + const nav = mockNavController(); + app.registerRootNav(nav); + const result = app.getNavByIdOrName(nav.id); + expect(result).toEqual(nav); + }); + + it('should return a child nav', () => { + const rootNav = mockNavController(); + app.registerRootNav(rootNav); + + const childNav = mockNavController(); + childNav.parent = rootNav; + rootNav.registerChildNav(childNav); + + const childChildNav = mockNavController(); + childChildNav.parent = childNav; + childNav.registerChildNav(childChildNav); + + + const expectedChildNav = app.getNavByIdOrName(childNav.id); + expect(expectedChildNav).toEqual(childNav); + + const expectedChildChildNav = app.getNavByIdOrName(childChildNav.id); + expect(expectedChildChildNav).toEqual(childChildNav); + }); + + it('should return a child nav when there is a tabs in there', () => { + const rootNav = mockNavController(); + app.registerRootNav(rootNav); + + const tabs = mockTabs(); + tabs.parent = rootNav; + rootNav.registerChildNav(tabs); + + const tab1 = mockTab(tabs); + const tab2 = mockTab(tabs); + const tab3 = mockTab(tabs); + + const tabChildNav = mockNavController(); + tabChildNav.parent = tab2; + tab2.registerChildNav(tabChildNav); + + const tabChildChildNav = mockNavController(); + tabChildChildNav.parent = tabChildNav; + tabChildNav.registerChildNav(tabChildChildNav); + + const expectedTab1 = app.getNavByIdOrName(tab1.id); + expect(expectedTab1).toEqual(tab1); + + const expectedTab2 = app.getNavByIdOrName(tab2.id); + expect(expectedTab2).toEqual(tab2); + + const expectedTab3 = app.getNavByIdOrName(tab3.id); + expect(expectedTab3).toEqual(tab3); + + const expectedTabChildNav = app.getNavByIdOrName(tabChildNav.id); + expect(expectedTabChildNav).toEqual(tabChildNav); + + const expectedTabChildChildNav = app.getNavByIdOrName(tabChildChildNav.id); + expect(expectedTabChildChildNav).toEqual(tabChildChildNav); + }); + + it('should return a basic root nav when the are multiple root navs', () => { + const rootNavOne = mockNavController(); + const rootNavTwo = mockNavController(); + const rootNavThree = mockNavController(); + app.registerRootNav(rootNavOne); + app.registerRootNav(rootNavTwo); + app.registerRootNav(rootNavThree); + + const expectedRootNavOne = app.getNavByIdOrName(rootNavOne.id); + expect(expectedRootNavOne).toEqual(rootNavOne); + + const expectedRootNavTwo = app.getNavByIdOrName(rootNavTwo.id); + expect(expectedRootNavTwo).toEqual(rootNavTwo); + + const expectedRootNavThree = app.getNavByIdOrName(rootNavThree.id); + expect(expectedRootNavThree).toEqual(rootNavThree); + }); + + it('should return a proper navs when there are multiple root navs with nested navs', () => { + const rootNavOne = mockNavController(); + const rootNavTwo = mockNavController(); + const rootNavThree = mockNavController(); + app.registerRootNav(rootNavOne); + app.registerRootNav(rootNavTwo); + app.registerRootNav(rootNavThree); + + const childNavOne = mockNavController(); + childNavOne.parent = rootNavOne; + rootNavOne.registerChildNav(childNavOne); + + const childChildNavOne = mockNavController(); + childChildNavOne.parent = childNavOne; + childNavOne.registerChildNav(childChildNavOne); + + const childNavTwo = mockNavController(); + childNavOne.parent = rootNavTwo; + rootNavTwo.registerChildNav(childNavTwo); + + const childChildNavTwo = mockNavController(); + childChildNavTwo.parent = childNavTwo; + childNavTwo.registerChildNav(childChildNavTwo); + + const childNavThree = mockNavController(); + childNavThree.parent = rootNavThree; + rootNavThree.registerChildNav(childNavThree); + + const childChildNavThree = mockNavController(); + childChildNavThree.parent = childNavThree; + childNavThree.registerChildNav(childChildNavThree); + + const expectedRootNavOne = app.getNavByIdOrName(rootNavOne.id); + expect(expectedRootNavOne).toEqual(rootNavOne); + + const expectedChildNavOne = app.getNavByIdOrName(childNavOne.id); + expect(expectedChildNavOne).toEqual(childNavOne); + + const expectedChildChildNavOne = app.getNavByIdOrName(childChildNavOne.id); + expect(expectedChildChildNavOne).toEqual(childChildNavOne); + + const expectedRootNavTwo = app.getNavByIdOrName(rootNavTwo.id); + expect(expectedRootNavTwo).toEqual(rootNavTwo); + + const expectedChildNavTwo = app.getNavByIdOrName(childNavTwo.id); + expect(expectedChildNavTwo).toEqual(childNavTwo); + + const expectedChildChildNavTwo = app.getNavByIdOrName(childChildNavTwo.id); + expect(expectedChildChildNavTwo).toEqual(childChildNavTwo); + + const expectedRootNavThree = app.getNavByIdOrName(rootNavThree.id); + expect(expectedRootNavThree).toEqual(rootNavThree); + + const expectedChildNavThree = app.getNavByIdOrName(childNavThree.id); + expect(expectedChildNavThree).toEqual(childNavThree); + + const expectedChildChildNavThree = app.getNavByIdOrName(childChildNavThree.id); + expect(expectedChildChildNavThree).toEqual(childChildNavThree); + }); + }); + let app: App; let config: Config; let plt: MockPlatform; diff --git a/src/components/nav/test/advanced-nav-then-tabs/app/app.component.ts b/src/components/nav/test/advanced-nav-then-tabs/app/app.component.ts index 558e9538fb..f18c526c21 100644 --- a/src/components/nav/test/advanced-nav-then-tabs/app/app.component.ts +++ b/src/components/nav/test/advanced-nav-then-tabs/app/app.component.ts @@ -3,8 +3,8 @@ import { Component } from '@angular/core'; @Component({ template: ` - - + + ` diff --git a/src/components/nav/test/advanced-tabs/pages/tabs-one/tab-one-page-three/tab-one-page-three.ts b/src/components/nav/test/advanced-tabs/pages/tabs-one/tab-one-page-three/tab-one-page-three.ts index ddc3d0dc36..5deb7fc068 100644 --- a/src/components/nav/test/advanced-tabs/pages/tabs-one/tab-one-page-three/tab-one-page-three.ts +++ b/src/components/nav/test/advanced-tabs/pages/tabs-one/tab-one-page-three/tab-one-page-three.ts @@ -19,7 +19,6 @@ import { IonicPage, NavController, NavParams } from '../../../../../../..';
Name: {{paramTwo}}
- ` }) diff --git a/src/components/nav/test/advanced-tabs/pages/tabs-one/tab-two-page-three/tab-two-page-three.ts b/src/components/nav/test/advanced-tabs/pages/tabs-one/tab-two-page-three/tab-two-page-three.ts index 1095975b5c..74c07a8f70 100644 --- a/src/components/nav/test/advanced-tabs/pages/tabs-one/tab-two-page-three/tab-two-page-three.ts +++ b/src/components/nav/test/advanced-tabs/pages/tabs-one/tab-two-page-three/tab-two-page-three.ts @@ -14,10 +14,10 @@ import { IonicPage, NavController, NavParams } from '../../../../../../..'; Tabs 1 Tab 2 Page 3
- Param One: {{userId}} + Param One: {{paramOne}}
- Param Two: {{name}} + Param Two: {{paramTwo}}
` diff --git a/src/components/nav/test/simple-nested-peer-navs/app/app.component.ts b/src/components/nav/test/simple-nested-peer-navs/app/app.component.ts new file mode 100644 index 0000000000..3337574380 --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/app/app.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + template: `` +}) +export class AppComponent { + root = 'LoginPage'; +} diff --git a/src/components/nav/test/simple-nested-peer-navs/app/app.module.ts b/src/components/nav/test/simple-nested-peer-navs/app/app.module.ts new file mode 100644 index 0000000000..55310fa215 --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/app/app.module.ts @@ -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 {} diff --git a/src/components/nav/test/simple-nested-peer-navs/app/main.ts b/src/components/nav/test/simple-nested-peer-navs/app/main.ts new file mode 100644 index 0000000000..6af7a5b2ae --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/app/main.ts @@ -0,0 +1,5 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/landing-page/landing-page.module.ts b/src/components/nav/test/simple-nested-peer-navs/pages/landing-page/landing-page.module.ts new file mode 100644 index 0000000000..ba7d5165e8 --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/landing-page/landing-page.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { IonicPageModule } from '../../../../../..'; +import { LandingPage } from './landing-page'; + +@NgModule({ + imports: [ + IonicPageModule.forChild(LandingPage) + ], + declarations: [ + LandingPage + ] +}) +export class LandingPageModule { } diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/landing-page/landing-page.ts b/src/components/nav/test/simple-nested-peer-navs/pages/landing-page/landing-page.ts new file mode 100644 index 0000000000..da2c93925c --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/landing-page/landing-page.ts @@ -0,0 +1,30 @@ +import { Component } from '@angular/core'; +import { IonicPage, NavController, } from '../../../../../..'; + +@IonicPage() +@Component({ + template: ` + + + My Super Cool, multi-pane App + + + + + + + + + + + + + + + ` +}) +export class LandingPage { + constructor(public nav: NavController) { + } + +} diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/left/first-page/first-page.html b/src/components/nav/test/simple-nested-peer-navs/pages/left/first-page/first-page.html new file mode 100644 index 0000000000..e82b2f2388 --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/left/first-page/first-page.html @@ -0,0 +1,9 @@ + + + Page One + + + +

Page One

+ +
\ No newline at end of file diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/left/first-page/first-page.module.ts b/src/components/nav/test/simple-nested-peer-navs/pages/left/first-page/first-page.module.ts new file mode 100644 index 0000000000..c17e0dde59 --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/left/first-page/first-page.module.ts @@ -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 { } diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/left/first-page/first-page.ts b/src/components/nav/test/simple-nested-peer-navs/pages/left/first-page/first-page.ts new file mode 100644 index 0000000000..027607bc6d --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/left/first-page/first-page.ts @@ -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'}); + } +} diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/left/second-page/second-page.html b/src/components/nav/test/simple-nested-peer-navs/pages/left/second-page/second-page.html new file mode 100644 index 0000000000..39d1124a07 --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/left/second-page/second-page.html @@ -0,0 +1,15 @@ + + + Page Two + + + +

Page Two

+
+ User ID: {{userId}} +
+
+ Name {{name}} +
+ +
\ No newline at end of file diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/left/second-page/second-page.module.ts b/src/components/nav/test/simple-nested-peer-navs/pages/left/second-page/second-page.module.ts new file mode 100644 index 0000000000..ce824037f8 --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/left/second-page/second-page.module.ts @@ -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 { } diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/left/second-page/second-page.ts b/src/components/nav/test/simple-nested-peer-navs/pages/left/second-page/second-page.ts new file mode 100644 index 0000000000..44abf580fe --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/left/second-page/second-page.ts @@ -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'}); + } +} diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/left/third-page/third-page.html b/src/components/nav/test/simple-nested-peer-navs/pages/left/third-page/third-page.html new file mode 100644 index 0000000000..af317e8de8 --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/left/third-page/third-page.html @@ -0,0 +1,14 @@ + + + Page Three + + + + Page Three +
+ Param One: {{paramOne}} +
+
+ Param Two: {{paramTwo}} +
+
\ No newline at end of file diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/left/third-page/third-page.module.ts b/src/components/nav/test/simple-nested-peer-navs/pages/left/third-page/third-page.module.ts new file mode 100644 index 0000000000..fab76aa598 --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/left/third-page/third-page.module.ts @@ -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 { } diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/left/third-page/third-page.ts b/src/components/nav/test/simple-nested-peer-navs/pages/left/third-page/third-page.ts new file mode 100644 index 0000000000..9cbbc0413f --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/left/third-page/third-page.ts @@ -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; + } +} diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/login-page/login-page.module.ts b/src/components/nav/test/simple-nested-peer-navs/pages/login-page/login-page.module.ts new file mode 100644 index 0000000000..e3cbf3d916 --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/login-page/login-page.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { IonicPageModule } from '../../../../../..'; +import { LoginPage } from './login-page'; + +@NgModule({ + imports: [ + IonicPageModule.forChild(LoginPage) + ], + declarations: [ + LoginPage + ] +}) +export class LoginPageModule { } diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/login-page/login-page.ts b/src/components/nav/test/simple-nested-peer-navs/pages/login-page/login-page.ts new file mode 100644 index 0000000000..3b781c7d4c --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/login-page/login-page.ts @@ -0,0 +1,24 @@ +import { Component } from '@angular/core'; +import { IonicPage, NavController, } from '../../../../../..'; + +@IonicPage() +@Component({ + template: ` + + + Login + + + + + + ` +}) +export class LoginPage { + constructor(public nav: NavController) { + } + + clickMe() { + this.nav.push('LandingPage'); + } +} diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/right/fifth-page/fifth-page.html b/src/components/nav/test/simple-nested-peer-navs/pages/right/fifth-page/fifth-page.html new file mode 100644 index 0000000000..3a700e617f --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/right/fifth-page/fifth-page.html @@ -0,0 +1,15 @@ + + + Page Five + + + +

Page Five

+
+ User ID: {{userId}} +
+
+ Name {{name}} +
+ +
\ No newline at end of file diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/right/fifth-page/fifth-page.module.ts b/src/components/nav/test/simple-nested-peer-navs/pages/right/fifth-page/fifth-page.module.ts new file mode 100644 index 0000000000..1499d707cf --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/right/fifth-page/fifth-page.module.ts @@ -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 { } diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/right/fifth-page/fifth-page.ts b/src/components/nav/test/simple-nested-peer-navs/pages/right/fifth-page/fifth-page.ts new file mode 100644 index 0000000000..fd3023b1f5 --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/right/fifth-page/fifth-page.ts @@ -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'}); + } +} diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/right/fouth-page/fourth-page.html b/src/components/nav/test/simple-nested-peer-navs/pages/right/fouth-page/fourth-page.html new file mode 100644 index 0000000000..a89fef0a4e --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/right/fouth-page/fourth-page.html @@ -0,0 +1,9 @@ + + + Page Four + + + +

Page Four

+ +
\ No newline at end of file diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/right/fouth-page/fourth-page.module.ts b/src/components/nav/test/simple-nested-peer-navs/pages/right/fouth-page/fourth-page.module.ts new file mode 100644 index 0000000000..d041e3bcc8 --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/right/fouth-page/fourth-page.module.ts @@ -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 { } diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/right/fouth-page/fourth-page.ts b/src/components/nav/test/simple-nested-peer-navs/pages/right/fouth-page/fourth-page.ts new file mode 100644 index 0000000000..6ad06442e0 --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/right/fouth-page/fourth-page.ts @@ -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'}); + } +} diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/right/sixth-page/sixth-page.html b/src/components/nav/test/simple-nested-peer-navs/pages/right/sixth-page/sixth-page.html new file mode 100644 index 0000000000..8732947e36 --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/right/sixth-page/sixth-page.html @@ -0,0 +1,14 @@ + + + Page Six + + + + Page Six +
+ Param One: {{paramOne}} +
+
+ Param Two: {{paramTwo}} +
+
\ No newline at end of file diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/right/sixth-page/sixth-page.module.ts b/src/components/nav/test/simple-nested-peer-navs/pages/right/sixth-page/sixth-page.module.ts new file mode 100644 index 0000000000..212bd5c889 --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/right/sixth-page/sixth-page.module.ts @@ -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 { } diff --git a/src/components/nav/test/simple-nested-peer-navs/pages/right/sixth-page/sixth-page.ts b/src/components/nav/test/simple-nested-peer-navs/pages/right/sixth-page/sixth-page.ts new file mode 100644 index 0000000000..6f075c5fa4 --- /dev/null +++ b/src/components/nav/test/simple-nested-peer-navs/pages/right/sixth-page/sixth-page.ts @@ -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; + } +} diff --git a/src/components/tabs/tabs.ts b/src/components/tabs/tabs.ts index 15fd28a2a2..302432d1e6 100644 --- a/src/components/tabs/tabs.ts +++ b/src/components/tabs/tabs.ts @@ -507,6 +507,13 @@ export class Tabs extends Ion implements AfterViewInit, RootNode, ITabs, Navigat return selected ? [selected] : []; } + /** + * @internal + */ + getAllChildNavs(): any[] { + return this._tabs; + } + /** * @internal */ diff --git a/src/module.ts b/src/module.ts index 6ac1054412..e9b48d7980 100644 --- a/src/module.ts +++ b/src/module.ts @@ -445,7 +445,7 @@ export class IonicModule { { provide: ModuleLoader, useFactory: provideModuleLoader, deps: [NgModuleLoader, Injector]}, { provide: LocationStrategy, useFactory: provideLocationStrategy, deps: [ PlatformLocation, [new Inject(APP_BASE_HREF), new Optional()], Config ] }, - { provide: UrlSerializer, useFactory: setupUrlSerializer, deps: [ DeepLinkConfigToken ] }, + { provide: UrlSerializer, useFactory: setupUrlSerializer, deps: [ App, DeepLinkConfigToken ] }, { provide: DeepLinker, useFactory: setupDeepLinker, deps: [ App, UrlSerializer, Location, ModuleLoader, ComponentFactoryResolver ] }, ] }; diff --git a/src/navigation/deep-linker.ts b/src/navigation/deep-linker.ts index 4fff116f0c..3c5d06c748 100644 --- a/src/navigation/deep-linker.ts +++ b/src/navigation/deep-linker.ts @@ -131,18 +131,23 @@ export class DeepLinker { */ navChange(direction: string) { if (direction) { - const rootNavContainers = this._app.getActiveNavContainers(); + const activeNavContainers = this._app.getActiveNavContainers(); // the only time you'll ever get a TABS here is when loading directly from a URL // this method will be called again when the TAB is loaded // so just don't worry about the TABS for now // if you encounter a TABS, just return - let segments: NavSegment[] = []; - for (const rootNavContainer of rootNavContainers) { - if (isTabs(rootNavContainer) || (rootNavContainer as NavController).isTransitioning()) { + for (const activeNavContainer of activeNavContainers) { + if (isTabs(activeNavContainer) || (activeNavContainer as NavController).isTransitioning()) { return; } - const segmentsForNav = this.getSegmentsFromNav(rootNavContainer); - segments = segments.concat(segmentsForNav); + } + + // okay, get the root navs and build the segments up + let segments: NavSegment[] = []; + const navContainers: NavigationContainer[] = this._app.getRootNavs(); + for (const navContainer of navContainers) { + const segmentsForNav = this.getSegmentsFromNav(navContainer); + segments = segments.concat(segmentsForNav); } segments = segments.filter(segment => !!segment); if (segments.length) { @@ -153,19 +158,16 @@ export class DeepLinker { } getSegmentsFromNav(nav: NavigationContainer): NavSegment[] { - const segments: NavSegment[] = []; - while (nav) { - if (isNav(nav)) { - segments.push(this.getSegmentFromNav(nav as NavController)); - nav = nav.parent; - } else if (isTab(nav)) { - segments.push(this.getSegmentFromTab(nav)); - nav = nav.parent && nav.parent.parent; - } else { - nav = nav.parent; - } + let segments: NavSegment[] = []; + if (isNav(nav)) { + segments.push(this.getSegmentFromNav(nav as NavController)); + } else if (isTab(nav)) { + segments.push(this.getSegmentFromTab(nav)); } - return segments.reverse(); + nav.getActiveChildNavs().forEach(child => { + segments = segments.concat(this.getSegmentsFromNav(child)); + }); + return segments; } getSegmentFromNav(nav: NavController, component?: any, data?: any): NavSegment { @@ -176,7 +178,7 @@ export class DeepLinker { data = viewController.data; } } - return this._serializer.serializeComponent({ navId: nav.name && nav.name.length ? nav.name : nav.id, secondaryId: null, type: 'nav'}, component, data); + return this._serializer.serializeComponent(nav, component, data); } getSegmentFromTab(navContainer: NavigationContainer, component?: any, data?: any): NavSegment { @@ -190,7 +192,7 @@ export class DeepLinker { component = viewController.component; data = viewController.data; } - return this._serializer.serializeComponent({ navId: tabsNavContainer.name || tabsNavContainer.id, secondaryId: tabsNavContainer.getSecondaryIdentifier(), type: 'tabs'}, component, data); + return this._serializer.serializeComponent(tabsNavContainer, component, data); } } } @@ -415,7 +417,7 @@ export class DeepLinker { return navController.popTo(viewController, { animate: false, updateUrl: false, - }, done); + }, {}, done); } } } diff --git a/src/navigation/nav-controller-base.ts b/src/navigation/nav-controller-base.ts index e921c52ce7..b1dae7203c 100644 --- a/src/navigation/nav-controller-base.ts +++ b/src/navigation/nav-controller-base.ts @@ -991,6 +991,10 @@ export class NavControllerBase extends Ion implements NavController { return this._children; } + getAllChildNavs(): any[] { + return this._children; + } + registerChildNav(container: NavigationContainer) { this._children.push(container); } diff --git a/src/navigation/nav-controller.ts b/src/navigation/nav-controller.ts index 0f02e0f0eb..58be7a19c3 100644 --- a/src/navigation/nav-controller.ts +++ b/src/navigation/nav-controller.ts @@ -599,6 +599,11 @@ export abstract class NavController implements NavigationContainer { */ abstract getActiveChildNav(): any; + /** + * Returns a list of all child navigation containers + */ + abstract getAllChildNavs(): any[]; + /** * Returns if the nav controller is actively transitioning or not. diff --git a/src/navigation/nav-util.ts b/src/navigation/nav-util.ts index 772978a5fa..1c947cacbe 100644 --- a/src/navigation/nav-util.ts +++ b/src/navigation/nav-util.ts @@ -163,16 +163,26 @@ export interface NavResult { direction?: string; } -export interface NavSegment { +export interface NavSegment extends DehydratedSegment { + type: string; + navId: string; + secondaryId: string; + requiresExplicitNavPrefix?: boolean; +} + +export interface DehydratedSegment { id: string; name: string; component?: any; loadChildren?: string; data: any; - type: string; - navId: string; - secondaryId: string; defaultHistory?: NavSegment[]; + secondaryId?: string; +} + +export interface DehydratedSegmentPair { + segments: DehydratedSegment[]; + navGroup: NavGroup; } export interface NavGroup { diff --git a/src/navigation/navigation-container.ts b/src/navigation/navigation-container.ts index 8cb8daf8a3..a5bbb93290 100644 --- a/src/navigation/navigation-container.ts +++ b/src/navigation/navigation-container.ts @@ -1,10 +1,14 @@ import { NavController } from './nav-controller'; +/** + * @hidden + */ export interface NavigationContainer { id: string; name: string; parent: NavController; getActiveChildNavs(): NavigationContainer[]; + getAllChildNavs?(): NavigationContainer[]; getType(): string; getSecondaryIdentifier(): string; } diff --git a/src/navigation/test/deep-linker.spec.ts b/src/navigation/test/deep-linker.spec.ts index ea7b50736a..5f23f4a633 100644 --- a/src/navigation/test/deep-linker.spec.ts +++ b/src/navigation/test/deep-linker.spec.ts @@ -457,9 +457,7 @@ describe('DeepLinker', () => { linker.getSegmentFromNav(mockNav, null, null); - expect(spy.calls.first().args[0].navId).toEqual(mockNav.name); - expect(spy.calls.first().args[0].secondaryId).toBeFalsy(); - expect(spy.calls.first().args[0].type).toEqual('nav'); + expect(spy.calls.first().args[0]).toEqual(mockNav); }); it('should use the id of the nav when name doesnt exists', () => { @@ -472,9 +470,7 @@ describe('DeepLinker', () => { linker.getSegmentFromNav(mockNav, null, null); - expect(spy.calls.first().args[0].navId).toEqual(mockNav.id); - expect(spy.calls.first().args[0].secondaryId).toBeFalsy(); - expect(spy.calls.first().args[0].type).toEqual('nav'); + expect(spy.calls.first().args[0]).toEqual(mockNav); }); }); @@ -493,7 +489,7 @@ describe('DeepLinker', () => { linker.getSegmentFromTab(tabOne, null, null); expect(spy).toHaveBeenCalled(); - expect(spy.calls.first().args[0].navId).toEqual(tabs.name); + expect(spy.calls.first().args[0]).toEqual(tabs); }); @@ -511,7 +507,7 @@ describe('DeepLinker', () => { linker.getSegmentFromTab(tabOne, null, null); expect(spy).toHaveBeenCalled(); - expect(spy.calls.first().args[0].navId).toEqual(tabs.id); + expect(spy.calls.first().args[0]).toEqual(tabs); }); }); @@ -685,7 +681,7 @@ describe('DeepLinker', () => { beforeEach(() => { let linkConfig = mockDeepLinkConfig(); - serializer = new UrlSerializer(linkConfig); + serializer = new UrlSerializer(mockApp(), linkConfig); let moduleLoader = mockModuleLoader(); let baseCfr: any = null; diff --git a/src/navigation/test/url-serializer.spec.ts b/src/navigation/test/url-serializer.spec.ts index ed3000a73b..40a4f29d08 100644 --- a/src/navigation/test/url-serializer.spec.ts +++ b/src/navigation/test/url-serializer.spec.ts @@ -1,17 +1,18 @@ import { NavLink, NavSegment } from '../nav-util'; import { - NavGroup, UrlSerializer, + convertUrlToDehydratedSegments, + convertUrlToSegments, createMatchedData, findLinkByComponentData, formatUrlPart, isPartMatch, navGroupStringtoObjects, normalizeLinks, - parseUrlParts, urlToNavGroupStrings, + urlToNavGroupStringsTwo, } from '../url-serializer'; -import { MockView1, MockView2, MockView3, mockDeepLinkConfig, mockNavController, noop } from '../../util/mock-providers'; +import { MockView1, MockView2, MockView3, mockApp, mockDeepLinkConfig, mockNavController, mockTab, mockTabs, noop } from '../../util/mock-providers'; describe('UrlSerializer', () => { @@ -22,32 +23,37 @@ describe('UrlSerializer', () => { const link1 = { component: MockView1, name: 'viewone', segment: 'view' }; const link2 = { component: MockView1, name: 'viewtwo', segment: 'view/:param1' }; const link3 = { component: MockView1, name: 'viewthree', segment: 'view/:param1/:param2' }; - const navGroup: NavGroup = { type: 'nav', navId: 'n1', secondaryId: null, segmentPieces: ['view']}; serializer = mockSerializer([link1, link2, link3]); serializer._createSegment = noop; spyOn(serializer, '_createSegment'); - serializer.serializeComponent(navGroup, MockView1, null); - expect(serializer._createSegment).toHaveBeenCalledWith(navGroup, link1, null); + const nav = mockNavController(); + serializer.serializeComponent(nav, MockView1, null); + expect(serializer._createSegment).toHaveBeenCalledWith(serializer._app, nav, link1, null); }); it('should create segment if component found in links', () => { serializer._createSegment = noop; spyOn(serializer, '_createSegment'); - serializer.serializeComponent({ type: 'nav', navId: 'n1', secondaryId: null, segmentPieces: ['view']}, MockView1, null); + const nav = mockNavController(); + serializer.serializeComponent(nav, MockView1, null); expect(serializer._createSegment).toHaveBeenCalled(); }); it('should return null if component not found in links', () => { serializer._createSegment = noop; spyOn(serializer, '_createSegment'); - serializer.serializeComponent({ type: 'nav', navId: 'n1', secondaryId: null, segmentPieces: ['view']}, NotFound, null); + const nav = mockNavController(); + serializer.serializeComponent(nav, NotFound, null); expect(serializer._createSegment).not.toHaveBeenCalled(); }); it('should create tab segment if component found in deep links', () => { serializer._createSegment = noop; spyOn(serializer, '_createSegment'); - serializer.serializeComponent({ type: 'nav', navId: 'n1', secondaryId: null, segmentPieces: ['view']}, MockView1, null); + + const tabs = mockTabs(); + const tab = mockTab(tabs); + serializer.serializeComponent(tab, MockView1, null); expect(serializer._createSegment).toHaveBeenCalled(); }); @@ -65,7 +71,8 @@ describe('UrlSerializer', () => { name: 'jenny' }; - const segment = serializer._createSegment({ navId: '1', type: 'nav', secondaryId: null}, link, data); + const nav = mockNavController(); + const segment = serializer._createSegment(serializer._app, nav, link, data); expect(segment.id).toEqual('userId/8675309/name/jenny'); expect(segment.component).toEqual(link.component); expect(segment.data.id).toEqual(data.id); @@ -84,7 +91,8 @@ describe('UrlSerializer', () => { id: char, name: 'jenny' }; - const segment = serializer._createSegment({ navId: '1', type: 'nav', secondaryId: null}, link, data); + const nav = mockNavController(); + const segment = serializer._createSegment(serializer._app, nav, link, data); expect(segment.id).toEqual(`userId/${encoded}/name/${data.name}`); expect(segment.component).toEqual(MockView1); expect(segment.data.id).toEqual(char); @@ -95,7 +103,8 @@ describe('UrlSerializer', () => { segmentParts: ['settings-view'], component: MockView1 }; - const segment = serializer._createSegment({ navId: '1', type: 'nav', secondaryId: null}, link, null); + const nav = mockNavController(); + const segment = serializer._createSegment(serializer._app, nav, link, null); expect(segment.id).toEqual('settings-view'); expect(segment.component).toEqual(MockView1); expect(segment.data).toEqual(null); @@ -179,106 +188,51 @@ describe('UrlSerializer', () => { describe('navGroupStringtoObjects', () => { it('should convert the nav group strings to objects', () => { - const urlChunks = ['nav/1/chunk/of/segment', 'tabs/1/tab-one/chunk/of/segment']; + const urlChunks = ['taco/burrito/pizza/nachos', 'nav/1/chunk/of/segment', 'tabs/1/tab-one/chunk/of/segment']; const objects = navGroupStringtoObjects(urlChunks); - expect(objects.length).toEqual(2); - expect(objects[0].type).toEqual('nav'); - expect(objects[0].navId).toEqual('1'); + expect(objects.length).toEqual(3); + expect(objects[0].type).toEqual(null); + expect(objects[0].navId).toEqual(null); expect(objects[0].secondaryId).toEqual(null); - expect(objects[0].segmentPieces.length).toEqual(3); - expect(objects[0].segmentPieces[0]).toEqual('chunk'); - expect(objects[0].segmentPieces[1]).toEqual('of'); - expect(objects[0].segmentPieces[2]).toEqual('segment'); - expect(objects[1].type).toEqual('tabs'); + expect(objects[0].segmentPieces.length).toEqual(4); + expect(objects[0].segmentPieces[0]).toEqual('taco'); + expect(objects[0].segmentPieces[1]).toEqual('burrito'); + expect(objects[0].segmentPieces[2]).toEqual('pizza'); + expect(objects[0].segmentPieces[3]).toEqual('nachos'); + expect(objects[1].type).toEqual('nav'); expect(objects[1].navId).toEqual('1'); - expect(objects[1].secondaryId).toEqual('tab-one'); + expect(objects[1].secondaryId).toEqual(null); expect(objects[1].segmentPieces.length).toEqual(3); expect(objects[1].segmentPieces[0]).toEqual('chunk'); expect(objects[1].segmentPieces[1]).toEqual('of'); expect(objects[1].segmentPieces[2]).toEqual('segment'); - }); - }); - - describe('parse', () => { - - it('should return empty list of segments for bogus url', () => { - serializer = mockSerializer([]); - - const segments = serializer.parse('/some/bogus/url'); - expect(segments.length).toEqual(0); - }); - - it('should return empty list of segments when there isnt a match', () => { - serializer = mockSerializer([ - { segment: 'some/chunk/of/url', name: 'viewone', component: MockView1 }, - { segment: 'another/section/of/url', name: 'viewtwo', component: MockView2 } - ]); - const segments = serializer.parse('/nav/n1/not/a/matching/url'); - expect(segments.length).toEqual(0); - }); - - it('should return the segments from the url with multiple navs', () => { - serializer = mockSerializer([ - { segment: 'userId/:id/name/:name', name: 'viewone', component: MockView1 }, - { segment: 'selectedId/:id/food/:food', name: 'viewtwo', component: MockView2 } - ]); - const segments = serializer.parse('/nav/n1/userId/123/name/Stanley/nav/n2/selectedId/456/food/tacos'); - expect(segments.length).toEqual(2); - expect(segments[0].name).toEqual('viewone'); - expect(segments[0].data.id).toEqual('123'); - expect(segments[0].data.name).toEqual('Stanley'); - expect(segments[1].name).toEqual('viewtwo'); - expect(segments[1].data.id).toEqual('456'); - expect(segments[1].data.food).toEqual('tacos'); - }); - - it('should return the segments from the url with multiple tabs', () => { - serializer = mockSerializer([ - { segment: 'userId/:id/name/:name', name: 'viewone', component: MockView1 }, - { segment: 'selectedId/:id/food/:food', name: 'viewtwo', component: MockView2 } - ]); - const segments = serializer.parse('/tabs/t1/tab-one/userId/123/name/Stanley/tabs/t2/tab-three/selectedId/456/food/tacos'); - expect(segments.length).toEqual(2); - expect(segments[0].name).toEqual('viewone'); - expect(segments[0].navId).toEqual('t1'); - expect(segments[0].data.id).toEqual('123'); - expect(segments[0].data.name).toEqual('Stanley'); - expect(segments[0].secondaryId).toEqual('tab-one'); - expect(segments[1].name).toEqual('viewtwo'); - expect(segments[1].navId).toEqual('t2'); - expect(segments[1].data.id).toEqual('456'); - expect(segments[1].data.food).toEqual('tacos'); - expect(segments[1].secondaryId).toEqual('tab-three'); - }); - - it('should return the segments from a mixed nav/tabs url', () => { - serializer = mockSerializer([ - { segment: 'userId/:id/name/:name', name: 'viewone', component: MockView1 }, - { segment: 'selectedId/:id/food/:food', name: 'viewtwo', component: MockView2 } - ]); - const segments = serializer.parse('/tabs/t1/tab-one/userId/123/name/Stanley/nav/n1/selectedId/456/food/tacos'); - expect(segments.length).toEqual(2); - expect(segments[0].name).toEqual('viewone'); - expect(segments[0].navId).toEqual('t1'); - expect(segments[0].data.id).toEqual('123'); - expect(segments[0].data.name).toEqual('Stanley'); - expect(segments[0].secondaryId).toEqual('tab-one'); - expect(segments[1].name).toEqual('viewtwo'); - expect(segments[1].navId).toEqual('n1'); - expect(segments[1].data.id).toEqual('456'); - expect(segments[1].data.food).toEqual('tacos'); - expect(segments[1].secondaryId).toEqual(null); + expect(objects[2].type).toEqual('tabs'); + expect(objects[2].navId).toEqual('1'); + expect(objects[2].secondaryId).toEqual('tab-one'); + expect(objects[2].segmentPieces.length).toEqual(3); + expect(objects[2].segmentPieces[0]).toEqual('chunk'); + expect(objects[2].segmentPieces[1]).toEqual('of'); + expect(objects[2].segmentPieces[2]).toEqual('segment'); }); }); describe('serialize', () => { + it('should serialize multiple segments into a url with explicit prefixs', () => { + let paths: NavSegment[] = [ + { type: 'nav', navId: 'whatup', secondaryId: null, id: 'some/url/chunks', name: 'viewOne', component: MockView1, data: null, requiresExplicitNavPrefix: true}, + { type: 'tabs', navId: 't1', secondaryId: 'tab-one', id: 'some/more/url/chunks', name: 'viewTwo', component: MockView1, data: null, requiresExplicitNavPrefix: true } + ]; + const result = serializer.serialize(paths); + expect(result).toEqual('/nav/whatup/some/url/chunks/tabs/t1/tab-one/some/more/url/chunks'); + }); + it('should serialize multiple segments into a url', () => { let paths: NavSegment[] = [ { type: 'nav', navId: 'whatup', secondaryId: null, id: 'some/url/chunks', name: 'viewOne', component: MockView1, data: null }, { type: 'tabs', navId: 't1', secondaryId: 'tab-one', id: 'some/more/url/chunks', name: 'viewTwo', component: MockView1, data: null } ]; const result = serializer.serialize(paths); - expect(result).toEqual('/nav/whatup/some/url/chunks/tabs/t1/tab-one/some/more/url/chunks'); + expect(result).toEqual('/some/url/chunks/tab-one/some/more/url/chunks'); }); it('should return default url when given empty list of segments', () => { @@ -342,194 +296,6 @@ describe('UrlSerializer', () => { }); - describe('parseUrlParts', () => { - - it('should return a single matching segment', () => { - // arrange - const navGroups = []; - const configLinks = []; - const segmentPieces = ['some', 'part', 'of', 'url']; - - const navGroup = { type: 'nav', navId: '1', secondaryId: '', segmentPieces: segmentPieces }; - navGroups.push(navGroup); - - const configLink = { - segmentParts: segmentPieces, - segmentPartsLen: 4, - component: {}, - name: 'someName', - loadChildren: 'someValue', - }; - configLinks.push(configLink); - - // act - const segments = parseUrlParts(navGroups, configLinks); - - // assert - expect(segments.length).toEqual(1); - expect(segments[0].id).toEqual(configLink.segmentParts.join('/')); - expect(segments[0].component).toEqual(configLink.component); - expect(segments[0].name).toEqual(configLink.name); - expect(segments[0].loadChildren).toEqual(configLink.loadChildren); - expect(segments[0].type).toEqual(navGroup.type); - expect(segments[0].navId).toEqual(navGroup.navId); - expect(segments[0].secondaryId).toEqual(navGroup.secondaryId); - }); - - it('should return single matching segment for tabs', () => { - // arrange - const navGroups = []; - const configLinks = []; - const segmentPieces = ['some', 'part', 'of', 'url']; - - const navGroup = { type: 'tabs', navId: '1', secondaryId: 'tab-one', segmentPieces: segmentPieces }; - navGroups.push(navGroup); - - const configLink = { - segmentParts: ['some', ':someVariable', 'of', ':someVariable2'], - segmentPartsLen: 4, - component: {}, - name: 'someName', - loadChildren: 'someValue', - }; - configLinks.push(configLink); - - // act - const segments = parseUrlParts(navGroups, configLinks); - - // assert - expect(segments.length).toEqual(1); - expect(segments[0].id).toEqual(configLink.segmentParts.join('/')); - expect(segments[0].component).toEqual(configLink.component); - expect(segments[0].name).toEqual(configLink.name); - expect(segments[0].loadChildren).toEqual(configLink.loadChildren); - expect(segments[0].type).toEqual(navGroup.type); - expect(segments[0].navId).toEqual(navGroup.navId); - expect(segments[0].secondaryId).toEqual(navGroup.secondaryId); - expect(segments[0].data.someVariable).toEqual('part'); - expect(segments[0].data.someVariable2).toEqual('url'); - }); - - it('should return an empty list of segments when there isnt a nav group', () => { - // arrange - const configLinks = []; - - const configLink = { - segmentParts: ['some', ':someVariable', 'of', ':someVariable2'], - segmentPartsLen: 4, - component: {}, - name: 'someName', - loadChildren: 'someValue', - }; - configLinks.push(configLink); - - // act - const segments = parseUrlParts([], configLinks); - - // assert - expect(segments.length).toEqual(0); - }); - - it('should return a list of segments', () => { - // arrange - const navGroups = []; - const configLinks = []; - const segmentPiecesOne = ['some', 'part', 'of', 'url']; - const navGroup = { type: 'tabs', navId: '1', secondaryId: 'tab-one', segmentPieces: segmentPiecesOne }; - const segmentPiecesTwo = ['userId', '123', 'name', 'Stanley Hudson']; - const navGroupTwo = { type: 'nav', navId: '2', secondaryId: '', segmentPieces: segmentPiecesTwo }; - - navGroups.push(navGroup); - navGroups.push(navGroupTwo); - - const configLink = { - segmentParts: ['some', ':someVariable', 'of', ':someVariable2'], - segmentPartsLen: 4, - component: {}, - name: 'someName', - loadChildren: 'someValue', - }; - const configLinkTwo = { - segmentParts: ['userId', ':userId', 'name', ':name'], - segmentPartsLen: 4, - component: {}, - name: 'nameTwo', - loadChildren: 'valueTwo', - }; - configLinks.push(configLink); - configLinks.push(configLinkTwo); - - // act - const segments = parseUrlParts(navGroups, configLinks); - - // assert - expect(segments.length).toEqual(2); - expect(segments[0].id).toEqual(configLink.segmentParts.join('/')); - expect(segments[0].component).toEqual(configLink.component); - expect(segments[0].name).toEqual(configLink.name); - expect(segments[0].loadChildren).toEqual(configLink.loadChildren); - expect(segments[0].type).toEqual(navGroup.type); - expect(segments[0].navId).toEqual(navGroup.navId); - expect(segments[0].secondaryId).toEqual(navGroup.secondaryId); - expect(segments[0].data.someVariable).toEqual('part'); - expect(segments[0].data.someVariable2).toEqual('url'); - - expect(segments[1].id).toEqual(configLinkTwo.segmentParts.join('/')); - expect(segments[1].component).toEqual(configLinkTwo.component); - expect(segments[1].name).toEqual(configLinkTwo.name); - expect(segments[1].loadChildren).toEqual(configLinkTwo.loadChildren); - expect(segments[1].type).toEqual(navGroupTwo.type); - expect(segments[1].navId).toEqual(navGroupTwo.navId); - expect(segments[1].secondaryId).toEqual(navGroupTwo.secondaryId); - expect(segments[1].data.userId).toEqual('123'); - expect(segments[1].data.name).toEqual('Stanley Hudson'); - }); - - it('should return only matching segments for the nav groups', () => { - // arrange - const navGroups = []; - const configLinks = []; - const segmentPiecesOne = ['some', 'part', 'of', 'url']; - const navGroup = { type: 'tabs', navId: '1', secondaryId: 'tab-one', segmentPieces: segmentPiecesOne }; - const segmentPiecesTwo = ['userId', '123', 'name', 'Stanley Hudson']; - const navGroupTwo = { type: 'nav', navId: '2', secondaryId: '', segmentPieces: segmentPiecesTwo }; - - navGroups.push(navGroup); - navGroups.push(navGroupTwo); - - const configLink = { - segmentParts: ['some', ':someVariable', 'of', ':someVariable2'], - segmentPartsLen: 4, - component: {}, - name: 'someName', - loadChildren: 'someValue', - }; - const configLinkTwo = { - segmentParts: ['some', 'bogus', 'content', 'wewontmatch'], - segmentPartsLen: 4, - component: {}, - name: 'nameTwo', - loadChildren: 'valueTwo', - }; - const configLinkThree = { - segmentParts: ['hi'], - segmentPartsLen: 1, - component: {}, - name: 'nameThree', - loadChildren: 'valueThree', - }; - configLinks.push(configLink); - configLinks.push(configLinkTwo); - configLinks.push(configLinkThree); - - const segments = parseUrlParts(navGroups, configLinks); - - expect(segments.length).toEqual(1); - expect(segments[0].name).toEqual(configLink.name); - }); - - }); - describe('isPartMatch', () => { it('should match if parts are equal', () => { @@ -764,6 +530,289 @@ describe('UrlSerializer', () => { }); + describe('urlToNavGroupStringsTwo', () => { + it('should get an array with a single piece url back', () => { + const url = 'test'; + const result = urlToNavGroupStringsTwo(url); + expect(result.length).toEqual(1); + expect(result[0]).toEqual('test'); + }); + + it('should get an array with multiple pieces back', () => { + const url = 'the/dog/jumps/high'; + const result = urlToNavGroupStringsTwo(url); + expect(result.length).toEqual(1); + expect(result[0]).toEqual('the/dog/jumps/high'); + }); + + it('should return a single entry with the nav prefix', () => { + const url = 'nav/myApp/the/dog/jumps/high'; + const result = urlToNavGroupStringsTwo(url); + expect(result.length).toEqual(1); + expect(result[0]).toEqual('nav/myApp/the/dog/jumps/high'); + }); + + it('should return a single entry with the tabs prefix', () => { + const url = 'tabs/myApp/tab-one/the/dog/jumps/high'; + const result = urlToNavGroupStringsTwo(url); + expect(result.length).toEqual(1); + expect(result[0]).toEqual('tabs/myApp/tab-one/the/dog/jumps/high'); + }); + + it('should return multiple entries with the nav prefix', () => { + const url = 'nav/myApp/the/dog/jumps/high/nav/someSubNav/taco/burrito/nav/thirdNav/banana/apple/orange'; + const result = urlToNavGroupStringsTwo(url); + expect(result.length).toEqual(3); + expect(result[0]).toEqual('nav/myApp/the/dog/jumps/high'); + expect(result[1]).toEqual('nav/someSubNav/taco/burrito'); + expect(result[2]).toEqual('nav/thirdNav/banana/apple/orange'); + }); + + it('should return multiple entries with the tabs prefix', () => { + const url = 'tabs/myApp/tab-one/the/dog/jumps/high/tabs/someSubNav/tab-two/taco/burrito/tabs/thirdNav/tab-three/banana/apple/orange'; + const result = urlToNavGroupStringsTwo(url); + expect(result.length).toEqual(3); + expect(result[0]).toEqual('tabs/myApp/tab-one/the/dog/jumps/high'); + expect(result[1]).toEqual('tabs/someSubNav/tab-two/taco/burrito'); + expect(result[2]).toEqual('tabs/thirdNav/tab-three/banana/apple/orange'); + }); + + it('should handle a nav in the middle of the url', () => { + const url = 'the/dog/jumps/high/nav/someSubNav/taco/burrito/nav/thirdNav/banana/apple/orange'; + const result = urlToNavGroupStringsTwo(url); + expect(result.length).toEqual(3); + expect(result[0]).toEqual('the/dog/jumps/high'); + expect(result[1]).toEqual('nav/someSubNav/taco/burrito'); + expect(result[2]).toEqual('nav/thirdNav/banana/apple/orange'); + }); + + it('should handle a tabs in the middle of the url', () => { + const url = 'the/dog/jumps/high/tabs/someSubNav/tab-two/taco/burrito/tabs/thirdNav/tab-three/banana/apple/orange'; + const result = urlToNavGroupStringsTwo(url); + expect(result.length).toEqual(3); + expect(result[0]).toEqual('the/dog/jumps/high'); + expect(result[1]).toEqual('tabs/someSubNav/tab-two/taco/burrito'); + expect(result[2]).toEqual('tabs/thirdNav/tab-three/banana/apple/orange'); + }); + + it('should handle a mixed url', () => { + const url = 'the/dog/jumps/high/tabs/someSubNav/tab-two/taco/burrito/nav/thirdNav/banana/apple/orange'; + const result = urlToNavGroupStringsTwo(url); + expect(result.length).toEqual(3); + expect(result[0]).toEqual('the/dog/jumps/high'); + expect(result[1]).toEqual('tabs/someSubNav/tab-two/taco/burrito'); + expect(result[2]).toEqual('nav/thirdNav/banana/apple/orange'); + }); + }); + + describe('convertUrlToDehydratedSegments', () => { + it('it should return a vanilla single segment', () => { + const link1 = { component: MockView1, name: 'viewone', segment: 'view-one' }; + const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two' }; + const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three' }; + + const links = normalizeLinks([link1, link2, link3]); + const url = 'view-two'; + const segmentPairs = convertUrlToDehydratedSegments(url, links); + expect(segmentPairs.length).toEqual(1); + expect(segmentPairs[0].segments.length).toEqual(1); + expect(segmentPairs[0].segments[0].id).toEqual('view-two'); + }); + + it('it should return a data-driven single segment', () => { + const link1 = { component: MockView1, name: 'viewone', segment: 'view-one' }; + const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two/paramOne/:paramOne/paramTwo/:paramTwo' }; + const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three' }; + + const links = normalizeLinks([link1, link2, link3]); + const url = 'view-two/paramOne/taco/paramTwo/burrito'; + const segmentPairs = convertUrlToDehydratedSegments(url, links); + expect(segmentPairs.length).toEqual(1); + expect(segmentPairs[0].segments.length).toEqual(1); + expect(segmentPairs[0].segments[0].id).toEqual('view-two/paramOne/taco/paramTwo/burrito'); + expect(segmentPairs[0].segments[0].data.paramOne).toEqual('taco'); + expect(segmentPairs[0].segments[0].data.paramTwo).toEqual('burrito'); + }); + + it('it should return a vanilla set of segments', () => { + const link1 = { component: MockView1, name: 'viewone', segment: 'view-one' }; + const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two' }; + const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three' }; + const link4 = { component: MockView1, name: 'viewfour', segment: 'view-four' }; + + const links = normalizeLinks([link1, link2, link3, link4]); + const url = 'view-two/view-one/view-three'; + const segmentPairs = convertUrlToDehydratedSegments(url, links); + expect(segmentPairs.length).toEqual(1); + expect(segmentPairs[0].segments.length).toEqual(3); + expect(segmentPairs[0].segments[0].id).toEqual('view-two'); + expect(segmentPairs[0].segments[1].id).toEqual('view-one'); + expect(segmentPairs[0].segments[2].id).toEqual('view-three'); + }); + + it('it should return a data-driven set of segments', () => { + const link1 = { component: MockView1, name: 'viewone', segment: 'view-one/paramOne/:paramOne/paramTwo/:paramTwo' }; + const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two/user/:userId' }; + const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three/:itemId' }; + const link4 = { component: MockView1, name: 'viewfour', segment: 'view-four' }; + + const links = normalizeLinks([link1, link2, link3, link4]); + const url = 'view-two/user/fred/view-one/paramOne/taco/paramTwo/burrito/view-three/12345'; + const segmentPairs = convertUrlToDehydratedSegments(url, links); + expect(segmentPairs.length).toEqual(1); + expect(segmentPairs[0].segments.length).toEqual(3); + expect(segmentPairs[0].segments[0].id).toEqual('view-two/user/fred'); + expect(segmentPairs[0].segments[0].data.userId).toEqual('fred'); + expect(segmentPairs[0].segments[1].id).toEqual('view-one/paramOne/taco/paramTwo/burrito'); + expect(segmentPairs[0].segments[1].data.paramOne).toEqual('taco'); + expect(segmentPairs[0].segments[1].data.paramTwo).toEqual('burrito'); + expect(segmentPairs[0].segments[2].id).toEqual('view-three/12345'); + expect(segmentPairs[0].segments[2].data.itemId).toEqual('12345'); + }); + + it('it should return a data-driven set of segments with a root nav prefix', () => { + const link1 = { component: MockView1, name: 'viewone', segment: 'view-one/paramOne/:paramOne/paramTwo/:paramTwo' }; + const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two/user/:userId' }; + const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three/:itemId' }; + const link4 = { component: MockView1, name: 'viewfour', segment: 'view-four' }; + + const links = normalizeLinks([link1, link2, link3, link4]); + const url = 'nav/123/view-two/user/fred/view-one/paramOne/taco/paramTwo/burrito/view-three/12345'; + const segmentPairs = convertUrlToDehydratedSegments(url, links); + expect(segmentPairs.length).toEqual(1); + expect(segmentPairs[0].segments.length).toEqual(3); + expect(segmentPairs[0].segments[0].id).toEqual('view-two/user/fred'); + expect(segmentPairs[0].segments[0].data.userId).toEqual('fred'); + expect(segmentPairs[0].segments[1].id).toEqual('view-one/paramOne/taco/paramTwo/burrito'); + expect(segmentPairs[0].segments[1].data.paramOne).toEqual('taco'); + expect(segmentPairs[0].segments[1].data.paramTwo).toEqual('burrito'); + expect(segmentPairs[0].segments[2].id).toEqual('view-three/12345'); + expect(segmentPairs[0].segments[2].data.itemId).toEqual('12345'); + }); + + it('it should return a data-driven set of segments with multiple nav segments in the middle', () => { + const link1 = { component: MockView1, name: 'viewone', segment: 'view-one/paramOne/:paramOne/paramTwo/:paramTwo' }; + const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two/user/:userId' }; + const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three/:itemId' }; + const link4 = { component: MockView1, name: 'viewfour', segment: 'view-four' }; + + const links = normalizeLinks([link1, link2, link3, link4]); + const url = 'view-two/user/fred/nav/123/view-one/paramOne/taco/paramTwo/burrito/nav/456/view-three/12345'; + const segmentPairs = convertUrlToDehydratedSegments(url, links); + expect(segmentPairs.length).toEqual(3); + expect(segmentPairs[0].segments.length).toEqual(1); + expect(segmentPairs[0].segments[0].id).toEqual('view-two/user/fred'); + expect(segmentPairs[0].segments[0].data.userId).toEqual('fred'); + + expect(segmentPairs[1].segments.length).toEqual(1); + expect(segmentPairs[1].segments[0].id).toEqual('view-one/paramOne/taco/paramTwo/burrito'); + expect(segmentPairs[1].segments[0].data.paramOne).toEqual('taco'); + expect(segmentPairs[1].segments[0].data.paramTwo).toEqual('burrito'); + + expect(segmentPairs[2].segments.length).toEqual(1); + expect(segmentPairs[2].segments[0].id).toEqual('view-three/12345'); + expect(segmentPairs[2].segments[0].data.itemId).toEqual('12345'); + }); + + it('it should return a data-driven set of segments with a root nav and multiple nav segments in the middle', () => { + const link1 = { component: MockView1, name: 'viewone', segment: 'view-one/paramOne/:paramOne/paramTwo/:paramTwo' }; + const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two/user/:userId' }; + const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three/:itemId' }; + const link4 = { component: MockView1, name: 'viewfour', segment: 'view-four' }; + + const links = normalizeLinks([link1, link2, link3, link4]); + const url = 'nav/app/view-two/user/fred/nav/123/view-one/paramOne/taco/paramTwo/burrito/nav/456/view-three/12345'; + const segmentPairs = convertUrlToDehydratedSegments(url, links); + expect(segmentPairs.length).toEqual(3); + expect(segmentPairs[0].segments.length).toEqual(1); + expect(segmentPairs[0].segments[0].id).toEqual('view-two/user/fred'); + expect(segmentPairs[0].segments[0].data.userId).toEqual('fred'); + + expect(segmentPairs[1].segments.length).toEqual(1); + expect(segmentPairs[1].segments[0].id).toEqual('view-one/paramOne/taco/paramTwo/burrito'); + expect(segmentPairs[1].segments[0].data.paramOne).toEqual('taco'); + expect(segmentPairs[1].segments[0].data.paramTwo).toEqual('burrito'); + + expect(segmentPairs[2].segments.length).toEqual(1); + expect(segmentPairs[2].segments[0].id).toEqual('view-three/12345'); + expect(segmentPairs[2].segments[0].data.itemId).toEqual('12345'); + }); + + it('it should return a data-driven set of segments with a root tabs and multiple tabs segments in the middle', () => { + const link1 = { component: MockView1, name: 'viewone', segment: 'view-one/paramOne/:paramOne/paramTwo/:paramTwo' }; + const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two/user/:userId' }; + const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three/:itemId' }; + const link4 = { component: MockView1, name: 'viewfour', segment: 'view-four' }; + + const links = normalizeLinks([link1, link2, link3, link4]); + const url = 'tabs/app/tab-two/view-two/user/fred/tabs/123/tab-three/view-one/paramOne/taco/paramTwo/burrito/tabs/456/tab-four/view-three/12345'; + const segmentPairs = convertUrlToDehydratedSegments(url, links); + expect(segmentPairs.length).toEqual(3); + expect(segmentPairs[0].segments.length).toEqual(1); + expect(segmentPairs[0].segments[0].id).toEqual('view-two/user/fred'); + expect(segmentPairs[0].segments[0].data.userId).toEqual('fred'); + + expect(segmentPairs[1].segments.length).toEqual(1); + expect(segmentPairs[1].segments[0].id).toEqual('view-one/paramOne/taco/paramTwo/burrito'); + expect(segmentPairs[1].segments[0].data.paramOne).toEqual('taco'); + expect(segmentPairs[1].segments[0].data.paramTwo).toEqual('burrito'); + + expect(segmentPairs[2].segments.length).toEqual(1); + expect(segmentPairs[2].segments[0].id).toEqual('view-three/12345'); + expect(segmentPairs[2].segments[0].data.itemId).toEqual('12345'); + }); + + it('should return a data-driven set of segments where the root tabs is implied but the secondary identifier is actually grabed', () => { + const link1 = { component: MockView1, name: 'viewone', segment: 'view-one/paramOne/:paramOne/paramTwo/:paramTwo' }; + const link2 = { component: MockView1, name: 'viewtwo', segment: 'view-two/user/:userId' }; + const link3 = { component: MockView1, name: 'viewthree', segment: 'view-three/:itemId' }; + const link4 = { component: MockView1, name: 'viewfour', segment: 'view-four' }; + + const links = normalizeLinks([link1, link2, link3, link4]); + const url = 'tab-two/view-two/user/fred/tab-three/view-one/paramOne/taco/paramTwo/burrito/tab-four/view-three/12345'; + + const segmentPairs = convertUrlToDehydratedSegments(url, links); + expect(segmentPairs.length).toEqual(1); + expect(segmentPairs[0].segments.length).toEqual(3); + expect(segmentPairs[0].segments[0].id).toEqual('view-two/user/fred'); + expect(segmentPairs[0].segments[0].name).toEqual('viewtwo'); + expect(segmentPairs[0].segments[0].secondaryId).toEqual('tab-two'); + expect(segmentPairs[0].segments[0].data.userId).toEqual('fred'); + + expect(segmentPairs[0].segments[1].id).toEqual('view-one/paramOne/taco/paramTwo/burrito'); + expect(segmentPairs[0].segments[1].name).toEqual('viewone'); + expect(segmentPairs[0].segments[1].data.paramOne).toEqual('taco'); + expect(segmentPairs[0].segments[1].data.paramTwo).toEqual('burrito'); + expect(segmentPairs[0].segments[1].secondaryId).toEqual('tab-three'); + + expect(segmentPairs[0].segments[2].id).toEqual('view-three/12345'); + expect(segmentPairs[0].segments[2].name).toEqual('viewthree'); + expect(segmentPairs[0].segments[2].data.itemId).toEqual('12345'); + expect(segmentPairs[0].segments[2].secondaryId).toEqual('tab-four'); + }); + + describe('convertUrlToSegments', () => { + it('it should return a vanilla single segment', () => { + + const link1 = { component: MockView1, name: 'login-page', segment: 'login-page' }; + const link2 = { component: MockView1, name: 'settings-page', segment: 'settings-page' }; + const link3 = { component: MockView1, name: 'details-page', segment: 'details-page' }; + + const mockNav = mockNavController(); + serializer._app.registerRootNav(mockNav); + + const links = normalizeLinks([link1, link2, link3]); + const url = 'settings-page'; + + + const segments = convertUrlToSegments(serializer._app, url, links); + expect(segments.length).toEqual(1); + expect(segments[0].type).toEqual('nav'); + expect(segments[0].navId).toEqual(mockNav.id); + }); + }); + }); + var serializer: UrlSerializer; beforeEach(() => { @@ -778,5 +827,5 @@ class NotFound {} function mockSerializer(navLinks?: NavLink[]) { let deepLinkConfig = mockDeepLinkConfig(navLinks); - return new UrlSerializer(deepLinkConfig); + return new UrlSerializer(mockApp(), deepLinkConfig); } diff --git a/src/navigation/url-serializer.ts b/src/navigation/url-serializer.ts index 4addf2617a..e5dd9febfa 100644 --- a/src/navigation/url-serializer.ts +++ b/src/navigation/url-serializer.ts @@ -1,7 +1,8 @@ import { OpaqueToken } from '@angular/core'; +import { App } from '../components/app/app'; import { NavigationContainer } from './navigation-container'; -import { DeepLinkConfig, NavLink, NavSegment } from './nav-util'; +import { DeepLinkConfig, DehydratedSegment, DehydratedSegmentPair, NavGroup, NavLink, NavSegment } from './nav-util'; import { isArray, isBlank, isPresent } from '../util/util'; @@ -11,7 +12,7 @@ import { isArray, isBlank, isPresent } from '../util/util'; export class UrlSerializer { links: NavLink[]; - constructor(config: DeepLinkConfig) { + constructor(public _app: App, config: DeepLinkConfig) { if (config && isArray(config.links)) { this.links = normalizeLinks(config.links); @@ -31,9 +32,7 @@ export class UrlSerializer { // trim off data after ? and # browserUrl = browserUrl.split('?')[0].split('#')[0]; - const navGroupStrings = urlToNavGroupStrings(browserUrl); - const navGroups = navGroupStringtoObjects(navGroupStrings); - return parseUrlParts(navGroups, this.links); + return convertUrlToSegments(this._app, browserUrl, this.links); } @@ -41,7 +40,7 @@ export class UrlSerializer { createSegmentFromName(navContainer: NavigationContainer, nameOrComponent: any): NavSegment { const configLink = this.getLinkFromName(nameOrComponent); if (configLink) { - return this._createSegment({ navId: navContainer.id, secondaryId: navContainer.getSecondaryIdentifier(), type: 'tabs'}, configLink, null); + return this._createSegment(this._app, navContainer, configLink, null); } return null; } @@ -63,9 +62,16 @@ export class UrlSerializer { } const sections = segments.map(segment => { if (segment.type === 'tabs') { - return `/${segment.type}/${segment.navId}/${segment.secondaryId}/${segment.id}`; + if (segment.requiresExplicitNavPrefix) { + return `/${segment.type}/${segment.navId}/${segment.secondaryId}/${segment.id}`; + } + return `/${segment.secondaryId}/${segment.id}`; } - return `/${segment.type}/${segment.navId}/${segment.id}`; + // it's a nav + if (segment.requiresExplicitNavPrefix) { + return `/${segment.type}/${segment.navId}/${segment.id}`; + } + return `/${segment.id}`; }); return sections.join(''); } @@ -73,18 +79,20 @@ export class UrlSerializer { /** * Serializes a component and its data into a NavSegment. */ - serializeComponent(navGroup: NavGroup, component: any, data: any): NavSegment { + serializeComponent(navContainer: NavigationContainer, component: any, data: any): NavSegment { if (component) { const link = findLinkByComponentData(this.links, component, data); if (link) { - return this._createSegment(navGroup, link, data); + return this._createSegment(this._app, navContainer, link, data); } } return null; } - /** @internal */ - _createSegment(navGroup: NavGroup, configLink: NavLink, data: any): NavSegment { + /** + * @internal + */ + _createSegment(app: App, navContainer: NavigationContainer, configLink: NavLink, data: any): NavSegment { let urlParts = configLink.segmentParts; if (isPresent(data)) { @@ -110,6 +118,14 @@ export class UrlSerializer { } } + let requiresExplicitPrefix = true; + if (navContainer.parent) { + requiresExplicitPrefix = navContainer.parent && navContainer.parent.getAllChildNavs().length > 1; + } else { + // if it's a root nav, and there are multiple root navs, we need an explicit prefix + requiresExplicitPrefix = app.getRootNavById(navContainer.id) && app.getRootNavs().length > 1; + } + return { id: urlParts.join('/'), name: configLink.name, @@ -117,9 +133,10 @@ export class UrlSerializer { loadChildren: configLink.loadChildren, data: data, defaultHistory: configLink.defaultHistory, - navId: navGroup.navId, - type: navGroup.type, - secondaryId: navGroup.secondaryId + navId: navContainer.name || navContainer.id, + type: navContainer.getType(), + secondaryId: navContainer.getSecondaryIdentifier(), + requiresExplicitNavPrefix: requiresExplicitPrefix }; } } @@ -141,39 +158,6 @@ export function formatUrlPart(name: string): string { return encodeURIComponent(name); } -export const parseUrlParts = (navGroups: NavGroup[], configLinks: NavLink[]): NavSegment[] => { - const segments: NavSegment[] = []; - for (const link of configLinks) { - for (const navGroup of navGroups) { - if (link.segmentPartsLen === navGroup.segmentPieces.length) { - // check if the segment pieces are a match - let allSegmentsMatch = true; - for (let i = 0; i < navGroup.segmentPieces.length; i++) { - if (!isPartMatch(navGroup.segmentPieces[i], link.segmentParts[i])) { - allSegmentsMatch = false; - break; - } - } - // sweet, we found a match! - if (allSegmentsMatch) { - segments.push({ - id: link.segmentParts.join('/'), - name: link.name, - component: link.component, - loadChildren: link.loadChildren, - data: createMatchedData(navGroup.segmentPieces, link), - defaultHistory: link.defaultHistory, - navId: navGroup.navId, - type: navGroup.type, - secondaryId: navGroup.secondaryId - }); - } - } - } - } - return segments; -}; - export const isPartMatch = (urlPart: string, configLinkPart: string) => { if (isPresent(urlPart) && isPresent(configLinkPart)) { if (configLinkPart.charAt(0) === ':') { @@ -300,8 +284,8 @@ const URL_REPLACE_REG = /\s+|\?|\!|\$|\,|\.|\+|\"|\'|\*|\^|\||\/|\\|\[|\]|#|%|`| */ export const DeepLinkConfigToken = new OpaqueToken('USERLINKS'); -export function setupUrlSerializer(userDeepLinkConfig: any): UrlSerializer { - return new UrlSerializer(userDeepLinkConfig); +export function setupUrlSerializer(app: App, userDeepLinkConfig: any): UrlSerializer { + return new UrlSerializer(app, userDeepLinkConfig); } export function urlToNavGroupStrings(url: string): string[] { @@ -334,20 +318,227 @@ export function navGroupStringtoObjects(navGroupStrings: string[]): NavGroup[] { secondaryId: null, segmentPieces: sections.splice(2) }; + } else if (sections[0] === 'tabs') { + return { + type: 'tabs', + navId: sections[1], + niceId: sections[1], + secondaryId: sections[2], + segmentPieces: sections.splice(3) + }; } return { - type: 'tabs', - navId: sections[1], - niceId: sections[1], - secondaryId: sections[2], - segmentPieces: sections.splice(3) + type: null, + navId: null, + niceId: null, + secondaryId: null, + segmentPieces: sections }; }); } -export interface NavGroup { - type: string; - navId: string; - secondaryId: string; - segmentPieces?: string[]; +export function urlToNavGroupStringsTwo(url: string) { + const tokens = url.split('/'); + const keywordIndexes = []; + for (let i = 0; i < tokens.length; i++) { + if (i !== 0 && (tokens[i] === 'nav' || tokens[i] === 'tabs')) { + keywordIndexes.push(i); + } + } + // append the last index + 1 to the list no matter what + keywordIndexes.push(tokens.length); + const groupings: string[] = []; + let activeKeywordIndex = 0; + let tmpArray: string[] = []; + for (let i = 0; i < tokens.length; i++) { + if (i >= keywordIndexes[activeKeywordIndex]) { + groupings.push(tmpArray.join('/')); + tmpArray = []; + activeKeywordIndex++; + } + tmpArray.push(tokens[i]); + } + // okay, after the loop we've gotta push one more time just to be safe + groupings.push(tmpArray.join('/')); + return groupings; +} + +export function convertUrlToSegments(app: App, url: string, navLinks: NavLink[]): NavSegment[] { + const pairs = convertUrlToDehydratedSegments(url, navLinks); + return hydrateSegmentsWithNav(app, pairs); +} + +export function convertUrlToDehydratedSegments(url: string, navLinks: NavLink[]): DehydratedSegmentPair[] { + const navGroupStrings = urlToNavGroupStringsTwo(url); + const navGroups = navGroupStringtoObjects(navGroupStrings); + return getSegmentsFromNavGroups(navGroups, navLinks); +} + +export function hydrateSegmentsWithNav(app: App, dehydratedSegmentPairs: DehydratedSegmentPair[]) { + const segments: NavSegment[] = []; + for (let i = 0; i < dehydratedSegmentPairs.length; i++) { + let navs = getNavFromNavGroup(dehydratedSegmentPairs[i].navGroup, app); + // okay, cool, let's walk through the segments and hydrate them + for (const dehydratedSegment of dehydratedSegmentPairs[i].segments) { + if (navs.length === 1) { + segments.push(hydrateSegment(dehydratedSegment, navs[0])); + } else if (navs.length > 1 || navs.length <= 0) { + break; + // throw new Error('Invalid URL - could not determine which nav to use'); + } + navs = navs[0].getActiveChildNavs(); + } + } + return segments; +} + +export function getNavFromNavGroup(navGroup: NavGroup, app: App): NavigationContainer[] { + if (navGroup.navId) { + const rootNav = app.getNavByIdOrName(navGroup.navId); + if (rootNav) { + return [rootNav]; + } + return []; + } + // we don't know what nav to use, so just use the root nav. + // if there is more than one root nav, throw an error + return app.getRootNavs(); +} + +/* + * Let's face the facts: Getting a dehydrated segment from the url is really hard + * because we need to do a ton of crazy looping + * the are chunks of a url that are totally irrelevant at this stage, such as the secondary identifier + * stating which tab is selected, etc. + * but is necessary. Ultimately, let's say that we have a url that looks like this + * /the/dog/jumps/high + * We need to check for a segment match this looping pattern + * the + * the dog + * the dog jumps + * the dog jumps high + * dog + * dog jumps + * dog jumps high + * jumps + * jumps high + * high + **/ +export function getSegmentsFromNavGroups(navGroups: NavGroup[], navLinks: NavLink[]) { + const pairs: DehydratedSegmentPair[] = []; + for (const navGroup of navGroups) { + const segments: DehydratedSegment[] = []; + // loop over each nav segment piece + for (let i = 0; i < navGroup.segmentPieces.length; i++) { + // do a second loop the length - i times + let created = false; + for (let j = 0; j < navGroup.segmentPieces.length - i; j++) { + // grab the subsection of the url to match the pattern above + const endIndex = 1 + i + j; + const subsetOfUrl = navGroup.segmentPieces.slice(i, endIndex); + for (const navLink of navLinks) { + const segment = getSegmentsFromUrlPieces(subsetOfUrl, navLink); + if (segment) { + created = true; + // sweet, we found a segment + segments.push(segment); + // now we want to null out the url subsection in the segmentPieces + for (let k = i; k < endIndex; k++) { + navGroup.segmentPieces[k] = null; + } + break; + } + } + } + if (!created && navGroup.segmentPieces[i]) { + // this is very likely a tab's secondary identifier + segments.push({ + id: null, + name: null, + secondaryId: navGroup.segmentPieces[i], + component: null, + loadChildren: null, + data: null, + defaultHistory: null + }); + } + } + + segments.forEach(segment => console.log('segment: ', segment)); + + // okay, this is the lazy persons approach here. + // so here's the deal! Right now if section of the url is not a part of a segment + // it is almost certainly the secondaryId for a tabs component + // basically, knowing the segment for the `tab` itself is good, but we also need to know + // which tab is selected, so we have an identifer in the url that is associated with the tabs component + // telling us which tab is selected. With that in mind, we are going to go through and find the segments with only secondary identifiers, + // and simply add the secondaryId to the next segment, and then remove the empty segment from the list + for (let i = 0; i < segments.length; i++) { + if (segments[i].secondaryId && !segments[i].id && ((i + 1) <= segments.length - 1)) { + segments[i + 1].secondaryId = segments[i].secondaryId; + segments[i] = null; + } + } + const cleanedSegments = segments.filter(segment => !!segment); + // if the nav group has a secondary id, make sure the first segment also has it set + if (navGroup.secondaryId && segments.length) { + cleanedSegments[0].secondaryId = navGroup.secondaryId; + } + + pairs.push({ + navGroup: navGroup, + segments: cleanedSegments + }); + } + return pairs; +} + +export function getSegmentsFromUrlPieces(urlSections: string[], navLink: NavLink): DehydratedSegment { + if (navLink.segmentPartsLen !== urlSections.length) { + return null; + } + for (let i = 0; i < urlSections.length; i++) { + if (!isPartMatch(urlSections[i], navLink.segmentParts[i])) { + // just return an empty array if the part doesn't match + return null; + } + } + return { + id: urlSections.join('/'), + name: navLink.name, + component: navLink.component, + loadChildren: navLink.loadChildren, + data: createMatchedData(urlSections, navLink), + defaultHistory: navLink.defaultHistory + }; +} + +export function hydrateSegment(segment: DehydratedSegment, nav: NavigationContainer) { + const hydratedSegment = Object.assign({}, segment) as NavSegment; + hydratedSegment.type = nav.getType(); + hydratedSegment.navId = nav.id; + // secondaryId is set on an empty dehydrated segment in the case of tabs to identify which tab is selected + hydratedSegment.secondaryId = segment.secondaryId; + return hydratedSegment; +} + +export function getNonHydratedSegmentIfLinkAndUrlMatch(urlChunks: string[], navLink: NavLink): DehydratedSegment { + let allSegmentsMatch = true; + for (let i = 0; i < urlChunks.length; i++) { + if (!isPartMatch(urlChunks[i], navLink.segmentParts[i])) { + allSegmentsMatch = false; + break; + } + } + if (allSegmentsMatch) { + return { + id: navLink.segmentParts.join('/'), + name: navLink.name, + component: navLink.component, + loadChildren: navLink.loadChildren, + data: createMatchedData(urlChunks, navLink), + defaultHistory: navLink.defaultHistory + }; + } + return null; } diff --git a/src/util/mock-providers.ts b/src/util/mock-providers.ts index 8e56f3dcf4..de863aea54 100644 --- a/src/util/mock-providers.ts +++ b/src/util/mock-providers.ts @@ -221,7 +221,7 @@ export function mockContent(): Content { } export function mockZone(): NgZone { - return new NgZone(false); + return new NgZone({enableLongStackTrace: false}); } export function mockChangeDetectorRef(): ChangeDetectorRef { @@ -390,8 +390,8 @@ export function mockComponentRef(): ComponentRef { } export function mockDeepLinker(linkConfig: DeepLinkConfig = null, app?: App) { - let serializer = new UrlSerializer(linkConfig); - + app = app || mockApp(mockConfig(), mockPlatform()); + let serializer = new UrlSerializer(app, linkConfig); let location = mockLocation(); return new DeepLinker(app || mockApp(), serializer, location, null, null); @@ -449,7 +449,7 @@ export function mockOverlayPortal(app: App, config: Config, plt: MockPlatform): let renderer = mockRenderer(); let componentFactoryResolver: any = null; let gestureCtrl = new GestureController(app); - let serializer = new UrlSerializer(null); + let serializer = new UrlSerializer(app, null); let location = mockLocation(); let deepLinker = new DeepLinker(app, serializer, location, null, null);