Skip to content

Commit

Permalink
feat(navigation): support for named ion-nav/ion-tabs to improve url i…
Browse files Browse the repository at this point in the history
…n the short term

support for named ion-nav/ion-tabs to improve url in the short term
  • Loading branch information
danbucholtz committed Jun 29, 2017
1 parent a7e5fa7 commit 486bff0
Show file tree
Hide file tree
Showing 13 changed files with 425 additions and 92 deletions.
7 changes: 6 additions & 1 deletion src/components/nav/nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export class Nav extends NavControllerBase implements AfterViewInit, RootNode, I
ngAfterViewInit() {
this._hasInit = true;

const segment = this._linker.getSegmentByNavId(this.id);
const segment = this._linker.getSegmentByNavIdOrName(this.id, this.name);

if (segment && (segment.component || segment.loadChildren)) {
return this._linker.initViews(segment).then(views => {
Expand Down Expand Up @@ -146,6 +146,11 @@ export class Nav extends NavControllerBase implements AfterViewInit, RootNode, I
*/
@Input() rootParams: any;

/**
* @input {string} a unique name for the nav element
*/
@Input() name: string;

/**
* @hidden
*/
Expand Down
6 changes: 3 additions & 3 deletions src/components/nav/test/nav.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ describe('Nav', () => {
component: knownComponent
};
const knownViews = {};
spyOn(nav._linker, 'getSegmentByNavId').and.returnValue(knownSegment);
spyOn(nav._linker, 'getSegmentByNavIdOrName').and.returnValue(knownSegment);
spyOn(nav._linker, 'initViews').and.returnValue(Promise.resolve(knownViews));
spyOn(nav, 'setPages');

const promise = nav.ngAfterViewInit();

promise.then(() => {
expect(nav._linker.getSegmentByNavId).toHaveBeenCalledWith(nav.id);
expect(nav._linker.getSegmentByNavIdOrName).toHaveBeenCalledWith(nav.id, nav.name);
expect(nav.setPages).toHaveBeenCalledWith(knownViews, null, null);
done();
}).catch((err: Error) => {
Expand All @@ -45,7 +45,7 @@ describe('Nav', () => {
loadChildren: knownLoadChildren
};
const knownViews = {};
spyOn(nav._linker, 'getSegmentByNavId').and.returnValue(knownSegment);
spyOn(nav._linker, 'getSegmentByNavIdOrName').and.returnValue(knownSegment);
spyOn(nav._linker, 'initViews').and.returnValue(Promise.resolve(knownViews));
spyOn(nav, 'setPages');

Expand Down
2 changes: 1 addition & 1 deletion src/components/nav/test/simple-nav/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component } from '@angular/core';

@Component({
template: `<ion-nav [root]="root"></ion-nav>`
template: `<ion-nav [root]="root" name="default"></ion-nav>`
})
export class AppComponent {
root = 'FirstPage';
Expand Down
2 changes: 1 addition & 1 deletion src/components/nav/test/simple-tabs/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Component } from '@angular/core';

@Component({
template: `
<ion-tabs>
<ion-tabs name="simple">
<ion-tab tabIcon="heart" [root]="tab1" tabTitle="Taco Burrito Enchilada"></ion-tab>
<ion-tab tabIcon="star" [root]="tab2"></ion-tab>
</ion-tabs>
Expand Down
8 changes: 7 additions & 1 deletion src/components/tabs/tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ export class Tabs extends Ion implements AfterViewInit, RootNode, ITabs, Navigat
/** @internal */
_onDestroy = new Subject<void>();


/**
* @input {string} A unique name for the tabs
*/
@Input() name: string;

/**
* @input {number} The default selected tab index when first loaded. If a selected index isn't provided then it will use `0`, the first tab.
*/
Expand Down Expand Up @@ -324,7 +330,7 @@ export class Tabs extends Ion implements AfterViewInit, RootNode, ITabs, Navigat
let selectedIndex = (isBlank(this.selectedIndex) ? 0 : parseInt(<any>this.selectedIndex, 10));

// now see if the deep linker can find a tab index
const tabsSegment = this._linker.getSegmentByNavId(this.id);
const tabsSegment = this._linker.getSegmentByNavIdOrName(this.id, this.name);
if (tabsSegment) {
// we found a segment which probably represents which tab to select
selectedIndex = this._getSelectedTabIndex(tabsSegment.secondaryId, selectedIndex);
Expand Down
136 changes: 68 additions & 68 deletions src/navigation/deep-linker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export class DeepLinker {
})
.filter(pair => !!pair)
.forEach(pair => {
_loadViewForSegment(pair.navContainer, pair.segment, () => {});
this._loadViewForSegment(pair.navContainer, pair.segment, () => {});
});
}
}
Expand Down Expand Up @@ -176,7 +176,7 @@ export class DeepLinker {
data = viewController.data;
}
}
return this._serializer.serializeComponent({ navId: nav.id, secondaryId: null, type: 'nav'}, component, data);
return this._serializer.serializeComponent({ navId: nav.name && nav.name.length ? nav.name : nav.id, secondaryId: null, type: 'nav'}, component, data);
}

getSegmentFromTab(navContainer: NavigationContainer, component?: any, data?: any): NavSegment {
Expand All @@ -189,7 +189,7 @@ export class DeepLinker {
component = viewController.component;
data = viewController.data;
}
return this._serializer.serializeComponent({ navId: tabsNavContainer.id, secondaryId: tabsNavContainer.getSecondaryIdentifier(), type: 'tabs'}, component, data);
return this._serializer.serializeComponent({ navId: tabsNavContainer.name || tabsNavContainer.id, secondaryId: tabsNavContainer.getSecondaryIdentifier(), type: 'tabs'}, component, data);
}
}

Expand Down Expand Up @@ -269,7 +269,7 @@ export class DeepLinker {
const allSegments = this.getCurrentSegments();
if (segment) {
for (let i = 0; i < allSegments.length; i++) {
if (allSegments[i].navId === navContainer.id) {
if (allSegments[i].navId === navContainer.name || allSegments[i].navId === navContainer.id) {
allSegments[i] = segment;
const url = this._serializer.serialize(allSegments);
return prepareExternalUrl ? this._location.prepareExternalUrl(url) : url;
Expand All @@ -285,11 +285,11 @@ export class DeepLinker {
* where it lives in the path and load up the correct component.
* @internal
*/
getSegmentByNavId(navId: string): NavSegment {
getSegmentByNavIdOrName(navId: string, name: string): NavSegment {
const browserUrl = normalizeUrl(this._location.path());
const segments = this._serializer.parse(browserUrl);
for (const segment of segments) {
if (segment.navId === navId) {
if (segment.navId === navId || segment.navId === name) {
return segment;
}
}
Expand Down Expand Up @@ -366,6 +366,67 @@ export class DeepLinker {
return `tab-${tab.index}`;
}

/**
* Using the known Path of Segments, walk down all descendents
* from the root NavController and load each NavController according
* to each Segment. This is usually called after a browser URL and
* Path changes and needs to update all NavControllers to match
* the new browser URL. Because the URL is already known, it will
* not update the browser's URL when transitions have completed.
*
* @internal
*/
_loadViewForSegment(navContainer: NavigationContainer, segment: NavSegment, done: Function) {
if (!segment) {
return done();
}

if (isTabs(navContainer) || (isTab(navContainer) && navContainer.parent)) {
const tabs = <Tabs> <any> (isTabs(navContainer) ? navContainer : navContainer.parent);
const selectedIndex = tabs._getSelectedTabIndex(segment.secondaryId);
const tab = tabs.getByIndex(selectedIndex);
tab._lazyRootFromUrl = segment.name;
tab._lazyRootFromUrlData = segment.data;
tabs.select(tab, {
updateUrl: false,
animate: false
}, true);
return done();
}

const navController = <NavController> <any> navContainer;
const numViews = navController.length() - 1;
// walk backwards to see if the exact view we want to show here
// is already in the stack that we can just pop back to
for (let i = numViews; i >= 0; i--) {
const viewController = navController.getByIndex(i);
if (viewController && (viewController.id === segment.id || viewController.id === segment.name)) {
// hooray! we've already got a view loaded in the stack
// matching the view they wanted to show
if (i === numViews) {
// this is the last view in the stack and it's the same
// as the segment so there's no change needed
return done();
} else {
// it's not the exact view as the end
// let's have this nav go back to this exact view
return navController.popTo(viewController, {
animate: false,
updateUrl: false,
}, done);
}
}
}

// ok, so we don't know about a view that they're navigating to
// so we might as well just call setRoot and make tthe view the first view
// this seems like the least bad option
return navController.setRoot(segment.component || segment.name, segment.data, {
id: segment.id, animate: false, updateUrl: false
}, done);

}

}


Expand All @@ -389,70 +450,9 @@ export function normalizeUrl(browserUrl: string): string {
return browserUrl;
}

/**
* Using the known Path of Segments, walk down all descendents
* from the root NavController and load each NavController according
* to each Segment. This is usually called after a browser URL and
* Path changes and needs to update all NavControllers to match
* the new browser URL. Because the URL is already known, it will
* not update the browser's URL when transitions have completed.
*
* @internal
*/
export function _loadViewForSegment(navContainer: NavigationContainer, segment: NavSegment, done: Function) {
if (!segment) {
return done();
}

if (isTabs(navContainer) || (isTab(navContainer) && navContainer.parent)) {
const tabs = <Tabs> <any> (isTabs(navContainer) ? navContainer : navContainer.parent);
const selectedIndex = tabs._getSelectedTabIndex(segment.secondaryId);
const tab = tabs.getByIndex(selectedIndex);
tab._lazyRootFromUrl = segment.name;
tab._lazyRootFromUrlData = segment.data;
tabs.select(tab, {
updateUrl: false,
animate: false
}, true);
return done();
}

const navController = <NavController> <any> navContainer;
const numViews = navController.length() - 1;
// walk backwards to see if the exact view we want to show here
// is already in the stack that we can just pop back to
for (let i = numViews; i >= 0; i--) {
const viewController = navController.getByIndex(i);
if (viewController && (viewController.id === segment.id || viewController.id === segment.name)) {
// hooray! we've already got a view loaded in the stack
// matching the view they wanted to show
if (i === numViews) {
// this is the last view in the stack and it's the same
// as the segment so there's no change needed
return done();
} else {
// it's not the exact view as the end
// let's have this nav go back to this exact view
return navController.popTo(viewController, {
animate: false,
updateUrl: false,
}, done);
}
}
}

// ok, so we don't know about a view that they're navigating to
// so we might as well just call setRoot and make tthe view the first view
// this seems like the least bad option
return navController.setRoot(segment.component || segment.name, segment.data, {
id: segment.id, animate: false, updateUrl: false
}, done);

}

export function getNavFromTree(nav: NavigationContainer, id: string) {
while (nav) {
if (nav.id === id) {
if (nav.id === id || nav.name === id) {
return nav;
}
nav = nav.parent;
Expand Down
1 change: 1 addition & 0 deletions src/navigation/nav-controller-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class NavControllerBase extends Ion implements NavController {
viewWillUnload: EventEmitter<any> = new EventEmitter();

id: string;
name: string;

@Input()
get swipeBackEnabled(): boolean {
Expand Down
5 changes: 5 additions & 0 deletions src/navigation/nav-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,11 @@ export abstract class NavController implements NavigationContainer {
*/
id: string;

/**
* @hidden
*/
name: string;

/**
* The parent navigation instance. If this is the root nav, then
* it'll be `null`. A `Tab` instance's parent is `Tabs`, otherwise
Expand Down
1 change: 1 addition & 0 deletions src/navigation/navigation-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NavController } from './nav-controller';

export interface NavigationContainer {
id: string;
name: string;
parent: NavController;
getActiveChildNav(): NavigationContainer;
getType(): string;
Expand Down
Loading

0 comments on commit 486bff0

Please sign in to comment.