OLD | NEW |
| (Empty) |
1 | |
2 (function(scope) { | |
3 var MoreRouting = scope.MoreRouting = scope.MoreRouting || {}; | |
4 MoreRouting.Route = Route; | |
5 | |
6 // Note that this can differ from the part separator defined by the driver. The | |
7 // driver's separator is used when parsing/generating URLs given to the client, | |
8 // whereas this one is for route definitions. | |
9 var PART_SEPARATOR = '/'; | |
10 var PARAM_SENTINEL = ':'; | |
11 var SEPARATOR_CLEANER = /\/\/+/g; | |
12 | |
13 /** | |
14 * TODO(nevir): Docs. | |
15 */ | |
16 function Route(path, parent) { | |
17 // For `MoreRouting.Emitter`; Emits changes for `active`. | |
18 this.__listeners = []; | |
19 | |
20 this.path = path; | |
21 this.parent = parent; | |
22 this.fullPath = path; | |
23 this.compiled = this._compile(this.path); | |
24 this.active = false; | |
25 this.driver = null; | |
26 | |
27 var params = MoreRouting.Params(namedParams(this.compiled), this.parent && thi
s.parent.params); | |
28 params.__subscribe(this._navigateToParams.bind(this)); | |
29 Object.defineProperty(this, 'params', { | |
30 get: function() { return params; }, | |
31 set: function() { throw new Error('Route#params cannot be overwritten'); }, | |
32 }); | |
33 | |
34 this.parts = []; | |
35 this.children = []; | |
36 | |
37 // Param values matching the current URL, or an empty object if not `active`. | |
38 // | |
39 // To make data "binding" easy, `Route` guarantees that `params` will always | |
40 // be the same object; just make a reference to it. | |
41 if (this.parent) { | |
42 this.parent.children.push(this); | |
43 this.fullPath = this.parent.fullPath + this.fullPath; | |
44 this.depth = this.parent.depth + this.compiled.length; | |
45 this.numParams = this.parent.numParams + countParams(this.compiled); | |
46 } else { | |
47 this.depth = this.compiled.length; | |
48 this.numParams = countParams(this.compiled); | |
49 } | |
50 } | |
51 Route.prototype = Object.create(MoreRouting.Emitter); | |
52 | |
53 Object.defineProperty(Route.prototype, 'active', { | |
54 get: function() { | |
55 return this._active; | |
56 }, | |
57 set: function(value) { | |
58 if (value !== this._active); | |
59 this._active = value; | |
60 this.__notify('active', value); | |
61 }, | |
62 }); | |
63 | |
64 Route.isPath = function isPath(pathOrName) { | |
65 return pathOrName.indexOf(PART_SEPARATOR) === 0; | |
66 }; | |
67 | |
68 Route.joinPath = function joinPath(paths) { | |
69 var joined = Array.prototype.join.call(arguments, PART_SEPARATOR); | |
70 joined = joined.replace(SEPARATOR_CLEANER, PART_SEPARATOR); | |
71 | |
72 var minLength = joined.length - PART_SEPARATOR.length; | |
73 if (joined.substr(minLength) === PART_SEPARATOR) { | |
74 joined = joined.substr(0, minLength); | |
75 } | |
76 | |
77 return joined; | |
78 }; | |
79 | |
80 Route.prototype.urlFor = function urlFor(params) { | |
81 return this.driver.urlForParts(this.partsForParams(params)); | |
82 }; | |
83 | |
84 Route.prototype.navigateTo = function navigateTo(params) { | |
85 return this.driver.navigateToParts(this.partsForParams(params)); | |
86 } | |
87 | |
88 Route.prototype.isCurrentUrl = function isCurrentUrl(params) { | |
89 if (!this.active) return false; | |
90 var currentKeys = Object.keys(this.params); | |
91 for (var i = 0, key; key = currentKeys[i]; i++) { | |
92 if (this.params[key] !== String(params[key])) { | |
93 return false; | |
94 } | |
95 } | |
96 return true; | |
97 }; | |
98 | |
99 // Driver Interface | |
100 | |
101 Route.prototype.partsForParams = function partsForParams(params, silent) { | |
102 var parts = this.parent && this.parent.partsForParams(params, silent) || []; | |
103 for (var i = 0, config; config = this.compiled[i]; i++) { | |
104 if (config.type === 'static') { | |
105 parts.push(config.part); | |
106 } else if (config.type === 'param') { | |
107 var value | |
108 if (params && config.name in params) { | |
109 value = params[config.name]; | |
110 } else { | |
111 value = this.params[config.name]; | |
112 } | |
113 if (value === undefined) { | |
114 if (silent) { | |
115 return null; | |
116 } else { | |
117 throw new Error('Missing param "' + config.name + '" for route ' + thi
s); | |
118 } | |
119 } | |
120 parts.push(value); | |
121 } | |
122 } | |
123 return parts; | |
124 }; | |
125 | |
126 /** | |
127 * Called by the driver whenever it has detected a change to the URL. | |
128 * | |
129 * @param {Array.<String>|null} parts The parts of the URL, or null if the | |
130 * route should be disabled. | |
131 */ | |
132 Route.prototype.processPathParts = function processPathParts(parts) { | |
133 this.parts = parts; | |
134 this.active = this.matchesPathParts(parts); | |
135 | |
136 // We don't want to notify of these changes; they'd be no-op noise. | |
137 this.params.__silent = true; | |
138 | |
139 if (this.active) { | |
140 var keys = Object.keys(this.params); | |
141 for (var i = 0; i < keys.length; i++) { | |
142 delete this.params[keys[i]]; | |
143 } | |
144 for (var i = 0, config; config = this.compiled[i]; i++) { | |
145 if (config.type === 'param') { | |
146 this.params[config.name] = parts[i]; | |
147 } | |
148 } | |
149 } else { | |
150 for (key in this.params) { | |
151 this.params[key] = undefined; | |
152 } | |
153 } | |
154 | |
155 delete this.params.__silent; | |
156 }; | |
157 | |
158 Route.prototype.matchesPathParts = function matchesPathParts(parts) { | |
159 if (!parts) return false; | |
160 if (parts.length < this.compiled.length) return false; | |
161 for (var i = 0, config; config = this.compiled[i]; i++) { | |
162 if (config.type === 'static' && parts[i] !== config.part) { | |
163 return false; | |
164 } | |
165 } | |
166 return true; | |
167 }; | |
168 | |
169 Route.prototype.toString = function toString() { | |
170 return this.path; | |
171 }; | |
172 | |
173 // Internal Implementation | |
174 | |
175 Route.prototype._compile = function _compile(rawPath) { | |
176 // Not strictly required, but helps us stay consistent w/ `getRoute`, etc. | |
177 if (rawPath.indexOf(PART_SEPARATOR) !== 0) { | |
178 throw new Error('Route paths must begin with a path separator; got: "' + raw
Path + '"'); | |
179 } | |
180 var path = rawPath.substr(PART_SEPARATOR.length); | |
181 if (path === '') return []; | |
182 | |
183 return path.split(PART_SEPARATOR).map(function(part) { | |
184 // raw fragment. | |
185 if (part.substr(0, 1) == PARAM_SENTINEL) { | |
186 return {type: 'param', name: part.substr(1)}; | |
187 } else { | |
188 return {type: 'static', part: part}; | |
189 } | |
190 }); | |
191 }; | |
192 | |
193 Route.prototype._navigateToParams = function _navigateToParams() { | |
194 var parts = this.partsForParams(this.params, true); | |
195 if (!parts) return; | |
196 this.driver.navigateToParts(parts); | |
197 }; | |
198 | |
199 function countParams(compiled) { | |
200 return compiled.reduce(function(count, part) { | |
201 return count + (part.type === 'param' ? 1 : 0); | |
202 }, 0); | |
203 } | |
204 | |
205 function namedParams(compiled) { | |
206 var result = []; | |
207 compiled.forEach(function(part) { | |
208 if (part.type === 'static') return; | |
209 result.push(part.name); | |
210 }); | |
211 return result; | |
212 } | |
213 | |
214 })(window); | |
OLD | NEW |