Skip to content

Commit

Permalink
feat(router): reverse lookup with params
Browse files Browse the repository at this point in the history
  • Loading branch information
manucorporat committed Mar 8, 2018
1 parent d1263a8 commit 3ce8a67
Show file tree
Hide file tree
Showing 17 changed files with 167 additions and 133 deletions.
14 changes: 9 additions & 5 deletions packages/core/src/components/nav/nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { Transition } from './transition';

import iosTransitionAnimation from './animations/ios.transition';
import mdTransitionAnimation from './animations/md.transition';
import { RouteID } from '../router/utils/interfaces';

const TrnsCtrl = new TransitionController();

Expand Down Expand Up @@ -215,16 +216,19 @@ export class NavControllerBase implements NavOutlet {
}

@Method()
getRouteId(): string | null {
const element = this.getContentElement();
if (element) {
return element.tagName;
getRouteId(): RouteID|null {
const active = this.getActive();
if (active) {
return {
id: active.element.tagName,
params: active.data
};
}
return null;
}

@Method()
getContentElement(): HTMLElement {
getContainerEl(): HTMLElement {
const active = this.getActive();
if (active) {
return active.element;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/components/nav/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ boolean
#### getByIndex()


#### getContentElement()
#### getContainerEl()


#### getPrevious()
Expand Down
32 changes: 17 additions & 15 deletions packages/core/src/components/router/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,20 @@ export class Router {
}
console.debug('[IN] nav changed -> update URL');
const { ids, pivot } = this.readNavState();
const { chain, matches } = routerIDsToChain(ids, this.routes);
if (chain.length > matches) {
// readNavState() found a pivot that is not initialized
console.debug('[IN] pivot uninitialized -> write partial nav state');
this.writeNavState(pivot, chain.slice(matches), 0);
const chain = routerIDsToChain(ids, this.routes);
if (chain) {
if (chain.length > ids.length) {
// readNavState() found a pivot that is not initialized
console.debug('[IN] pivot uninitialized -> write partial nav state');
this.writeNavState(pivot, chain.slice(ids.length), 0);
}

const isPop = ev.detail.isPop === true;
const path = chainToPath(chain);
this.writePath(path, isPop);
} else {
console.warn('no matching URL for ', ids.map(i => i.id));
}

const isPop = ev.detail.isPop === true;
const path = chainToPath(chain);
this.writePath(path, isPop);
}

@Method()
Expand All @@ -80,7 +84,7 @@ export class Router {
}
const direction = window.history.state >= this.state ? 1 : -1;
const node = document.querySelector('ion-app');
const {chain} = routerPathToChain(currentPath, this.routes);
const chain = routerPathToChain(currentPath, this.routes);
return this.writeNavState(node, chain, direction);
}

Expand All @@ -100,14 +104,12 @@ export class Router {
}

private writePath(path: string[], isPop: boolean) {
this.state = writePath(window.history, this.base, this.useHash, path, isPop, this.state);
// busyURL is used to prevent reentering in the popstate event
this.state++;
writePath(window.history, this.base, this.useHash, path, isPop, this.state);
}

private readPath(): string[] | null {
return readPath(window.location, this.base, this.useHash);
}

render() {
return <slot/>;
}
}
38 changes: 28 additions & 10 deletions packages/core/src/components/router/test/e2e.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RouteChain } from '../utils/interfaces';
import { RouteChain, RouteID } from '../utils/interfaces';
import { routerIDsToChain, routerPathToChain } from '../utils/matching';
import { mockRouteElement } from './parser.spec';
import { chainToPath, generatePath, parsePath } from '../utils/path';
Expand All @@ -18,19 +18,37 @@ describe('ionic-conference-app', () => {
expect(getRouteIDs('/about', routes)).toEqual(['page-tabs', 'page-about']);
expect(getRouteIDs('/tutorial', routes)).toEqual(['page-tutorial']);

expect(getRoutePaths(['page-tabs', 'tab-schedule', 'page-schedule'], routes)).toEqual('/');
expect(getRoutePaths(['page-tabs', 'tab-speaker', 'page-speaker-list'], routes)).toEqual('/speaker');
expect(getRoutePaths(['page-tabs', 'page-map'], routes)).toEqual('/map');
expect(getRoutePaths(['page-tabs', 'page-about'], routes)).toEqual('/about');
expect(getRoutePaths(['page-tutorial'], routes)).toEqual('/tutorial');
expect(getRoutePath([
{id: 'PAGE-TABS'},
{id: 'tab-schedule'},
{id: 'page-schedule'}], routes)).toEqual('/');

expect(getRoutePath([
{id: 'page-tabs'},
{id: 'TAB-SPEAKER'}], routes)).toEqual('/speaker');

expect(getRoutePath([
{id: 'page-tabs'},
{id: 'TAB-SPEAKER'},
{id: 'page-speaker-list'}], routes)).toEqual('/speaker');

expect(getRoutePath([
{id: 'page-tabs'},
{id: 'PAGE-MAP'}], routes)).toEqual('/map');

expect(getRoutePath([
{id: 'page-tabs'},
{id: 'page-about'}], routes)).toEqual('/about');

expect(getRoutePath([
{id: 'page-tutorial'}], routes)).toEqual('/tutorial');
});
});


function conferenceAppRouting() {
const p2 = mockRouteElement('/', 'tab-schedule');
const p3 = mockRouteElement('/', 'page-schedule');
const p3 = mockRouteElement('/', 'PAGE-SCHEDULE');
p2.appendChild(p3);

const p4 = mockRouteElement('/speaker', 'tab-speaker');
Expand All @@ -56,10 +74,10 @@ function conferenceAppRouting() {


function getRouteIDs(path: string, routes: RouteChain[]): string[] {
return routerPathToChain(parsePath(path), routes).chain.map(r => r.id);
return routerPathToChain(parsePath(path), routes).map(r => r.id);
}

function getRoutePaths(ids: string[], routes: RouteChain[]): string {
return generatePath(chainToPath(routerIDsToChain(ids, routes).chain));
function getRoutePath(ids: RouteID[], routes: RouteChain[]): string {
return generatePath(chainToPath(routerIDsToChain(ids, routes)));
}

42 changes: 10 additions & 32 deletions packages/core/src/components/router/test/matching.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,34 +132,12 @@ describe('routerPathToChain', () => {
chain3,
chain4
];
expect(routerPathToChain(['to'], routes)).toEqual({
chain: null,
matches: 0,
});

expect(routerPathToChain([''], routes)).toEqual({
chain: null,
matches: 0,
});
expect(routerPathToChain(['segment', 'to'], routes)).toEqual({
chain: null,
matches: 0,
});

expect(routerPathToChain(['hola'], routes)).toEqual({
chain: chain3,
matches: 1,
});
expect(routerPathToChain(['hola', 'hola'], routes)).toEqual({
chain: chain3,
matches: 1,
});

expect(routerPathToChain(['hola', 'adios'], routes)).toEqual({
chain: chain4,
matches: 2,
});

expect(routerPathToChain(['to'], routes)).toEqual(null);
expect(routerPathToChain([''], routes)).toEqual(null);
expect(routerPathToChain(['segment', 'to'], routes)).toEqual(null);
expect(routerPathToChain(['hola'], routes)).toEqual(chain3);
expect(routerPathToChain(['hola', 'hola'], routes)).toEqual(chain3);
expect(routerPathToChain(['hola', 'adios'], routes)).toEqual(chain4);
});

it('should match the default route', () => {
Expand All @@ -174,11 +152,11 @@ describe('routerPathToChain', () => {
{ id: 'page2', path: [''], params: undefined }
];

expect(routerPathToChain([''], [chain1])).toEqual({chain: chain1, matches: 3});
expect(routerPathToChain(['tab2'], [chain1])).toEqual({chain: null, matches: 0});
expect(routerPathToChain([''], [chain1])).toEqual(chain1);
expect(routerPathToChain(['tab2'], [chain1])).toEqual(null);

expect(routerPathToChain([''], [chain2])).toEqual({chain: null, matches: 0});
expect(routerPathToChain(['tab2'], [chain2])).toEqual({chain: chain2, matches: 3});
expect(routerPathToChain([''], [chain2])).toEqual(null);
expect(routerPathToChain(['tab2'], [chain2])).toEqual(chain2);
});
});

Expand Down
26 changes: 13 additions & 13 deletions packages/core/src/components/router/test/parser.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { RouteTree } from '../utils/interfaces';
describe('readRoutes', () => {
it('should read URL', () => {
const root = mockElement('div');
const r1 = mockRouteElement('/', 'main-page');
const r1 = mockRouteElement('/', 'MAIN-PAGE');
const r2 = mockRouteElement('/one-page', 'one-page');
const r3 = mockRouteElement('secondpage', 'second-page');
const r4 = mockRouteElement('/5/hola', '4');
Expand All @@ -20,12 +20,12 @@ describe('readRoutes', () => {
r4.appendChild(r6);

const expected: RouteTree = [
{ path: [''], id: 'main-page', children: [], props: undefined },
{ path: ['one-page'], id: 'one-page', children: [], props: undefined },
{ path: ['secondpage'], id: 'second-page', props: undefined, children: [
{ path: ['5', 'hola'], id: '4', props: undefined, children: [
{ path: ['path', 'to', 'five'], id: '5', children: [], props: undefined },
{ path: ['path', 'to', 'five2'], id: '6', children: [], props: undefined }
{ path: [''], id: 'main-page', children: [], params: undefined },
{ path: ['one-page'], id: 'one-page', children: [], params: undefined },
{ path: ['secondpage'], id: 'second-page', params: undefined, children: [
{ path: ['5', 'hola'], id: '4', params: undefined, children: [
{ path: ['path', 'to', 'five'], id: '5', children: [], params: undefined },
{ path: ['path', 'to', 'five2'], id: '6', children: [], params: undefined }
] }
] }
];
Expand All @@ -36,12 +36,12 @@ describe('readRoutes', () => {
describe('flattenRouterTree', () => {
it('should process routes', () => {
const entries: RouteTree = [
{ path: [''], id: 'hola', children: [], props: undefined },
{ path: ['one-page'], id: 'one-page', children: [], props: undefined },
{ path: ['secondpage'], id: 'second-page', props: undefined, children: [
{ path: ['5', 'hola'], id: '4', props: undefined, children: [
{ path: ['path', 'to', 'five'], id: '5', children: [], props: undefined },
{ path: ['path', 'to', 'five2'], id: '6', children: [], props: undefined }
{ path: [''], id: 'hola', children: [], params: undefined },
{ path: ['one-page'], id: 'one-page', children: [], params: undefined },
{ path: ['secondpage'], id: 'second-page', params: undefined, children: [
{ path: ['5', 'hola'], id: '4', params: undefined, children: [
{ path: ['path', 'to', 'five'], id: '5', children: [], params: undefined },
{ path: ['path', 'to', 'five2'], id: '6', children: [], params: undefined }
] }
] }
];
Expand Down
39 changes: 38 additions & 1 deletion packages/core/src/components/router/test/path.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { generatePath, parsePath } from '../utils/path';
import { chainToPath, generatePath, parsePath } from '../utils/path';
import { RouteChain } from '../utils/interfaces';

describe('parseURL', () => {
it('should parse empty path', () => {
Expand Down Expand Up @@ -53,3 +54,39 @@ describe('generatePath', () => {

});
});


describe('chainToPath', () => {
it('should generate a simple URL', () => {
const chain: RouteChain = [
{ id: '2', path: [''], params: undefined },
{ id: '1', path: [''], params: undefined },
{ id: '3', path: ['segment', 'to'], params: undefined },
{ id: '4', path: [''], params: undefined },
{ id: '5', path: ['hola', '', 'hey'], params: undefined },
{ id: '6', path: [''], params: undefined },
{ id: '7', path: [':param'], params: {param: 'name'} },
{ id: '8', path: ['adios', ':name', ':id'], params: {name: 'manu', id: '123'} },
];
expect(chainToPath(chain)).toEqual(
['segment', 'to', 'hola', 'hey', 'name', 'adios', 'manu', '123']
);
});

it('should raise an exception', () => {
const chain: RouteChain = [
{ id: '3', path: ['segment'], params: undefined },
{ id: '8', path: [':name'], params: undefined },
];
expect(() => chainToPath(chain)).toThrowError('missing param name');
});

it('should raise an exception 2', () => {
const chain: RouteChain = [
{ id: '3', path: ['segment'], params: undefined },
{ id: '8', path: [':name', ':id'], params: {name: 'hey'} },
];
expect(() => chainToPath(chain)).toThrowError('missing param id');
});
});

7 changes: 0 additions & 7 deletions packages/core/src/components/router/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,6 @@ export class RouterSegments {
this.path = path.slice();
}

isDefault(): boolean {
if (this.path.length > 0) {
return this.path[0] === '';
}
return true;
}

next(): string {
if (this.path.length > 0) {
return this.path.shift() as string;
Expand Down
19 changes: 8 additions & 11 deletions packages/core/src/components/router/utils/dom.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { breadthFirstSearch } from './common';
import { NavOutlet, RouteChain } from './interfaces';
import { NavOutlet, RouteChain, RouteID } from './interfaces';

export function writeNavState(root: HTMLElement, chain: RouteChain|null, index: number, direction: number): Promise<void> {
if (!chain || index >= chain.length) {
Expand All @@ -16,7 +16,7 @@ export function writeNavState(root: HTMLElement, chain: RouteChain|null, index:
if (changed) {
direction = 0;
}
const nextEl = node.getContentElement();
const nextEl = node.getContainerEl();
const promise = (nextEl)
? writeNavState(nextEl, chain, index + 1, direction)
: Promise.resolve();
Expand All @@ -29,24 +29,21 @@ export function writeNavState(root: HTMLElement, chain: RouteChain|null, index:
}

export function readNavState(node: HTMLElement) {
const stack: string[] = [];
const ids: RouteID[] = [];
let pivot: NavOutlet|null;
while (true) {
pivot = breadthFirstSearch(node);
if (pivot) {
const cmp = pivot.getRouteId();
if (cmp) {
node = pivot.getContentElement();
stack.push(cmp.toLowerCase());
const id = pivot.getRouteId();
if (id) {
node = pivot.getContainerEl();
ids.push(id);
} else {
break;
}
} else {
break;
}
}
return {
ids: stack,
pivot: pivot,
};
return {ids, pivot};
}
10 changes: 5 additions & 5 deletions packages/core/src/components/router/utils/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
export interface NavOutlet {
setRouteId(id: string, data: any, direction: number): Promise<boolean>;
markVisible?(): Promise<void>;
getRouteId(): string;
getRouteId(): RouteID|null;

getContentElement(): HTMLElement | null;
getContainerEl(): HTMLElement | null;
}

export interface RouteMatch {
chain: RouteChain;
matches: number;
export interface RouteID {
id: string;
params?: any;
}

export type NavOutletElement = NavOutlet & HTMLStencilElement;
Expand Down
Loading

0 comments on commit 3ce8a67

Please sign in to comment.