|
| 1 | +import {isBlank, isPresent} from 'angular2/src/facade/lang'; |
| 2 | +import {BaseException, WrappedException} from 'angular2/src/facade/exceptions'; |
| 3 | +import {Map, MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; |
| 4 | +import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; |
| 5 | + |
| 6 | +import { |
| 7 | + AbstractRecognizer, |
| 8 | + RouteRecognizer, |
| 9 | + RedirectRecognizer, |
| 10 | + RouteMatch |
| 11 | +} from './route_recognizer'; |
| 12 | +import {Route, AsyncRoute, AuxRoute, Redirect, RouteDefinition} from './route_config_impl'; |
| 13 | +import {AsyncRouteHandler} from './async_route_handler'; |
| 14 | +import {SyncRouteHandler} from './sync_route_handler'; |
| 15 | +import {Url} from './url_parser'; |
| 16 | +import {ComponentInstruction} from './instruction'; |
| 17 | + |
| 18 | + |
| 19 | +/** |
| 20 | + * `ComponentRecognizer` is responsible for recognizing routes for a single component. |
| 21 | + * It is consumed by `RouteRegistry`, which knows how to recognize an entire hierarchy of |
| 22 | + * components. |
| 23 | + */ |
| 24 | +export class ComponentRecognizer { |
| 25 | + names = new Map<string, RouteRecognizer>(); |
| 26 | + |
| 27 | + // map from name to recognizer |
| 28 | + auxNames = new Map<string, RouteRecognizer>(); |
| 29 | + |
| 30 | + // map from starting path to recognizer |
| 31 | + auxRoutes = new Map<string, RouteRecognizer>(); |
| 32 | + |
| 33 | + // TODO: optimize this into a trie |
| 34 | + matchers: AbstractRecognizer[] = []; |
| 35 | + |
| 36 | + defaultRoute: RouteRecognizer = null; |
| 37 | + |
| 38 | + /** |
| 39 | + * returns whether or not the config is terminal |
| 40 | + */ |
| 41 | + config(config: RouteDefinition): boolean { |
| 42 | + var handler; |
| 43 | + |
| 44 | + if (isPresent(config.name) && config.name[0].toUpperCase() != config.name[0]) { |
| 45 | + var suggestedName = config.name[0].toUpperCase() + config.name.substring(1); |
| 46 | + throw new BaseException( |
| 47 | + `Route "${config.path}" with name "${config.name}" does not begin with an uppercase letter. Route names should be CamelCase like "${suggestedName}".`); |
| 48 | + } |
| 49 | + |
| 50 | + if (config instanceof AuxRoute) { |
| 51 | + handler = new SyncRouteHandler(config.component, config.data); |
| 52 | + let path = config.path.startsWith('/') ? config.path.substring(1) : config.path; |
| 53 | + var recognizer = new RouteRecognizer(config.path, handler); |
| 54 | + this.auxRoutes.set(path, recognizer); |
| 55 | + if (isPresent(config.name)) { |
| 56 | + this.auxNames.set(config.name, recognizer); |
| 57 | + } |
| 58 | + return recognizer.terminal; |
| 59 | + } |
| 60 | + |
| 61 | + var useAsDefault = false; |
| 62 | + |
| 63 | + if (config instanceof Redirect) { |
| 64 | + let redirector = new RedirectRecognizer(config.path, config.redirectTo); |
| 65 | + this._assertNoHashCollision(redirector.hash, config.path); |
| 66 | + this.matchers.push(redirector); |
| 67 | + return true; |
| 68 | + } |
| 69 | + |
| 70 | + if (config instanceof Route) { |
| 71 | + handler = new SyncRouteHandler(config.component, config.data); |
| 72 | + useAsDefault = isPresent(config.useAsDefault) && config.useAsDefault; |
| 73 | + } else if (config instanceof AsyncRoute) { |
| 74 | + handler = new AsyncRouteHandler(config.loader, config.data); |
| 75 | + useAsDefault = isPresent(config.useAsDefault) && config.useAsDefault; |
| 76 | + } |
| 77 | + var recognizer = new RouteRecognizer(config.path, handler); |
| 78 | + |
| 79 | + this._assertNoHashCollision(recognizer.hash, config.path); |
| 80 | + |
| 81 | + if (useAsDefault) { |
| 82 | + if (isPresent(this.defaultRoute)) { |
| 83 | + throw new BaseException(`Only one route can be default`); |
| 84 | + } |
| 85 | + this.defaultRoute = recognizer; |
| 86 | + } |
| 87 | + |
| 88 | + this.matchers.push(recognizer); |
| 89 | + if (isPresent(config.name)) { |
| 90 | + this.names.set(config.name, recognizer); |
| 91 | + } |
| 92 | + return recognizer.terminal; |
| 93 | + } |
| 94 | + |
| 95 | + |
| 96 | + private _assertNoHashCollision(hash: string, path) { |
| 97 | + this.matchers.forEach((matcher) => { |
| 98 | + if (hash == matcher.hash) { |
| 99 | + throw new BaseException( |
| 100 | + `Configuration '${path}' conflicts with existing route '${matcher.path}'`); |
| 101 | + } |
| 102 | + }); |
| 103 | + } |
| 104 | + |
| 105 | + |
| 106 | + /** |
| 107 | + * Given a URL, returns a list of `RouteMatch`es, which are partial recognitions for some route. |
| 108 | + */ |
| 109 | + recognize(urlParse: Url): Promise<RouteMatch>[] { |
| 110 | + var solutions = []; |
| 111 | + |
| 112 | + this.matchers.forEach((routeRecognizer: AbstractRecognizer) => { |
| 113 | + var pathMatch = routeRecognizer.recognize(urlParse); |
| 114 | + |
| 115 | + if (isPresent(pathMatch)) { |
| 116 | + solutions.push(pathMatch); |
| 117 | + } |
| 118 | + }); |
| 119 | + |
| 120 | + return solutions; |
| 121 | + } |
| 122 | + |
| 123 | + recognizeAuxiliary(urlParse: Url): Promise<RouteMatch>[] { |
| 124 | + var routeRecognizer: RouteRecognizer = this.auxRoutes.get(urlParse.path); |
| 125 | + if (isPresent(routeRecognizer)) { |
| 126 | + return [routeRecognizer.recognize(urlParse)]; |
| 127 | + } |
| 128 | + |
| 129 | + return [PromiseWrapper.resolve(null)]; |
| 130 | + } |
| 131 | + |
| 132 | + hasRoute(name: string): boolean { return this.names.has(name); } |
| 133 | + |
| 134 | + componentLoaded(name: string): boolean { |
| 135 | + return this.hasRoute(name) && isPresent(this.names.get(name).handler.componentType); |
| 136 | + } |
| 137 | + |
| 138 | + loadComponent(name: string): Promise<any> { |
| 139 | + return this.names.get(name).handler.resolveComponentType(); |
| 140 | + } |
| 141 | + |
| 142 | + generate(name: string, params: any): ComponentInstruction { |
| 143 | + var pathRecognizer: RouteRecognizer = this.names.get(name); |
| 144 | + if (isBlank(pathRecognizer)) { |
| 145 | + return null; |
| 146 | + } |
| 147 | + return pathRecognizer.generate(params); |
| 148 | + } |
| 149 | + |
| 150 | + generateAuxiliary(name: string, params: any): ComponentInstruction { |
| 151 | + var pathRecognizer: RouteRecognizer = this.auxNames.get(name); |
| 152 | + if (isBlank(pathRecognizer)) { |
| 153 | + return null; |
| 154 | + } |
| 155 | + return pathRecognizer.generate(params); |
| 156 | + } |
| 157 | +} |
0 commit comments