angular-cn/modules/@angular/router/src/create_url_tree.ts

243 lines
8.2 KiB
TypeScript
Raw Normal View History

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
2016-06-08 14:13:41 -04:00
import {ActivatedRoute} from './router_state';
import {PRIMARY_OUTLET, Params} from './shared';
2016-06-14 17:55:59 -04:00
import {UrlPathWithParams, UrlSegment, UrlTree} from './url_tree';
2016-06-08 14:13:41 -04:00
import {forEach, shallowEqual} from './utils/collection';
export function createUrlTree(
route: ActivatedRoute, urlTree: UrlTree, commands: any[], queryParams: Params,
fragment: string): UrlTree {
2016-05-26 19:51:19 -04:00
if (commands.length === 0) {
2016-06-14 17:55:59 -04:00
return tree(urlTree.root, urlTree.root, urlTree, queryParams, fragment);
2016-05-26 19:51:19 -04:00
}
const normalizedCommands = normalizeCommands(commands);
if (navigateToRoot(normalizedCommands)) {
2016-06-14 17:55:59 -04:00
return tree(urlTree.root, new UrlSegment([], {}), urlTree, queryParams, fragment);
2016-05-26 19:51:19 -04:00
}
2016-06-14 17:55:59 -04:00
const startingPosition = findStartingPosition(normalizedCommands, urlTree, route);
const segment = startingPosition.processChildren ?
updateSegmentChildren(
startingPosition.segment, startingPosition.index, normalizedCommands.commands) :
updateSegment(startingPosition.segment, startingPosition.index, normalizedCommands.commands);
return tree(startingPosition.segment, segment, urlTree, queryParams, fragment);
2016-05-26 19:51:19 -04:00
}
2016-06-08 14:13:41 -04:00
function tree(
oldSegment: UrlSegment, newSegment: UrlSegment, urlTree: UrlTree, queryParams: Params,
fragment: string): UrlTree {
const q = queryParams ? stringify(queryParams) : urlTree.queryParams;
2016-05-26 19:51:19 -04:00
const f = fragment ? fragment : urlTree.fragment;
2016-06-14 17:55:59 -04:00
if (urlTree.root === oldSegment) {
return new UrlTree(newSegment, q, f);
} else {
return new UrlTree(replaceSegment(urlTree.root, oldSegment, newSegment), q, f);
}
}
function replaceSegment(
current: UrlSegment, oldSegment: UrlSegment, newSegment: UrlSegment): UrlSegment {
const children: {[key: string]: UrlSegment} = {};
forEach(current.children, (c: UrlSegment, outletName: string) => {
2016-06-14 17:55:59 -04:00
if (c === oldSegment) {
children[outletName] = newSegment;
2016-06-14 17:55:59 -04:00
} else {
children[outletName] = replaceSegment(c, oldSegment, newSegment);
2016-06-14 17:55:59 -04:00
}
});
return new UrlSegment(current.pathsWithParams, children);
2016-05-26 19:51:19 -04:00
}
function navigateToRoot(normalizedChange: NormalizedNavigationCommands): boolean {
return normalizedChange.isAbsolute && normalizedChange.commands.length === 1 &&
2016-06-08 14:13:41 -04:00
normalizedChange.commands[0] == '/';
2016-05-26 19:51:19 -04:00
}
class NormalizedNavigationCommands {
2016-06-08 14:13:41 -04:00
constructor(
public isAbsolute: boolean, public numberOfDoubleDots: number, public commands: any[]) {}
2016-05-26 19:51:19 -04:00
}
function normalizeCommands(commands: any[]): NormalizedNavigationCommands {
2016-06-08 14:13:41 -04:00
if ((typeof commands[0] === 'string') && commands.length === 1 && commands[0] == '/') {
2016-05-26 19:51:19 -04:00
return new NormalizedNavigationCommands(true, 0, commands);
}
let numberOfDoubleDots = 0;
let isAbsolute = false;
const res: any[] = [];
2016-05-26 19:51:19 -04:00
for (let i = 0; i < commands.length; ++i) {
const c = commands[i];
2016-06-08 14:13:41 -04:00
if (!(typeof c === 'string')) {
2016-05-26 19:51:19 -04:00
res.push(c);
continue;
}
const parts = c.split('/');
for (let j = 0; j < parts.length; ++j) {
let cc = parts[j];
// first exp is treated in a special way
if (i == 0) {
2016-06-08 14:13:41 -04:00
if (j == 0 && cc == '.') { // './a'
2016-05-26 19:51:19 -04:00
// skip it
2016-06-08 14:13:41 -04:00
} else if (j == 0 && cc == '') { // '/a'
2016-05-26 19:51:19 -04:00
isAbsolute = true;
2016-06-08 14:13:41 -04:00
} else if (cc == '..') { // '../a'
2016-05-26 19:51:19 -04:00
numberOfDoubleDots++;
} else if (cc != '') {
res.push(cc);
}
} else {
if (cc != '') {
res.push(cc);
}
}
}
}
return new NormalizedNavigationCommands(isAbsolute, numberOfDoubleDots, res);
}
2016-06-14 17:55:59 -04:00
class Position {
constructor(public segment: UrlSegment, public processChildren: boolean, public index: number) {}
}
function findStartingPosition(
2016-06-08 14:13:41 -04:00
normalizedChange: NormalizedNavigationCommands, urlTree: UrlTree,
2016-06-14 17:55:59 -04:00
route: ActivatedRoute): Position {
2016-05-26 19:51:19 -04:00
if (normalizedChange.isAbsolute) {
2016-06-14 17:55:59 -04:00
return new Position(urlTree.root, true, 0);
} else if (route.snapshot._lastPathIndex === -1) {
return new Position(route.snapshot._urlSegment, true, 0);
} else if (route.snapshot._lastPathIndex + 1 - normalizedChange.numberOfDoubleDots >= 0) {
return new Position(
route.snapshot._urlSegment, false,
route.snapshot._lastPathIndex + 1 - normalizedChange.numberOfDoubleDots);
2016-05-26 19:51:19 -04:00
} else {
2016-06-14 17:55:59 -04:00
throw new Error('Invalid number of \'../\'');
2016-05-26 19:51:19 -04:00
}
}
2016-06-14 17:55:59 -04:00
function getPath(command: any): any {
if (!(typeof command === 'string')) return command.toString();
2016-06-14 17:55:59 -04:00
const parts = command.toString().split(':');
return parts.length > 1 ? parts[1] : command;
2016-05-26 19:51:19 -04:00
}
2016-06-14 17:55:59 -04:00
function getOutlet(commands: any[]): string {
if (!(typeof commands[0] === 'string')) return PRIMARY_OUTLET;
const parts = commands[0].toString().split(':');
return parts.length > 1 ? parts[0] : PRIMARY_OUTLET;
2016-05-26 19:51:19 -04:00
}
2016-06-14 17:55:59 -04:00
function updateSegment(segment: UrlSegment, startIndex: number, commands: any[]): UrlSegment {
if (!segment) {
segment = new UrlSegment([], {});
}
if (segment.pathsWithParams.length === 0 && segment.hasChildren()) {
2016-06-14 17:55:59 -04:00
return updateSegmentChildren(segment, startIndex, commands);
}
const m = prefixedWith(segment, startIndex, commands);
const slicedCommands = commands.slice(m.lastIndex);
if (m.match && slicedCommands.length === 0) {
return new UrlSegment(segment.pathsWithParams, {});
} else if (m.match && !segment.hasChildren()) {
2016-06-14 17:55:59 -04:00
return createNewSegment(segment, startIndex, commands);
} else if (m.match) {
return updateSegmentChildren(segment, 0, slicedCommands);
2016-05-26 19:51:19 -04:00
} else {
2016-06-14 17:55:59 -04:00
return createNewSegment(segment, startIndex, commands);
2016-05-26 19:51:19 -04:00
}
}
2016-06-14 17:55:59 -04:00
function updateSegmentChildren(
segment: UrlSegment, startIndex: number, commands: any[]): UrlSegment {
if (commands.length === 0) {
return new UrlSegment(segment.pathsWithParams, {});
2016-05-26 19:51:19 -04:00
} else {
2016-06-14 17:55:59 -04:00
const outlet = getOutlet(commands);
const children: {[key: string]: UrlSegment} = {};
2016-06-14 17:55:59 -04:00
children[outlet] = updateSegment(segment.children[outlet], startIndex, commands);
forEach(segment.children, (child: UrlSegment, childOutlet: string) => {
2016-06-14 17:55:59 -04:00
if (childOutlet !== outlet) {
children[childOutlet] = child;
}
});
return new UrlSegment(segment.pathsWithParams, children);
2016-05-26 19:51:19 -04:00
}
}
2016-06-14 17:55:59 -04:00
function prefixedWith(segment: UrlSegment, startIndex: number, commands: any[]) {
let currentCommandIndex = 0;
let currentPathIndex = startIndex;
const noMatch = {match: false, lastIndex: 0};
while (currentPathIndex < segment.pathsWithParams.length) {
if (currentCommandIndex >= commands.length) return noMatch;
const path = segment.pathsWithParams[currentPathIndex];
const curr = getPath(commands[currentCommandIndex]);
const next =
currentCommandIndex < commands.length - 1 ? commands[currentCommandIndex + 1] : null;
if (curr && next && (typeof next === 'object')) {
if (!compare(curr, next, path)) return noMatch;
currentCommandIndex += 2;
} else {
if (!compare(curr, {}, path)) return noMatch;
currentCommandIndex++;
}
currentPathIndex++;
}
2016-05-26 19:51:19 -04:00
return {match: true, lastIndex: currentCommandIndex};
2016-05-26 19:51:19 -04:00
}
2016-06-14 17:55:59 -04:00
function createNewSegment(segment: UrlSegment, startIndex: number, commands: any[]): UrlSegment {
const paths = segment.pathsWithParams.slice(0, startIndex);
let i = 0;
while (i < commands.length) {
// if we start with an object literal, we need to reuse the path part from the segment
2016-06-14 17:55:59 -04:00
if (i === 0 && (typeof commands[0] === 'object')) {
const p = segment.pathsWithParams[startIndex];
paths.push(new UrlPathWithParams(p.path, commands[0]));
i++;
continue;
}
const curr = getPath(commands[i]);
const next = (i < commands.length - 1) ? commands[i + 1] : null;
if (curr && next && (typeof next === 'object')) {
paths.push(new UrlPathWithParams(curr, stringify(next)));
i += 2;
} else {
paths.push(new UrlPathWithParams(curr, {}));
i++;
}
2016-05-26 19:51:19 -04:00
}
2016-06-14 17:55:59 -04:00
return new UrlSegment(paths, {});
2016-05-26 19:51:19 -04:00
}
function stringify(params: {[key: string]: any}): {[key: string]: string} {
const res: {[key: string]: string} = {};
forEach(params, (v: any, k: string) => res[k] = `${v}`);
2016-05-26 19:51:19 -04:00
return res;
}
2016-06-14 17:55:59 -04:00
function compare(
path: string, params: {[key: string]: any}, pathWithParams: UrlPathWithParams): boolean {
return path == pathWithParams.path && shallowEqual(params, pathWithParams.parameters);
2016-05-26 19:51:19 -04:00
}