Skip to content

Commit

Permalink
fix(navigation): navs can have n child navs instead of just one
Browse files Browse the repository at this point in the history
  • Loading branch information
danbucholtz committed Jun 30, 2017
1 parent 04e78d8 commit fce4422
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 60 deletions.
47 changes: 28 additions & 19 deletions src/components/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,18 +208,18 @@ 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()`
*/
getActiveNav(navId?: string): NavControllerBase {
getActiveNavs(navId?: string): NavControllerBase[] {
const portal = this._appRoot._getPortal(Constants.PORTAL_MODAL);
if (portal.length() > 0) {
return <NavControllerBase> findTopNav(portal);
return <NavControllerBase[]> findTopNavs(portal);
}
if (!this._rootNavs || !this._rootNavs.size) {
return null;
return [];
}
if (this._rootNavs.size === 1) {
return <NavControllerBase> findTopNav(this._rootNavs.values().next().value);
return <NavControllerBase[]> findTopNavs(this._rootNavs.values().next().value);
}
return <NavControllerBase> findTopNav(this.getRootNavById(navId));
return <NavControllerBase[]> findTopNavs(this.getRootNavById(navId));
}

getRootNav(): any {
Expand Down Expand Up @@ -256,9 +256,9 @@ export class App {

getActiveNavContainers(): NavigationContainer[] {
// for each root nav container, get it's active nav
const list: NavigationContainer[] = [];
let list: NavigationContainer[] = [];
this._rootNavs.forEach((container: NavigationContainer) => {
list.push(findTopNav(container));
list = list.concat(findTopNavs(container));
});
return list;
}
Expand Down Expand Up @@ -328,15 +328,16 @@ export class App {
let navToPop: NavControllerBase = null;
let mostRecentVC: ViewController = null;
this._rootNavs.forEach((navContainer: NavigationContainer) => {
const activeNav = this.getActiveNav(navContainer.id);
const poppable = getPoppableNav(activeNav);
if (poppable) {
const activeNavs = this.getActiveNavs(navContainer.id);
activeNavs.forEach(activeNav => console.log('navId: ', activeNav.id));
const poppableNavs = activeNavs.map(activeNav => getPoppableNav(activeNav)).filter(nav => !!nav);
poppableNavs.forEach(poppable => {
const topViewController = poppable.last();
if (poppable._isPortal || (topViewController && poppable.length() > 1 && (!mostRecentVC || topViewController._ts >= mostRecentVC._ts))) {
mostRecentVC = topViewController;
navToPop = poppable;
}
}
});
});
if (navToPop) {
return navToPop.pop();
Expand Down Expand Up @@ -419,15 +420,23 @@ function getPoppableNav(nav: NavControllerBase): NavControllerBase {
return getPoppableNav(nav.parent);
}

function findTopNav(nav: NavigationContainer): NavigationContainer {
while (nav) {
const childNav = nav.getActiveChildNav();
if (!childNav) {
break;
}
nav = childNav;
export function findTopNavs(nav: NavigationContainer): NavigationContainer[] {
//console.log('findTopNavs: nav.id: ', nav.id);
let containers: NavigationContainer[] = [];
const childNavs = nav.getActiveChildNavs();
//console.log('findTopNavs: child navs: ', childNavs.length);
if (!childNavs || !childNavs.length) {
//console.log('pushing a nav: ', nav.id);
containers.push(nav);
} else {
childNavs.forEach(childNav => {
//console.log('calling findTopNavs with child: ', childNav.id);
const topNavs = findTopNavs(childNav);
containers = containers.concat(topNavs);
});
}
return nav;
//console.log('returning container: ', containers.length);
return containers;
}

const SKIP_BLURRING = ['INPUT', 'TEXTAREA', 'ION-INPUT', 'ION-TEXTAREA'];
Expand Down
40 changes: 24 additions & 16 deletions src/components/app/test/app.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { App } from '../app';
import { App, findTopNavs } from '../app';
import { ClickBlock } from '../click-block';
import { Config } from '../../../config/config';
import { mockApp, mockConfig, mockElementRef, mockNavController, mockPlatform, MockPlatform, mockRenderer, mockTab, mockTabs, mockView, mockViews } from '../../../util/mock-providers';
Expand Down Expand Up @@ -210,7 +210,7 @@ describe('App', () => {
const view1 = mockView();
mockViews(nav, [view1]);

expect(app.getActiveNav(nav.id)).toBe(nav);
expect(app.getActiveNavs(nav.id)[0]).toBe(nav);
expect(nav.first()).toBe(view1);

app.goBack();
Expand All @@ -233,7 +233,7 @@ describe('App', () => {
const view1 = mockView();
mockViews(nav, [view1]);

expect(app.getActiveNav(nav.id)).toBe(nav);
expect(app.getActiveNavs(nav.id)[0]).toBe(nav);
expect(nav.first()).toBe(view1);

app.goBack();
Expand Down Expand Up @@ -325,13 +325,21 @@ describe('App', () => {

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

expect(app.getActiveNav(nav.id)).toBe(nav3);
const activeNavs = app.getActiveNavs(nav.id);
expect(activeNavs.length).toEqual(2);
expect(activeNavs[0]).toEqual(nav2);
expect(activeNavs[1]).toEqual(nav3);
});

it('should get active NavController when using tabs, nested in a root nav', () => {
Expand All @@ -346,11 +354,11 @@ describe('App', () => {

tab2.setSelected(true);

expect(app.getActiveNav(nav.id)).toBe(tab2);
expect(app.getActiveNavs(nav.id)[0]).toBe(tab2);

tab2.setSelected(false);
tab3.setSelected(true);
expect(app.getActiveNav(nav.id)).toBe(tab3);
expect(app.getActiveNavs(nav.id)[0]).toBe(tab3);
});

it('should get active tab NavController when using tabs, and tabs is the root', () => {
Expand All @@ -362,11 +370,11 @@ describe('App', () => {

tab2.setSelected(true);

expect(app.getActiveNav(tabs.id)).toBe(tab2);
expect(app.getActiveNavs(tabs.id)[0]).toBe(tab2);

tab2.setSelected(false);
tab3.setSelected(true);
expect(app.getActiveNav(tabs.id)).toBe(tab3);
expect(app.getActiveNavs(tabs.id)[0]).toBe(tab3);
});

it('should get active NavController when nested 3 deep', () => {
Expand All @@ -378,7 +386,7 @@ describe('App', () => {
nav1.registerChildNav(nav2);
nav2.registerChildNav(nav3);

expect(app.getActiveNav(nav1.id)).toBe(nav3);
expect(app.getActiveNavs(nav1.id)[0]).toBe(nav3);
});

it('should get active NavController when nested 2 deep', () => {
Expand All @@ -388,15 +396,15 @@ describe('App', () => {

nav1.registerChildNav(nav2);

const activeNav = app.getActiveNav(nav1.id);
const activeNav = app.getActiveNavs(nav1.id)[0];

expect(activeNav).toBe(nav2);
});

it('should get active NavController when only one nav controller', () => {
const nav = mockNavController();
app.registerRootNav(nav);
expect(app.getActiveNav(nav.id)).toBe(nav);
expect(app.getActiveNavs(nav.id)[0]).toBe(nav);
});

it('should set/get the root nav controller', () => {
Expand All @@ -406,9 +414,9 @@ describe('App', () => {
});

it('should not get an active NavController if there is not root set', () => {
const activeNav = app.getActiveNav('');
const activeNav = app.getActiveNavs('');
const rootNav = app.getRootNavById('');
expect(activeNav).toBeFalsy();
expect(activeNav.length).toEqual(0);
expect(rootNav).toBeFalsy();
});

Expand All @@ -425,8 +433,8 @@ describe('App', () => {
rootNavOne.registerChildNav(childNavOne);
rootNavTwo.registerChildNav(childNavTwo);

const activeNavOne = app.getActiveNav(rootNavOne.id);
const activeNavTwo = app.getActiveNav(rootNavTwo.id);
const activeNavOne = app.getActiveNavs(rootNavOne.id)[0];
const activeNavTwo = app.getActiveNavs(rootNavTwo.id)[0];

expect(activeNavOne).toBe(childNavOne);
expect(activeNavTwo).toBe(childNavTwo);
Expand All @@ -439,7 +447,7 @@ describe('App', () => {
const childNavOne = mockNavController();
rootNavOne.registerChildNav(childNavOne);

const result = app.getActiveNav();
const result = app.getActiveNavs()[0];

expect(result).toEqual(childNavOne);
});
Expand Down
11 changes: 6 additions & 5 deletions src/components/tabs/tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -502,8 +502,9 @@ export class Tabs extends Ion implements AfterViewInit, RootNode, ITabs, Navigat
/**
* @internal
*/
getActiveChildNav(): Tab {
return this.getSelected();
getActiveChildNavs(): Tab[] {
const selected = this.getSelected();
return selected ? [selected] : [];
}

/**
Expand Down Expand Up @@ -624,9 +625,9 @@ export class Tabs extends Ion implements AfterViewInit, RootNode, ITabs, Navigat
* @private
*/
getSecondaryIdentifier(): string {
const tab = this.getActiveChildNav();
if (tab) {
return this._linker._getTabSelector(tab);
const tabs = this.getActiveChildNavs();
if (tabs && tabs.length) {
return this._linker._getTabSelector(tabs[0]);
}
return '';
}
Expand Down
16 changes: 9 additions & 7 deletions src/navigation/deep-linker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,16 @@ export class DeepLinker {
getSegmentFromTab(navContainer: NavigationContainer, component?: any, data?: any): NavSegment {
if (navContainer && navContainer.parent) {
const tabsNavContainer = navContainer.parent as NavigationContainer;
const activeChildNav = tabsNavContainer.getActiveChildNav();
// since it's a tabs, we know that the activeChildNav is a tab
const viewController = (activeChildNav as NavController).getActive(true);
if (viewController) {
component = viewController.component;
data = viewController.data;
const activeChildNavs = tabsNavContainer.getActiveChildNavs();
if (activeChildNavs && activeChildNavs.length) {
const activeChildNav = activeChildNavs[0];
const viewController = (activeChildNav as NavController).getActive(true);
if (viewController) {
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({ navId: tabsNavContainer.name || tabsNavContainer.id, secondaryId: tabsNavContainer.getSecondaryIdentifier(), type: 'tabs'}, component, data);
}
}

Expand Down
20 changes: 10 additions & 10 deletions src/navigation/nav-controller-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { TransitionController } from '../transitions/transition-controller';
*/
export class NavControllerBase extends Ion implements NavController {

_child: NavigationContainer;
_children: NavigationContainer[];
_ids: number = -1;
_init = false;
_isPortal: boolean;
Expand Down Expand Up @@ -77,7 +77,7 @@ export class NavControllerBase extends Ion implements NavController {
super(config, elementRef, renderer);

this._sbEnabled = config.getBoolean('swipeBackEnabled');

this._children = [];
this.id = 'n' + (++ctrlIds);
}

Expand Down Expand Up @@ -755,7 +755,7 @@ export class NavControllerBase extends Ion implements NavController {
// TODO - probably could be resolved in a better way
this.setTransitioning(false);

if (!this.hasChild() && opts.updateUrl !== false) {
if (!this.hasChildren() && opts.updateUrl !== false) {
// notify deep linker of the nav change
// if a direction was provided and should update url
this._linker.navChange(this.id, opts.direction);
Expand Down Expand Up @@ -960,20 +960,20 @@ export class NavControllerBase extends Ion implements NavController {
}
}

hasChild(): boolean {
return !!this._child;
hasChildren(): boolean {
return this._children && this._children.length > 0;
}

getActiveChildNav(): any {
return this._child;
getActiveChildNavs(): any[] {
return this._children;
}

registerChildNav(container: NavigationContainer) {
this._child = container;
this._children.push(container);
}

unregisterChildNav(nav: any) {
this._child = null;
this._children = this._children.filter(child => child !== nav);
}

destroy() {
Expand Down Expand Up @@ -1048,7 +1048,7 @@ export class NavControllerBase extends Ion implements NavController {
canSwipeBack(): boolean {
return (this._sbEnabled &&
!this._isPortal &&
!this._child &&
!this._children.length &&
!this.isTransitioning() &&
this._app.isEnabled() &&
this.canGoBack());
Expand Down
2 changes: 1 addition & 1 deletion src/navigation/nav-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ export abstract class NavController implements NavigationContainer {
/**
* Returns the active child navigation.
*/
abstract getActiveChildNav(): any;
abstract getActiveChildNavs(): any[];

/**
* Returns if the nav controller is actively transitioning or not.
Expand Down
2 changes: 1 addition & 1 deletion src/navigation/navigation-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export interface NavigationContainer {
id: string;
name: string;
parent: NavController;
getActiveChildNav(): NavigationContainer;
getActiveChildNavs(): NavigationContainer[];
getType(): string;
getSecondaryIdentifier(): string;
}
2 changes: 1 addition & 1 deletion src/navigation/test/nav-controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1128,7 +1128,7 @@ describe('NavController', () => {

it('should not swipe back if it has a child nav', () => {
nav._sbEnabled = true;
nav._child = mockNavController();
nav.registerChildNav(mockNavController());

const view1 = mockView();
const view2 = mockView();
Expand Down

0 comments on commit fce4422

Please sign in to comment.