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.github.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/CONTRIBUTORS.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/PATENTS.txt
9 <link rel=
"import" href=
"emitter.html">
10 <link rel=
"import" href=
"params.html">
14 var MoreRouting
= scope
.MoreRouting
= scope
.MoreRouting
|| {};
15 MoreRouting
.Route
= Route
;
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;
27 function Route(path
, parent
) {
28 // For `MoreRouting.Emitter`; Emits changes for `active`.
29 this.__listeners
= [];
34 this.compiled
= this._compile(this.path
);
38 var params
= MoreRouting
.Params(namedParams(this.compiled
), this.parent
&& this.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'); },
48 // Param values matching the current URL, or an empty object if not `active`.
50 // To make data "binding" easy, `Route` guarantees that `params` will always
51 // be the same object; just make a reference to it.
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
);
58 this.depth
= this.compiled
.length
;
59 this.numParams
= countParams(this.compiled
);
62 Route
.prototype = Object
.create(MoreRouting
.Emitter
);
64 Object
.defineProperty(Route
.prototype, 'active', {
68 set: function(value
) {
69 if (value
!== this._active
);
71 this.__notify('active', value
);
75 Route
.isPath
= function isPath(pathOrName
) {
76 return pathOrName
.indexOf(PART_SEPARATOR
) === 0;
79 Route
.joinPath
= function joinPath(paths
) {
80 var joined
= Array
.prototype.join
.call(arguments
, PART_SEPARATOR
);
81 joined
= joined
.replace(SEPARATOR_CLEANER
, PART_SEPARATOR
);
83 var minLength
= joined
.length
- PART_SEPARATOR
.length
;
84 if (joined
.substr(minLength
) === PART_SEPARATOR
) {
85 joined
= joined
.substr(0, minLength
);
91 Route
.prototype.urlFor
= function urlFor(params
) {
92 return this.driver
.urlForParts(this.partsForParams(params
));
95 Route
.prototype.navigateTo
= function navigateTo(params
) {
96 return this.driver
.navigateToParts(this.partsForParams(params
));
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
])) {
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') {
119 if (params
&& config
.name
in params
) {
120 value
= params
[config
.name
];
122 value
= this.params
[config
.name
];
124 if (value
=== undefined) {
128 throw new Error('Missing param "' + config
.name
+ '" for route ' + this);
138 * Called by the driver whenever it has detected a change to the URL.
140 * @param {Array.<String>|null} parts The parts of the URL, or null if the
141 * route should be disabled.
143 Route
.prototype.processPathParts
= function processPathParts(parts
) {
145 this.active
= this.matchesPathParts(parts
);
147 // We don't want to notify of these changes; they'd be no-op noise.
148 this.params
.__silent
= true;
151 var keys
= Object
.keys(this.params
);
152 for (var i
= 0; i
< keys
.length
; i
++) {
153 delete this.params
[keys
[i
]];
155 for (var i
= 0, config
; config
= this.compiled
[i
]; i
++) {
156 if (config
.type
=== 'param') {
157 this.params
[config
.name
] = parts
[i
];
161 for (key
in this.params
) {
162 this.params
[key
] = undefined;
166 delete this.params
.__silent
;
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
) {
180 Route
.prototype.toString
= function toString() {
184 // Internal Implementation
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: "' + rawPath
+ '"');
191 var path
= rawPath
.substr(PART_SEPARATOR
.length
);
192 if (path
=== '') return [];
194 return path
.split(PART_SEPARATOR
).map(function(part
) {
196 if (part
.substr(0, 1) == PARAM_SENTINEL
) {
197 return {type
: 'param', name
: part
.substr(1)};
199 return {type
: 'static', part
: part
};
204 Route
.prototype._navigateToParams
= function _navigateToParams() {
205 var parts
= this.partsForParams(this.params
, true);
207 this.driver
.navigateToParts(parts
);
210 function countParams(compiled
) {
211 return compiled
.reduce(function(count
, part
) {
212 return count
+ (part
.type
=== 'param' ? 1 : 0);
216 function namedParams(compiled
) {
218 compiled
.forEach(function(part
) {
219 if (part
.type
=== 'static') return;
220 result
.push(part
.name
);