Skip to content

Commit 1b2754d

Browse files
committedApr 28, 2015
feat(router): add initial implementation
1 parent e617ca6 commit 1b2754d

16 files changed

+1095
-0
lines changed
 

‎karma-dart.conf.js

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ module.exports = function(config) {
5050
'/packages/core': 'http://localhost:9877/base/modules/core',
5151
'/packages/change_detection': 'http://localhost:9877/base/modules/change_detection',
5252
'/packages/reflection': 'http://localhost:9877/base/modules/reflection',
53+
'/packages/router': 'http://localhost:9877/base/modules/router',
5354
'/packages/di': 'http://localhost:9877/base/modules/di',
5455
'/packages/directives': 'http://localhost:9877/base/modules/directives',
5556
'/packages/facade': 'http://localhost:9877/base/modules/facade',

‎modules/angular2/router.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* @module
3+
* @public
4+
* @description
5+
* Maps application URLs into application states, to support deep-linking and navigation.
6+
*/
7+
8+
9+
export {Router} from './src/router/router';
10+
export {RouterOutlet} from './src/router/router_outlet';
11+
export {RouterLink} from './src/router/router_link';
12+
export {RouteParams} from './src/router/instruction';
13+
export {RouteConfig} from './src/router/route_config';
+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import {Map, MapWrapper, StringMap, StringMapWrapper, List, ListWrapper} from 'angular2/src/facade/collection';
2+
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
3+
import {isPresent} from 'angular2/src/facade/lang';
4+
5+
export class RouteParams {
6+
params:Map<string, string>;
7+
constructor(params:StringMap) {
8+
this.params = params;
9+
}
10+
11+
get(param:string) {
12+
return StringMapWrapper.get(this.params, param);
13+
}
14+
}
15+
16+
export class Instruction {
17+
component:any;
18+
_children:Map<string, Instruction>;
19+
router:any;
20+
matchedUrl:string;
21+
params:Map<string, string>;
22+
23+
constructor({params, component, children, matchedUrl}:{params:StringMap, component:any, children:Map, matchedUrl:string} = {}) {
24+
this.matchedUrl = matchedUrl;
25+
if (isPresent(children)) {
26+
this._children = children;
27+
var childUrl;
28+
StringMapWrapper.forEach(this._children, (child, _) => {
29+
childUrl = child.matchedUrl;
30+
});
31+
if (isPresent(childUrl)) {
32+
this.matchedUrl += childUrl;
33+
}
34+
} else {
35+
this._children = StringMapWrapper.create();
36+
}
37+
this.component = component;
38+
this.params = params;
39+
}
40+
41+
getChildInstruction(outletName:string) {
42+
return StringMapWrapper.get(this._children, outletName);
43+
}
44+
45+
forEachChild(fn:Function) {
46+
StringMapWrapper.forEach(this._children, fn);
47+
}
48+
49+
mapChildrenAsync(fn):Promise {
50+
return mapObjAsync(this._children, fn);
51+
}
52+
53+
/**
54+
* Takes a function:
55+
* (parent:Instruction, child:Instruction) => {}
56+
*/
57+
traverseSync(fn:Function) {
58+
this.forEachChild((childInstruction, _) => fn(this, childInstruction));
59+
this.forEachChild((childInstruction, _) => childInstruction.traverseSync(fn));
60+
}
61+
}
62+
63+
function mapObjAsync(obj:StringMap, fn) {
64+
return PromiseWrapper.all(mapObj(obj, fn));
65+
}
66+
67+
function mapObj(obj:StringMap, fn):List {
68+
var result = ListWrapper.create();
69+
StringMapWrapper.forEach(obj, (value, key) => ListWrapper.push(result, fn(value, key)));
70+
return result;
71+
}
72+
73+
export var noopInstruction = new Instruction();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import {RegExp, RegExpWrapper, RegExpMatcherWrapper, StringWrapper, isPresent} from 'angular2/src/facade/lang';
2+
import {Map, MapWrapper, StringMap, StringMapWrapper, List, ListWrapper} from 'angular2/src/facade/collection';
3+
4+
import {escapeRegex} from './url';
5+
6+
class StaticSegment {
7+
string:string;
8+
regex:string;
9+
name:string;
10+
constructor(string:string) {
11+
this.string = string;
12+
this.name = '';
13+
this.regex = escapeRegex(string);
14+
}
15+
16+
generate(params) {
17+
return this.string;
18+
}
19+
}
20+
21+
class DynamicSegment {
22+
name:string;
23+
regex:string;
24+
constructor(name:string) {
25+
this.name = name;
26+
this.regex = "([^/]+)";
27+
}
28+
29+
generate(params:StringMap) {
30+
return StringMapWrapper.get(params, this.name);
31+
}
32+
}
33+
34+
35+
class StarSegment {
36+
name:string;
37+
regex:string;
38+
constructor(name:string) {
39+
this.name = name;
40+
this.regex = "(.+)";
41+
}
42+
43+
generate(params:StringMap) {
44+
return StringMapWrapper.get(params, this.name);
45+
}
46+
}
47+
48+
49+
var paramMatcher = RegExpWrapper.create("^:([^\/]+)$");
50+
var wildcardMatcher = RegExpWrapper.create("^\\*([^\/]+)$");
51+
52+
function parsePathString(route:string):List {
53+
// normalize route as not starting with a "/". Recognition will
54+
// also normalize.
55+
if (route[0] === "/") {
56+
route = StringWrapper.substring(route, 1);
57+
}
58+
59+
var segments = splitBySlash(route);
60+
var results = ListWrapper.create();
61+
62+
for (var i=0; i<segments.length; i++) {
63+
var segment = segments[i],
64+
match;
65+
66+
if (isPresent(match = RegExpWrapper.firstMatch(paramMatcher, segment))) {
67+
ListWrapper.push(results, new DynamicSegment(match[1]));
68+
} else if (isPresent(match = RegExpWrapper.firstMatch(wildcardMatcher, segment))) {
69+
ListWrapper.push(results, new StarSegment(match[1]));
70+
} else if (segment.length > 0) {
71+
ListWrapper.push(results, new StaticSegment(segment));
72+
}
73+
}
74+
75+
return results;
76+
}
77+
78+
var SLASH_RE = RegExpWrapper.create('/');
79+
function splitBySlash (url:string):List<string> {
80+
return StringWrapper.split(url, SLASH_RE);
81+
}
82+
83+
84+
// represents something like '/foo/:bar'
85+
export class PathRecognizer {
86+
segments:List;
87+
regex:RegExp;
88+
handler:any;
89+
90+
constructor(path:string, handler:any) {
91+
this.handler = handler;
92+
this.segments = ListWrapper.create();
93+
94+
var segments = parsePathString(path);
95+
var regexString = '^';
96+
97+
ListWrapper.forEach(segments, (segment) => {
98+
regexString += '/' + segment.regex;
99+
});
100+
101+
this.regex = RegExpWrapper.create(regexString);
102+
this.segments = segments;
103+
}
104+
105+
parseParams(url:string):StringMap {
106+
var params = StringMapWrapper.create();
107+
var urlPart = url;
108+
for(var i=0; i<this.segments.length; i++) {
109+
var segment = this.segments[i];
110+
var match = RegExpWrapper.firstMatch(RegExpWrapper.create('/' + segment.regex), urlPart);
111+
urlPart = StringWrapper.substring(urlPart, match[0].length);
112+
if (segment.name.length > 0) {
113+
StringMapWrapper.set(params, segment.name, match[1]);
114+
}
115+
}
116+
117+
return params;
118+
}
119+
120+
generate(params:StringMap):string {
121+
return ListWrapper.join(ListWrapper.map(this.segments, (segment) => '/' + segment.generate(params)), '');
122+
}
123+
}
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
2+
import {List, ListWrapper} from 'angular2/src/facade/collection';
3+
import {Instruction} from './instruction';
4+
5+
/**
6+
* Responsible for performing each step of navigation.
7+
* "Steps" are conceptually similar to "middleware"
8+
*/
9+
export class Pipeline {
10+
steps:List;
11+
constructor() {
12+
this.steps = [
13+
instruction => instruction.traverseSync((parentInstruction, childInstruction) => {
14+
childInstruction.router = parentInstruction.router.childRouter(childInstruction.component);
15+
}),
16+
instruction => instruction.router.traverseOutlets((outlet, name) => {
17+
return outlet.canDeactivate(instruction.getChildInstruction(name));
18+
}),
19+
instruction => instruction.router.traverseOutlets((outlet, name) => {
20+
return outlet.canActivate(instruction.getChildInstruction(name));
21+
}),
22+
instruction => instruction.router.activateOutlets(instruction)
23+
];
24+
}
25+
26+
process(instruction:Instruction):Promise {
27+
var steps = this.steps,
28+
currentStep = 0;
29+
30+
function processOne(result:any = true):Promise {
31+
if (currentStep >= steps.length) {
32+
return PromiseWrapper.resolve(result);
33+
}
34+
var step = steps[currentStep];
35+
currentStep += 1;
36+
return PromiseWrapper.resolve(step(instruction)).then(processOne);
37+
}
38+
39+
return processOne();
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {CONST} from 'angular2/src/facade/lang';
2+
3+
/**
4+
* You use the RouteConfig annotation to ...
5+
*/
6+
export class RouteConfig {
7+
path:string;
8+
redirectTo:string;
9+
component:any;
10+
//TODO: "alias," or "as"
11+
12+
@CONST()
13+
constructor({path, component, redirectTo}:{path:string, component:any, redirectTo:string} = {}) {
14+
this.path = path;
15+
this.component = component;
16+
this.redirectTo = redirectTo;
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import {RegExp, RegExpWrapper, StringWrapper, isPresent} from 'angular2/src/facade/lang';
2+
import {Map, MapWrapper, List, ListWrapper, StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
3+
4+
import {PathRecognizer} from './path_recognizer';
5+
6+
export class RouteRecognizer {
7+
names:Map<string, PathRecognizer>;
8+
redirects:Map<string, string>;
9+
matchers:Map<RegExp, PathRecognizer>;
10+
11+
constructor() {
12+
this.names = MapWrapper.create();
13+
this.matchers = MapWrapper.create();
14+
this.redirects = MapWrapper.create();
15+
}
16+
17+
addRedirect(path:string, target:string) {
18+
MapWrapper.set(this.redirects, path, target);
19+
}
20+
21+
addConfig(path:string, handler:any, alias:string = null) {
22+
var recognizer = new PathRecognizer(path, handler);
23+
MapWrapper.set(this.matchers, recognizer.regex, recognizer);
24+
if (isPresent(alias)) {
25+
MapWrapper.set(this.names, alias, recognizer);
26+
}
27+
}
28+
29+
recognize(url:string):List<StringMap> {
30+
var solutions = [];
31+
MapWrapper.forEach(this.redirects, (target, path) => {
32+
//TODO: "/" redirect case
33+
if (StringWrapper.startsWith(url, path)) {
34+
url = target + StringWrapper.substring(url, path.length);
35+
}
36+
});
37+
38+
MapWrapper.forEach(this.matchers, (pathRecognizer, regex) => {
39+
var match;
40+
if (isPresent(match = RegExpWrapper.firstMatch(regex, url))) {
41+
var solution = StringMapWrapper.create();
42+
StringMapWrapper.set(solution, 'handler', pathRecognizer.handler);
43+
StringMapWrapper.set(solution, 'params', pathRecognizer.parseParams(url));
44+
StringMapWrapper.set(solution, 'matchedUrl', match[0]);
45+
46+
var unmatchedUrl = StringWrapper.substring(url, match[0].length);
47+
StringMapWrapper.set(solution, 'unmatchedUrl', unmatchedUrl);
48+
49+
ListWrapper.push(solutions, solution);
50+
}
51+
});
52+
53+
return solutions;
54+
}
55+
56+
hasRoute(name:string) {
57+
return MapWrapper.contains(this.names, name);
58+
}
59+
60+
generate(name:string, params:any) {
61+
var pathRecognizer = MapWrapper.get(this.names, name);
62+
return pathRecognizer.generate(params);
63+
}
64+
}

0 commit comments

Comments
 (0)