| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  | import { | 
					
						
							|  |  |  |   RegExp, | 
					
						
							|  |  |  |   RegExpWrapper, | 
					
						
							|  |  |  |   RegExpMatcherWrapper, | 
					
						
							|  |  |  |   StringWrapper, | 
					
						
							|  |  |  |   isPresent, | 
					
						
							|  |  |  |   isBlank, | 
					
						
							|  |  |  |   BaseException, | 
					
						
							|  |  |  |   normalizeBlank | 
					
						
							|  |  |  | } from 'angular2/src/facade/lang'; | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   Map, | 
					
						
							|  |  |  |   MapWrapper, | 
					
						
							|  |  |  |   StringMap, | 
					
						
							|  |  |  |   StringMapWrapper, | 
					
						
							|  |  |  |   List, | 
					
						
							|  |  |  |   ListWrapper | 
					
						
							|  |  |  | } from 'angular2/src/facade/collection'; | 
					
						
							|  |  |  | import {IMPLEMENTS} from 'angular2/src/facade/lang'; | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | import {escapeRegex} from './url'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  | // TODO(jeffbcross): implement as interface when ts2dart adds support:
 | 
					
						
							|  |  |  | // https://github.com/angular/ts2dart/issues/173
 | 
					
						
							|  |  |  | export class Segment { | 
					
						
							|  |  |  |   name: string; | 
					
						
							|  |  |  |   regex: string; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class StaticSegment extends Segment { | 
					
						
							|  |  |  |   regex: string; | 
					
						
							|  |  |  |   name: string; | 
					
						
							| 
									
										
										
										
											2015-05-14 15:24:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  |   constructor(public string: string) { | 
					
						
							|  |  |  |     super(); | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  |     this.name = ''; | 
					
						
							|  |  |  |     this.regex = escapeRegex(string); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  |   generate(params): string { return this.string; } | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  | @IMPLEMENTS(Segment) | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  | class DynamicSegment { | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  |   regex: string; | 
					
						
							|  |  |  |   constructor(public name: string) { this.regex = "([^/]+)"; } | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  |   generate(params: StringMap<string, string>): string { | 
					
						
							| 
									
										
										
										
											2015-05-06 18:30:37 -07:00
										 |  |  |     if (!StringMapWrapper.contains(params, this.name)) { | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  |       throw new BaseException( | 
					
						
							|  |  |  |           `Route generator for '${this.name}' was not included in parameters passed.`) | 
					
						
							| 
									
										
										
										
											2015-05-06 18:30:37 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-05-14 15:24:35 +02:00
										 |  |  |     return normalizeBlank(StringMapWrapper.get(params, this.name)); | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class StarSegment { | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  |   regex: string; | 
					
						
							|  |  |  |   constructor(public name: string) { this.regex = "(.+)"; } | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  |   generate(params: StringMap<string, string>): string { | 
					
						
							| 
									
										
										
										
											2015-05-14 15:24:35 +02:00
										 |  |  |     return normalizeBlank(StringMapWrapper.get(params, this.name)); | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var paramMatcher = RegExpWrapper.create("^:([^\/]+)$"); | 
					
						
							|  |  |  | var wildcardMatcher = RegExpWrapper.create("^\\*([^\/]+)$"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  | function parsePathString(route: string) { | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  |   // normalize route as not starting with a "/". Recognition will
 | 
					
						
							|  |  |  |   // also normalize.
 | 
					
						
							|  |  |  |   if (route[0] === "/") { | 
					
						
							|  |  |  |     route = StringWrapper.substring(route, 1); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   var segments = splitBySlash(route); | 
					
						
							|  |  |  |   var results = ListWrapper.create(); | 
					
						
							| 
									
										
										
										
											2015-05-15 02:05:57 -07:00
										 |  |  |   var specificity = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  |   // The "specificity" of a path is used to determine which route is used when multiple routes match
 | 
					
						
							|  |  |  |   // a URL.
 | 
					
						
							|  |  |  |   // Static segments (like "/foo") are the most specific, followed by dynamic segments (like
 | 
					
						
							|  |  |  |   // "/:id"). Star segments
 | 
					
						
							| 
									
										
										
										
											2015-05-15 02:05:57 -07:00
										 |  |  |   // add no specificity. Segments at the start of the path are more specific than proceeding ones.
 | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  |   // The code below uses place values to combine the different types of segments into a single
 | 
					
						
							|  |  |  |   // integer that we can
 | 
					
						
							|  |  |  |   // sort later. Each static segment is worth hundreds of points of specificity (10000, 9900, ...,
 | 
					
						
							|  |  |  |   // 200), and each
 | 
					
						
							| 
									
										
										
										
											2015-05-15 02:05:57 -07:00
										 |  |  |   // dynamic segment is worth single points of specificity (100, 99, ... 2).
 | 
					
						
							|  |  |  |   if (segments.length > 98) { | 
					
						
							|  |  |  |     throw new BaseException(`'${route}' has more than the maximum supported number of segments.`); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  |   for (var i = 0; i < segments.length; i++) { | 
					
						
							|  |  |  |     var segment = segments[i], match; | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (isPresent(match = RegExpWrapper.firstMatch(paramMatcher, segment))) { | 
					
						
							|  |  |  |       ListWrapper.push(results, new DynamicSegment(match[1])); | 
					
						
							| 
									
										
										
										
											2015-05-15 02:05:57 -07:00
										 |  |  |       specificity += (100 - i); | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  |     } else if (isPresent(match = RegExpWrapper.firstMatch(wildcardMatcher, segment))) { | 
					
						
							|  |  |  |       ListWrapper.push(results, new StarSegment(match[1])); | 
					
						
							|  |  |  |     } else if (segment.length > 0) { | 
					
						
							|  |  |  |       ListWrapper.push(results, new StaticSegment(segment)); | 
					
						
							| 
									
										
										
										
											2015-05-15 02:05:57 -07:00
										 |  |  |       specificity += 100 * (100 - i); | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-15 02:05:57 -07:00
										 |  |  |   return {segments: results, specificity}; | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  | function splitBySlash(url: string): List<string> { | 
					
						
							| 
									
										
										
										
											2015-05-14 15:24:35 +02:00
										 |  |  |   return url.split('/'); | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // represents something like '/foo/:bar'
 | 
					
						
							|  |  |  | export class PathRecognizer { | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  |   segments: List<Segment>; | 
					
						
							|  |  |  |   regex: RegExp; | 
					
						
							|  |  |  |   specificity: number; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   constructor(public path: string, public handler: any) { | 
					
						
							| 
									
										
										
										
											2015-05-14 15:24:35 +02:00
										 |  |  |     this.segments = []; | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-12 14:53:13 -07:00
										 |  |  |     // TODO: use destructuring assignment
 | 
					
						
							|  |  |  |     // see https://github.com/angular/ts2dart/issues/158
 | 
					
						
							|  |  |  |     var parsed = parsePathString(path); | 
					
						
							| 
									
										
										
										
											2015-05-15 02:05:57 -07:00
										 |  |  |     var specificity = parsed['specificity']; | 
					
						
							| 
									
										
										
										
											2015-05-12 14:53:13 -07:00
										 |  |  |     var segments = parsed['segments']; | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  |     var regexString = '^'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  |     ListWrapper.forEach(segments, (segment) => { regexString += '/' + segment.regex; }); | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     this.regex = RegExpWrapper.create(regexString); | 
					
						
							|  |  |  |     this.segments = segments; | 
					
						
							| 
									
										
										
										
											2015-05-15 02:05:57 -07:00
										 |  |  |     this.specificity = specificity; | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  |   parseParams(url: string): StringMap<string, string> { | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  |     var params = StringMapWrapper.create(); | 
					
						
							|  |  |  |     var urlPart = url; | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  |     for (var i = 0; i < this.segments.length; i++) { | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  |       var segment = this.segments[i]; | 
					
						
							|  |  |  |       var match = RegExpWrapper.firstMatch(RegExpWrapper.create('/' + segment.regex), urlPart); | 
					
						
							|  |  |  |       urlPart = StringWrapper.substring(urlPart, match[0].length); | 
					
						
							|  |  |  |       if (segment.name.length > 0) { | 
					
						
							|  |  |  |         StringMapWrapper.set(params, segment.name, match[1]); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return params; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-29 14:58:41 -07:00
										 |  |  |   generate(params: StringMap<string, string>): string { | 
					
						
							|  |  |  |     return ListWrapper.join( | 
					
						
							|  |  |  |         ListWrapper.map(this.segments, (segment) => '/' + segment.generate(params)), ''); | 
					
						
							| 
									
										
										
										
											2015-04-17 09:59:56 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | } |