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