Compare commits

..

21 Commits

Author SHA1 Message Date
Dan Bucholtz
584afd040f fix(navigation): fix null pointer exceptions that would occur when destroying a nav controller while its transitioning 2017-07-26 00:42:28 -05:00
Dan Bucholtz
de0f9d5f28 fix(nav): make call to setPages return the promise so if it rejects it doesn't get lost 2017-07-25 23:57:04 -05:00
Dan Bucholtz
4596dbe5c0 fix(navigation): account for race conditions in developer's code 2017-07-25 13:27:21 -05:00
Mike Hartington
400aa549d4 feat(generators): refactor generators
* feat(generators): update templates

* feat(generators): add pipeName to templates
2017-07-25 11:28:13 -04:00
Dan Bucholtz
add0c4ecfe fix(navigation): fix bug where that occurred when tab identifier and segment had the exact same string 2017-07-24 17:03:36 -05:00
Dan Bucholtz
519d657e6e chore(karma): fix duplicate console.log issue 2017-07-24 17:02:57 -05:00
Dan Bucholtz
a8ceee467b fix(navigation): reduce urls to minimum set of fields
* wip

* simple-nav, simple-tabs, simple-nav-then-tabs, simple-nested-navs all pass w00t w00t

* updates

* fix tests

* update test
2017-07-21 23:47:45 -05:00
pwespi
97f9522110 fix(list): remove margin of MD buttons in ion-item-options (#12263) 2017-07-21 12:48:38 -04:00
Samuel Goodell
961bfc3ebb chore(docs): add anchor links to documentation sub-sections (#12386)
* chore(docs): add anchor links to documentation sub-sections

* chore(docs): make entire section headings clickable as anchors
2017-07-18 15:16:11 -05:00
Dan Bucholtz
5b9fe5e81a chore(changelog): 3.5.3 release 2017-07-14 12:25:46 -05:00
Dan Bucholtz
c4e7552b56 chore(dependencies): update the lock file 2017-07-14 12:03:16 -05:00
Dan Bucholtz
cec718c6c7 chore(dependencies): removing tsc-wrapped since angular explicitely says not to do this 2017-07-14 12:00:48 -05:00
Dan Bucholtz
ab511c4744 chore(dependencies): add angular/tsc-wrapper to package.json to avoid npm issues people are having 2017-07-14 11:55:44 -05:00
Dan Bucholtz
2d49e10da4 fix(app): restore getActiveNav api
restore getActiveNav api
2017-07-14 10:41:45 -05:00
Dan Bucholtz
ce46c24413 chore(changelog): 3.5.2 changelog
3.5.2 changelog
2017-07-14 10:41:45 -05:00
peterpeterparker
f7fce5fa16 chore(changelog): update changelog to save app-scripts as devDependency 2017-07-14 10:02:41 -05:00
B
d23b9f7d49 docs(toast): add import statement in usage (#12278)
Cannot use ToastController without knowing from where to import it
2017-07-13 12:05:56 -04:00
Dan Bucholtz
4c13535416 chore(changelog): additional details about upgrading and updating app-scripts 2017-07-13 10:12:26 -05:00
Dan Bucholtz
889b49f372 chore(changelog): 3.5.2 upgrade instructions 2017-07-13 09:57:08 -05:00
Dan Bucholtz
acb6facc7b chore(changelog): 3.5.2 release
3.5.2 release
2017-07-13 09:52:21 -05:00
Dan Bucholtz
2153940de8 chore(changelog): update to 3.5.1
update to 3.5.1
2017-07-13 09:48:16 -05:00
55 changed files with 1690 additions and 575 deletions

View File

@@ -1,3 +1,83 @@
<a name="3.5.3"></a>
## [3.5.3](https://github.com/ionic-team/ionic/compare/v3.5.2...v3.5.3) (2017-07-14)
## Upgrade Instructions
`ionic-angular@3.5.3` is a drop-in replacement for `3.5.2`. To install it, simply run `npm install ionic-angular@3.5.3 --save --save-exact`.
### Bug Fixes
* **app:** restore getActiveNav api ([2d49e10](https://github.com/ionic-team/ionic/commit/2d49e10))
<a name="3.5.2"></a>
## [3.5.2](https://github.com/ionic-team/ionic/compare/v3.5.1...v3.5.2) (2017-07-13)
## Upgrade Instructions
`ionic-angular@3.5.2` is a drop-in replacement for `3.5.1`. To install it, simply run `npm install ionic-angular@3.5.2 --save --save-exact`.
We have released a new version of our build process for `ionic-angular` apps, `@ionic/app-scripts` in conjunction with this release of `ionic-angular`. While it's not a required update, we recommend it because we have greatly improved the developer experience. Incremental, or update builds while developing are much faster now. We've also added `scope hoisting` for better start-up performance on production builds.
To upgrade to `@ionic/app-scripts`, run the following command:
```
rm -rf node_modules
npm install @ionic/app-scripts@2.0.2 --save-dev --save-exact
```
After installing the update, you'll need to make a minor change to the `src/index.html` file to include a new `<script>` tag for `build/vendor.js`. The reason for this breaking change in `@ionic/app-scripts` is for faster builds. By separating out the `node_modules` dependencies into a `vendor.js` file, the incremental build is faster.
```
...
<body>
<!-- Ionic's root component and where the app will load -->
<ion-app></ion-app>
<!-- cordova.js required for cordova apps -->
<script src="cordova.js"></script>
<!-- The polyfills js is generated during the build process -->
<script src="build/polyfills.js"></script>
<!-- The vendor js is generated during the build process
and includes all files in the node_modules directory -->
<script src="build/vendor.js"></script>
<!-- The bundle js is generated during the build process -->
<script src="build/main.js"></script>
</body>
...
```
If you're customizing `@ionic/app-scripts`, we recommend you review the [changelog](https://github.com/ionic-team/ionic-app-scripts/blob/master/CHANGELOG.md), and update any of your configs accordingly.
## Notes
`3.5.2` is the same as `3.5.1`. We had a small publishing error.
### Bug Fixes
* **navigation:** fix swipe-to-go-back ([04e78d8](https://github.com/ionic-team/ionic/commit/04e78d8))
* **navigation:** mark as not transitioning on success in addition to '_transitionFinish', provide no ([48b3243](https://github.com/ionic-team/ionic/commit/48b3243))
* **navigation:** navs can have n child navs instead of just one ([fce4422](https://github.com/ionic-team/ionic/commit/fce4422))
* **navigation:** restore getActiveChildNav method to maintain old API, add deprecation notice ([d22d77b](https://github.com/ionic-team/ionic/commit/d22d77b))
* **navigation:** ts2.4 compatibility ([08be9dc](https://github.com/ionic-team/ionic/commit/08be9dc)), closes [#12233](https://github.com/ionic-team/ionic/issues/12233) [#12235](https://github.com/ionic-team/ionic/issues/12235)
* **select:** not activated on enter in input field ([ad25cd1](https://github.com/ionic-team/ionic/commit/ad25cd1)), closes [#12202](https://github.com/ionic-team/ionic/issues/12202)
* **sliding-item:** ionSwipe event is fired ([#12157](https://github.com/ionic-team/ionic/issues/12157)) ([b5aa304](https://github.com/ionic-team/ionic/commit/b5aa304)), closes [#12146](https://github.com/ionic-team/ionic/issues/12146)
* **tabs:** have tabs behavior match nav when navigating back/forth via the url ([3f39e14](https://github.com/ionic-team/ionic/commit/3f39e14))
### Features
* **navigation:** support for named ion-nav/ion-tabs to improve url in the short term ([486bff0](https://github.com/ionic-team/ionic/commit/486bff0))
<a name="3.5.1"></a>
## [3.5.1](https://github.com/ionic-team/ionic/compare/v3.5.0...v3.5.1) (2017-07-13)
See the [3.5.2](https://github.com/ionic-team/ionic/blob/master/CHANGELOG.md#352-2017-07-13) changelog. We had a publishing error here.
<a name="3.5.0"></a>
# [3.5.0](https://github.com/ionic-team/ionic/compare/v3.4.2...v3.5.0) (2017-06-28)

68
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "ionic2",
"version": "3.5.0",
"version": "3.5.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -249,7 +249,7 @@
"integrity": "sha1-Co7eMJzg69ui8FNOV0aSUR3XHrY=",
"dev": true,
"requires": {
"@types/node": "6.0.81"
"@types/node": "6.0.83"
}
},
"@types/del": {
@@ -267,7 +267,7 @@
"integrity": "sha512-b7mVHoURu1xaP/V6xw1sYwyv9V0EZ7euyi+sdnbnTZxEkAh4/hzPsI6Eflq+ZzHQ/Tgl7l16Jz+0oz8F46MLnA==",
"dev": true,
"requires": {
"@types/node": "6.0.81"
"@types/node": "6.0.83"
}
},
"@types/fs-extra": {
@@ -276,7 +276,7 @@
"integrity": "sha1-GV8RvNmhuX2eQSxrZombVFRxofc=",
"dev": true,
"requires": {
"@types/node": "6.0.81"
"@types/node": "6.0.83"
}
},
"@types/glob": {
@@ -286,7 +286,7 @@
"dev": true,
"requires": {
"@types/minimatch": "2.0.29",
"@types/node": "6.0.81"
"@types/node": "6.0.83"
}
},
"@types/gulp": {
@@ -295,7 +295,7 @@
"integrity": "sha1-g8WcaBzCM9Hsf4LSaVVVZvoTMVY=",
"dev": true,
"requires": {
"@types/node": "6.0.81",
"@types/node": "6.0.83",
"@types/orchestrator": "0.3.0",
"@types/vinyl": "2.0.0"
}
@@ -306,7 +306,7 @@
"integrity": "sha1-P+Cy9g/QR+/1gijqE5TTXV30a3E=",
"dev": true,
"requires": {
"@types/node": "6.0.81"
"@types/node": "6.0.83"
}
},
"@types/hammerjs": {
@@ -333,7 +333,7 @@
"integrity": "sha1-y1UumCbKPAjZ5lSd6ezY8VQX8G0=",
"dev": true,
"requires": {
"@types/node": "6.0.81"
"@types/node": "6.0.83"
}
},
"@types/mime": {
@@ -355,9 +355,9 @@
"dev": true
},
"@types/node": {
"version": "6.0.81",
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.81.tgz",
"integrity": "sha512-KdtXOH8l9O2wwOOX+swjbFx+YW/RJFfI14o6S50+Zy79FK1WFGkzFdDsiuNjrG5L6FaBSKpKzSpWgTvXurbbYg==",
"version": "6.0.83",
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.83.tgz",
"integrity": "sha512-Q92+tkWnX7nmT0ZG+/wFxzJr+idr00T12MgsY3p0sZIu8nfvYF8i5pbY3BVZw6ad6yS2MLF71sfMr+ySatc2Gw==",
"dev": true
},
"@types/orchestrator": {
@@ -366,7 +366,7 @@
"integrity": "sha1-v4ShaZyTMNT+ic2BJj6PwJ+zKXg=",
"dev": true,
"requires": {
"@types/node": "6.0.81",
"@types/node": "6.0.83",
"@types/q": "0.0.35"
}
},
@@ -392,7 +392,7 @@
"dev": true,
"requires": {
"@types/gulp": "3.8.32",
"@types/node": "6.0.81"
"@types/node": "6.0.83"
}
},
"@types/selenium-webdriver": {
@@ -429,7 +429,7 @@
"integrity": "sha1-abK91NNhjIoSY7i6p2ObLDDtgn4=",
"dev": true,
"requires": {
"@types/node": "6.0.81"
"@types/node": "6.0.83"
}
},
"@types/vinyl": {
@@ -438,7 +438,7 @@
"integrity": "sha1-/SE79/QTbd4h/hiVUAsSwYb4wmg=",
"dev": true,
"requires": {
"@types/node": "6.0.81"
"@types/node": "6.0.83"
}
},
"abbrev": {
@@ -842,7 +842,7 @@
"dev": true,
"requires": {
"browserslist": "2.1.5",
"caniuse-lite": "1.0.30000700",
"caniuse-lite": "1.0.30000701",
"normalize-range": "0.1.2",
"num2fraction": "1.2.2",
"postcss": "6.0.6",
@@ -2198,7 +2198,7 @@
"integrity": "sha1-6IJVDfPRzW1IHBo+ADjyuvE6RxE=",
"dev": true,
"requires": {
"caniuse-lite": "1.0.30000700",
"caniuse-lite": "1.0.30000701",
"electron-to-chromium": "1.3.15"
}
},
@@ -2311,15 +2311,15 @@
}
},
"caniuse-db": {
"version": "1.0.30000700",
"resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000700.tgz",
"integrity": "sha1-l8/Eg4Ze6oV33Ho2dJKbmr9VMJU=",
"version": "1.0.30000701",
"resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000701.tgz",
"integrity": "sha1-LjKwaZO/Pb2QtD2T8E4m0Rr93Lo=",
"dev": true
},
"caniuse-lite": {
"version": "1.0.30000700",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000700.tgz",
"integrity": "sha1-YISHHsdcb6YjJ96XYiUU+V2dsmo=",
"version": "1.0.30000701",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000701.tgz",
"integrity": "sha1-nWc89rdNyz1cIdITF2sBGsakW6o=",
"dev": true
},
"canonical-path": {
@@ -2341,9 +2341,9 @@
"dev": true
},
"catharsis": {
"version": "0.8.8",
"resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.8.tgz",
"integrity": "sha1-aTR59DqsVJ2Aa9c+kkzQ2USVGgY=",
"version": "0.8.9",
"resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.9.tgz",
"integrity": "sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=",
"dev": true,
"requires": {
"underscore-contrib": "0.3.0"
@@ -3489,7 +3489,7 @@
"dev": true,
"requires": {
"canonical-path": "0.0.2",
"catharsis": "0.8.8",
"catharsis": "0.8.9",
"change-case": "3.0.0",
"dgeni": "0.4.9",
"espree": "2.2.5",
@@ -6219,7 +6219,7 @@
"dev": true,
"requires": {
"browserslist": "1.7.7",
"caniuse-db": "1.0.30000700",
"caniuse-db": "1.0.30000701",
"normalize-range": "0.1.2",
"num2fraction": "1.2.2",
"postcss": "5.2.17",
@@ -6232,7 +6232,7 @@
"integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=",
"dev": true,
"requires": {
"caniuse-db": "1.0.30000700",
"caniuse-db": "1.0.30000701",
"electron-to-chromium": "1.3.15"
}
}
@@ -7711,7 +7711,7 @@
"dev": true,
"requires": {
"camel-case": "3.0.0",
"clean-css": "4.1.6",
"clean-css": "4.1.7",
"commander": "2.9.0",
"he": "1.1.1",
"ncname": "1.0.0",
@@ -7721,9 +7721,9 @@
},
"dependencies": {
"clean-css": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.6.tgz",
"integrity": "sha1-Wke+tSaZTLT3vzYYilXtO0VSjws=",
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.7.tgz",
"integrity": "sha1-ua6k+FZ5iJzz6ui0A0nsTr390DI=",
"dev": true,
"requires": {
"source-map": "0.5.6"
@@ -10936,7 +10936,7 @@
"integrity": "sha1-myIXQXCaTGLVzVPGqt1UpxE36V8=",
"dev": true,
"requires": {
"@types/node": "6.0.81",
"@types/node": "6.0.83",
"@types/q": "0.0.32",
"@types/selenium-webdriver": "2.53.42",
"blocking-proxy": "0.0.5",

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "ionic2",
"version": "3.5.0",
"version": "3.5.3",
"description": "A powerful framework for building mobile and progressive web apps with JavaScript and Angular",
"keywords": [
"ionic",

View File

@@ -29,6 +29,14 @@ module.exports = function jekyll(renderDocsProcessor) {
if (docs[i].href) {
docs[i].href = doc.href.replace('content/', '');
}
if (docs[i].description) {
docs[i].description = docs[i].description.replace(/(\#\#\#).+/g, (section) => {
const title = section.replace(/^(\#+\s?)/, '');
const segment = title.replace(/[^a-zA-Z0-9]+/g, '-').toLowerCase();
return `\n<h3><a class="anchor" name="${segment}" href="#${segment}">${title}</a></h3>\n`;
});
}
});
docs.push({

View File

@@ -242,7 +242,7 @@ Improve this doc
<!-- @usage tag -->
<@ if doc.usage @>
<h2><a class="anchor" name="usage" href="#usage"></a>Usage</h2>
<h2><a class="anchor" name="usage" href="#usage">Usage</a></h2>
<@ block usage @>
<$ doc.usage | marked $>
<@ endblock @>
@@ -250,7 +250,7 @@ Improve this doc
<!-- @property tags -->
<@ if doc.properties @>
<h2><a class="anchor" name="attributes" href="#attributes"></a>Attributes:</h2>
<h2><a class="anchor" name="attributes" href="#attributes">Attributes</a></h2>
<table class="table" style="margin:0;">
<thead>
<tr>
@@ -293,10 +293,10 @@ Improve this doc
<@- if doc.statics.length -@>
<h2><a class="anchor" name="static-members" href="#static-members"></a>Static Members</h2>
<h2><a class="anchor" name="static-members" href="#static-members">Static Members</a></h2>
<@- for method in doc.statics @><@ if not method.internal @>
<div id="<$ method.name $>"></div>
<h3><a class="anchor" name="<$ method.name $>" href="#<$ method.name $>"></a><$ functionSyntax(method) $></h3>
<h3><a class="anchor" name="<$ method.name $>" href="#<$ method.name $>"><$ functionSyntax(method) $></a></h3>
<$ method.description $>
@@ -327,14 +327,15 @@ Improve this doc
<!-- instance methods on the class -->
<@- if doc.members and doc.members.length @>
<h2><a class="anchor" name="instance-members" href="#instance-members"></a>Instance Members</h2>
<h2><a class="anchor" name="instance-members" href="#instance-members">Instance Members</a></h2>
<@- for method in doc.members @>
<div id="<$ method.name $>"></div>
<h3>
<a class="anchor" name="<$ method.name $>" href="#<$ method.name $>"></a>
<a class="anchor" name="<$ method.name $>" href="#<$ method.name $>">
<$ functionSyntax(method) $>
</a>
</h3>
<$ method.description $>
@@ -366,26 +367,26 @@ Improve this doc
<@- if doc.inputs and doc.inputs.length @>
<!-- input methods on the class -->
<h2><a class="anchor" name="input-properties" href="#input-properties"></a>Input Properties</h2>
<h2><a class="anchor" name="input-properties" href="#input-properties">Input Properties</a></h2>
<$ inputTable(doc.inputs) $>
<@- endif -@>
<@- if doc.outputs and doc.outputs.length @>
<!-- output events on the class -->
<h2><a class="anchor" name="output-events" href="#output-events"></a>Output Events</h2>
<h2><a class="anchor" name="output-events" href="#output-events">Output Events</a></h2>
<$ outputTable(doc.outputs) $>
<@- endif -@>
<@ block advanced @>
<@- if doc.advanced -@>
<h2><a class="anchor" name="advanced" href="#advanced"></a>Advanced</h2>
<h2><a class="anchor" name="advanced" href="#advanced">Advanced</a></h2>
<$ doc.advanced | marked $>
<@- endif -@>
<@ endblock @>
<@ if doc.sassVariables @>
<h2 id="sass-variable-header"><a class="anchor" name="sass-variables" href="#sass-variables"></a>Sass Variables</h2>
<h2 id="sass-variable-header"><a class="anchor" name="sass-variables" href="#sass-variables">Sass Variables</a></h2>
<$ sassTable(doc.sassVariables) $>
<@ endif @>
@@ -393,7 +394,7 @@ Improve this doc
<!-- related link -->
<@- if doc.see @>
<h2><a class="anchor" name="related" href="#related"></a>Related</h2>
<h2><a class="anchor" name="related" href="#related">Related</a></h2>
<@ for s in doc.see @>
<$ s | safe $> <@- if not loop.last @>,<@- endif -@>
<@- endfor -@>

View File

@@ -46,7 +46,7 @@ export function config(config) {
'dist/ionic-angular/umd/**/!(*spec).js': ['coverage'],
'dist/ionic-angular/**/*.js': ['sourcemap']
},
reporters: ['dots', 'coverage', 'spec'],
reporters: ['coverage', 'spec'],
specReporter: {
maxLogLines: 5, // limit number of lines logged per test
suppressErrorSummary: true, // do not print error summary

View File

@@ -9,8 +9,5 @@ import { $CLASSNAME } from './$FILENAME';
imports: [
IonicPageModule.forChild($CLASSNAME),
],
exports: [
$CLASSNAME
]
})
export class $CLASSNAMEModule {}

View File

@@ -1,5 +1,5 @@
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
$IMPORTSTATEMENT
/**
* Generated class for the $CLASSNAME page.
@@ -7,7 +7,7 @@ import { NavController, NavParams } from 'ionic-angular';
* See http://ionicframework.com/docs/components/#navigation for more info
* on Ionic pages and navigation.
*/
$IONICPAGE
@Component({
selector: 'page-$FILENAME',
templateUrl: '$FILENAME.html',

View File

@@ -7,7 +7,7 @@ import { Pipe, PipeTransform } from '@angular/core';
* Angular Pipes.
*/
@Pipe({
name: '$FILENAME',
name: '$PIPENAME',
})
export class $CLASSNAME implements PipeTransform {
/**

View File

@@ -1,5 +1,5 @@
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
$TABS_IMPORTSTATEMENT
/**
* Generated class for the $CLASSNAME tabs.
@@ -7,6 +7,7 @@ import { NavController } from 'ionic-angular';
* See https://angular.io/docs/ts/latest/guide/dependency-injection.html for
* more info on providers and Angular DI.
*/
$IONICPAGE
@Component({
selector: 'page-$FILENAME',
templateUrl: '$FILENAME.html'

View File

@@ -206,9 +206,21 @@ export class App {
}
/**
* @return {NavController} Returns the active NavController. Using this method is preferred when we need access to the top-level navigation controller while on the outside views and handlers like `registerBackButtonAction()`
* @return {NavController} Returns the first Active Nav Controller from the list. This method is deprecated
*/
getActiveNavs(navId?: string): NavControllerBase[] {
getActiveNav(): NavControllerBase {
console.warn('(getActiveNav) is deprecated and will be removed in the next major release. Use getActiveNavs instead.');
const navs = this.getActiveNavs();
if (navs && navs.length) {
return navs[0];
}
return null;
}
/**
* @return {NavController[]} Returns the active NavControllers. Using this method is preferred when we need access to the top-level navigation controller while on the outside views and handlers like `registerBackButtonAction()`
*/
getActiveNavs(rootNavId?: string): NavControllerBase[] {
const portal = this._appRoot._getPortal(Constants.PORTAL_MODAL);
if (portal.length() > 0) {
return <NavControllerBase[]> findTopNavs(portal);
@@ -219,7 +231,16 @@ export class App {
if (this._rootNavs.size === 1) {
return <NavControllerBase[]> findTopNavs(this._rootNavs.values().next().value);
}
return <NavControllerBase[]> findTopNavs(this.getRootNavById(navId));
if (rootNavId) {
return <NavControllerBase[]> findTopNavs(this._rootNavs.get(rootNavId));
}
// fallback to just using all root names
let activeNavs: NavigationContainer[] = [];
this._rootNavs.forEach(nav => {
const topNavs = findTopNavs(nav);
activeNavs = activeNavs.concat(topNavs);
});
return <NavControllerBase[]> activeNavs;
}
getRootNav(): any {
@@ -398,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) {
@@ -434,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;

View File

@@ -312,7 +312,7 @@ describe('App', () => {
});
});
describe('getActiveNav', () => {
describe('getActiveNavs', () => {
it('should get active NavController when using tabs with nested nav', () => {
const nav = mockNavController();
@@ -340,6 +340,11 @@ describe('App', () => {
expect(activeNavs.length).toEqual(2);
expect(activeNavs[0]).toEqual(nav2);
expect(activeNavs[1]).toEqual(nav3);
const activeNavsTwo = app.getActiveNavs();
expect(activeNavsTwo.length).toEqual(2);
expect(activeNavsTwo[0]).toEqual(nav2);
expect(activeNavsTwo[1]).toEqual(nav3);
});
it('should get active NavController when using tabs, nested in a root nav', () => {
@@ -355,10 +360,12 @@ describe('App', () => {
tab2.setSelected(true);
expect(app.getActiveNavs(nav.id)[0]).toBe(tab2);
expect(app.getActiveNavs()[0]).toBe(tab2);
tab2.setSelected(false);
tab3.setSelected(true);
expect(app.getActiveNavs(nav.id)[0]).toBe(tab3);
expect(app.getActiveNavs()[0]).toBe(tab3);
});
it('should get active tab NavController when using tabs, and tabs is the root', () => {
@@ -371,10 +378,12 @@ describe('App', () => {
tab2.setSelected(true);
expect(app.getActiveNavs(tabs.id)[0]).toBe(tab2);
expect(app.getActiveNavs()[0]).toBe(tab2);
tab2.setSelected(false);
tab3.setSelected(true);
expect(app.getActiveNavs(tabs.id)[0]).toBe(tab3);
expect(app.getActiveNavs()[0]).toBe(tab3);
});
it('should get active NavController when nested 3 deep', () => {
@@ -387,6 +396,8 @@ describe('App', () => {
nav2.registerChildNav(nav3);
expect(app.getActiveNavs(nav1.id)[0]).toBe(nav3);
expect(app.getActiveNavs()[0]).toBe(nav3);
expect(app.getActiveNavs().length).toBe(1);
});
it('should get active NavController when nested 2 deep', () => {
@@ -399,12 +410,15 @@ describe('App', () => {
const activeNav = app.getActiveNavs(nav1.id)[0];
expect(activeNav).toBe(nav2);
expect(app.getActiveNavs()[0]).toBe(nav2);
});
it('should get active NavController when only one nav controller', () => {
const nav = mockNavController();
app.registerRootNav(nav);
expect(app.getActiveNavs(nav.id)[0]).toBe(nav);
expect(app.getActiveNavs()[0]).toBe(nav);
});
it('should set/get the root nav controller', () => {
@@ -414,9 +428,9 @@ describe('App', () => {
});
it('should not get an active NavController if there is not root set', () => {
const activeNav = app.getActiveNavs('');
const activeNavs = app.getActiveNavs();
const rootNav = app.getRootNavById('');
expect(activeNav.length).toEqual(0);
expect(activeNavs.length).toEqual(0);
expect(rootNav).toBeFalsy();
});
@@ -438,6 +452,9 @@ describe('App', () => {
expect(activeNavOne).toBe(childNavOne);
expect(activeNavTwo).toBe(childNavTwo);
expect(app.getActiveNavs()[0]).toBe(childNavOne);
expect(app.getActiveNavs()[1]).toBe(childNavTwo);
});
it('should get the active nav when no id is provided assuming there is one nav', () => {
@@ -451,6 +468,167 @@ describe('App', () => {
expect(result).toEqual(childNavOne);
});
it('should return the all the active navs when there is not an id passed', () => {
const rootNavOne = mockNavController();
app.registerRootNav(rootNavOne);
const rootNavTwo = mockNavController();
app.registerRootNav(rootNavTwo);
const childNavOne = mockNavController();
rootNavOne.registerChildNav(childNavOne);
const childChildNavOne = mockNavController();
childNavOne.registerChildNav(childChildNavOne);
const childNavTwo = mockNavController();
rootNavTwo.registerChildNav(childNavTwo);
const childChildNavTwo = mockNavController();
childNavTwo.registerChildNav(childChildNavTwo);
const results = app.getActiveNavs();
expect(results.length).toEqual(2);
expect(results[0]).toEqual(childChildNavOne);
expect(results[1]).toEqual(childChildNavTwo);
const withIdResultsOne = app.getActiveNavs(rootNavOne.id);
expect(withIdResultsOne.length).toEqual(1);
expect(withIdResultsOne[0]).toEqual(childChildNavOne);
const withIdResultsTwo = app.getActiveNavs(rootNavTwo.id);
expect(withIdResultsTwo.length).toEqual(1);
expect(withIdResultsTwo[0]).toEqual(childChildNavTwo);
});
});
describe('getActiveNav', () => {
it('should get active NavController when using tabs with nested nav', () => {
const nav = mockNavController();
app.registerRootNav(nav);
const tabs = mockTabs();
const tab1 = mockTab(tabs);
const tab2 = mockTab(tabs);
nav.registerChildNav(tabs);
tab2.setSelected(true);
const nav2 = mockNavController();
nav2.name = 'nav2';
const nav3 = mockNavController();
nav3.name = 'nav3';
const nav4 = mockNavController();
nav4.name = 'nav4';
tab1.registerChildNav(nav4);
// tab 2 registers two child navs!!
tab2.registerChildNav(nav2);
tab2.registerChildNav(nav3);
const activeNav = app.getActiveNav();
expect(activeNav).toEqual(nav2);
});
it('should get active NavController when using tabs, nested in a root nav', () => {
const nav = mockNavController();
app.registerRootNav(nav);
const tabs = mockTabs();
mockTab(tabs);
const tab2 = mockTab(tabs);
const tab3 = mockTab(tabs);
nav.registerChildNav(tabs);
tab2.setSelected(true);
expect(app.getActiveNav()).toBe(tab2);
tab2.setSelected(false);
tab3.setSelected(true);
expect(app.getActiveNav()).toBe(tab3);
});
it('should get active tab NavController when using tabs, and tabs is the root', () => {
const tabs = mockTabs();
mockTab(tabs);
const tab2 = mockTab(tabs);
const tab3 = mockTab(tabs);
app.registerRootNav(tabs);
tab2.setSelected(true);
expect(app.getActiveNav()).toBe(tab2);
tab2.setSelected(false);
tab3.setSelected(true);
expect(app.getActiveNav()).toBe(tab3);
});
it('should get active NavController when nested 3 deep', () => {
const nav1 = mockNavController();
const nav2 = mockNavController();
const nav3 = mockNavController();
app.registerRootNav(nav1);
nav1.registerChildNav(nav2);
nav2.registerChildNav(nav3);
expect(app.getActiveNav()).toBe(nav3);
});
it('should get active NavController when nested 2 deep', () => {
const nav1 = mockNavController();
const nav2 = mockNavController();
app.registerRootNav(nav1);
nav1.registerChildNav(nav2);
const activeNav = app.getActiveNav();
expect(activeNav).toBe(nav2);
});
it('should get active NavController when only one nav controller', () => {
const nav = mockNavController();
app.registerRootNav(nav);
expect(app.getActiveNav()).toBe(nav);
});
it('should not get an active NavController if there is not root set', () => {
const activeNav = app.getActiveNav();
expect(activeNav).toBeFalsy();
});
it('should just work when there are multiple active navs', () => {
const rootNavOne = mockNavController();
const rootNavTwo = mockNavController();
app.registerRootNav(rootNavOne);
app.registerRootNav(rootNavTwo);
const childNavOne = mockNavController();
const childNavTwo = mockNavController();
rootNavOne.registerChildNav(childNavOne);
rootNavTwo.registerChildNav(childNavTwo);
const activeNavOne = app.getActiveNav();
expect(activeNavOne).toBe(childNavOne);
});
it('should get the active nav when no id is provided assuming there is one nav', () => {
const rootNavOne = mockNavController();
app.registerRootNav(rootNavOne);
const childNavOne = mockNavController();
rootNavOne.registerChildNav(childNavOne);
const result = app.getActiveNav();
expect(result).toEqual(childNavOne);
});
});
describe('getRootNavs', () => {
@@ -589,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;

View File

@@ -87,14 +87,14 @@ $list-md-header-color: #757575 !default;
}
.list-md ion-item-options .button {
@include margin(1px, 0);
@include margin(0);
@include border-radius(0);
display: inline-flex;
align-items: center;
height: calc(100% - 2px);
height: 100%;
border: 0;

View File

@@ -114,7 +114,7 @@ export class Nav extends NavControllerBase implements AfterViewInit, RootNode, I
if (segment && (segment.component || segment.loadChildren)) {
return this._linker.initViews(segment).then(views => {
this.setPages(views, null, null);
return this.setPages(views, null, null);
});
} else if (this._root) {
// no segment match, so use the root property but don't set the url I guess
@@ -133,6 +133,7 @@ export class Nav extends NavControllerBase implements AfterViewInit, RootNode, I
get root(): any {
return this._root;
}
set root(page: any) {
this._root = page;

View File

@@ -3,8 +3,8 @@ 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-nav [root]="rootOne" name="left"></ion-nav>
<ion-nav [root]="rootTwo" main #content name="right"></ion-nav>
</ion-split-pane>
`

View File

@@ -19,7 +19,6 @@ import { IonicPage, NavController, NavParams } from '../../../../../../..';
<div>
Name: {{paramTwo}}
</div>
<button ion-button (click)="goToNext()">Next</button>
</ion-content>
`
})

View File

@@ -14,10 +14,10 @@ import { IonicPage, NavController, NavParams } from '../../../../../../..';
<ion-content>
Tabs 1 Tab 2 Page 3
<div>
Param One: {{userId}}
Param One: {{paramOne}}
</div>
<div>
Param Two: {{name}}
Param Two: {{paramTwo}}
</div>
</ion-content>
`

View File

@@ -0,0 +1,8 @@
import { Component } from '@angular/core';
@Component({
template: `<ion-nav [root]="root"></ion-nav>`
})
export class AppComponent {
root = 'LoginPage';
}

View File

@@ -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 {}

View File

@@ -0,0 +1,5 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);

View File

@@ -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 { }

View File

@@ -0,0 +1,30 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, } from '../../../../../..';
@IonicPage()
@Component({
template: `
<ion-header>
<ion-navbar>
<ion-title>My Super Cool, multi-pane App</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-grid>
<ion-row>
<ion-col style="height: 1000px; background-color: blue">
<ion-nav root="FirstPage" name="left"></ion-nav>
</ion-col>
<ion-col style="height: 1000px; background-color: green">
<ion-nav root="FourthPage" name="right"></ion-nav>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>
`
})
export class LandingPage {
constructor(public nav: NavController) {
}
}

View File

@@ -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>

View File

@@ -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 { }

View File

@@ -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'});
}
}

View File

@@ -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>

View File

@@ -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 { }

View File

@@ -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'});
}
}

View File

@@ -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>

View File

@@ -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 { }

View File

@@ -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;
}
}

View File

@@ -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 { }

View File

@@ -0,0 +1,24 @@
import { Component } from '@angular/core';
import { IonicPage, NavController, } from '../../../../../..';
@IonicPage()
@Component({
template: `
<ion-header>
<ion-navbar>
<ion-title>Login</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<button ion-button (click)="clickMe()">Login</button>
</ion-content>
`
})
export class LoginPage {
constructor(public nav: NavController) {
}
clickMe() {
this.nav.push('LandingPage');
}
}

View File

@@ -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>

View File

@@ -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 { }

View File

@@ -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'});
}
}

View File

@@ -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>

View File

@@ -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 { }

View File

@@ -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'});
}
}

View File

@@ -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>

View File

@@ -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 { }

View File

@@ -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;
}
}

View File

@@ -507,6 +507,13 @@ export class Tabs extends Ion implements AfterViewInit, RootNode, ITabs, Navigat
return selected ? [selected] : [];
}
/**
* @internal
*/
getAllChildNavs(): any[] {
return this._tabs;
}
/**
* @internal
*/

View File

@@ -37,6 +37,8 @@ import { ToastOptions } from './toast-options';
*
* @usage
* ```ts
* import { ToastController } from 'ionic-angular';
*
* constructor(private toastCtrl: ToastController) {
*
* }

View File

@@ -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 ] },
]
};

View File

@@ -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);
}
}
}

View File

@@ -62,6 +62,7 @@ export class NavControllerBase extends Ion implements NavController {
_viewport: ViewContainerRef;
_views: ViewController[] = [];
_zIndexOffset: number = 0;
_destroyed: boolean;
viewDidLoad: EventEmitter<any> = new EventEmitter();
viewWillEnter: EventEmitter<any> = new EventEmitter();
@@ -102,6 +103,7 @@ export class NavControllerBase extends Ion implements NavController {
this._sbEnabled = config.getBoolean('swipeBackEnabled');
this._children = [];
this.id = 'n' + (++ctrlIds);
this._destroyed = false;
}
push(page: any, params?: any, opts?: NavOptions, done?: () => void): Promise<any> {
@@ -284,7 +286,7 @@ export class NavControllerBase extends Ion implements NavController {
if (ti.done) {
ti.done(false, false, rejectReason);
}
if (ti.reject) {
if (ti.reject && !this._destroyed) {
ti.reject(rejectReason);
} else {
ti.resolve(false);
@@ -594,7 +596,6 @@ export class NavControllerBase extends Ion implements NavController {
}
_transition(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction): Promise<NavResult> {
if (!ti.requiresTransition) {
// transition is not required, so we are already done!
// they're inserting/removing the views somewhere in the middle or
@@ -856,35 +857,38 @@ export class NavControllerBase extends Ion implements NavController {
_cleanup(activeView: ViewController) {
// ok, cleanup time!! Destroy all of the views that are
// INACTIVE and come after the active view
const activeViewIndex = this._views.indexOf(activeView);
const views = this._views;
let reorderZIndexes = false;
let view: ViewController;
let i: number;
// only do this if the views exist, though
if (!this._destroyed) {
const activeViewIndex = this._views.indexOf(activeView);
const views = this._views;
let reorderZIndexes = false;
let view: ViewController;
let i: number;
for (i = views.length - 1; i >= 0; i--) {
view = views[i];
if (i > activeViewIndex) {
// this view comes after the active view
// let's unload it
this._willUnload(view);
this._destroyView(view);
} else if (i < activeViewIndex && !this._isPortal) {
// this view comes before the active view
// and it is not a portal then ensure it is hidden
view._domShow(false, this._renderer);
}
if (view._zIndex <= 0) {
reorderZIndexes = true;
}
}
if (!this._isPortal && reorderZIndexes) {
for (i = 0; i < views.length; i++) {
for (i = views.length - 1; i >= 0; i--) {
view = views[i];
// ******** DOM WRITE ****************
view._setZIndex(view._zIndex + INIT_ZINDEX + 1, this._renderer);
if (i > activeViewIndex) {
// this view comes after the active view
// let's unload it
this._willUnload(view);
this._destroyView(view);
} else if (i < activeViewIndex && !this._isPortal) {
// this view comes before the active view
// and it is not a portal then ensure it is hidden
view._domShow(false, this._renderer);
}
if (view._zIndex <= 0) {
reorderZIndexes = true;
}
}
if (!this._isPortal && reorderZIndexes) {
for (i = 0; i < views.length; i++) {
view = views[i];
// ******** DOM WRITE ****************
view._setZIndex(view._zIndex + INIT_ZINDEX + 1, this._renderer);
}
}
}
}
@@ -991,6 +995,10 @@ export class NavControllerBase extends Ion implements NavController {
return this._children;
}
getAllChildNavs(): any[] {
return this._children;
}
registerChildNav(container: NavigationContainer) {
this._children.push(container);
}
@@ -1017,6 +1025,8 @@ export class NavControllerBase extends Ion implements NavController {
if (this.parent && this.parent.unregisterChildNav) {
this.parent.unregisterChildNav(this);
}
this._destroyed = true;
}
swipeBackStart() {

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -1099,13 +1099,13 @@ describe('NavController', () => {
it('should not crash when destroyed while transitioning', (done) => {
let view1 = mockView(MockView1);
nav.push(view1).then(() => {
fail('it should not succeed');
nav.push(view1).then((succeded: boolean) => {
expect(succeded).toEqual(false);
done();
}).catch((err: any) => {
expect(err).toEqual('nav controller was destroyed');
}).catch(() => {
fail('should never get here');
done();
});
});
nav.destroy();
}, 10000);
});

View File

@@ -1,17 +1,17 @@
import { NavLink, NavSegment } from '../nav-util';
import {
NavGroup,
UrlSerializer,
convertUrlToDehydratedSegments,
convertUrlToSegments,
createMatchedData,
findLinkByComponentData,
formatUrlPart,
isPartMatch,
navGroupStringtoObjects,
normalizeLinks,
parseUrlParts,
urlToNavGroupStrings,
} 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 +22,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 +70,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 +90,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 +102,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);
@@ -103,182 +111,66 @@ describe('UrlSerializer', () => {
});
describe('urlToNavGroupStrings', () => {
it('should return an empty array of groups when there isnt a nav/tabs keyword', () => {
const url = 'some/bogus/url';
const result = urlToNavGroupStrings(url);
expect(result.length).toEqual(0);
});
it('should return a single nav group', () => {
const url = 'nav/23/chunk/of/segment';
const result = urlToNavGroupStrings(url);
expect(result.length).toEqual(1);
expect(result[0]).toEqual(url);
});
it('should return multiple nav groups', () => {
const urlGroupOne = 'nav/1/chunk/of/segment';
const urlGroupTwo = 'nav/2/chunk/two';
const urlGroupThree = 'nav/3/chunk/three';
const url = `${urlGroupOne}/${urlGroupTwo}/${urlGroupThree}`;
const result = urlToNavGroupStrings(url);
expect(result.length).toEqual(3);
expect(result[0]).toEqual(urlGroupOne);
expect(result[1]).toEqual(urlGroupTwo);
expect(result[2]).toEqual(urlGroupThree);
});
it('should return a single tabs group', () => {
const url = 'tabs/1/tab-one/chunk/of/segment';
const result = urlToNavGroupStrings(url);
expect(result.length).toEqual(1);
expect(result[0]).toEqual(url);
});
it('should return multiple tabs groups', () => {
const urlGroupOne = 'tabs/1/tab-one/chunk/of/segment';
const urlGroupTwo = 'tabs/2/tab-one/chunk/two';
const urlGroupThree = 'tabs/3/tab-two/chunk/three';
const url = `${urlGroupOne}/${urlGroupTwo}/${urlGroupThree}`;
const result = urlToNavGroupStrings(url);
expect(result.length).toEqual(3);
expect(result[0]).toEqual(urlGroupOne);
expect(result[1]).toEqual(urlGroupTwo);
expect(result[2]).toEqual(urlGroupThree);
});
it('should return groups when url has both nav and tabs and starts with tabs', () => {
const urlGroupOne = 'tabs/1/tab-one/chunk/of/segment';
const urlGroupTwo = 'nav/2/chunk/two';
const urlGroupThree = 'tabs/3/tab-two/chunk/three';
const urlGroupFour = 'nav/4/chunk/four';
const url = `${urlGroupOne}/${urlGroupTwo}/${urlGroupThree}/${urlGroupFour}`;
const result = urlToNavGroupStrings(url);
expect(result.length).toEqual(4);
expect(result[0]).toEqual(urlGroupOne);
expect(result[1]).toEqual(urlGroupTwo);
expect(result[2]).toEqual(urlGroupThree);
expect(result[3]).toEqual(urlGroupFour);
});
it('should return groups when url has both nav and tabs and starts with nav', () => {
const urlGroupOne = 'nav/1/chunk/of/segment';
const urlGroupTwo = 'tabs/1/tab-one/chunk/of/segment';
const urlGroupThree = 'tabs/3/tab-two/chunk/three';
const urlGroupFour = 'nav/4/chunk/four';
const url = `${urlGroupOne}/${urlGroupTwo}/${urlGroupThree}/${urlGroupFour}`;
const result = urlToNavGroupStrings(url);
expect(result.length).toEqual(4);
expect(result[0]).toEqual(urlGroupOne);
expect(result[1]).toEqual(urlGroupTwo);
expect(result[2]).toEqual(urlGroupThree);
expect(result[3]).toEqual(urlGroupFour);
});
});
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', 'schedule', 'taco/burrito'];
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(5);
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');
});
});
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('parse', () => {
expect(objects[3].type).toEqual(null);
expect(objects[3].navId).toEqual(null);
expect(objects[3].secondaryId).toEqual(null);
expect(objects[3].segmentPieces.length).toEqual(1);
expect(objects[3].segmentPieces[0]).toEqual('schedule');
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[4].type).toEqual(null);
expect(objects[4].navId).toEqual(null);
expect(objects[4].secondaryId).toEqual(null);
expect(objects[4].segmentPieces.length).toEqual(2);
expect(objects[4].segmentPieces[0]).toEqual('taco');
expect(objects[4].segmentPieces[1]).toEqual('burrito');
});
});
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 +234,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 +468,358 @@ describe('UrlSerializer', () => {
});
describe('urlToNavGroupStrings', () => {
it('should get an array with a single piece url back', () => {
const url = 'test';
const result = urlToNavGroupStrings(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 = urlToNavGroupStrings(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 = urlToNavGroupStrings(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 = urlToNavGroupStrings(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 = urlToNavGroupStrings(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 = urlToNavGroupStrings(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 = urlToNavGroupStrings(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 = urlToNavGroupStrings(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 = urlToNavGroupStrings(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');
});
it('should return a segment w/ secondary id even if it has the same name as a router link basic', () => {
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: 'schedule', segment: 'schedule' };
const links = normalizeLinks([link1, link2, link3, link4]);
const url = 'schedule/schedule';
const segmentPairs = convertUrlToDehydratedSegments(url, links);
expect(segmentPairs.length).toEqual(1);
expect(segmentPairs[0].segments.length).toEqual(1);
expect(segmentPairs[0].segments[0].id).toEqual('schedule');
expect(segmentPairs[0].segments[0].name).toEqual('schedule');
expect(segmentPairs[0].segments[0].secondaryId).toEqual('schedule');
});
it('should return a segment for the secondary id even if it has the same name as a router link advanced', () => {
const link1 = { component: MockView1, name: 'about', segment: 'about/:id' };
const link2 = { component: MockView1, name: 'schedule', segment: 'schedule/paramOne/:paramOne/paramTwo/:paramTwo' };
const link3 = { component: MockView1, name: 'ThirdPage', segment: 'third-page' };
const link4 = { component: MockView1, name: 'FourthPage', segment: 'fourth-page/object/:objectId' };
const link5 = { component: MockView1, name: 'taco-page', segment: 'taco-page' };
const links = normalizeLinks([link1, link2, link3, link4, link5]);
const url = 'schedule/schedule/paramOne/hello/paramTwo/goodbye'
+ '/about/about/123'
+ '/tabs/t1/tab-one/third-page'
+ '/tabs/t2/tab-two/fourth-page/object/456'
+ '/fifth-page/taco-page';
const segmentPairs = convertUrlToDehydratedSegments(url, links);
expect(segmentPairs.length).toEqual(3);
expect(segmentPairs[0].segments.length).toEqual(2);
expect(segmentPairs[0].segments[0].id).toEqual('schedule/paramOne/hello/paramTwo/goodbye');
expect(segmentPairs[0].segments[0].name).toEqual('schedule');
expect(segmentPairs[0].segments[0].secondaryId).toEqual('schedule');
expect(segmentPairs[0].segments[0].data.paramOne).toEqual('hello');
expect(segmentPairs[0].segments[0].data.paramTwo).toEqual('goodbye');
expect(segmentPairs[0].segments[1].id).toEqual('about/123');
expect(segmentPairs[0].segments[1].name).toEqual('about');
expect(segmentPairs[0].segments[1].secondaryId).toEqual('about');
expect(segmentPairs[0].segments[1].data.id).toEqual('123');
expect(segmentPairs[1].segments.length).toEqual(1);
expect(segmentPairs[1].navGroup.navId).toEqual('t1');
expect(segmentPairs[1].navGroup.type).toEqual('tabs');
expect(segmentPairs[1].segments[0].id).toEqual('third-page');
expect(segmentPairs[1].segments[0].name).toEqual('ThirdPage');
expect(segmentPairs[1].segments[0].secondaryId).toEqual('tab-one');
expect(segmentPairs[2].segments.length).toEqual(2);
expect(segmentPairs[2].navGroup.navId).toEqual('t2');
expect(segmentPairs[2].navGroup.type).toEqual('tabs');
expect(segmentPairs[2].segments[0].id).toEqual('fourth-page/object/456');
expect(segmentPairs[2].segments[0].name).toEqual('FourthPage');
expect(segmentPairs[2].segments[0].secondaryId).toEqual('tab-two');
expect(segmentPairs[2].segments[0].data.objectId).toEqual('456');
expect(segmentPairs[2].segments[1].id).toEqual('taco-page');
expect(segmentPairs[2].segments[1].name).toEqual('taco-page');
expect(segmentPairs[2].segments[1].secondaryId).toEqual('fifth-page');
});
});
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 +834,5 @@ class NotFound {}
function mockSerializer(navLinks?: NavLink[]) {
let deepLinkConfig = mockDeepLinkConfig(navLinks);
return new UrlSerializer(deepLinkConfig);
return new UrlSerializer(mockApp(), deepLinkConfig);
}

View File

@@ -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,26 +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 urlToNavGroupStrings(url: string): string[] {
const tokens = url.split('/');
const keywordIndexes = [];
for (let i = 0; i < tokens.length; i++) {
if (tokens[i] === 'nav' || tokens[i] === 'tabs') {
keywordIndexes.push(i);
}
}
const groupings: string[] = [];
for (let i = 0; i < keywordIndexes.length; i++) {
const startIndex = keywordIndexes[i];
const endIndex = keywordIndexes[i + 1 < keywordIndexes.length ? i + 1 : keywordIndexes.length];
const group = tokens.slice(startIndex, endIndex);
groupings.push(group.join('/'));
}
return groupings;
export function setupUrlSerializer(app: App, userDeepLinkConfig: any): UrlSerializer {
return new UrlSerializer(app, userDeepLinkConfig);
}
export function navGroupStringtoObjects(navGroupStrings: string[]): NavGroup[] {
@@ -334,20 +300,251 @@ 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 urlToNavGroupStrings(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 = urlToNavGroupStrings(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]));
navs = navs[0].getActiveChildNavs();
} else if (navs.length > 1) {
// this is almost certainly an async race condition bug in userland
// if you're in this state, it would be nice to just bail here
// but alas we must perservere and handle the issue
// the simple solution is to just use the last child
// because that is probably what the user wants anyway
// remember, do not harm, even if it makes our shizzle ugly
segments.push(hydrateSegment(dehydratedSegment, navs[navs.length - 1]));
navs = navs[navs.length - 1].getActiveChildNavs();
} else {
break;
}
}
}
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.
* We look at segment pieces in reverse order to try to build segments
* as in, if you had an array like this
* ['my', 'super', 'cool', 'url']
* we want to look at the pieces in reverse order:
* url
* cool url
* super cool url
* my super cool url
* cool
* super cool
* my super cool
* super
* my super
* my
**/
export function getSegmentsFromNavGroups(navGroups: NavGroup[], navLinks: NavLink[]) {
const pairs: DehydratedSegmentPair[] = [];
const usedNavLinks = new Set<string>();
for (const navGroup of navGroups) {
const segments: DehydratedSegment[] = [];
const segmentPieces = navGroup.segmentPieces.concat([]);
for (let i = segmentPieces.length; i >= 0; i--) {
let created = false;
for (let j = 0; j < i; j++) {
const startIndex = i - j - 1;
const endIndex = i;
const subsetOfUrl = segmentPieces.slice(startIndex, endIndex);
for (const navLink of navLinks) {
if (!usedNavLinks.has(navLink.name)) {
const segment = getSegmentsFromUrlPieces(subsetOfUrl, navLink);
if (segment) {
i = startIndex + 1;
usedNavLinks.add(navLink.name);
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 = startIndex; k < endIndex; k++) {
segmentPieces[k] = null;
}
break;
}
}
}
if (created) {
break;
}
}
if (!created && segmentPieces[i - 1]) {
// this is very likely a tab's secondary identifier
segments.push({
id: null,
name: null,
secondaryId: segmentPieces[i - 1],
component: null,
loadChildren: null,
data: null,
defaultHistory: null
});
}
}
// since we're getting segments in from right-to-left in the url, reverse them
// so they're in the correct order. Also filter out and bogus segments
const orderedSegments = segments.reverse();
// 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 < orderedSegments.length; i++) {
if (orderedSegments[i].secondaryId && !orderedSegments[i].id && ((i + 1) <= orderedSegments.length - 1)) {
orderedSegments[i + 1].secondaryId = orderedSegments[i].secondaryId;
orderedSegments[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.name || 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;
}

View File

@@ -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<any> {
}
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);