Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion modules/angular2/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ import {List} from './src/facade/collection';
export const routerDirectives: List<any> = CONST_EXPR([RouterOutlet, RouterLink]);

export var routerInjectables: List<any> = [
RouteRegistry,
bind(RouteRegistry)
.toFactory((appRoot) => new RouteRegistry(appRoot), [appComponentTypeToken]),
Pipeline,
bind(LocationStrategy).toClass(HTML5LocationStrategy),
Location,
Expand Down
7 changes: 7 additions & 0 deletions modules/angular2/src/facade/collection.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
library facade.collection;

import 'dart:collection' show IterableBase, Iterator;
import 'dart:convert' show JsonEncoder;
export 'dart:core' show Map, List, Set;
import 'dart:math' show max, min;

var jsonEncoder = new JsonEncoder();

class MapIterator extends Iterator<List> {
final Iterator _iterator;
final Map _map;
Expand Down Expand Up @@ -170,6 +173,10 @@ class ListWrapper {
}
}

static String toJSON(List l) {
return jsonEncoder.convert(l);
}

// JS splice, slice, fill functions can take start < 0 which indicates a position relative to
// the end of the list
static int _startOffset(List l, int start) {
Expand Down
1 change: 1 addition & 0 deletions modules/angular2/src/facade/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ export class ListWrapper {
}
}
static toString<T>(l: List<T>): string { return l.toString(); }
static toJSON<T>(l: List<T>): string { return JSON.stringify(l); }
}

export function isListLikeIterable(obj): boolean {
Expand Down
21 changes: 21 additions & 0 deletions modules/angular2/src/router/async_route_handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {RouteHandler} from './route_handler';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {isPresent, Type} from 'angular2/src/facade/lang';

export class AsyncRouteHandler implements RouteHandler {
_resolvedComponent: Promise<any> = null;
componentType: Type;

constructor(private _loader: Function) {}

resolveComponentType(): Promise<any> {
if (isPresent(this._resolvedComponent)) {
return this._resolvedComponent;
}

return this._resolvedComponent = this._loader().then((componentType) => {
this.componentType = componentType;
return componentType;
});
}
}
47 changes: 21 additions & 26 deletions modules/angular2/src/router/instruction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,49 @@ import {
List,
ListWrapper
} from 'angular2/src/facade/collection';
import {isPresent, normalizeBlank} from 'angular2/src/facade/lang';
import {isPresent, isBlank, normalizeBlank} from 'angular2/src/facade/lang';

import {PathRecognizer} from './path_recognizer';

export class RouteParams {
constructor(public params: StringMap<string, string>) {}

get(param: string): string { return normalizeBlank(StringMapWrapper.get(this.params, param)); }
}


/**
* An `Instruction` represents the component hierarchy of the application based on a given route
*/
export class Instruction {
component: any;
child: Instruction;

// the part of the URL captured by this instruction
capturedUrl: string;

// the part of the URL captured by this instruction and all children
// "capturedUrl" is the part of the URL captured by this instruction
// "accumulatedUrl" is the part of the URL captured by this instruction and all children
accumulatedUrl: string;

params: StringMap<string, string>;
reuse: boolean;
reuse: boolean = false;
specificity: number;

constructor({params, component, child, matchedUrl, parentSpecificity}: {
params?: StringMap<string, any>,
component?: any,
child?: Instruction,
matchedUrl?: string,
parentSpecificity?: number
} = {}) {
this.reuse = false;
this.capturedUrl = matchedUrl;
this.accumulatedUrl = matchedUrl;
this.specificity = parentSpecificity;
private _params: StringMap<string, string>;

constructor(public component: any, public capturedUrl: string,
private _recognizer: PathRecognizer, public child: Instruction = null) {
this.accumulatedUrl = capturedUrl;
this.specificity = _recognizer.specificity;
if (isPresent(child)) {
this.child = child;
this.specificity += child.specificity;
var childUrl = child.accumulatedUrl;
if (isPresent(childUrl)) {
this.accumulatedUrl += childUrl;
}
} else {
this.child = null;
}
this.component = component;
this.params = params;
}

params(): StringMap<string, string> {
if (isBlank(this._params)) {
this._params = this._recognizer.parseParams(this.capturedUrl);
}
return this._params;
}

hasChild(): boolean { return isPresent(this.child); }
Expand All @@ -73,5 +68,5 @@ export class Instruction {

function shouldReuseComponent(instr1: Instruction, instr2: Instruction): boolean {
return instr1.component == instr2.component &&
StringMapWrapper.equals(instr1.params, instr2.params);
StringMapWrapper.equals(instr1.params(), instr2.params());
}
14 changes: 9 additions & 5 deletions modules/angular2/src/router/path_recognizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
BaseException,
normalizeBlank
} from 'angular2/src/facade/lang';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {
Map,
MapWrapper,
Expand All @@ -19,6 +20,7 @@ import {
import {IMPLEMENTS} from 'angular2/src/facade/lang';

import {escapeRegex} from './url';
import {RouteHandler} from './route_handler';

// TODO(jeffbcross): implement as interface when ts2dart adds support:
// https://github.com/angular/ts2dart/issues/173
Expand All @@ -27,7 +29,7 @@ export class Segment {
regex: string;
}

export class ContinuationSegment extends Segment {
class ContinuationSegment extends Segment {
generate(params): string { return ''; }
}

Expand All @@ -52,7 +54,7 @@ class DynamicSegment {
generate(params: StringMap<string, string>): string {
if (!StringMapWrapper.contains(params, this.name)) {
throw new BaseException(
`Route generator for '${this.name}' was not included in parameters passed.`)
`Route generator for '${this.name}' was not included in parameters passed.`);
}
return normalizeBlank(StringMapWrapper.get(params, this.name));
}
Expand Down Expand Up @@ -135,7 +137,7 @@ export class PathRecognizer {
specificity: number;
terminal: boolean = true;

constructor(public path: string, public handler: any) {
constructor(public path: string, public handler: RouteHandler) {
var parsed = parsePathString(path);
var specificity = parsed['specificity'];
var segments = parsed['segments'];
Expand Down Expand Up @@ -178,7 +180,9 @@ export class PathRecognizer {
}

generate(params: StringMap<string, string>): string {
return ListWrapper.join(
ListWrapper.map(this.segments, (segment) => '/' + segment.generate(params)), '');
return ListWrapper.join(ListWrapper.map(this.segments, (segment) => segment.generate(params)),
'/');
}

resolveComponentType(): Promise<any> { return this.handler.resolveComponentType(); }
}
7 changes: 7 additions & 0 deletions modules/angular2/src/router/route_handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {Type} from 'angular2/src/facade/lang';

export interface RouteHandler {
componentType: Type;
resolveComponentType(): Promise<any>;
}
75 changes: 42 additions & 33 deletions modules/angular2/src/router/route_recognizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import {
RegExp,
RegExpWrapper,
StringWrapper,
isBlank,
isPresent,
isType,
isStringMap,
BaseException
} from 'angular2/src/facade/lang';
import {
Expand All @@ -14,7 +17,10 @@ import {
StringMapWrapper
} from 'angular2/src/facade/collection';

import {PathRecognizer, ContinuationSegment} from './path_recognizer';
import {PathRecognizer} from './path_recognizer';
import {RouteHandler} from './route_handler';
import {AsyncRouteHandler} from './async_route_handler';
import {SyncRouteHandler} from './sync_route_handler';

/**
* `RouteRecognizer` is responsible for recognizing routes for a single component.
Expand All @@ -33,7 +39,8 @@ export class RouteRecognizer {
this.redirects.set(path, target);
}

addConfig(path: string, handler: any, alias: string = null): boolean {
addConfig(path: string, handlerObj: any, alias: string = null): boolean {
var handler = configObjToHandler(handlerObj['component']);
var recognizer = new PathRecognizer(path, handler);
MapWrapper.forEach(this.matchers, (matcher, _) => {
if (recognizer.regex.toString() == matcher.regex.toString()) {
Expand Down Expand Up @@ -65,28 +72,21 @@ export class RouteRecognizer {
if (path == url) {
url = target;
}
} else if (StringWrapper.startsWith(url, path)) {
url = target + StringWrapper.substring(url, path.length);
} else if (url.startsWith(path)) {
url = target + url.substring(path.length);
}
});

MapWrapper.forEach(this.matchers, (pathRecognizer, regex) => {
var match;
if (isPresent(match = RegExpWrapper.firstMatch(regex, url))) {
// TODO(btford): determine a good generic way to deal with terminal matches
var matchedUrl = '/';
var unmatchedUrl = '';
if (url != '/') {
matchedUrl = match[0];
unmatchedUrl = StringWrapper.substring(url, match[0].length);
unmatchedUrl = url.substring(match[0].length);
}
solutions.push(new RouteMatch({
specificity: pathRecognizer.specificity,
handler: pathRecognizer.handler,
params: pathRecognizer.parseParams(url),
matchedUrl: matchedUrl,
unmatchedUrl: unmatchedUrl
}));
solutions.push(new RouteMatch(pathRecognizer, matchedUrl, unmatchedUrl));
}
});

Expand All @@ -95,30 +95,39 @@ export class RouteRecognizer {

hasRoute(name: string): boolean { return this.names.has(name); }

generate(name: string, params: any): string {
var pathRecognizer = this.names.get(name);
return isPresent(pathRecognizer) ? pathRecognizer.generate(params) : null;
generate(name: string, params: any): StringMap<string, any> {
var pathRecognizer: PathRecognizer = this.names.get(name);
if (isBlank(pathRecognizer)) {
return null;
}
var url = pathRecognizer.generate(params);
return {url, 'nextComponent': pathRecognizer.handler.componentType};
}
}

export class RouteMatch {
specificity: number;
handler: StringMap<string, any>;
params: StringMap<string, string>;
matchedUrl: string;
unmatchedUrl: string;
constructor(public recognizer: PathRecognizer, public matchedUrl: string,
public unmatchedUrl: string) {}

params(): StringMap<string, string> { return this.recognizer.parseParams(this.matchedUrl); }
}

constructor({specificity, handler, params, matchedUrl, unmatchedUrl}: {
specificity?: number,
handler?: StringMap<string, any>,
params?: StringMap<string, string>,
matchedUrl?: string,
unmatchedUrl?: string
} = {}) {
this.specificity = specificity;
this.handler = handler;
this.params = params;
this.matchedUrl = matchedUrl;
this.unmatchedUrl = unmatchedUrl;
function configObjToHandler(config: any): RouteHandler {
if (isType(config)) {
return new SyncRouteHandler(config);
} else if (isStringMap(config)) {
if (isBlank(config['type'])) {
throw new BaseException(
`Component declaration when provided as a map should include a 'type' property`);
}
var componentType = config['type'];
if (componentType == 'constructor') {
return new SyncRouteHandler(config['constructor']);
} else if (componentType == 'loader') {
return new AsyncRouteHandler(config['loader']);
} else {
throw new BaseException(`oops`);
}
}
throw new BaseException(`Unexpected component "${config}".`);
}
Loading