angular-ui-router-rails 0.2.15

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 17cb7d79e4bdb83ed169a686b3ada43db6d3bd3e
4
+ data.tar.gz: 4ad1c124fe344339c0cc6aa83561c86f8f26c8d4
5
+ SHA512:
6
+ metadata.gz: 337fa294b05d35989e4503225b8af21cefe891a2c9f4802a5ff2af4af1dd3929a6a861f497a3e70bd919f33e5f111b97313e335a37bdc0b7003b8d9f77082d72
7
+ data.tar.gz: 4baafc97bec7c7c66ad97f8f851eeaa8c13bed8d4cc40f0314e827e21ff27a2cdaca5522430c6815928bc8a771544a3307b8d4b3b075adf5abc3bc41183d60ae
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Andrew H. Yi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,24 @@
1
+ # angular-ui-router-rails
2
+
3
+ angular-ui-router-rails wraps the [Angular UI Router](https://github.com/angular-ui/ui-router) library for use in Rails 4.0 and above. Assets will minify automatically during production.
4
+
5
+ ## Usage
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'angular-ui-router-rails'
11
+ ```
12
+
13
+ Add the following directive to your JavaScript manifest file (application.js):
14
+
15
+ //= require angular-ui-router
16
+
17
+ ## Contributing
18
+
19
+ Bug reports and pull requests are welcome on GitHub at https://github.com/AndrewHYi/angular-ui-router-rails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
20
+
21
+
22
+ ## License
23
+
24
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,11 @@
1
+ require "angular-ui-router-rails/version"
2
+
3
+ module AngularUIRouter
4
+ module Rails
5
+ if defined? ::Rails::Engine
6
+ require "angular-ui-router-rails/engine"
7
+ elsif defined? Sprockets
8
+ require "angular-ui-router-rails/sprockets"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module AngularUIRouter
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ require 'sprockets'
2
+
3
+ Sprockets.append_path File.expand_path("../../../vendor/assets/javascripts", __FILE__)
@@ -0,0 +1,5 @@
1
+ module AngularUIRouter
2
+ module Rails
3
+ VERSION = "0.2.15"
4
+ end
5
+ end
@@ -0,0 +1,4370 @@
1
+ /**
2
+ * State-based routing for AngularJS
3
+ * @version v0.2.15
4
+ * @link http://angular-ui.github.com/
5
+ * @license MIT License, http://www.opensource.org/licenses/MIT
6
+ */
7
+
8
+ /* commonjs package manager support (eg componentjs) */
9
+ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){
10
+ module.exports = 'ui.router';
11
+ }
12
+
13
+ (function (window, angular, undefined) {
14
+ /*jshint globalstrict:true*/
15
+ /*global angular:false*/
16
+ 'use strict';
17
+
18
+ var isDefined = angular.isDefined,
19
+ isFunction = angular.isFunction,
20
+ isString = angular.isString,
21
+ isObject = angular.isObject,
22
+ isArray = angular.isArray,
23
+ forEach = angular.forEach,
24
+ extend = angular.extend,
25
+ copy = angular.copy;
26
+
27
+ function inherit(parent, extra) {
28
+ return extend(new (extend(function() {}, { prototype: parent }))(), extra);
29
+ }
30
+
31
+ function merge(dst) {
32
+ forEach(arguments, function(obj) {
33
+ if (obj !== dst) {
34
+ forEach(obj, function(value, key) {
35
+ if (!dst.hasOwnProperty(key)) dst[key] = value;
36
+ });
37
+ }
38
+ });
39
+ return dst;
40
+ }
41
+
42
+ /**
43
+ * Finds the common ancestor path between two states.
44
+ *
45
+ * @param {Object} first The first state.
46
+ * @param {Object} second The second state.
47
+ * @return {Array} Returns an array of state names in descending order, not including the root.
48
+ */
49
+ function ancestors(first, second) {
50
+ var path = [];
51
+
52
+ for (var n in first.path) {
53
+ if (first.path[n] !== second.path[n]) break;
54
+ path.push(first.path[n]);
55
+ }
56
+ return path;
57
+ }
58
+
59
+ /**
60
+ * IE8-safe wrapper for `Object.keys()`.
61
+ *
62
+ * @param {Object} object A JavaScript object.
63
+ * @return {Array} Returns the keys of the object as an array.
64
+ */
65
+ function objectKeys(object) {
66
+ if (Object.keys) {
67
+ return Object.keys(object);
68
+ }
69
+ var result = [];
70
+
71
+ forEach(object, function(val, key) {
72
+ result.push(key);
73
+ });
74
+ return result;
75
+ }
76
+
77
+ /**
78
+ * IE8-safe wrapper for `Array.prototype.indexOf()`.
79
+ *
80
+ * @param {Array} array A JavaScript array.
81
+ * @param {*} value A value to search the array for.
82
+ * @return {Number} Returns the array index value of `value`, or `-1` if not present.
83
+ */
84
+ function indexOf(array, value) {
85
+ if (Array.prototype.indexOf) {
86
+ return array.indexOf(value, Number(arguments[2]) || 0);
87
+ }
88
+ var len = array.length >>> 0, from = Number(arguments[2]) || 0;
89
+ from = (from < 0) ? Math.ceil(from) : Math.floor(from);
90
+
91
+ if (from < 0) from += len;
92
+
93
+ for (; from < len; from++) {
94
+ if (from in array && array[from] === value) return from;
95
+ }
96
+ return -1;
97
+ }
98
+
99
+ /**
100
+ * Merges a set of parameters with all parameters inherited between the common parents of the
101
+ * current state and a given destination state.
102
+ *
103
+ * @param {Object} currentParams The value of the current state parameters ($stateParams).
104
+ * @param {Object} newParams The set of parameters which will be composited with inherited params.
105
+ * @param {Object} $current Internal definition of object representing the current state.
106
+ * @param {Object} $to Internal definition of object representing state to transition to.
107
+ */
108
+ function inheritParams(currentParams, newParams, $current, $to) {
109
+ var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = [];
110
+
111
+ for (var i in parents) {
112
+ if (!parents[i].params) continue;
113
+ parentParams = objectKeys(parents[i].params);
114
+ if (!parentParams.length) continue;
115
+
116
+ for (var j in parentParams) {
117
+ if (indexOf(inheritList, parentParams[j]) >= 0) continue;
118
+ inheritList.push(parentParams[j]);
119
+ inherited[parentParams[j]] = currentParams[parentParams[j]];
120
+ }
121
+ }
122
+ return extend({}, inherited, newParams);
123
+ }
124
+
125
+ /**
126
+ * Performs a non-strict comparison of the subset of two objects, defined by a list of keys.
127
+ *
128
+ * @param {Object} a The first object.
129
+ * @param {Object} b The second object.
130
+ * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified,
131
+ * it defaults to the list of keys in `a`.
132
+ * @return {Boolean} Returns `true` if the keys match, otherwise `false`.
133
+ */
134
+ function equalForKeys(a, b, keys) {
135
+ if (!keys) {
136
+ keys = [];
137
+ for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility
138
+ }
139
+
140
+ for (var i=0; i<keys.length; i++) {
141
+ var k = keys[i];
142
+ if (a[k] != b[k]) return false; // Not '===', values aren't necessarily normalized
143
+ }
144
+ return true;
145
+ }
146
+
147
+ /**
148
+ * Returns the subset of an object, based on a list of keys.
149
+ *
150
+ * @param {Array} keys
151
+ * @param {Object} values
152
+ * @return {Boolean} Returns a subset of `values`.
153
+ */
154
+ function filterByKeys(keys, values) {
155
+ var filtered = {};
156
+
157
+ forEach(keys, function (name) {
158
+ filtered[name] = values[name];
159
+ });
160
+ return filtered;
161
+ }
162
+
163
+ // like _.indexBy
164
+ // when you know that your index values will be unique, or you want last-one-in to win
165
+ function indexBy(array, propName) {
166
+ var result = {};
167
+ forEach(array, function(item) {
168
+ result[item[propName]] = item;
169
+ });
170
+ return result;
171
+ }
172
+
173
+ // extracted from underscore.js
174
+ // Return a copy of the object only containing the whitelisted properties.
175
+ function pick(obj) {
176
+ var copy = {};
177
+ var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
178
+ forEach(keys, function(key) {
179
+ if (key in obj) copy[key] = obj[key];
180
+ });
181
+ return copy;
182
+ }
183
+
184
+ // extracted from underscore.js
185
+ // Return a copy of the object omitting the blacklisted properties.
186
+ function omit(obj) {
187
+ var copy = {};
188
+ var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
189
+ for (var key in obj) {
190
+ if (indexOf(keys, key) == -1) copy[key] = obj[key];
191
+ }
192
+ return copy;
193
+ }
194
+
195
+ function pluck(collection, key) {
196
+ var result = isArray(collection) ? [] : {};
197
+
198
+ forEach(collection, function(val, i) {
199
+ result[i] = isFunction(key) ? key(val) : val[key];
200
+ });
201
+ return result;
202
+ }
203
+
204
+ function filter(collection, callback) {
205
+ var array = isArray(collection);
206
+ var result = array ? [] : {};
207
+ forEach(collection, function(val, i) {
208
+ if (callback(val, i)) {
209
+ result[array ? result.length : i] = val;
210
+ }
211
+ });
212
+ return result;
213
+ }
214
+
215
+ function map(collection, callback) {
216
+ var result = isArray(collection) ? [] : {};
217
+
218
+ forEach(collection, function(val, i) {
219
+ result[i] = callback(val, i);
220
+ });
221
+ return result;
222
+ }
223
+
224
+ /**
225
+ * @ngdoc overview
226
+ * @name ui.router.util
227
+ *
228
+ * @description
229
+ * # ui.router.util sub-module
230
+ *
231
+ * This module is a dependency of other sub-modules. Do not include this module as a dependency
232
+ * in your angular app (use {@link ui.router} module instead).
233
+ *
234
+ */
235
+ angular.module('ui.router.util', ['ng']);
236
+
237
+ /**
238
+ * @ngdoc overview
239
+ * @name ui.router.router
240
+ *
241
+ * @requires ui.router.util
242
+ *
243
+ * @description
244
+ * # ui.router.router sub-module
245
+ *
246
+ * This module is a dependency of other sub-modules. Do not include this module as a dependency
247
+ * in your angular app (use {@link ui.router} module instead).
248
+ */
249
+ angular.module('ui.router.router', ['ui.router.util']);
250
+
251
+ /**
252
+ * @ngdoc overview
253
+ * @name ui.router.state
254
+ *
255
+ * @requires ui.router.router
256
+ * @requires ui.router.util
257
+ *
258
+ * @description
259
+ * # ui.router.state sub-module
260
+ *
261
+ * This module is a dependency of the main ui.router module. Do not include this module as a dependency
262
+ * in your angular app (use {@link ui.router} module instead).
263
+ *
264
+ */
265
+ angular.module('ui.router.state', ['ui.router.router', 'ui.router.util']);
266
+
267
+ /**
268
+ * @ngdoc overview
269
+ * @name ui.router
270
+ *
271
+ * @requires ui.router.state
272
+ *
273
+ * @description
274
+ * # ui.router
275
+ *
276
+ * ## The main module for ui.router
277
+ * There are several sub-modules included with the ui.router module, however only this module is needed
278
+ * as a dependency within your angular app. The other modules are for organization purposes.
279
+ *
280
+ * The modules are:
281
+ * * ui.router - the main "umbrella" module
282
+ * * ui.router.router -
283
+ *
284
+ * *You'll need to include **only** this module as the dependency within your angular app.*
285
+ *
286
+ * <pre>
287
+ * <!doctype html>
288
+ * <html ng-app="myApp">
289
+ * <head>
290
+ * <script src="js/angular.js"></script>
291
+ * <!-- Include the ui-router script -->
292
+ * <script src="js/angular-ui-router.min.js"></script>
293
+ * <script>
294
+ * // ...and add 'ui.router' as a dependency
295
+ * var myApp = angular.module('myApp', ['ui.router']);
296
+ * </script>
297
+ * </head>
298
+ * <body>
299
+ * </body>
300
+ * </html>
301
+ * </pre>
302
+ */
303
+ angular.module('ui.router', ['ui.router.state']);
304
+
305
+ angular.module('ui.router.compat', ['ui.router']);
306
+
307
+ /**
308
+ * @ngdoc object
309
+ * @name ui.router.util.$resolve
310
+ *
311
+ * @requires $q
312
+ * @requires $injector
313
+ *
314
+ * @description
315
+ * Manages resolution of (acyclic) graphs of promises.
316
+ */
317
+ $Resolve.$inject = ['$q', '$injector'];
318
+ function $Resolve( $q, $injector) {
319
+
320
+ var VISIT_IN_PROGRESS = 1,
321
+ VISIT_DONE = 2,
322
+ NOTHING = {},
323
+ NO_DEPENDENCIES = [],
324
+ NO_LOCALS = NOTHING,
325
+ NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING });
326
+
327
+
328
+ /**
329
+ * @ngdoc function
330
+ * @name ui.router.util.$resolve#study
331
+ * @methodOf ui.router.util.$resolve
332
+ *
333
+ * @description
334
+ * Studies a set of invocables that are likely to be used multiple times.
335
+ * <pre>
336
+ * $resolve.study(invocables)(locals, parent, self)
337
+ * </pre>
338
+ * is equivalent to
339
+ * <pre>
340
+ * $resolve.resolve(invocables, locals, parent, self)
341
+ * </pre>
342
+ * but the former is more efficient (in fact `resolve` just calls `study`
343
+ * internally).
344
+ *
345
+ * @param {object} invocables Invocable objects
346
+ * @return {function} a function to pass in locals, parent and self
347
+ */
348
+ this.study = function (invocables) {
349
+ if (!isObject(invocables)) throw new Error("'invocables' must be an object");
350
+ var invocableKeys = objectKeys(invocables || {});
351
+
352
+ // Perform a topological sort of invocables to build an ordered plan
353
+ var plan = [], cycle = [], visited = {};
354
+ function visit(value, key) {
355
+ if (visited[key] === VISIT_DONE) return;
356
+
357
+ cycle.push(key);
358
+ if (visited[key] === VISIT_IN_PROGRESS) {
359
+ cycle.splice(0, indexOf(cycle, key));
360
+ throw new Error("Cyclic dependency: " + cycle.join(" -> "));
361
+ }
362
+ visited[key] = VISIT_IN_PROGRESS;
363
+
364
+ if (isString(value)) {
365
+ plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES);
366
+ } else {
367
+ var params = $injector.annotate(value);
368
+ forEach(params, function (param) {
369
+ if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param);
370
+ });
371
+ plan.push(key, value, params);
372
+ }
373
+
374
+ cycle.pop();
375
+ visited[key] = VISIT_DONE;
376
+ }
377
+ forEach(invocables, visit);
378
+ invocables = cycle = visited = null; // plan is all that's required
379
+
380
+ function isResolve(value) {
381
+ return isObject(value) && value.then && value.$$promises;
382
+ }
383
+
384
+ return function (locals, parent, self) {
385
+ if (isResolve(locals) && self === undefined) {
386
+ self = parent; parent = locals; locals = null;
387
+ }
388
+ if (!locals) locals = NO_LOCALS;
389
+ else if (!isObject(locals)) {
390
+ throw new Error("'locals' must be an object");
391
+ }
392
+ if (!parent) parent = NO_PARENT;
393
+ else if (!isResolve(parent)) {
394
+ throw new Error("'parent' must be a promise returned by $resolve.resolve()");
395
+ }
396
+
397
+ // To complete the overall resolution, we have to wait for the parent
398
+ // promise and for the promise for each invokable in our plan.
399
+ var resolution = $q.defer(),
400
+ result = resolution.promise,
401
+ promises = result.$$promises = {},
402
+ values = extend({}, locals),
403
+ wait = 1 + plan.length/3,
404
+ merged = false;
405
+
406
+ function done() {
407
+ // Merge parent values we haven't got yet and publish our own $$values
408
+ if (!--wait) {
409
+ if (!merged) merge(values, parent.$$values);
410
+ result.$$values = values;
411
+ result.$$promises = result.$$promises || true; // keep for isResolve()
412
+ delete result.$$inheritedValues;
413
+ resolution.resolve(values);
414
+ }
415
+ }
416
+
417
+ function fail(reason) {
418
+ result.$$failure = reason;
419
+ resolution.reject(reason);
420
+ }
421
+
422
+ // Short-circuit if parent has already failed
423
+ if (isDefined(parent.$$failure)) {
424
+ fail(parent.$$failure);
425
+ return result;
426
+ }
427
+
428
+ if (parent.$$inheritedValues) {
429
+ merge(values, omit(parent.$$inheritedValues, invocableKeys));
430
+ }
431
+
432
+ // Merge parent values if the parent has already resolved, or merge
433
+ // parent promises and wait if the parent resolve is still in progress.
434
+ extend(promises, parent.$$promises);
435
+ if (parent.$$values) {
436
+ merged = merge(values, omit(parent.$$values, invocableKeys));
437
+ result.$$inheritedValues = omit(parent.$$values, invocableKeys);
438
+ done();
439
+ } else {
440
+ if (parent.$$inheritedValues) {
441
+ result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys);
442
+ }
443
+ parent.then(done, fail);
444
+ }
445
+
446
+ // Process each invocable in the plan, but ignore any where a local of the same name exists.
447
+ for (var i=0, ii=plan.length; i<ii; i+=3) {
448
+ if (locals.hasOwnProperty(plan[i])) done();
449
+ else invoke(plan[i], plan[i+1], plan[i+2]);
450
+ }
451
+
452
+ function invoke(key, invocable, params) {
453
+ // Create a deferred for this invocation. Failures will propagate to the resolution as well.
454
+ var invocation = $q.defer(), waitParams = 0;
455
+ function onfailure(reason) {
456
+ invocation.reject(reason);
457
+ fail(reason);
458
+ }
459
+ // Wait for any parameter that we have a promise for (either from parent or from this
460
+ // resolve; in that case study() will have made sure it's ordered before us in the plan).
461
+ forEach(params, function (dep) {
462
+ if (promises.hasOwnProperty(dep) && !locals.hasOwnProperty(dep)) {
463
+ waitParams++;
464
+ promises[dep].then(function (result) {
465
+ values[dep] = result;
466
+ if (!(--waitParams)) proceed();
467
+ }, onfailure);
468
+ }
469
+ });
470
+ if (!waitParams) proceed();
471
+ function proceed() {
472
+ if (isDefined(result.$$failure)) return;
473
+ try {
474
+ invocation.resolve($injector.invoke(invocable, self, values));
475
+ invocation.promise.then(function (result) {
476
+ values[key] = result;
477
+ done();
478
+ }, onfailure);
479
+ } catch (e) {
480
+ onfailure(e);
481
+ }
482
+ }
483
+ // Publish promise synchronously; invocations further down in the plan may depend on it.
484
+ promises[key] = invocation.promise;
485
+ }
486
+
487
+ return result;
488
+ };
489
+ };
490
+
491
+ /**
492
+ * @ngdoc function
493
+ * @name ui.router.util.$resolve#resolve
494
+ * @methodOf ui.router.util.$resolve
495
+ *
496
+ * @description
497
+ * Resolves a set of invocables. An invocable is a function to be invoked via
498
+ * `$injector.invoke()`, and can have an arbitrary number of dependencies.
499
+ * An invocable can either return a value directly,
500
+ * or a `$q` promise. If a promise is returned it will be resolved and the
501
+ * resulting value will be used instead. Dependencies of invocables are resolved
502
+ * (in this order of precedence)
503
+ *
504
+ * - from the specified `locals`
505
+ * - from another invocable that is part of this `$resolve` call
506
+ * - from an invocable that is inherited from a `parent` call to `$resolve`
507
+ * (or recursively
508
+ * - from any ancestor `$resolve` of that parent).
509
+ *
510
+ * The return value of `$resolve` is a promise for an object that contains
511
+ * (in this order of precedence)
512
+ *
513
+ * - any `locals` (if specified)
514
+ * - the resolved return values of all injectables
515
+ * - any values inherited from a `parent` call to `$resolve` (if specified)
516
+ *
517
+ * The promise will resolve after the `parent` promise (if any) and all promises
518
+ * returned by injectables have been resolved. If any invocable
519
+ * (or `$injector.invoke`) throws an exception, or if a promise returned by an
520
+ * invocable is rejected, the `$resolve` promise is immediately rejected with the
521
+ * same error. A rejection of a `parent` promise (if specified) will likewise be
522
+ * propagated immediately. Once the `$resolve` promise has been rejected, no
523
+ * further invocables will be called.
524
+ *
525
+ * Cyclic dependencies between invocables are not permitted and will caues `$resolve`
526
+ * to throw an error. As a special case, an injectable can depend on a parameter
527
+ * with the same name as the injectable, which will be fulfilled from the `parent`
528
+ * injectable of the same name. This allows inherited values to be decorated.
529
+ * Note that in this case any other injectable in the same `$resolve` with the same
530
+ * dependency would see the decorated value, not the inherited value.
531
+ *
532
+ * Note that missing dependencies -- unlike cyclic dependencies -- will cause an
533
+ * (asynchronous) rejection of the `$resolve` promise rather than a (synchronous)
534
+ * exception.
535
+ *
536
+ * Invocables are invoked eagerly as soon as all dependencies are available.
537
+ * This is true even for dependencies inherited from a `parent` call to `$resolve`.
538
+ *
539
+ * As a special case, an invocable can be a string, in which case it is taken to
540
+ * be a service name to be passed to `$injector.get()`. This is supported primarily
541
+ * for backwards-compatibility with the `resolve` property of `$routeProvider`
542
+ * routes.
543
+ *
544
+ * @param {object} invocables functions to invoke or
545
+ * `$injector` services to fetch.
546
+ * @param {object} locals values to make available to the injectables
547
+ * @param {object} parent a promise returned by another call to `$resolve`.
548
+ * @param {object} self the `this` for the invoked methods
549
+ * @return {object} Promise for an object that contains the resolved return value
550
+ * of all invocables, as well as any inherited and local values.
551
+ */
552
+ this.resolve = function (invocables, locals, parent, self) {
553
+ return this.study(invocables)(locals, parent, self);
554
+ };
555
+ }
556
+
557
+ angular.module('ui.router.util').service('$resolve', $Resolve);
558
+
559
+
560
+ /**
561
+ * @ngdoc object
562
+ * @name ui.router.util.$templateFactory
563
+ *
564
+ * @requires $http
565
+ * @requires $templateCache
566
+ * @requires $injector
567
+ *
568
+ * @description
569
+ * Service. Manages loading of templates.
570
+ */
571
+ $TemplateFactory.$inject = ['$http', '$templateCache', '$injector'];
572
+ function $TemplateFactory( $http, $templateCache, $injector) {
573
+
574
+ /**
575
+ * @ngdoc function
576
+ * @name ui.router.util.$templateFactory#fromConfig
577
+ * @methodOf ui.router.util.$templateFactory
578
+ *
579
+ * @description
580
+ * Creates a template from a configuration object.
581
+ *
582
+ * @param {object} config Configuration object for which to load a template.
583
+ * The following properties are search in the specified order, and the first one
584
+ * that is defined is used to create the template:
585
+ *
586
+ * @param {string|object} config.template html string template or function to
587
+ * load via {@link ui.router.util.$templateFactory#fromString fromString}.
588
+ * @param {string|object} config.templateUrl url to load or a function returning
589
+ * the url to load via {@link ui.router.util.$templateFactory#fromUrl fromUrl}.
590
+ * @param {Function} config.templateProvider function to invoke via
591
+ * {@link ui.router.util.$templateFactory#fromProvider fromProvider}.
592
+ * @param {object} params Parameters to pass to the template function.
593
+ * @param {object} locals Locals to pass to `invoke` if the template is loaded
594
+ * via a `templateProvider`. Defaults to `{ params: params }`.
595
+ *
596
+ * @return {string|object} The template html as a string, or a promise for
597
+ * that string,or `null` if no template is configured.
598
+ */
599
+ this.fromConfig = function (config, params, locals) {
600
+ return (
601
+ isDefined(config.template) ? this.fromString(config.template, params) :
602
+ isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) :
603
+ isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) :
604
+ null
605
+ );
606
+ };
607
+
608
+ /**
609
+ * @ngdoc function
610
+ * @name ui.router.util.$templateFactory#fromString
611
+ * @methodOf ui.router.util.$templateFactory
612
+ *
613
+ * @description
614
+ * Creates a template from a string or a function returning a string.
615
+ *
616
+ * @param {string|object} template html template as a string or function that
617
+ * returns an html template as a string.
618
+ * @param {object} params Parameters to pass to the template function.
619
+ *
620
+ * @return {string|object} The template html as a string, or a promise for that
621
+ * string.
622
+ */
623
+ this.fromString = function (template, params) {
624
+ return isFunction(template) ? template(params) : template;
625
+ };
626
+
627
+ /**
628
+ * @ngdoc function
629
+ * @name ui.router.util.$templateFactory#fromUrl
630
+ * @methodOf ui.router.util.$templateFactory
631
+ *
632
+ * @description
633
+ * Loads a template from the a URL via `$http` and `$templateCache`.
634
+ *
635
+ * @param {string|Function} url url of the template to load, or a function
636
+ * that returns a url.
637
+ * @param {Object} params Parameters to pass to the url function.
638
+ * @return {string|Promise.<string>} The template html as a string, or a promise
639
+ * for that string.
640
+ */
641
+ this.fromUrl = function (url, params) {
642
+ if (isFunction(url)) url = url(params);
643
+ if (url == null) return null;
644
+ else return $http
645
+ .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }})
646
+ .then(function(response) { return response.data; });
647
+ };
648
+
649
+ /**
650
+ * @ngdoc function
651
+ * @name ui.router.util.$templateFactory#fromProvider
652
+ * @methodOf ui.router.util.$templateFactory
653
+ *
654
+ * @description
655
+ * Creates a template by invoking an injectable provider function.
656
+ *
657
+ * @param {Function} provider Function to invoke via `$injector.invoke`
658
+ * @param {Object} params Parameters for the template.
659
+ * @param {Object} locals Locals to pass to `invoke`. Defaults to
660
+ * `{ params: params }`.
661
+ * @return {string|Promise.<string>} The template html as a string, or a promise
662
+ * for that string.
663
+ */
664
+ this.fromProvider = function (provider, params, locals) {
665
+ return $injector.invoke(provider, null, locals || { params: params });
666
+ };
667
+ }
668
+
669
+ angular.module('ui.router.util').service('$templateFactory', $TemplateFactory);
670
+
671
+ var $$UMFP; // reference to $UrlMatcherFactoryProvider
672
+
673
+ /**
674
+ * @ngdoc object
675
+ * @name ui.router.util.type:UrlMatcher
676
+ *
677
+ * @description
678
+ * Matches URLs against patterns and extracts named parameters from the path or the search
679
+ * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list
680
+ * of search parameters. Multiple search parameter names are separated by '&'. Search parameters
681
+ * do not influence whether or not a URL is matched, but their values are passed through into
682
+ * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}.
683
+ *
684
+ * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace
685
+ * syntax, which optionally allows a regular expression for the parameter to be specified:
686
+ *
687
+ * * `':'` name - colon placeholder
688
+ * * `'*'` name - catch-all placeholder
689
+ * * `'{' name '}'` - curly placeholder
690
+ * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the
691
+ * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash.
692
+ *
693
+ * Parameter names may contain only word characters (latin letters, digits, and underscore) and
694
+ * must be unique within the pattern (across both path and search parameters). For colon
695
+ * placeholders or curly placeholders without an explicit regexp, a path parameter matches any
696
+ * number of characters other than '/'. For catch-all placeholders the path parameter matches
697
+ * any number of characters.
698
+ *
699
+ * Examples:
700
+ *
701
+ * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for
702
+ * trailing slashes, and patterns have to match the entire path, not just a prefix.
703
+ * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or
704
+ * '/user/bob/details'. The second path segment will be captured as the parameter 'id'.
705
+ * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax.
706
+ * * `'/user/{id:[^/]*}'` - Same as the previous example.
707
+ * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id
708
+ * parameter consists of 1 to 8 hex digits.
709
+ * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the
710
+ * path into the parameter 'path'.
711
+ * * `'/files/*path'` - ditto.
712
+ * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined
713
+ * in the built-in `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start
714
+ *
715
+ * @param {string} pattern The pattern to compile into a matcher.
716
+ * @param {Object} config A configuration object hash:
717
+ * @param {Object=} parentMatcher Used to concatenate the pattern/config onto
718
+ * an existing UrlMatcher
719
+ *
720
+ * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`.
721
+ * * `strict` - `false` if matching against a URL with a trailing slash should be treated as equivalent to a URL without a trailing slash, the default value is `true`.
722
+ *
723
+ * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any
724
+ * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns
725
+ * non-null) will start with this prefix.
726
+ *
727
+ * @property {string} source The pattern that was passed into the constructor
728
+ *
729
+ * @property {string} sourcePath The path portion of the source property
730
+ *
731
+ * @property {string} sourceSearch The search portion of the source property
732
+ *
733
+ * @property {string} regex The constructed regex that will be used to match against the url when
734
+ * it is time to determine which url will match.
735
+ *
736
+ * @returns {Object} New `UrlMatcher` object
737
+ */
738
+ function UrlMatcher(pattern, config, parentMatcher) {
739
+ config = extend({ params: {} }, isObject(config) ? config : {});
740
+
741
+ // Find all placeholders and create a compiled pattern, using either classic or curly syntax:
742
+ // '*' name
743
+ // ':' name
744
+ // '{' name '}'
745
+ // '{' name ':' regexp '}'
746
+ // The regular expression is somewhat complicated due to the need to allow curly braces
747
+ // inside the regular expression. The placeholder regexp breaks down as follows:
748
+ // ([:*])([\w\[\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case)
749
+ // \{([\w\[\]]+)(?:\:( ... ))?\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case
750
+ // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either
751
+ // [^{}\\]+ - anything other than curly braces or backslash
752
+ // \\. - a backslash escape
753
+ // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms
754
+ var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
755
+ searchPlaceholder = /([:]?)([\w\[\]-]+)|\{([\w\[\]-]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
756
+ compiled = '^', last = 0, m,
757
+ segments = this.segments = [],
758
+ parentParams = parentMatcher ? parentMatcher.params : {},
759
+ params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(),
760
+ paramNames = [];
761
+
762
+ function addParameter(id, type, config, location) {
763
+ paramNames.push(id);
764
+ if (parentParams[id]) return parentParams[id];
765
+ if (!/^\w+(-+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
766
+ if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
767
+ params[id] = new $$UMFP.Param(id, type, config, location);
768
+ return params[id];
769
+ }
770
+
771
+ function quoteRegExp(string, pattern, squash, optional) {
772
+ var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
773
+ if (!pattern) return result;
774
+ switch(squash) {
775
+ case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break;
776
+ case true: surroundPattern = ['?(', ')?']; break;
777
+ default: surroundPattern = ['(' + squash + "|", ')?']; break;
778
+ }
779
+ return result + surroundPattern[0] + pattern + surroundPattern[1];
780
+ }
781
+
782
+ this.source = pattern;
783
+
784
+ // Split into static segments separated by path parameter placeholders.
785
+ // The number of segments is always 1 more than the number of parameters.
786
+ function matchDetails(m, isSearch) {
787
+ var id, regexp, segment, type, cfg, arrayMode;
788
+ id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
789
+ cfg = config.params[id];
790
+ segment = pattern.substring(last, m.index);
791
+ regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null);
792
+ type = $$UMFP.type(regexp || "string") || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) });
793
+ return {
794
+ id: id, regexp: regexp, segment: segment, type: type, cfg: cfg
795
+ };
796
+ }
797
+
798
+ var p, param, segment;
799
+ while ((m = placeholder.exec(pattern))) {
800
+ p = matchDetails(m, false);
801
+ if (p.segment.indexOf('?') >= 0) break; // we're into the search part
802
+
803
+ param = addParameter(p.id, p.type, p.cfg, "path");
804
+ compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional);
805
+ segments.push(p.segment);
806
+ last = placeholder.lastIndex;
807
+ }
808
+ segment = pattern.substring(last);
809
+
810
+ // Find any search parameter names and remove them from the last segment
811
+ var i = segment.indexOf('?');
812
+
813
+ if (i >= 0) {
814
+ var search = this.sourceSearch = segment.substring(i);
815
+ segment = segment.substring(0, i);
816
+ this.sourcePath = pattern.substring(0, last + i);
817
+
818
+ if (search.length > 0) {
819
+ last = 0;
820
+ while ((m = searchPlaceholder.exec(search))) {
821
+ p = matchDetails(m, true);
822
+ param = addParameter(p.id, p.type, p.cfg, "search");
823
+ last = placeholder.lastIndex;
824
+ // check if ?&
825
+ }
826
+ }
827
+ } else {
828
+ this.sourcePath = pattern;
829
+ this.sourceSearch = '';
830
+ }
831
+
832
+ compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$';
833
+ segments.push(segment);
834
+
835
+ this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
836
+ this.prefix = segments[0];
837
+ this.$$paramNames = paramNames;
838
+ }
839
+
840
+ /**
841
+ * @ngdoc function
842
+ * @name ui.router.util.type:UrlMatcher#concat
843
+ * @methodOf ui.router.util.type:UrlMatcher
844
+ *
845
+ * @description
846
+ * Returns a new matcher for a pattern constructed by appending the path part and adding the
847
+ * search parameters of the specified pattern to this pattern. The current pattern is not
848
+ * modified. This can be understood as creating a pattern for URLs that are relative to (or
849
+ * suffixes of) the current pattern.
850
+ *
851
+ * @example
852
+ * The following two matchers are equivalent:
853
+ * <pre>
854
+ * new UrlMatcher('/user/{id}?q').concat('/details?date');
855
+ * new UrlMatcher('/user/{id}/details?q&date');
856
+ * </pre>
857
+ *
858
+ * @param {string} pattern The pattern to append.
859
+ * @param {Object} config An object hash of the configuration for the matcher.
860
+ * @returns {UrlMatcher} A matcher for the concatenated pattern.
861
+ */
862
+ UrlMatcher.prototype.concat = function (pattern, config) {
863
+ // Because order of search parameters is irrelevant, we can add our own search
864
+ // parameters to the end of the new pattern. Parse the new pattern by itself
865
+ // and then join the bits together, but it's much easier to do this on a string level.
866
+ var defaultConfig = {
867
+ caseInsensitive: $$UMFP.caseInsensitive(),
868
+ strict: $$UMFP.strictMode(),
869
+ squash: $$UMFP.defaultSquashPolicy()
870
+ };
871
+ return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this);
872
+ };
873
+
874
+ UrlMatcher.prototype.toString = function () {
875
+ return this.source;
876
+ };
877
+
878
+ /**
879
+ * @ngdoc function
880
+ * @name ui.router.util.type:UrlMatcher#exec
881
+ * @methodOf ui.router.util.type:UrlMatcher
882
+ *
883
+ * @description
884
+ * Tests the specified path against this matcher, and returns an object containing the captured
885
+ * parameter values, or null if the path does not match. The returned object contains the values
886
+ * of any search parameters that are mentioned in the pattern, but their value may be null if
887
+ * they are not present in `searchParams`. This means that search parameters are always treated
888
+ * as optional.
889
+ *
890
+ * @example
891
+ * <pre>
892
+ * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {
893
+ * x: '1', q: 'hello'
894
+ * });
895
+ * // returns { id: 'bob', q: 'hello', r: null }
896
+ * </pre>
897
+ *
898
+ * @param {string} path The URL path to match, e.g. `$location.path()`.
899
+ * @param {Object} searchParams URL search parameters, e.g. `$location.search()`.
900
+ * @returns {Object} The captured parameter values.
901
+ */
902
+ UrlMatcher.prototype.exec = function (path, searchParams) {
903
+ var m = this.regexp.exec(path);
904
+ if (!m) return null;
905
+ searchParams = searchParams || {};
906
+
907
+ var paramNames = this.parameters(), nTotal = paramNames.length,
908
+ nPath = this.segments.length - 1,
909
+ values = {}, i, j, cfg, paramName;
910
+
911
+ if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'");
912
+
913
+ function decodePathArray(string) {
914
+ function reverseString(str) { return str.split("").reverse().join(""); }
915
+ function unquoteDashes(str) { return str.replace(/\\-/g, "-"); }
916
+
917
+ var split = reverseString(string).split(/-(?!\\)/);
918
+ var allReversed = map(split, reverseString);
919
+ return map(allReversed, unquoteDashes).reverse();
920
+ }
921
+
922
+ for (i = 0; i < nPath; i++) {
923
+ paramName = paramNames[i];
924
+ var param = this.params[paramName];
925
+ var paramVal = m[i+1];
926
+ // if the param value matches a pre-replace pair, replace the value before decoding.
927
+ for (j = 0; j < param.replace; j++) {
928
+ if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
929
+ }
930
+ if (paramVal && param.array === true) paramVal = decodePathArray(paramVal);
931
+ values[paramName] = param.value(paramVal);
932
+ }
933
+ for (/**/; i < nTotal; i++) {
934
+ paramName = paramNames[i];
935
+ values[paramName] = this.params[paramName].value(searchParams[paramName]);
936
+ }
937
+
938
+ return values;
939
+ };
940
+
941
+ /**
942
+ * @ngdoc function
943
+ * @name ui.router.util.type:UrlMatcher#parameters
944
+ * @methodOf ui.router.util.type:UrlMatcher
945
+ *
946
+ * @description
947
+ * Returns the names of all path and search parameters of this pattern in an unspecified order.
948
+ *
949
+ * @returns {Array.<string>} An array of parameter names. Must be treated as read-only. If the
950
+ * pattern has no parameters, an empty array is returned.
951
+ */
952
+ UrlMatcher.prototype.parameters = function (param) {
953
+ if (!isDefined(param)) return this.$$paramNames;
954
+ return this.params[param] || null;
955
+ };
956
+
957
+ /**
958
+ * @ngdoc function
959
+ * @name ui.router.util.type:UrlMatcher#validate
960
+ * @methodOf ui.router.util.type:UrlMatcher
961
+ *
962
+ * @description
963
+ * Checks an object hash of parameters to validate their correctness according to the parameter
964
+ * types of this `UrlMatcher`.
965
+ *
966
+ * @param {Object} params The object hash of parameters to validate.
967
+ * @returns {boolean} Returns `true` if `params` validates, otherwise `false`.
968
+ */
969
+ UrlMatcher.prototype.validates = function (params) {
970
+ return this.params.$$validates(params);
971
+ };
972
+
973
+ /**
974
+ * @ngdoc function
975
+ * @name ui.router.util.type:UrlMatcher#format
976
+ * @methodOf ui.router.util.type:UrlMatcher
977
+ *
978
+ * @description
979
+ * Creates a URL that matches this pattern by substituting the specified values
980
+ * for the path and search parameters. Null values for path parameters are
981
+ * treated as empty strings.
982
+ *
983
+ * @example
984
+ * <pre>
985
+ * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
986
+ * // returns '/user/bob?q=yes'
987
+ * </pre>
988
+ *
989
+ * @param {Object} values the values to substitute for the parameters in this pattern.
990
+ * @returns {string} the formatted URL (path and optionally search part).
991
+ */
992
+ UrlMatcher.prototype.format = function (values) {
993
+ values = values || {};
994
+ var segments = this.segments, params = this.parameters(), paramset = this.params;
995
+ if (!this.validates(values)) return null;
996
+
997
+ var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0];
998
+
999
+ function encodeDashes(str) { // Replace dashes with encoded "\-"
1000
+ return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); });
1001
+ }
1002
+
1003
+ for (i = 0; i < nTotal; i++) {
1004
+ var isPathParam = i < nPath;
1005
+ var name = params[i], param = paramset[name], value = param.value(values[name]);
1006
+ var isDefaultValue = param.isOptional && param.type.equals(param.value(), value);
1007
+ var squash = isDefaultValue ? param.squash : false;
1008
+ var encoded = param.type.encode(value);
1009
+
1010
+ if (isPathParam) {
1011
+ var nextSegment = segments[i + 1];
1012
+ if (squash === false) {
1013
+ if (encoded != null) {
1014
+ if (isArray(encoded)) {
1015
+ result += map(encoded, encodeDashes).join("-");
1016
+ } else {
1017
+ result += encodeURIComponent(encoded);
1018
+ }
1019
+ }
1020
+ result += nextSegment;
1021
+ } else if (squash === true) {
1022
+ var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/;
1023
+ result += nextSegment.match(capture)[1];
1024
+ } else if (isString(squash)) {
1025
+ result += squash + nextSegment;
1026
+ }
1027
+ } else {
1028
+ if (encoded == null || (isDefaultValue && squash !== false)) continue;
1029
+ if (!isArray(encoded)) encoded = [ encoded ];
1030
+ encoded = map(encoded, encodeURIComponent).join('&' + name + '=');
1031
+ result += (search ? '&' : '?') + (name + '=' + encoded);
1032
+ search = true;
1033
+ }
1034
+ }
1035
+
1036
+ return result;
1037
+ };
1038
+
1039
+ /**
1040
+ * @ngdoc object
1041
+ * @name ui.router.util.type:Type
1042
+ *
1043
+ * @description
1044
+ * Implements an interface to define custom parameter types that can be decoded from and encoded to
1045
+ * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`}
1046
+ * objects when matching or formatting URLs, or comparing or validating parameter values.
1047
+ *
1048
+ * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more
1049
+ * information on registering custom types.
1050
+ *
1051
+ * @param {Object} config A configuration object which contains the custom type definition. The object's
1052
+ * properties will override the default methods and/or pattern in `Type`'s public interface.
1053
+ * @example
1054
+ * <pre>
1055
+ * {
1056
+ * decode: function(val) { return parseInt(val, 10); },
1057
+ * encode: function(val) { return val && val.toString(); },
1058
+ * equals: function(a, b) { return this.is(a) && a === b; },
1059
+ * is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; },
1060
+ * pattern: /\d+/
1061
+ * }
1062
+ * </pre>
1063
+ *
1064
+ * @property {RegExp} pattern The regular expression pattern used to match values of this type when
1065
+ * coming from a substring of a URL.
1066
+ *
1067
+ * @returns {Object} Returns a new `Type` object.
1068
+ */
1069
+ function Type(config) {
1070
+ extend(this, config);
1071
+ }
1072
+
1073
+ /**
1074
+ * @ngdoc function
1075
+ * @name ui.router.util.type:Type#is
1076
+ * @methodOf ui.router.util.type:Type
1077
+ *
1078
+ * @description
1079
+ * Detects whether a value is of a particular type. Accepts a native (decoded) value
1080
+ * and determines whether it matches the current `Type` object.
1081
+ *
1082
+ * @param {*} val The value to check.
1083
+ * @param {string} key Optional. If the type check is happening in the context of a specific
1084
+ * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the
1085
+ * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects.
1086
+ * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`.
1087
+ */
1088
+ Type.prototype.is = function(val, key) {
1089
+ return true;
1090
+ };
1091
+
1092
+ /**
1093
+ * @ngdoc function
1094
+ * @name ui.router.util.type:Type#encode
1095
+ * @methodOf ui.router.util.type:Type
1096
+ *
1097
+ * @description
1098
+ * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the
1099
+ * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it
1100
+ * only needs to be a representation of `val` that has been coerced to a string.
1101
+ *
1102
+ * @param {*} val The value to encode.
1103
+ * @param {string} key The name of the parameter in which `val` is stored. Can be used for
1104
+ * meta-programming of `Type` objects.
1105
+ * @returns {string} Returns a string representation of `val` that can be encoded in a URL.
1106
+ */
1107
+ Type.prototype.encode = function(val, key) {
1108
+ return val;
1109
+ };
1110
+
1111
+ /**
1112
+ * @ngdoc function
1113
+ * @name ui.router.util.type:Type#decode
1114
+ * @methodOf ui.router.util.type:Type
1115
+ *
1116
+ * @description
1117
+ * Converts a parameter value (from URL string or transition param) to a custom/native value.
1118
+ *
1119
+ * @param {string} val The URL parameter value to decode.
1120
+ * @param {string} key The name of the parameter in which `val` is stored. Can be used for
1121
+ * meta-programming of `Type` objects.
1122
+ * @returns {*} Returns a custom representation of the URL parameter value.
1123
+ */
1124
+ Type.prototype.decode = function(val, key) {
1125
+ return val;
1126
+ };
1127
+
1128
+ /**
1129
+ * @ngdoc function
1130
+ * @name ui.router.util.type:Type#equals
1131
+ * @methodOf ui.router.util.type:Type
1132
+ *
1133
+ * @description
1134
+ * Determines whether two decoded values are equivalent.
1135
+ *
1136
+ * @param {*} a A value to compare against.
1137
+ * @param {*} b A value to compare against.
1138
+ * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`.
1139
+ */
1140
+ Type.prototype.equals = function(a, b) {
1141
+ return a == b;
1142
+ };
1143
+
1144
+ Type.prototype.$subPattern = function() {
1145
+ var sub = this.pattern.toString();
1146
+ return sub.substr(1, sub.length - 2);
1147
+ };
1148
+
1149
+ Type.prototype.pattern = /.*/;
1150
+
1151
+ Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };
1152
+
1153
+ /** Given an encoded string, or a decoded object, returns a decoded object */
1154
+ Type.prototype.$normalize = function(val) {
1155
+ return this.is(val) ? val : this.decode(val);
1156
+ };
1157
+
1158
+ /*
1159
+ * Wraps an existing custom Type as an array of Type, depending on 'mode'.
1160
+ * e.g.:
1161
+ * - urlmatcher pattern "/path?{queryParam[]:int}"
1162
+ * - url: "/path?queryParam=1&queryParam=2
1163
+ * - $stateParams.queryParam will be [1, 2]
1164
+ * if `mode` is "auto", then
1165
+ * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1
1166
+ * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2]
1167
+ */
1168
+ Type.prototype.$asArray = function(mode, isSearch) {
1169
+ if (!mode) return this;
1170
+ if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only");
1171
+
1172
+ function ArrayType(type, mode) {
1173
+ function bindTo(type, callbackName) {
1174
+ return function() {
1175
+ return type[callbackName].apply(type, arguments);
1176
+ };
1177
+ }
1178
+
1179
+ // Wrap non-array value as array
1180
+ function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); }
1181
+ // Unwrap array value for "auto" mode. Return undefined for empty array.
1182
+ function arrayUnwrap(val) {
1183
+ switch(val.length) {
1184
+ case 0: return undefined;
1185
+ case 1: return mode === "auto" ? val[0] : val;
1186
+ default: return val;
1187
+ }
1188
+ }
1189
+ function falsey(val) { return !val; }
1190
+
1191
+ // Wraps type (.is/.encode/.decode) functions to operate on each value of an array
1192
+ function arrayHandler(callback, allTruthyMode) {
1193
+ return function handleArray(val) {
1194
+ val = arrayWrap(val);
1195
+ var result = map(val, callback);
1196
+ if (allTruthyMode === true)
1197
+ return filter(result, falsey).length === 0;
1198
+ return arrayUnwrap(result);
1199
+ };
1200
+ }
1201
+
1202
+ // Wraps type (.equals) functions to operate on each value of an array
1203
+ function arrayEqualsHandler(callback) {
1204
+ return function handleArray(val1, val2) {
1205
+ var left = arrayWrap(val1), right = arrayWrap(val2);
1206
+ if (left.length !== right.length) return false;
1207
+ for (var i = 0; i < left.length; i++) {
1208
+ if (!callback(left[i], right[i])) return false;
1209
+ }
1210
+ return true;
1211
+ };
1212
+ }
1213
+
1214
+ this.encode = arrayHandler(bindTo(type, 'encode'));
1215
+ this.decode = arrayHandler(bindTo(type, 'decode'));
1216
+ this.is = arrayHandler(bindTo(type, 'is'), true);
1217
+ this.equals = arrayEqualsHandler(bindTo(type, 'equals'));
1218
+ this.pattern = type.pattern;
1219
+ this.$normalize = arrayHandler(bindTo(type, '$normalize'));
1220
+ this.name = type.name;
1221
+ this.$arrayMode = mode;
1222
+ }
1223
+
1224
+ return new ArrayType(this, mode);
1225
+ };
1226
+
1227
+
1228
+
1229
+ /**
1230
+ * @ngdoc object
1231
+ * @name ui.router.util.$urlMatcherFactory
1232
+ *
1233
+ * @description
1234
+ * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory
1235
+ * is also available to providers under the name `$urlMatcherFactoryProvider`.
1236
+ */
1237
+ function $UrlMatcherFactory() {
1238
+ $$UMFP = this;
1239
+
1240
+ var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false;
1241
+
1242
+ function valToString(val) { return val != null ? val.toString().replace(/\//g, "%2F") : val; }
1243
+ function valFromString(val) { return val != null ? val.toString().replace(/%2F/g, "/") : val; }
1244
+
1245
+ var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = {
1246
+ string: {
1247
+ encode: valToString,
1248
+ decode: valFromString,
1249
+ // TODO: in 1.0, make string .is() return false if value is undefined/null by default.
1250
+ // In 0.2.x, string params are optional by default for backwards compat
1251
+ is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; },
1252
+ pattern: /[^/]*/
1253
+ },
1254
+ int: {
1255
+ encode: valToString,
1256
+ decode: function(val) { return parseInt(val, 10); },
1257
+ is: function(val) { return isDefined(val) && this.decode(val.toString()) === val; },
1258
+ pattern: /\d+/
1259
+ },
1260
+ bool: {
1261
+ encode: function(val) { return val ? 1 : 0; },
1262
+ decode: function(val) { return parseInt(val, 10) !== 0; },
1263
+ is: function(val) { return val === true || val === false; },
1264
+ pattern: /0|1/
1265
+ },
1266
+ date: {
1267
+ encode: function (val) {
1268
+ if (!this.is(val))
1269
+ return undefined;
1270
+ return [ val.getFullYear(),
1271
+ ('0' + (val.getMonth() + 1)).slice(-2),
1272
+ ('0' + val.getDate()).slice(-2)
1273
+ ].join("-");
1274
+ },
1275
+ decode: function (val) {
1276
+ if (this.is(val)) return val;
1277
+ var match = this.capture.exec(val);
1278
+ return match ? new Date(match[1], match[2] - 1, match[3]) : undefined;
1279
+ },
1280
+ is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); },
1281
+ equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); },
1282
+ pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,
1283
+ capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/
1284
+ },
1285
+ json: {
1286
+ encode: angular.toJson,
1287
+ decode: angular.fromJson,
1288
+ is: angular.isObject,
1289
+ equals: angular.equals,
1290
+ pattern: /[^/]*/
1291
+ },
1292
+ any: { // does not encode/decode
1293
+ encode: angular.identity,
1294
+ decode: angular.identity,
1295
+ equals: angular.equals,
1296
+ pattern: /.*/
1297
+ }
1298
+ };
1299
+
1300
+ function getDefaultConfig() {
1301
+ return {
1302
+ strict: isStrictMode,
1303
+ caseInsensitive: isCaseInsensitive
1304
+ };
1305
+ }
1306
+
1307
+ function isInjectable(value) {
1308
+ return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1])));
1309
+ }
1310
+
1311
+ /**
1312
+ * [Internal] Get the default value of a parameter, which may be an injectable function.
1313
+ */
1314
+ $UrlMatcherFactory.$$getDefaultValue = function(config) {
1315
+ if (!isInjectable(config.value)) return config.value;
1316
+ if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
1317
+ return injector.invoke(config.value);
1318
+ };
1319
+
1320
+ /**
1321
+ * @ngdoc function
1322
+ * @name ui.router.util.$urlMatcherFactory#caseInsensitive
1323
+ * @methodOf ui.router.util.$urlMatcherFactory
1324
+ *
1325
+ * @description
1326
+ * Defines whether URL matching should be case sensitive (the default behavior), or not.
1327
+ *
1328
+ * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`;
1329
+ * @returns {boolean} the current value of caseInsensitive
1330
+ */
1331
+ this.caseInsensitive = function(value) {
1332
+ if (isDefined(value))
1333
+ isCaseInsensitive = value;
1334
+ return isCaseInsensitive;
1335
+ };
1336
+
1337
+ /**
1338
+ * @ngdoc function
1339
+ * @name ui.router.util.$urlMatcherFactory#strictMode
1340
+ * @methodOf ui.router.util.$urlMatcherFactory
1341
+ *
1342
+ * @description
1343
+ * Defines whether URLs should match trailing slashes, or not (the default behavior).
1344
+ *
1345
+ * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`.
1346
+ * @returns {boolean} the current value of strictMode
1347
+ */
1348
+ this.strictMode = function(value) {
1349
+ if (isDefined(value))
1350
+ isStrictMode = value;
1351
+ return isStrictMode;
1352
+ };
1353
+
1354
+ /**
1355
+ * @ngdoc function
1356
+ * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy
1357
+ * @methodOf ui.router.util.$urlMatcherFactory
1358
+ *
1359
+ * @description
1360
+ * Sets the default behavior when generating or matching URLs with default parameter values.
1361
+ *
1362
+ * @param {string} value A string that defines the default parameter URL squashing behavior.
1363
+ * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL
1364
+ * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the
1365
+ * parameter is surrounded by slashes, squash (remove) one slash from the URL
1366
+ * any other string, e.g. "~": When generating an href with a default parameter value, squash (remove)
1367
+ * the parameter value from the URL and replace it with this string.
1368
+ */
1369
+ this.defaultSquashPolicy = function(value) {
1370
+ if (!isDefined(value)) return defaultSquashPolicy;
1371
+ if (value !== true && value !== false && !isString(value))
1372
+ throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string");
1373
+ defaultSquashPolicy = value;
1374
+ return value;
1375
+ };
1376
+
1377
+ /**
1378
+ * @ngdoc function
1379
+ * @name ui.router.util.$urlMatcherFactory#compile
1380
+ * @methodOf ui.router.util.$urlMatcherFactory
1381
+ *
1382
+ * @description
1383
+ * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern.
1384
+ *
1385
+ * @param {string} pattern The URL pattern.
1386
+ * @param {Object} config The config object hash.
1387
+ * @returns {UrlMatcher} The UrlMatcher.
1388
+ */
1389
+ this.compile = function (pattern, config) {
1390
+ return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
1391
+ };
1392
+
1393
+ /**
1394
+ * @ngdoc function
1395
+ * @name ui.router.util.$urlMatcherFactory#isMatcher
1396
+ * @methodOf ui.router.util.$urlMatcherFactory
1397
+ *
1398
+ * @description
1399
+ * Returns true if the specified object is a `UrlMatcher`, or false otherwise.
1400
+ *
1401
+ * @param {Object} object The object to perform the type check against.
1402
+ * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by
1403
+ * implementing all the same methods.
1404
+ */
1405
+ this.isMatcher = function (o) {
1406
+ if (!isObject(o)) return false;
1407
+ var result = true;
1408
+
1409
+ forEach(UrlMatcher.prototype, function(val, name) {
1410
+ if (isFunction(val)) {
1411
+ result = result && (isDefined(o[name]) && isFunction(o[name]));
1412
+ }
1413
+ });
1414
+ return result;
1415
+ };
1416
+
1417
+ /**
1418
+ * @ngdoc function
1419
+ * @name ui.router.util.$urlMatcherFactory#type
1420
+ * @methodOf ui.router.util.$urlMatcherFactory
1421
+ *
1422
+ * @description
1423
+ * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to
1424
+ * generate URLs with typed parameters.
1425
+ *
1426
+ * @param {string} name The type name.
1427
+ * @param {Object|Function} definition The type definition. See
1428
+ * {@link ui.router.util.type:Type `Type`} for information on the values accepted.
1429
+ * @param {Object|Function} definitionFn (optional) A function that is injected before the app
1430
+ * runtime starts. The result of this function is merged into the existing `definition`.
1431
+ * See {@link ui.router.util.type:Type `Type`} for information on the values accepted.
1432
+ *
1433
+ * @returns {Object} Returns `$urlMatcherFactoryProvider`.
1434
+ *
1435
+ * @example
1436
+ * This is a simple example of a custom type that encodes and decodes items from an
1437
+ * array, using the array index as the URL-encoded value:
1438
+ *
1439
+ * <pre>
1440
+ * var list = ['John', 'Paul', 'George', 'Ringo'];
1441
+ *
1442
+ * $urlMatcherFactoryProvider.type('listItem', {
1443
+ * encode: function(item) {
1444
+ * // Represent the list item in the URL using its corresponding index
1445
+ * return list.indexOf(item);
1446
+ * },
1447
+ * decode: function(item) {
1448
+ * // Look up the list item by index
1449
+ * return list[parseInt(item, 10)];
1450
+ * },
1451
+ * is: function(item) {
1452
+ * // Ensure the item is valid by checking to see that it appears
1453
+ * // in the list
1454
+ * return list.indexOf(item) > -1;
1455
+ * }
1456
+ * });
1457
+ *
1458
+ * $stateProvider.state('list', {
1459
+ * url: "/list/{item:listItem}",
1460
+ * controller: function($scope, $stateParams) {
1461
+ * console.log($stateParams.item);
1462
+ * }
1463
+ * });
1464
+ *
1465
+ * // ...
1466
+ *
1467
+ * // Changes URL to '/list/3', logs "Ringo" to the console
1468
+ * $state.go('list', { item: "Ringo" });
1469
+ * </pre>
1470
+ *
1471
+ * This is a more complex example of a type that relies on dependency injection to
1472
+ * interact with services, and uses the parameter name from the URL to infer how to
1473
+ * handle encoding and decoding parameter values:
1474
+ *
1475
+ * <pre>
1476
+ * // Defines a custom type that gets a value from a service,
1477
+ * // where each service gets different types of values from
1478
+ * // a backend API:
1479
+ * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {
1480
+ *
1481
+ * // Matches up services to URL parameter names
1482
+ * var services = {
1483
+ * user: Users,
1484
+ * post: Posts
1485
+ * };
1486
+ *
1487
+ * return {
1488
+ * encode: function(object) {
1489
+ * // Represent the object in the URL using its unique ID
1490
+ * return object.id;
1491
+ * },
1492
+ * decode: function(value, key) {
1493
+ * // Look up the object by ID, using the parameter
1494
+ * // name (key) to call the correct service
1495
+ * return services[key].findById(value);
1496
+ * },
1497
+ * is: function(object, key) {
1498
+ * // Check that object is a valid dbObject
1499
+ * return angular.isObject(object) && object.id && services[key];
1500
+ * }
1501
+ * equals: function(a, b) {
1502
+ * // Check the equality of decoded objects by comparing
1503
+ * // their unique IDs
1504
+ * return a.id === b.id;
1505
+ * }
1506
+ * };
1507
+ * });
1508
+ *
1509
+ * // In a config() block, you can then attach URLs with
1510
+ * // type-annotated parameters:
1511
+ * $stateProvider.state('users', {
1512
+ * url: "/users",
1513
+ * // ...
1514
+ * }).state('users.item', {
1515
+ * url: "/{user:dbObject}",
1516
+ * controller: function($scope, $stateParams) {
1517
+ * // $stateParams.user will now be an object returned from
1518
+ * // the Users service
1519
+ * },
1520
+ * // ...
1521
+ * });
1522
+ * </pre>
1523
+ */
1524
+ this.type = function (name, definition, definitionFn) {
1525
+ if (!isDefined(definition)) return $types[name];
1526
+ if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined.");
1527
+
1528
+ $types[name] = new Type(extend({ name: name }, definition));
1529
+ if (definitionFn) {
1530
+ typeQueue.push({ name: name, def: definitionFn });
1531
+ if (!enqueue) flushTypeQueue();
1532
+ }
1533
+ return this;
1534
+ };
1535
+
1536
+ // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s
1537
+ function flushTypeQueue() {
1538
+ while(typeQueue.length) {
1539
+ var type = typeQueue.shift();
1540
+ if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime.");
1541
+ angular.extend($types[type.name], injector.invoke(type.def));
1542
+ }
1543
+ }
1544
+
1545
+ // Register default types. Store them in the prototype of $types.
1546
+ forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); });
1547
+ $types = inherit($types, {});
1548
+
1549
+ /* No need to document $get, since it returns this */
1550
+ this.$get = ['$injector', function ($injector) {
1551
+ injector = $injector;
1552
+ enqueue = false;
1553
+ flushTypeQueue();
1554
+
1555
+ forEach(defaultTypes, function(type, name) {
1556
+ if (!$types[name]) $types[name] = new Type(type);
1557
+ });
1558
+ return this;
1559
+ }];
1560
+
1561
+ this.Param = function Param(id, type, config, location) {
1562
+ var self = this;
1563
+ config = unwrapShorthand(config);
1564
+ type = getType(config, type, location);
1565
+ var arrayMode = getArrayMode();
1566
+ type = arrayMode ? type.$asArray(arrayMode, location === "search") : type;
1567
+ if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined)
1568
+ config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to ""
1569
+ var isOptional = config.value !== undefined;
1570
+ var squash = getSquashPolicy(config, isOptional);
1571
+ var replace = getReplace(config, arrayMode, isOptional, squash);
1572
+
1573
+ function unwrapShorthand(config) {
1574
+ var keys = isObject(config) ? objectKeys(config) : [];
1575
+ var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 &&
1576
+ indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1;
1577
+ if (isShorthand) config = { value: config };
1578
+ config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; };
1579
+ return config;
1580
+ }
1581
+
1582
+ function getType(config, urlType, location) {
1583
+ if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations.");
1584
+ if (urlType) return urlType;
1585
+ if (!config.type) return (location === "config" ? $types.any : $types.string);
1586
+ return config.type instanceof Type ? config.type : new Type(config.type);
1587
+ }
1588
+
1589
+ // array config: param name (param[]) overrides default settings. explicit config overrides param name.
1590
+ function getArrayMode() {
1591
+ var arrayDefaults = { array: (location === "search" ? "auto" : false) };
1592
+ var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {};
1593
+ return extend(arrayDefaults, arrayParamNomenclature, config).array;
1594
+ }
1595
+
1596
+ /**
1597
+ * returns false, true, or the squash value to indicate the "default parameter url squash policy".
1598
+ */
1599
+ function getSquashPolicy(config, isOptional) {
1600
+ var squash = config.squash;
1601
+ if (!isOptional || squash === false) return false;
1602
+ if (!isDefined(squash) || squash == null) return defaultSquashPolicy;
1603
+ if (squash === true || isString(squash)) return squash;
1604
+ throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string");
1605
+ }
1606
+
1607
+ function getReplace(config, arrayMode, isOptional, squash) {
1608
+ var replace, configuredKeys, defaultPolicy = [
1609
+ { from: "", to: (isOptional || arrayMode ? undefined : "") },
1610
+ { from: null, to: (isOptional || arrayMode ? undefined : "") }
1611
+ ];
1612
+ replace = isArray(config.replace) ? config.replace : [];
1613
+ if (isString(squash))
1614
+ replace.push({ from: squash, to: undefined });
1615
+ configuredKeys = map(replace, function(item) { return item.from; } );
1616
+ return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace);
1617
+ }
1618
+
1619
+ /**
1620
+ * [Internal] Get the default value of a parameter, which may be an injectable function.
1621
+ */
1622
+ function $$getDefaultValue() {
1623
+ if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
1624
+ var defaultValue = injector.invoke(config.$$fn);
1625
+ if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue))
1626
+ throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")");
1627
+ return defaultValue;
1628
+ }
1629
+
1630
+ /**
1631
+ * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the
1632
+ * default value, which may be the result of an injectable function.
1633
+ */
1634
+ function $value(value) {
1635
+ function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; }
1636
+ function $replace(value) {
1637
+ var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; });
1638
+ return replacement.length ? replacement[0] : value;
1639
+ }
1640
+ value = $replace(value);
1641
+ return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value);
1642
+ }
1643
+
1644
+ function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; }
1645
+
1646
+ extend(this, {
1647
+ id: id,
1648
+ type: type,
1649
+ location: location,
1650
+ array: arrayMode,
1651
+ squash: squash,
1652
+ replace: replace,
1653
+ isOptional: isOptional,
1654
+ value: $value,
1655
+ dynamic: undefined,
1656
+ config: config,
1657
+ toString: toString
1658
+ });
1659
+ };
1660
+
1661
+ function ParamSet(params) {
1662
+ extend(this, params || {});
1663
+ }
1664
+
1665
+ ParamSet.prototype = {
1666
+ $$new: function() {
1667
+ return inherit(this, extend(new ParamSet(), { $$parent: this}));
1668
+ },
1669
+ $$keys: function () {
1670
+ var keys = [], chain = [], parent = this,
1671
+ ignore = objectKeys(ParamSet.prototype);
1672
+ while (parent) { chain.push(parent); parent = parent.$$parent; }
1673
+ chain.reverse();
1674
+ forEach(chain, function(paramset) {
1675
+ forEach(objectKeys(paramset), function(key) {
1676
+ if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key);
1677
+ });
1678
+ });
1679
+ return keys;
1680
+ },
1681
+ $$values: function(paramValues) {
1682
+ var values = {}, self = this;
1683
+ forEach(self.$$keys(), function(key) {
1684
+ values[key] = self[key].value(paramValues && paramValues[key]);
1685
+ });
1686
+ return values;
1687
+ },
1688
+ $$equals: function(paramValues1, paramValues2) {
1689
+ var equal = true, self = this;
1690
+ forEach(self.$$keys(), function(key) {
1691
+ var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key];
1692
+ if (!self[key].type.equals(left, right)) equal = false;
1693
+ });
1694
+ return equal;
1695
+ },
1696
+ $$validates: function $$validate(paramValues) {
1697
+ var keys = this.$$keys(), i, param, rawVal, normalized, encoded;
1698
+ for (i = 0; i < keys.length; i++) {
1699
+ param = this[keys[i]];
1700
+ rawVal = paramValues[keys[i]];
1701
+ if ((rawVal === undefined || rawVal === null) && param.isOptional)
1702
+ break; // There was no parameter value, but the param is optional
1703
+ normalized = param.type.$normalize(rawVal);
1704
+ if (!param.type.is(normalized))
1705
+ return false; // The value was not of the correct Type, and could not be decoded to the correct Type
1706
+ encoded = param.type.encode(normalized);
1707
+ if (angular.isString(encoded) && !param.type.pattern.exec(encoded))
1708
+ return false; // The value was of the correct type, but when encoded, did not match the Type's regexp
1709
+ }
1710
+ return true;
1711
+ },
1712
+ $$parent: undefined
1713
+ };
1714
+
1715
+ this.ParamSet = ParamSet;
1716
+ }
1717
+
1718
+ // Register as a provider so it's available to other providers
1719
+ angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory);
1720
+ angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]);
1721
+
1722
+ /**
1723
+ * @ngdoc object
1724
+ * @name ui.router.router.$urlRouterProvider
1725
+ *
1726
+ * @requires ui.router.util.$urlMatcherFactoryProvider
1727
+ * @requires $locationProvider
1728
+ *
1729
+ * @description
1730
+ * `$urlRouterProvider` has the responsibility of watching `$location`.
1731
+ * When `$location` changes it runs through a list of rules one by one until a
1732
+ * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify
1733
+ * a url in a state configuration. All urls are compiled into a UrlMatcher object.
1734
+ *
1735
+ * There are several methods on `$urlRouterProvider` that make it useful to use directly
1736
+ * in your module config.
1737
+ */
1738
+ $UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
1739
+ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
1740
+ var rules = [], otherwise = null, interceptDeferred = false, listener;
1741
+
1742
+ // Returns a string that is a prefix of all strings matching the RegExp
1743
+ function regExpPrefix(re) {
1744
+ var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source);
1745
+ return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : '';
1746
+ }
1747
+
1748
+ // Interpolates matched values into a String.replace()-style pattern
1749
+ function interpolate(pattern, match) {
1750
+ return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) {
1751
+ return match[what === '$' ? 0 : Number(what)];
1752
+ });
1753
+ }
1754
+
1755
+ /**
1756
+ * @ngdoc function
1757
+ * @name ui.router.router.$urlRouterProvider#rule
1758
+ * @methodOf ui.router.router.$urlRouterProvider
1759
+ *
1760
+ * @description
1761
+ * Defines rules that are used by `$urlRouterProvider` to find matches for
1762
+ * specific URLs.
1763
+ *
1764
+ * @example
1765
+ * <pre>
1766
+ * var app = angular.module('app', ['ui.router.router']);
1767
+ *
1768
+ * app.config(function ($urlRouterProvider) {
1769
+ * // Here's an example of how you might allow case insensitive urls
1770
+ * $urlRouterProvider.rule(function ($injector, $location) {
1771
+ * var path = $location.path(),
1772
+ * normalized = path.toLowerCase();
1773
+ *
1774
+ * if (path !== normalized) {
1775
+ * return normalized;
1776
+ * }
1777
+ * });
1778
+ * });
1779
+ * </pre>
1780
+ *
1781
+ * @param {object} rule Handler function that takes `$injector` and `$location`
1782
+ * services as arguments. You can use them to return a valid path as a string.
1783
+ *
1784
+ * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
1785
+ */
1786
+ this.rule = function (rule) {
1787
+ if (!isFunction(rule)) throw new Error("'rule' must be a function");
1788
+ rules.push(rule);
1789
+ return this;
1790
+ };
1791
+
1792
+ /**
1793
+ * @ngdoc object
1794
+ * @name ui.router.router.$urlRouterProvider#otherwise
1795
+ * @methodOf ui.router.router.$urlRouterProvider
1796
+ *
1797
+ * @description
1798
+ * Defines a path that is used when an invalid route is requested.
1799
+ *
1800
+ * @example
1801
+ * <pre>
1802
+ * var app = angular.module('app', ['ui.router.router']);
1803
+ *
1804
+ * app.config(function ($urlRouterProvider) {
1805
+ * // if the path doesn't match any of the urls you configured
1806
+ * // otherwise will take care of routing the user to the
1807
+ * // specified url
1808
+ * $urlRouterProvider.otherwise('/index');
1809
+ *
1810
+ * // Example of using function rule as param
1811
+ * $urlRouterProvider.otherwise(function ($injector, $location) {
1812
+ * return '/a/valid/url';
1813
+ * });
1814
+ * });
1815
+ * </pre>
1816
+ *
1817
+ * @param {string|object} rule The url path you want to redirect to or a function
1818
+ * rule that returns the url path. The function version is passed two params:
1819
+ * `$injector` and `$location` services, and must return a url string.
1820
+ *
1821
+ * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
1822
+ */
1823
+ this.otherwise = function (rule) {
1824
+ if (isString(rule)) {
1825
+ var redirect = rule;
1826
+ rule = function () { return redirect; };
1827
+ }
1828
+ else if (!isFunction(rule)) throw new Error("'rule' must be a function");
1829
+ otherwise = rule;
1830
+ return this;
1831
+ };
1832
+
1833
+
1834
+ function handleIfMatch($injector, handler, match) {
1835
+ if (!match) return false;
1836
+ var result = $injector.invoke(handler, handler, { $match: match });
1837
+ return isDefined(result) ? result : true;
1838
+ }
1839
+
1840
+ /**
1841
+ * @ngdoc function
1842
+ * @name ui.router.router.$urlRouterProvider#when
1843
+ * @methodOf ui.router.router.$urlRouterProvider
1844
+ *
1845
+ * @description
1846
+ * Registers a handler for a given url matching. if handle is a string, it is
1847
+ * treated as a redirect, and is interpolated according to the syntax of match
1848
+ * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
1849
+ *
1850
+ * If the handler is a function, it is injectable. It gets invoked if `$location`
1851
+ * matches. You have the option of inject the match object as `$match`.
1852
+ *
1853
+ * The handler can return
1854
+ *
1855
+ * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter`
1856
+ * will continue trying to find another one that matches.
1857
+ * - **string** which is treated as a redirect and passed to `$location.url()`
1858
+ * - **void** or any **truthy** value tells `$urlRouter` that the url was handled.
1859
+ *
1860
+ * @example
1861
+ * <pre>
1862
+ * var app = angular.module('app', ['ui.router.router']);
1863
+ *
1864
+ * app.config(function ($urlRouterProvider) {
1865
+ * $urlRouterProvider.when($state.url, function ($match, $stateParams) {
1866
+ * if ($state.$current.navigable !== state ||
1867
+ * !equalForKeys($match, $stateParams) {
1868
+ * $state.transitionTo(state, $match, false);
1869
+ * }
1870
+ * });
1871
+ * });
1872
+ * </pre>
1873
+ *
1874
+ * @param {string|object} what The incoming path that you want to redirect.
1875
+ * @param {string|object} handler The path you want to redirect your user to.
1876
+ */
1877
+ this.when = function (what, handler) {
1878
+ var redirect, handlerIsString = isString(handler);
1879
+ if (isString(what)) what = $urlMatcherFactory.compile(what);
1880
+
1881
+ if (!handlerIsString && !isFunction(handler) && !isArray(handler))
1882
+ throw new Error("invalid 'handler' in when()");
1883
+
1884
+ var strategies = {
1885
+ matcher: function (what, handler) {
1886
+ if (handlerIsString) {
1887
+ redirect = $urlMatcherFactory.compile(handler);
1888
+ handler = ['$match', function ($match) { return redirect.format($match); }];
1889
+ }
1890
+ return extend(function ($injector, $location) {
1891
+ return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));
1892
+ }, {
1893
+ prefix: isString(what.prefix) ? what.prefix : ''
1894
+ });
1895
+ },
1896
+ regex: function (what, handler) {
1897
+ if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky");
1898
+
1899
+ if (handlerIsString) {
1900
+ redirect = handler;
1901
+ handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
1902
+ }
1903
+ return extend(function ($injector, $location) {
1904
+ return handleIfMatch($injector, handler, what.exec($location.path()));
1905
+ }, {
1906
+ prefix: regExpPrefix(what)
1907
+ });
1908
+ }
1909
+ };
1910
+
1911
+ var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };
1912
+
1913
+ for (var n in check) {
1914
+ if (check[n]) return this.rule(strategies[n](what, handler));
1915
+ }
1916
+
1917
+ throw new Error("invalid 'what' in when()");
1918
+ };
1919
+
1920
+ /**
1921
+ * @ngdoc function
1922
+ * @name ui.router.router.$urlRouterProvider#deferIntercept
1923
+ * @methodOf ui.router.router.$urlRouterProvider
1924
+ *
1925
+ * @description
1926
+ * Disables (or enables) deferring location change interception.
1927
+ *
1928
+ * If you wish to customize the behavior of syncing the URL (for example, if you wish to
1929
+ * defer a transition but maintain the current URL), call this method at configuration time.
1930
+ * Then, at run time, call `$urlRouter.listen()` after you have configured your own
1931
+ * `$locationChangeSuccess` event handler.
1932
+ *
1933
+ * @example
1934
+ * <pre>
1935
+ * var app = angular.module('app', ['ui.router.router']);
1936
+ *
1937
+ * app.config(function ($urlRouterProvider) {
1938
+ *
1939
+ * // Prevent $urlRouter from automatically intercepting URL changes;
1940
+ * // this allows you to configure custom behavior in between
1941
+ * // location changes and route synchronization:
1942
+ * $urlRouterProvider.deferIntercept();
1943
+ *
1944
+ * }).run(function ($rootScope, $urlRouter, UserService) {
1945
+ *
1946
+ * $rootScope.$on('$locationChangeSuccess', function(e) {
1947
+ * // UserService is an example service for managing user state
1948
+ * if (UserService.isLoggedIn()) return;
1949
+ *
1950
+ * // Prevent $urlRouter's default handler from firing
1951
+ * e.preventDefault();
1952
+ *
1953
+ * UserService.handleLogin().then(function() {
1954
+ * // Once the user has logged in, sync the current URL
1955
+ * // to the router:
1956
+ * $urlRouter.sync();
1957
+ * });
1958
+ * });
1959
+ *
1960
+ * // Configures $urlRouter's listener *after* your custom listener
1961
+ * $urlRouter.listen();
1962
+ * });
1963
+ * </pre>
1964
+ *
1965
+ * @param {boolean} defer Indicates whether to defer location change interception. Passing
1966
+ no parameter is equivalent to `true`.
1967
+ */
1968
+ this.deferIntercept = function (defer) {
1969
+ if (defer === undefined) defer = true;
1970
+ interceptDeferred = defer;
1971
+ };
1972
+
1973
+ /**
1974
+ * @ngdoc object
1975
+ * @name ui.router.router.$urlRouter
1976
+ *
1977
+ * @requires $location
1978
+ * @requires $rootScope
1979
+ * @requires $injector
1980
+ * @requires $browser
1981
+ *
1982
+ * @description
1983
+ *
1984
+ */
1985
+ this.$get = $get;
1986
+ $get.$inject = ['$location', '$rootScope', '$injector', '$browser'];
1987
+ function $get( $location, $rootScope, $injector, $browser) {
1988
+
1989
+ var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl;
1990
+
1991
+ function appendBasePath(url, isHtml5, absolute) {
1992
+ if (baseHref === '/') return url;
1993
+ if (isHtml5) return baseHref.slice(0, -1) + url;
1994
+ if (absolute) return baseHref.slice(1) + url;
1995
+ return url;
1996
+ }
1997
+
1998
+ // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
1999
+ function update(evt) {
2000
+ if (evt && evt.defaultPrevented) return;
2001
+ var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;
2002
+ lastPushedUrl = undefined;
2003
+ // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573
2004
+ //if (ignoreUpdate) return true;
2005
+
2006
+ function check(rule) {
2007
+ var handled = rule($injector, $location);
2008
+
2009
+ if (!handled) return false;
2010
+ if (isString(handled)) $location.replace().url(handled);
2011
+ return true;
2012
+ }
2013
+ var n = rules.length, i;
2014
+
2015
+ for (i = 0; i < n; i++) {
2016
+ if (check(rules[i])) return;
2017
+ }
2018
+ // always check otherwise last to allow dynamic updates to the set of rules
2019
+ if (otherwise) check(otherwise);
2020
+ }
2021
+
2022
+ function listen() {
2023
+ listener = listener || $rootScope.$on('$locationChangeSuccess', update);
2024
+ return listener;
2025
+ }
2026
+
2027
+ if (!interceptDeferred) listen();
2028
+
2029
+ return {
2030
+ /**
2031
+ * @ngdoc function
2032
+ * @name ui.router.router.$urlRouter#sync
2033
+ * @methodOf ui.router.router.$urlRouter
2034
+ *
2035
+ * @description
2036
+ * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.
2037
+ * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,
2038
+ * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed
2039
+ * with the transition by calling `$urlRouter.sync()`.
2040
+ *
2041
+ * @example
2042
+ * <pre>
2043
+ * angular.module('app', ['ui.router'])
2044
+ * .run(function($rootScope, $urlRouter) {
2045
+ * $rootScope.$on('$locationChangeSuccess', function(evt) {
2046
+ * // Halt state change from even starting
2047
+ * evt.preventDefault();
2048
+ * // Perform custom logic
2049
+ * var meetsRequirement = ...
2050
+ * // Continue with the update and state transition if logic allows
2051
+ * if (meetsRequirement) $urlRouter.sync();
2052
+ * });
2053
+ * });
2054
+ * </pre>
2055
+ */
2056
+ sync: function() {
2057
+ update();
2058
+ },
2059
+
2060
+ listen: function() {
2061
+ return listen();
2062
+ },
2063
+
2064
+ update: function(read) {
2065
+ if (read) {
2066
+ location = $location.url();
2067
+ return;
2068
+ }
2069
+ if ($location.url() === location) return;
2070
+
2071
+ $location.url(location);
2072
+ $location.replace();
2073
+ },
2074
+
2075
+ push: function(urlMatcher, params, options) {
2076
+ var url = urlMatcher.format(params || {});
2077
+
2078
+ // Handle the special hash param, if needed
2079
+ if (url !== null && params && params['#']) {
2080
+ url += '#' + params['#'];
2081
+ }
2082
+
2083
+ $location.url(url);
2084
+ lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;
2085
+ if (options && options.replace) $location.replace();
2086
+ },
2087
+
2088
+ /**
2089
+ * @ngdoc function
2090
+ * @name ui.router.router.$urlRouter#href
2091
+ * @methodOf ui.router.router.$urlRouter
2092
+ *
2093
+ * @description
2094
+ * A URL generation method that returns the compiled URL for a given
2095
+ * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters.
2096
+ *
2097
+ * @example
2098
+ * <pre>
2099
+ * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
2100
+ * person: "bob"
2101
+ * });
2102
+ * // $bob == "/about/bob";
2103
+ * </pre>
2104
+ *
2105
+ * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate.
2106
+ * @param {object=} params An object of parameter values to fill the matcher's required parameters.
2107
+ * @param {object=} options Options object. The options are:
2108
+ *
2109
+ * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
2110
+ *
2111
+ * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`
2112
+ */
2113
+ href: function(urlMatcher, params, options) {
2114
+ if (!urlMatcher.validates(params)) return null;
2115
+
2116
+ var isHtml5 = $locationProvider.html5Mode();
2117
+ if (angular.isObject(isHtml5)) {
2118
+ isHtml5 = isHtml5.enabled;
2119
+ }
2120
+
2121
+ var url = urlMatcher.format(params);
2122
+ options = options || {};
2123
+
2124
+ if (!isHtml5 && url !== null) {
2125
+ url = "#" + $locationProvider.hashPrefix() + url;
2126
+ }
2127
+
2128
+ // Handle special hash param, if needed
2129
+ if (url !== null && params && params['#']) {
2130
+ url += '#' + params['#'];
2131
+ }
2132
+
2133
+ url = appendBasePath(url, isHtml5, options.absolute);
2134
+
2135
+ if (!options.absolute || !url) {
2136
+ return url;
2137
+ }
2138
+
2139
+ var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();
2140
+ port = (port === 80 || port === 443 ? '' : ':' + port);
2141
+
2142
+ return [$location.protocol(), '://', $location.host(), port, slash, url].join('');
2143
+ }
2144
+ };
2145
+ }
2146
+ }
2147
+
2148
+ angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);
2149
+
2150
+ /**
2151
+ * @ngdoc object
2152
+ * @name ui.router.state.$stateProvider
2153
+ *
2154
+ * @requires ui.router.router.$urlRouterProvider
2155
+ * @requires ui.router.util.$urlMatcherFactoryProvider
2156
+ *
2157
+ * @description
2158
+ * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely
2159
+ * on state.
2160
+ *
2161
+ * A state corresponds to a "place" in the application in terms of the overall UI and
2162
+ * navigation. A state describes (via the controller / template / view properties) what
2163
+ * the UI looks like and does at that place.
2164
+ *
2165
+ * States often have things in common, and the primary way of factoring out these
2166
+ * commonalities in this model is via the state hierarchy, i.e. parent/child states aka
2167
+ * nested states.
2168
+ *
2169
+ * The `$stateProvider` provides interfaces to declare these states for your app.
2170
+ */
2171
+ $StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];
2172
+ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
2173
+
2174
+ var root, states = {}, $state, queue = {}, abstractKey = 'abstract';
2175
+
2176
+ // Builds state properties from definition passed to registerState()
2177
+ var stateBuilder = {
2178
+
2179
+ // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.
2180
+ // state.children = [];
2181
+ // if (parent) parent.children.push(state);
2182
+ parent: function(state) {
2183
+ if (isDefined(state.parent) && state.parent) return findState(state.parent);
2184
+ // regex matches any valid composite state name
2185
+ // would match "contact.list" but not "contacts"
2186
+ var compositeName = /^(.+)\.[^.]+$/.exec(state.name);
2187
+ return compositeName ? findState(compositeName[1]) : root;
2188
+ },
2189
+
2190
+ // inherit 'data' from parent and override by own values (if any)
2191
+ data: function(state) {
2192
+ if (state.parent && state.parent.data) {
2193
+ state.data = state.self.data = extend({}, state.parent.data, state.data);
2194
+ }
2195
+ return state.data;
2196
+ },
2197
+
2198
+ // Build a URLMatcher if necessary, either via a relative or absolute URL
2199
+ url: function(state) {
2200
+ var url = state.url, config = { params: state.params || {} };
2201
+
2202
+ if (isString(url)) {
2203
+ if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config);
2204
+ return (state.parent.navigable || root).url.concat(url, config);
2205
+ }
2206
+
2207
+ if (!url || $urlMatcherFactory.isMatcher(url)) return url;
2208
+ throw new Error("Invalid url '" + url + "' in state '" + state + "'");
2209
+ },
2210
+
2211
+ // Keep track of the closest ancestor state that has a URL (i.e. is navigable)
2212
+ navigable: function(state) {
2213
+ return state.url ? state : (state.parent ? state.parent.navigable : null);
2214
+ },
2215
+
2216
+ // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params
2217
+ ownParams: function(state) {
2218
+ var params = state.url && state.url.params || new $$UMFP.ParamSet();
2219
+ forEach(state.params || {}, function(config, id) {
2220
+ if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config");
2221
+ });
2222
+ return params;
2223
+ },
2224
+
2225
+ // Derive parameters for this state and ensure they're a super-set of parent's parameters
2226
+ params: function(state) {
2227
+ return state.parent && state.parent.params ? extend(state.parent.params.$$new(), state.ownParams) : new $$UMFP.ParamSet();
2228
+ },
2229
+
2230
+ // If there is no explicit multi-view configuration, make one up so we don't have
2231
+ // to handle both cases in the view directive later. Note that having an explicit
2232
+ // 'views' property will mean the default unnamed view properties are ignored. This
2233
+ // is also a good time to resolve view names to absolute names, so everything is a
2234
+ // straight lookup at link time.
2235
+ views: function(state) {
2236
+ var views = {};
2237
+
2238
+ forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) {
2239
+ if (name.indexOf('@') < 0) name += '@' + state.parent.name;
2240
+ views[name] = view;
2241
+ });
2242
+ return views;
2243
+ },
2244
+
2245
+ // Keep a full path from the root down to this state as this is needed for state activation.
2246
+ path: function(state) {
2247
+ return state.parent ? state.parent.path.concat(state) : []; // exclude root from path
2248
+ },
2249
+
2250
+ // Speed up $state.contains() as it's used a lot
2251
+ includes: function(state) {
2252
+ var includes = state.parent ? extend({}, state.parent.includes) : {};
2253
+ includes[state.name] = true;
2254
+ return includes;
2255
+ },
2256
+
2257
+ $delegates: {}
2258
+ };
2259
+
2260
+ function isRelative(stateName) {
2261
+ return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0;
2262
+ }
2263
+
2264
+ function findState(stateOrName, base) {
2265
+ if (!stateOrName) return undefined;
2266
+
2267
+ var isStr = isString(stateOrName),
2268
+ name = isStr ? stateOrName : stateOrName.name,
2269
+ path = isRelative(name);
2270
+
2271
+ if (path) {
2272
+ if (!base) throw new Error("No reference point given for path '" + name + "'");
2273
+ base = findState(base);
2274
+
2275
+ var rel = name.split("."), i = 0, pathLength = rel.length, current = base;
2276
+
2277
+ for (; i < pathLength; i++) {
2278
+ if (rel[i] === "" && i === 0) {
2279
+ current = base;
2280
+ continue;
2281
+ }
2282
+ if (rel[i] === "^") {
2283
+ if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'");
2284
+ current = current.parent;
2285
+ continue;
2286
+ }
2287
+ break;
2288
+ }
2289
+ rel = rel.slice(i).join(".");
2290
+ name = current.name + (current.name && rel ? "." : "") + rel;
2291
+ }
2292
+ var state = states[name];
2293
+
2294
+ if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) {
2295
+ return state;
2296
+ }
2297
+ return undefined;
2298
+ }
2299
+
2300
+ function queueState(parentName, state) {
2301
+ if (!queue[parentName]) {
2302
+ queue[parentName] = [];
2303
+ }
2304
+ queue[parentName].push(state);
2305
+ }
2306
+
2307
+ function flushQueuedChildren(parentName) {
2308
+ var queued = queue[parentName] || [];
2309
+ while(queued.length) {
2310
+ registerState(queued.shift());
2311
+ }
2312
+ }
2313
+
2314
+ function registerState(state) {
2315
+ // Wrap a new object around the state so we can store our private details easily.
2316
+ state = inherit(state, {
2317
+ self: state,
2318
+ resolve: state.resolve || {},
2319
+ toString: function() { return this.name; }
2320
+ });
2321
+
2322
+ var name = state.name;
2323
+ if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name");
2324
+ if (states.hasOwnProperty(name)) throw new Error("State '" + name + "'' is already defined");
2325
+
2326
+ // Get parent name
2327
+ var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.'))
2328
+ : (isString(state.parent)) ? state.parent
2329
+ : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name
2330
+ : '';
2331
+
2332
+ // If parent is not registered yet, add state to queue and register later
2333
+ if (parentName && !states[parentName]) {
2334
+ return queueState(parentName, state.self);
2335
+ }
2336
+
2337
+ for (var key in stateBuilder) {
2338
+ if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]);
2339
+ }
2340
+ states[name] = state;
2341
+
2342
+ // Register the state in the global state list and with $urlRouter if necessary.
2343
+ if (!state[abstractKey] && state.url) {
2344
+ $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {
2345
+ if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {
2346
+ $state.transitionTo(state, $match, { inherit: true, location: false });
2347
+ }
2348
+ }]);
2349
+ }
2350
+
2351
+ // Register any queued children
2352
+ flushQueuedChildren(name);
2353
+
2354
+ return state;
2355
+ }
2356
+
2357
+ // Checks text to see if it looks like a glob.
2358
+ function isGlob (text) {
2359
+ return text.indexOf('*') > -1;
2360
+ }
2361
+
2362
+ // Returns true if glob matches current $state name.
2363
+ function doesStateMatchGlob (glob) {
2364
+ var globSegments = glob.split('.'),
2365
+ segments = $state.$current.name.split('.');
2366
+
2367
+ //match single stars
2368
+ for (var i = 0, l = globSegments.length; i < l; i++) {
2369
+ if (globSegments[i] === '*') {
2370
+ segments[i] = '*';
2371
+ }
2372
+ }
2373
+
2374
+ //match greedy starts
2375
+ if (globSegments[0] === '**') {
2376
+ segments = segments.slice(indexOf(segments, globSegments[1]));
2377
+ segments.unshift('**');
2378
+ }
2379
+ //match greedy ends
2380
+ if (globSegments[globSegments.length - 1] === '**') {
2381
+ segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);
2382
+ segments.push('**');
2383
+ }
2384
+
2385
+ if (globSegments.length != segments.length) {
2386
+ return false;
2387
+ }
2388
+
2389
+ return segments.join('') === globSegments.join('');
2390
+ }
2391
+
2392
+
2393
+ // Implicit root state that is always active
2394
+ root = registerState({
2395
+ name: '',
2396
+ url: '^',
2397
+ views: null,
2398
+ 'abstract': true
2399
+ });
2400
+ root.navigable = null;
2401
+
2402
+
2403
+ /**
2404
+ * @ngdoc function
2405
+ * @name ui.router.state.$stateProvider#decorator
2406
+ * @methodOf ui.router.state.$stateProvider
2407
+ *
2408
+ * @description
2409
+ * Allows you to extend (carefully) or override (at your own peril) the
2410
+ * `stateBuilder` object used internally by `$stateProvider`. This can be used
2411
+ * to add custom functionality to ui-router, for example inferring templateUrl
2412
+ * based on the state name.
2413
+ *
2414
+ * When passing only a name, it returns the current (original or decorated) builder
2415
+ * function that matches `name`.
2416
+ *
2417
+ * The builder functions that can be decorated are listed below. Though not all
2418
+ * necessarily have a good use case for decoration, that is up to you to decide.
2419
+ *
2420
+ * In addition, users can attach custom decorators, which will generate new
2421
+ * properties within the state's internal definition. There is currently no clear
2422
+ * use-case for this beyond accessing internal states (i.e. $state.$current),
2423
+ * however, expect this to become increasingly relevant as we introduce additional
2424
+ * meta-programming features.
2425
+ *
2426
+ * **Warning**: Decorators should not be interdependent because the order of
2427
+ * execution of the builder functions in non-deterministic. Builder functions
2428
+ * should only be dependent on the state definition object and super function.
2429
+ *
2430
+ *
2431
+ * Existing builder functions and current return values:
2432
+ *
2433
+ * - **parent** `{object}` - returns the parent state object.
2434
+ * - **data** `{object}` - returns state data, including any inherited data that is not
2435
+ * overridden by own values (if any).
2436
+ * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher}
2437
+ * or `null`.
2438
+ * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is
2439
+ * navigable).
2440
+ * - **params** `{object}` - returns an array of state params that are ensured to
2441
+ * be a super-set of parent's params.
2442
+ * - **views** `{object}` - returns a views object where each key is an absolute view
2443
+ * name (i.e. "viewName@stateName") and each value is the config object
2444
+ * (template, controller) for the view. Even when you don't use the views object
2445
+ * explicitly on a state config, one is still created for you internally.
2446
+ * So by decorating this builder function you have access to decorating template
2447
+ * and controller properties.
2448
+ * - **ownParams** `{object}` - returns an array of params that belong to the state,
2449
+ * not including any params defined by ancestor states.
2450
+ * - **path** `{string}` - returns the full path from the root down to this state.
2451
+ * Needed for state activation.
2452
+ * - **includes** `{object}` - returns an object that includes every state that
2453
+ * would pass a `$state.includes()` test.
2454
+ *
2455
+ * @example
2456
+ * <pre>
2457
+ * // Override the internal 'views' builder with a function that takes the state
2458
+ * // definition, and a reference to the internal function being overridden:
2459
+ * $stateProvider.decorator('views', function (state, parent) {
2460
+ * var result = {},
2461
+ * views = parent(state);
2462
+ *
2463
+ * angular.forEach(views, function (config, name) {
2464
+ * var autoName = (state.name + '.' + name).replace('.', '/');
2465
+ * config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
2466
+ * result[name] = config;
2467
+ * });
2468
+ * return result;
2469
+ * });
2470
+ *
2471
+ * $stateProvider.state('home', {
2472
+ * views: {
2473
+ * 'contact.list': { controller: 'ListController' },
2474
+ * 'contact.item': { controller: 'ItemController' }
2475
+ * }
2476
+ * });
2477
+ *
2478
+ * // ...
2479
+ *
2480
+ * $state.go('home');
2481
+ * // Auto-populates list and item views with /partials/home/contact/list.html,
2482
+ * // and /partials/home/contact/item.html, respectively.
2483
+ * </pre>
2484
+ *
2485
+ * @param {string} name The name of the builder function to decorate.
2486
+ * @param {object} func A function that is responsible for decorating the original
2487
+ * builder function. The function receives two parameters:
2488
+ *
2489
+ * - `{object}` - state - The state config object.
2490
+ * - `{object}` - super - The original builder function.
2491
+ *
2492
+ * @return {object} $stateProvider - $stateProvider instance
2493
+ */
2494
+ this.decorator = decorator;
2495
+ function decorator(name, func) {
2496
+ /*jshint validthis: true */
2497
+ if (isString(name) && !isDefined(func)) {
2498
+ return stateBuilder[name];
2499
+ }
2500
+ if (!isFunction(func) || !isString(name)) {
2501
+ return this;
2502
+ }
2503
+ if (stateBuilder[name] && !stateBuilder.$delegates[name]) {
2504
+ stateBuilder.$delegates[name] = stateBuilder[name];
2505
+ }
2506
+ stateBuilder[name] = func;
2507
+ return this;
2508
+ }
2509
+
2510
+ /**
2511
+ * @ngdoc function
2512
+ * @name ui.router.state.$stateProvider#state
2513
+ * @methodOf ui.router.state.$stateProvider
2514
+ *
2515
+ * @description
2516
+ * Registers a state configuration under a given state name. The stateConfig object
2517
+ * has the following acceptable properties.
2518
+ *
2519
+ * @param {string} name A unique state name, e.g. "home", "about", "contacts".
2520
+ * To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
2521
+ * @param {object} stateConfig State configuration object.
2522
+ * @param {string|function=} stateConfig.template
2523
+ * <a id='template'></a>
2524
+ * html template as a string or a function that returns
2525
+ * an html template as a string which should be used by the uiView directives. This property
2526
+ * takes precedence over templateUrl.
2527
+ *
2528
+ * If `template` is a function, it will be called with the following parameters:
2529
+ *
2530
+ * - {array.&lt;object&gt;} - state parameters extracted from the current $location.path() by
2531
+ * applying the current state
2532
+ *
2533
+ * <pre>template:
2534
+ * "<h1>inline template definition</h1>" +
2535
+ * "<div ui-view></div>"</pre>
2536
+ * <pre>template: function(params) {
2537
+ * return "<h1>generated template</h1>"; }</pre>
2538
+ * </div>
2539
+ *
2540
+ * @param {string|function=} stateConfig.templateUrl
2541
+ * <a id='templateUrl'></a>
2542
+ *
2543
+ * path or function that returns a path to an html
2544
+ * template that should be used by uiView.
2545
+ *
2546
+ * If `templateUrl` is a function, it will be called with the following parameters:
2547
+ *
2548
+ * - {array.&lt;object&gt;} - state parameters extracted from the current $location.path() by
2549
+ * applying the current state
2550
+ *
2551
+ * <pre>templateUrl: "home.html"</pre>
2552
+ * <pre>templateUrl: function(params) {
2553
+ * return myTemplates[params.pageId]; }</pre>
2554
+ *
2555
+ * @param {function=} stateConfig.templateProvider
2556
+ * <a id='templateProvider'></a>
2557
+ * Provider function that returns HTML content string.
2558
+ * <pre> templateProvider:
2559
+ * function(MyTemplateService, params) {
2560
+ * return MyTemplateService.getTemplate(params.pageId);
2561
+ * }</pre>
2562
+ *
2563
+ * @param {string|function=} stateConfig.controller
2564
+ * <a id='controller'></a>
2565
+ *
2566
+ * Controller fn that should be associated with newly
2567
+ * related scope or the name of a registered controller if passed as a string.
2568
+ * Optionally, the ControllerAs may be declared here.
2569
+ * <pre>controller: "MyRegisteredController"</pre>
2570
+ * <pre>controller:
2571
+ * "MyRegisteredController as fooCtrl"}</pre>
2572
+ * <pre>controller: function($scope, MyService) {
2573
+ * $scope.data = MyService.getData(); }</pre>
2574
+ *
2575
+ * @param {function=} stateConfig.controllerProvider
2576
+ * <a id='controllerProvider'></a>
2577
+ *
2578
+ * Injectable provider function that returns the actual controller or string.
2579
+ * <pre>controllerProvider:
2580
+ * function(MyResolveData) {
2581
+ * if (MyResolveData.foo)
2582
+ * return "FooCtrl"
2583
+ * else if (MyResolveData.bar)
2584
+ * return "BarCtrl";
2585
+ * else return function($scope) {
2586
+ * $scope.baz = "Qux";
2587
+ * }
2588
+ * }</pre>
2589
+ *
2590
+ * @param {string=} stateConfig.controllerAs
2591
+ * <a id='controllerAs'></a>
2592
+ *
2593
+ * A controller alias name. If present the controller will be
2594
+ * published to scope under the controllerAs name.
2595
+ * <pre>controllerAs: "myCtrl"</pre>
2596
+ *
2597
+ * @param {string|object=} stateConfig.parent
2598
+ * <a id='parent'></a>
2599
+ * Optionally specifies the parent state of this state.
2600
+ *
2601
+ * <pre>parent: 'parentState'</pre>
2602
+ * <pre>parent: parentState // JS variable</pre>
2603
+ *
2604
+ * @param {object=} stateConfig.resolve
2605
+ * <a id='resolve'></a>
2606
+ *
2607
+ * An optional map&lt;string, function&gt; of dependencies which
2608
+ * should be injected into the controller. If any of these dependencies are promises,
2609
+ * the router will wait for them all to be resolved before the controller is instantiated.
2610
+ * If all the promises are resolved successfully, the $stateChangeSuccess event is fired
2611
+ * and the values of the resolved promises are injected into any controllers that reference them.
2612
+ * If any of the promises are rejected the $stateChangeError event is fired.
2613
+ *
2614
+ * The map object is:
2615
+ *
2616
+ * - key - {string}: name of dependency to be injected into controller
2617
+ * - factory - {string|function}: If string then it is alias for service. Otherwise if function,
2618
+ * it is injected and return value it treated as dependency. If result is a promise, it is
2619
+ * resolved before its value is injected into controller.
2620
+ *
2621
+ * <pre>resolve: {
2622
+ * myResolve1:
2623
+ * function($http, $stateParams) {
2624
+ * return $http.get("/api/foos/"+stateParams.fooID);
2625
+ * }
2626
+ * }</pre>
2627
+ *
2628
+ * @param {string=} stateConfig.url
2629
+ * <a id='url'></a>
2630
+ *
2631
+ * A url fragment with optional parameters. When a state is navigated or
2632
+ * transitioned to, the `$stateParams` service will be populated with any
2633
+ * parameters that were passed.
2634
+ *
2635
+ * (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for
2636
+ * more details on acceptable patterns )
2637
+ *
2638
+ * examples:
2639
+ * <pre>url: "/home"
2640
+ * url: "/users/:userid"
2641
+ * url: "/books/{bookid:[a-zA-Z_-]}"
2642
+ * url: "/books/{categoryid:int}"
2643
+ * url: "/books/{publishername:string}/{categoryid:int}"
2644
+ * url: "/messages?before&after"
2645
+ * url: "/messages?{before:date}&{after:date}"
2646
+ * url: "/messages/:mailboxid?{before:date}&{after:date}"
2647
+ * </pre>
2648
+ *
2649
+ * @param {object=} stateConfig.views
2650
+ * <a id='views'></a>
2651
+ * an optional map&lt;string, object&gt; which defined multiple views, or targets views
2652
+ * manually/explicitly.
2653
+ *
2654
+ * Examples:
2655
+ *
2656
+ * Targets three named `ui-view`s in the parent state's template
2657
+ * <pre>views: {
2658
+ * header: {
2659
+ * controller: "headerCtrl",
2660
+ * templateUrl: "header.html"
2661
+ * }, body: {
2662
+ * controller: "bodyCtrl",
2663
+ * templateUrl: "body.html"
2664
+ * }, footer: {
2665
+ * controller: "footCtrl",
2666
+ * templateUrl: "footer.html"
2667
+ * }
2668
+ * }</pre>
2669
+ *
2670
+ * Targets named `ui-view="header"` from grandparent state 'top''s template, and named `ui-view="body" from parent state's template.
2671
+ * <pre>views: {
2672
+ * 'header@top': {
2673
+ * controller: "msgHeaderCtrl",
2674
+ * templateUrl: "msgHeader.html"
2675
+ * }, 'body': {
2676
+ * controller: "messagesCtrl",
2677
+ * templateUrl: "messages.html"
2678
+ * }
2679
+ * }</pre>
2680
+ *
2681
+ * @param {boolean=} [stateConfig.abstract=false]
2682
+ * <a id='abstract'></a>
2683
+ * An abstract state will never be directly activated,
2684
+ * but can provide inherited properties to its common children states.
2685
+ * <pre>abstract: true</pre>
2686
+ *
2687
+ * @param {function=} stateConfig.onEnter
2688
+ * <a id='onEnter'></a>
2689
+ *
2690
+ * Callback function for when a state is entered. Good way
2691
+ * to trigger an action or dispatch an event, such as opening a dialog.
2692
+ * If minifying your scripts, make sure to explictly annotate this function,
2693
+ * because it won't be automatically annotated by your build tools.
2694
+ *
2695
+ * <pre>onEnter: function(MyService, $stateParams) {
2696
+ * MyService.foo($stateParams.myParam);
2697
+ * }</pre>
2698
+ *
2699
+ * @param {function=} stateConfig.onExit
2700
+ * <a id='onExit'></a>
2701
+ *
2702
+ * Callback function for when a state is exited. Good way to
2703
+ * trigger an action or dispatch an event, such as opening a dialog.
2704
+ * If minifying your scripts, make sure to explictly annotate this function,
2705
+ * because it won't be automatically annotated by your build tools.
2706
+ *
2707
+ * <pre>onExit: function(MyService, $stateParams) {
2708
+ * MyService.cleanup($stateParams.myParam);
2709
+ * }</pre>
2710
+ *
2711
+ * @param {boolean=} [stateConfig.reloadOnSearch=true]
2712
+ * <a id='reloadOnSearch'></a>
2713
+ *
2714
+ * If `false`, will not retrigger the same state
2715
+ * just because a search/query parameter has changed (via $location.search() or $location.hash()).
2716
+ * Useful for when you'd like to modify $location.search() without triggering a reload.
2717
+ * <pre>reloadOnSearch: false</pre>
2718
+ *
2719
+ * @param {object=} stateConfig.data
2720
+ * <a id='data'></a>
2721
+ *
2722
+ * Arbitrary data object, useful for custom configuration. The parent state's `data` is
2723
+ * prototypally inherited. In other words, adding a data property to a state adds it to
2724
+ * the entire subtree via prototypal inheritance.
2725
+ *
2726
+ * <pre>data: {
2727
+ * requiredRole: 'foo'
2728
+ * } </pre>
2729
+ *
2730
+ * @param {object=} stateConfig.params
2731
+ * <a id='params'></a>
2732
+ *
2733
+ * A map which optionally configures parameters declared in the `url`, or
2734
+ * defines additional non-url parameters. For each parameter being
2735
+ * configured, add a configuration object keyed to the name of the parameter.
2736
+ *
2737
+ * Each parameter configuration object may contain the following properties:
2738
+ *
2739
+ * - ** value ** - {object|function=}: specifies the default value for this
2740
+ * parameter. This implicitly sets this parameter as optional.
2741
+ *
2742
+ * When UI-Router routes to a state and no value is
2743
+ * specified for this parameter in the URL or transition, the
2744
+ * default value will be used instead. If `value` is a function,
2745
+ * it will be injected and invoked, and the return value used.
2746
+ *
2747
+ * *Note*: `undefined` is treated as "no default value" while `null`
2748
+ * is treated as "the default value is `null`".
2749
+ *
2750
+ * *Shorthand*: If you only need to configure the default value of the
2751
+ * parameter, you may use a shorthand syntax. In the **`params`**
2752
+ * map, instead mapping the param name to a full parameter configuration
2753
+ * object, simply set map it to the default parameter value, e.g.:
2754
+ *
2755
+ * <pre>// define a parameter's default value
2756
+ * params: {
2757
+ * param1: { value: "defaultValue" }
2758
+ * }
2759
+ * // shorthand default values
2760
+ * params: {
2761
+ * param1: "defaultValue",
2762
+ * param2: "param2Default"
2763
+ * }</pre>
2764
+ *
2765
+ * - ** array ** - {boolean=}: *(default: false)* If true, the param value will be
2766
+ * treated as an array of values. If you specified a Type, the value will be
2767
+ * treated as an array of the specified Type. Note: query parameter values
2768
+ * default to a special `"auto"` mode.
2769
+ *
2770
+ * For query parameters in `"auto"` mode, if multiple values for a single parameter
2771
+ * are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values
2772
+ * are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`). However, if
2773
+ * only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single
2774
+ * value (e.g.: `{ foo: '1' }`).
2775
+ *
2776
+ * <pre>params: {
2777
+ * param1: { array: true }
2778
+ * }</pre>
2779
+ *
2780
+ * - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when
2781
+ * the current parameter value is the same as the default value. If `squash` is not set, it uses the
2782
+ * configured default squash policy.
2783
+ * (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`})
2784
+ *
2785
+ * There are three squash settings:
2786
+ *
2787
+ * - false: The parameter's default value is not squashed. It is encoded and included in the URL
2788
+ * - true: The parameter's default value is omitted from the URL. If the parameter is preceeded and followed
2789
+ * by slashes in the state's `url` declaration, then one of those slashes are omitted.
2790
+ * This can allow for cleaner looking URLs.
2791
+ * - `"<arbitrary string>"`: The parameter's default value is replaced with an arbitrary placeholder of your choice.
2792
+ *
2793
+ * <pre>params: {
2794
+ * param1: {
2795
+ * value: "defaultId",
2796
+ * squash: true
2797
+ * } }
2798
+ * // squash "defaultValue" to "~"
2799
+ * params: {
2800
+ * param1: {
2801
+ * value: "defaultValue",
2802
+ * squash: "~"
2803
+ * } }
2804
+ * </pre>
2805
+ *
2806
+ *
2807
+ * @example
2808
+ * <pre>
2809
+ * // Some state name examples
2810
+ *
2811
+ * // stateName can be a single top-level name (must be unique).
2812
+ * $stateProvider.state("home", {});
2813
+ *
2814
+ * // Or it can be a nested state name. This state is a child of the
2815
+ * // above "home" state.
2816
+ * $stateProvider.state("home.newest", {});
2817
+ *
2818
+ * // Nest states as deeply as needed.
2819
+ * $stateProvider.state("home.newest.abc.xyz.inception", {});
2820
+ *
2821
+ * // state() returns $stateProvider, so you can chain state declarations.
2822
+ * $stateProvider
2823
+ * .state("home", {})
2824
+ * .state("about", {})
2825
+ * .state("contacts", {});
2826
+ * </pre>
2827
+ *
2828
+ */
2829
+ this.state = state;
2830
+ function state(name, definition) {
2831
+ /*jshint validthis: true */
2832
+ if (isObject(name)) definition = name;
2833
+ else definition.name = name;
2834
+ registerState(definition);
2835
+ return this;
2836
+ }
2837
+
2838
+ /**
2839
+ * @ngdoc object
2840
+ * @name ui.router.state.$state
2841
+ *
2842
+ * @requires $rootScope
2843
+ * @requires $q
2844
+ * @requires ui.router.state.$view
2845
+ * @requires $injector
2846
+ * @requires ui.router.util.$resolve
2847
+ * @requires ui.router.state.$stateParams
2848
+ * @requires ui.router.router.$urlRouter
2849
+ *
2850
+ * @property {object} params A param object, e.g. {sectionId: section.id)}, that
2851
+ * you'd like to test against the current active state.
2852
+ * @property {object} current A reference to the state's config object. However
2853
+ * you passed it in. Useful for accessing custom data.
2854
+ * @property {object} transition Currently pending transition. A promise that'll
2855
+ * resolve or reject.
2856
+ *
2857
+ * @description
2858
+ * `$state` service is responsible for representing states as well as transitioning
2859
+ * between them. It also provides interfaces to ask for current state or even states
2860
+ * you're coming from.
2861
+ */
2862
+ this.$get = $get;
2863
+ $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory'];
2864
+ function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) {
2865
+
2866
+ var TransitionSuperseded = $q.reject(new Error('transition superseded'));
2867
+ var TransitionPrevented = $q.reject(new Error('transition prevented'));
2868
+ var TransitionAborted = $q.reject(new Error('transition aborted'));
2869
+ var TransitionFailed = $q.reject(new Error('transition failed'));
2870
+
2871
+ // Handles the case where a state which is the target of a transition is not found, and the user
2872
+ // can optionally retry or defer the transition
2873
+ function handleRedirect(redirect, state, params, options) {
2874
+ /**
2875
+ * @ngdoc event
2876
+ * @name ui.router.state.$state#$stateNotFound
2877
+ * @eventOf ui.router.state.$state
2878
+ * @eventType broadcast on root scope
2879
+ * @description
2880
+ * Fired when a requested state **cannot be found** using the provided state name during transition.
2881
+ * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
2882
+ * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler,
2883
+ * you can see its three properties in the example. You can use `event.preventDefault()` to abort the
2884
+ * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value.
2885
+ *
2886
+ * @param {Object} event Event object.
2887
+ * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties.
2888
+ * @param {State} fromState Current state object.
2889
+ * @param {Object} fromParams Current state params.
2890
+ *
2891
+ * @example
2892
+ *
2893
+ * <pre>
2894
+ * // somewhere, assume lazy.state has not been defined
2895
+ * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
2896
+ *
2897
+ * // somewhere else
2898
+ * $scope.$on('$stateNotFound',
2899
+ * function(event, unfoundState, fromState, fromParams){
2900
+ * console.log(unfoundState.to); // "lazy.state"
2901
+ * console.log(unfoundState.toParams); // {a:1, b:2}
2902
+ * console.log(unfoundState.options); // {inherit:false} + default options
2903
+ * })
2904
+ * </pre>
2905
+ */
2906
+ var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params);
2907
+
2908
+ if (evt.defaultPrevented) {
2909
+ $urlRouter.update();
2910
+ return TransitionAborted;
2911
+ }
2912
+
2913
+ if (!evt.retry) {
2914
+ return null;
2915
+ }
2916
+
2917
+ // Allow the handler to return a promise to defer state lookup retry
2918
+ if (options.$retry) {
2919
+ $urlRouter.update();
2920
+ return TransitionFailed;
2921
+ }
2922
+ var retryTransition = $state.transition = $q.when(evt.retry);
2923
+
2924
+ retryTransition.then(function() {
2925
+ if (retryTransition !== $state.transition) return TransitionSuperseded;
2926
+ redirect.options.$retry = true;
2927
+ return $state.transitionTo(redirect.to, redirect.toParams, redirect.options);
2928
+ }, function() {
2929
+ return TransitionAborted;
2930
+ });
2931
+ $urlRouter.update();
2932
+
2933
+ return retryTransition;
2934
+ }
2935
+
2936
+ root.locals = { resolve: null, globals: { $stateParams: {} } };
2937
+
2938
+ $state = {
2939
+ params: {},
2940
+ current: root.self,
2941
+ $current: root,
2942
+ transition: null
2943
+ };
2944
+
2945
+ /**
2946
+ * @ngdoc function
2947
+ * @name ui.router.state.$state#reload
2948
+ * @methodOf ui.router.state.$state
2949
+ *
2950
+ * @description
2951
+ * A method that force reloads the current state. All resolves are re-resolved,
2952
+ * controllers reinstantiated, and events re-fired.
2953
+ *
2954
+ * @example
2955
+ * <pre>
2956
+ * var app angular.module('app', ['ui.router']);
2957
+ *
2958
+ * app.controller('ctrl', function ($scope, $state) {
2959
+ * $scope.reload = function(){
2960
+ * $state.reload();
2961
+ * }
2962
+ * });
2963
+ * </pre>
2964
+ *
2965
+ * `reload()` is just an alias for:
2966
+ * <pre>
2967
+ * $state.transitionTo($state.current, $stateParams, {
2968
+ * reload: true, inherit: false, notify: true
2969
+ * });
2970
+ * </pre>
2971
+ *
2972
+ * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved.
2973
+ * @example
2974
+ * <pre>
2975
+ * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item'
2976
+ * //and current state is 'contacts.detail.item'
2977
+ * var app angular.module('app', ['ui.router']);
2978
+ *
2979
+ * app.controller('ctrl', function ($scope, $state) {
2980
+ * $scope.reload = function(){
2981
+ * //will reload 'contact.detail' and 'contact.detail.item' states
2982
+ * $state.reload('contact.detail');
2983
+ * }
2984
+ * });
2985
+ * </pre>
2986
+ *
2987
+ * `reload()` is just an alias for:
2988
+ * <pre>
2989
+ * $state.transitionTo($state.current, $stateParams, {
2990
+ * reload: true, inherit: false, notify: true
2991
+ * });
2992
+ * </pre>
2993
+
2994
+ * @returns {promise} A promise representing the state of the new transition. See
2995
+ * {@link ui.router.state.$state#methods_go $state.go}.
2996
+ */
2997
+ $state.reload = function reload(state) {
2998
+ return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true});
2999
+ };
3000
+
3001
+ /**
3002
+ * @ngdoc function
3003
+ * @name ui.router.state.$state#go
3004
+ * @methodOf ui.router.state.$state
3005
+ *
3006
+ * @description
3007
+ * Convenience method for transitioning to a new state. `$state.go` calls
3008
+ * `$state.transitionTo` internally but automatically sets options to
3009
+ * `{ location: true, inherit: true, relative: $state.$current, notify: true }`.
3010
+ * This allows you to easily use an absolute or relative to path and specify
3011
+ * only the parameters you'd like to update (while letting unspecified parameters
3012
+ * inherit from the currently active ancestor states).
3013
+ *
3014
+ * @example
3015
+ * <pre>
3016
+ * var app = angular.module('app', ['ui.router']);
3017
+ *
3018
+ * app.controller('ctrl', function ($scope, $state) {
3019
+ * $scope.changeState = function () {
3020
+ * $state.go('contact.detail');
3021
+ * };
3022
+ * });
3023
+ * </pre>
3024
+ * <img src='../ngdoc_assets/StateGoExamples.png'/>
3025
+ *
3026
+ * @param {string} to Absolute state name or relative state path. Some examples:
3027
+ *
3028
+ * - `$state.go('contact.detail')` - will go to the `contact.detail` state
3029
+ * - `$state.go('^')` - will go to a parent state
3030
+ * - `$state.go('^.sibling')` - will go to a sibling state
3031
+ * - `$state.go('.child.grandchild')` - will go to grandchild state
3032
+ *
3033
+ * @param {object=} params A map of the parameters that will be sent to the state,
3034
+ * will populate $stateParams. Any parameters that are not specified will be inherited from currently
3035
+ * defined parameters. This allows, for example, going to a sibling state that shares parameters
3036
+ * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e.
3037
+ * transitioning to a sibling will get you the parameters for all parents, transitioning to a child
3038
+ * will get you all current parameters, etc.
3039
+ * @param {object=} options Options object. The options are:
3040
+ *
3041
+ * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
3042
+ * will not. If string, must be `"replace"`, which will update url and also replace last history record.
3043
+ * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
3044
+ * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
3045
+ * defines which state to be relative from.
3046
+ * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
3047
+ * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params
3048
+ * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
3049
+ * use this when you want to force a reload when *everything* is the same, including search params.
3050
+ *
3051
+ * @returns {promise} A promise representing the state of the new transition.
3052
+ *
3053
+ * Possible success values:
3054
+ *
3055
+ * - $state.current
3056
+ *
3057
+ * <br/>Possible rejection values:
3058
+ *
3059
+ * - 'transition superseded' - when a newer transition has been started after this one
3060
+ * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener
3061
+ * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or
3062
+ * when a `$stateNotFound` `event.retry` promise errors.
3063
+ * - 'transition failed' - when a state has been unsuccessfully found after 2 tries.
3064
+ * - *resolve error* - when an error has occurred with a `resolve`
3065
+ *
3066
+ */
3067
+ $state.go = function go(to, params, options) {
3068
+ return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options));
3069
+ };
3070
+
3071
+ /**
3072
+ * @ngdoc function
3073
+ * @name ui.router.state.$state#transitionTo
3074
+ * @methodOf ui.router.state.$state
3075
+ *
3076
+ * @description
3077
+ * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go}
3078
+ * uses `transitionTo` internally. `$state.go` is recommended in most situations.
3079
+ *
3080
+ * @example
3081
+ * <pre>
3082
+ * var app = angular.module('app', ['ui.router']);
3083
+ *
3084
+ * app.controller('ctrl', function ($scope, $state) {
3085
+ * $scope.changeState = function () {
3086
+ * $state.transitionTo('contact.detail');
3087
+ * };
3088
+ * });
3089
+ * </pre>
3090
+ *
3091
+ * @param {string} to State name.
3092
+ * @param {object=} toParams A map of the parameters that will be sent to the state,
3093
+ * will populate $stateParams.
3094
+ * @param {object=} options Options object. The options are:
3095
+ *
3096
+ * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
3097
+ * will not. If string, must be `"replace"`, which will update url and also replace last history record.
3098
+ * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url.
3099
+ * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'),
3100
+ * defines which state to be relative from.
3101
+ * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
3102
+ * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params
3103
+ * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
3104
+ * use this when you want to force a reload when *everything* is the same, including search params.
3105
+ * if String, then will reload the state with the name given in reload, and any children.
3106
+ * if Object, then a stateObj is expected, will reload the state found in stateObj, and any children.
3107
+ *
3108
+ * @returns {promise} A promise representing the state of the new transition. See
3109
+ * {@link ui.router.state.$state#methods_go $state.go}.
3110
+ */
3111
+ $state.transitionTo = function transitionTo(to, toParams, options) {
3112
+ toParams = toParams || {};
3113
+ options = extend({
3114
+ location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false
3115
+ }, options || {});
3116
+
3117
+ var from = $state.$current, fromParams = $state.params, fromPath = from.path;
3118
+ var evt, toState = findState(to, options.relative);
3119
+
3120
+ // Store the hash param for later (since it will be stripped out by various methods)
3121
+ var hash = toParams['#'];
3122
+
3123
+ if (!isDefined(toState)) {
3124
+ var redirect = { to: to, toParams: toParams, options: options };
3125
+ var redirectResult = handleRedirect(redirect, from.self, fromParams, options);
3126
+
3127
+ if (redirectResult) {
3128
+ return redirectResult;
3129
+ }
3130
+
3131
+ // Always retry once if the $stateNotFound was not prevented
3132
+ // (handles either redirect changed or state lazy-definition)
3133
+ to = redirect.to;
3134
+ toParams = redirect.toParams;
3135
+ options = redirect.options;
3136
+ toState = findState(to, options.relative);
3137
+
3138
+ if (!isDefined(toState)) {
3139
+ if (!options.relative) throw new Error("No such state '" + to + "'");
3140
+ throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'");
3141
+ }
3142
+ }
3143
+ if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'");
3144
+ if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState);
3145
+ if (!toState.params.$$validates(toParams)) return TransitionFailed;
3146
+
3147
+ toParams = toState.params.$$values(toParams);
3148
+ to = toState;
3149
+
3150
+ var toPath = to.path;
3151
+
3152
+ // Starting from the root of the path, keep all levels that haven't changed
3153
+ var keep = 0, state = toPath[keep], locals = root.locals, toLocals = [];
3154
+
3155
+ if (!options.reload) {
3156
+ while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) {
3157
+ locals = toLocals[keep] = state.locals;
3158
+ keep++;
3159
+ state = toPath[keep];
3160
+ }
3161
+ } else if (isString(options.reload) || isObject(options.reload)) {
3162
+ if (isObject(options.reload) && !options.reload.name) {
3163
+ throw new Error('Invalid reload state object');
3164
+ }
3165
+
3166
+ var reloadState = options.reload === true ? fromPath[0] : findState(options.reload);
3167
+ if (options.reload && !reloadState) {
3168
+ throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'");
3169
+ }
3170
+
3171
+ while (state && state === fromPath[keep] && state !== reloadState) {
3172
+ locals = toLocals[keep] = state.locals;
3173
+ keep++;
3174
+ state = toPath[keep];
3175
+ }
3176
+ }
3177
+
3178
+ // If we're going to the same state and all locals are kept, we've got nothing to do.
3179
+ // But clear 'transition', as we still want to cancel any other pending transitions.
3180
+ // TODO: We may not want to bump 'transition' if we're called from a location change
3181
+ // that we've initiated ourselves, because we might accidentally abort a legitimate
3182
+ // transition initiated from code?
3183
+ if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) {
3184
+ if (hash) toParams['#'] = hash;
3185
+ $state.params = toParams;
3186
+ copy($state.params, $stateParams);
3187
+ if (options.location && to.navigable && to.navigable.url) {
3188
+ $urlRouter.push(to.navigable.url, toParams, {
3189
+ $$avoidResync: true, replace: options.location === 'replace'
3190
+ });
3191
+ $urlRouter.update(true);
3192
+ }
3193
+ $state.transition = null;
3194
+ return $q.when($state.current);
3195
+ }
3196
+
3197
+ // Filter parameters before we pass them to event handlers etc.
3198
+ toParams = filterByKeys(to.params.$$keys(), toParams || {});
3199
+
3200
+ // Broadcast start event and cancel the transition if requested
3201
+ if (options.notify) {
3202
+ /**
3203
+ * @ngdoc event
3204
+ * @name ui.router.state.$state#$stateChangeStart
3205
+ * @eventOf ui.router.state.$state
3206
+ * @eventType broadcast on root scope
3207
+ * @description
3208
+ * Fired when the state transition **begins**. You can use `event.preventDefault()`
3209
+ * to prevent the transition from happening and then the transition promise will be
3210
+ * rejected with a `'transition prevented'` value.
3211
+ *
3212
+ * @param {Object} event Event object.
3213
+ * @param {State} toState The state being transitioned to.
3214
+ * @param {Object} toParams The params supplied to the `toState`.
3215
+ * @param {State} fromState The current state, pre-transition.
3216
+ * @param {Object} fromParams The params supplied to the `fromState`.
3217
+ *
3218
+ * @example
3219
+ *
3220
+ * <pre>
3221
+ * $rootScope.$on('$stateChangeStart',
3222
+ * function(event, toState, toParams, fromState, fromParams){
3223
+ * event.preventDefault();
3224
+ * // transitionTo() promise will be rejected with
3225
+ * // a 'transition prevented' error
3226
+ * })
3227
+ * </pre>
3228
+ */
3229
+ if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams).defaultPrevented) {
3230
+ $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams);
3231
+ $urlRouter.update();
3232
+ return TransitionPrevented;
3233
+ }
3234
+ }
3235
+
3236
+ // Resolve locals for the remaining states, but don't update any global state just
3237
+ // yet -- if anything fails to resolve the current state needs to remain untouched.
3238
+ // We also set up an inheritance chain for the locals here. This allows the view directive
3239
+ // to quickly look up the correct definition for each view in the current state. Even
3240
+ // though we create the locals object itself outside resolveState(), it is initially
3241
+ // empty and gets filled asynchronously. We need to keep track of the promise for the
3242
+ // (fully resolved) current locals, and pass this down the chain.
3243
+ var resolved = $q.when(locals);
3244
+
3245
+ for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
3246
+ locals = toLocals[l] = inherit(locals);
3247
+ resolved = resolveState(state, toParams, state === to, resolved, locals, options);
3248
+ }
3249
+
3250
+ // Once everything is resolved, we are ready to perform the actual transition
3251
+ // and return a promise for the new state. We also keep track of what the
3252
+ // current promise is, so that we can detect overlapping transitions and
3253
+ // keep only the outcome of the last transition.
3254
+ var transition = $state.transition = resolved.then(function () {
3255
+ var l, entering, exiting;
3256
+
3257
+ if ($state.transition !== transition) return TransitionSuperseded;
3258
+
3259
+ // Exit 'from' states not kept
3260
+ for (l = fromPath.length - 1; l >= keep; l--) {
3261
+ exiting = fromPath[l];
3262
+ if (exiting.self.onExit) {
3263
+ $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals);
3264
+ }
3265
+ exiting.locals = null;
3266
+ }
3267
+
3268
+ // Enter 'to' states not kept
3269
+ for (l = keep; l < toPath.length; l++) {
3270
+ entering = toPath[l];
3271
+ entering.locals = toLocals[l];
3272
+ if (entering.self.onEnter) {
3273
+ $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals);
3274
+ }
3275
+ }
3276
+
3277
+ // Re-add the saved hash before we start returning things
3278
+ if (hash) toParams['#'] = hash;
3279
+
3280
+ // Run it again, to catch any transitions in callbacks
3281
+ if ($state.transition !== transition) return TransitionSuperseded;
3282
+
3283
+ // Update globals in $state
3284
+ $state.$current = to;
3285
+ $state.current = to.self;
3286
+ $state.params = toParams;
3287
+ copy($state.params, $stateParams);
3288
+ $state.transition = null;
3289
+
3290
+ if (options.location && to.navigable) {
3291
+ $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, {
3292
+ $$avoidResync: true, replace: options.location === 'replace'
3293
+ });
3294
+ }
3295
+
3296
+ if (options.notify) {
3297
+ /**
3298
+ * @ngdoc event
3299
+ * @name ui.router.state.$state#$stateChangeSuccess
3300
+ * @eventOf ui.router.state.$state
3301
+ * @eventType broadcast on root scope
3302
+ * @description
3303
+ * Fired once the state transition is **complete**.
3304
+ *
3305
+ * @param {Object} event Event object.
3306
+ * @param {State} toState The state being transitioned to.
3307
+ * @param {Object} toParams The params supplied to the `toState`.
3308
+ * @param {State} fromState The current state, pre-transition.
3309
+ * @param {Object} fromParams The params supplied to the `fromState`.
3310
+ */
3311
+ $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
3312
+ }
3313
+ $urlRouter.update(true);
3314
+
3315
+ return $state.current;
3316
+ }, function (error) {
3317
+ if ($state.transition !== transition) return TransitionSuperseded;
3318
+
3319
+ $state.transition = null;
3320
+ /**
3321
+ * @ngdoc event
3322
+ * @name ui.router.state.$state#$stateChangeError
3323
+ * @eventOf ui.router.state.$state
3324
+ * @eventType broadcast on root scope
3325
+ * @description
3326
+ * Fired when an **error occurs** during transition. It's important to note that if you
3327
+ * have any errors in your resolve functions (javascript errors, non-existent services, etc)
3328
+ * they will not throw traditionally. You must listen for this $stateChangeError event to
3329
+ * catch **ALL** errors.
3330
+ *
3331
+ * @param {Object} event Event object.
3332
+ * @param {State} toState The state being transitioned to.
3333
+ * @param {Object} toParams The params supplied to the `toState`.
3334
+ * @param {State} fromState The current state, pre-transition.
3335
+ * @param {Object} fromParams The params supplied to the `fromState`.
3336
+ * @param {Error} error The resolve error object.
3337
+ */
3338
+ evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);
3339
+
3340
+ if (!evt.defaultPrevented) {
3341
+ $urlRouter.update();
3342
+ }
3343
+
3344
+ return $q.reject(error);
3345
+ });
3346
+
3347
+ return transition;
3348
+ };
3349
+
3350
+ /**
3351
+ * @ngdoc function
3352
+ * @name ui.router.state.$state#is
3353
+ * @methodOf ui.router.state.$state
3354
+ *
3355
+ * @description
3356
+ * Similar to {@link ui.router.state.$state#methods_includes $state.includes},
3357
+ * but only checks for the full state name. If params is supplied then it will be
3358
+ * tested for strict equality against the current active params object, so all params
3359
+ * must match with none missing and no extras.
3360
+ *
3361
+ * @example
3362
+ * <pre>
3363
+ * $state.$current.name = 'contacts.details.item';
3364
+ *
3365
+ * // absolute name
3366
+ * $state.is('contact.details.item'); // returns true
3367
+ * $state.is(contactDetailItemStateObject); // returns true
3368
+ *
3369
+ * // relative name (. and ^), typically from a template
3370
+ * // E.g. from the 'contacts.details' template
3371
+ * <div ng-class="{highlighted: $state.is('.item')}">Item</div>
3372
+ * </pre>
3373
+ *
3374
+ * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check.
3375
+ * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like
3376
+ * to test against the current active state.
3377
+ * @param {object=} options An options object. The options are:
3378
+ *
3379
+ * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will
3380
+ * test relative to `options.relative` state (or name).
3381
+ *
3382
+ * @returns {boolean} Returns true if it is the state.
3383
+ */
3384
+ $state.is = function is(stateOrName, params, options) {
3385
+ options = extend({ relative: $state.$current }, options || {});
3386
+ var state = findState(stateOrName, options.relative);
3387
+
3388
+ if (!isDefined(state)) { return undefined; }
3389
+ if ($state.$current !== state) { return false; }
3390
+ return params ? equalForKeys(state.params.$$values(params), $stateParams) : true;
3391
+ };
3392
+
3393
+ /**
3394
+ * @ngdoc function
3395
+ * @name ui.router.state.$state#includes
3396
+ * @methodOf ui.router.state.$state
3397
+ *
3398
+ * @description
3399
+ * A method to determine if the current active state is equal to or is the child of the
3400
+ * state stateName. If any params are passed then they will be tested for a match as well.
3401
+ * Not all the parameters need to be passed, just the ones you'd like to test for equality.
3402
+ *
3403
+ * @example
3404
+ * Partial and relative names
3405
+ * <pre>
3406
+ * $state.$current.name = 'contacts.details.item';
3407
+ *
3408
+ * // Using partial names
3409
+ * $state.includes("contacts"); // returns true
3410
+ * $state.includes("contacts.details"); // returns true
3411
+ * $state.includes("contacts.details.item"); // returns true
3412
+ * $state.includes("contacts.list"); // returns false
3413
+ * $state.includes("about"); // returns false
3414
+ *
3415
+ * // Using relative names (. and ^), typically from a template
3416
+ * // E.g. from the 'contacts.details' template
3417
+ * <div ng-class="{highlighted: $state.includes('.item')}">Item</div>
3418
+ * </pre>
3419
+ *
3420
+ * Basic globbing patterns
3421
+ * <pre>
3422
+ * $state.$current.name = 'contacts.details.item.url';
3423
+ *
3424
+ * $state.includes("*.details.*.*"); // returns true
3425
+ * $state.includes("*.details.**"); // returns true
3426
+ * $state.includes("**.item.**"); // returns true
3427
+ * $state.includes("*.details.item.url"); // returns true
3428
+ * $state.includes("*.details.*.url"); // returns true
3429
+ * $state.includes("*.details.*"); // returns false
3430
+ * $state.includes("item.**"); // returns false
3431
+ * </pre>
3432
+ *
3433
+ * @param {string} stateOrName A partial name, relative name, or glob pattern
3434
+ * to be searched for within the current state name.
3435
+ * @param {object=} params A param object, e.g. `{sectionId: section.id}`,
3436
+ * that you'd like to test against the current active state.
3437
+ * @param {object=} options An options object. The options are:
3438
+ *
3439
+ * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set,
3440
+ * .includes will test relative to `options.relative` state (or name).
3441
+ *
3442
+ * @returns {boolean} Returns true if it does include the state
3443
+ */
3444
+ $state.includes = function includes(stateOrName, params, options) {
3445
+ options = extend({ relative: $state.$current }, options || {});
3446
+ if (isString(stateOrName) && isGlob(stateOrName)) {
3447
+ if (!doesStateMatchGlob(stateOrName)) {
3448
+ return false;
3449
+ }
3450
+ stateOrName = $state.$current.name;
3451
+ }
3452
+
3453
+ var state = findState(stateOrName, options.relative);
3454
+ if (!isDefined(state)) { return undefined; }
3455
+ if (!isDefined($state.$current.includes[state.name])) { return false; }
3456
+ return params ? equalForKeys(state.params.$$values(params), $stateParams, objectKeys(params)) : true;
3457
+ };
3458
+
3459
+
3460
+ /**
3461
+ * @ngdoc function
3462
+ * @name ui.router.state.$state#href
3463
+ * @methodOf ui.router.state.$state
3464
+ *
3465
+ * @description
3466
+ * A url generation method that returns the compiled url for the given state populated with the given params.
3467
+ *
3468
+ * @example
3469
+ * <pre>
3470
+ * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
3471
+ * </pre>
3472
+ *
3473
+ * @param {string|object} stateOrName The state name or state object you'd like to generate a url from.
3474
+ * @param {object=} params An object of parameter values to fill the state's required parameters.
3475
+ * @param {object=} options Options object. The options are:
3476
+ *
3477
+ * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the
3478
+ * first parameter, then the constructed href url will be built from the first navigable ancestor (aka
3479
+ * ancestor with a valid url).
3480
+ * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
3481
+ * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
3482
+ * defines which state to be relative from.
3483
+ * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
3484
+ *
3485
+ * @returns {string} compiled state url
3486
+ */
3487
+ $state.href = function href(stateOrName, params, options) {
3488
+ options = extend({
3489
+ lossy: true,
3490
+ inherit: true,
3491
+ absolute: false,
3492
+ relative: $state.$current
3493
+ }, options || {});
3494
+
3495
+ var state = findState(stateOrName, options.relative);
3496
+
3497
+ if (!isDefined(state)) return null;
3498
+ if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state);
3499
+
3500
+ var nav = (state && options.lossy) ? state.navigable : state;
3501
+
3502
+ if (!nav || nav.url === undefined || nav.url === null) {
3503
+ return null;
3504
+ }
3505
+ return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), {
3506
+ absolute: options.absolute
3507
+ });
3508
+ };
3509
+
3510
+ /**
3511
+ * @ngdoc function
3512
+ * @name ui.router.state.$state#get
3513
+ * @methodOf ui.router.state.$state
3514
+ *
3515
+ * @description
3516
+ * Returns the state configuration object for any specific state or all states.
3517
+ *
3518
+ * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for
3519
+ * the requested state. If not provided, returns an array of ALL state configs.
3520
+ * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context.
3521
+ * @returns {Object|Array} State configuration object or array of all objects.
3522
+ */
3523
+ $state.get = function (stateOrName, context) {
3524
+ if (arguments.length === 0) return map(objectKeys(states), function(name) { return states[name].self; });
3525
+ var state = findState(stateOrName, context || $state.$current);
3526
+ return (state && state.self) ? state.self : null;
3527
+ };
3528
+
3529
+ function resolveState(state, params, paramsAreFiltered, inherited, dst, options) {
3530
+ // Make a restricted $stateParams with only the parameters that apply to this state if
3531
+ // necessary. In addition to being available to the controller and onEnter/onExit callbacks,
3532
+ // we also need $stateParams to be available for any $injector calls we make during the
3533
+ // dependency resolution process.
3534
+ var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
3535
+ var locals = { $stateParams: $stateParams };
3536
+
3537
+ // Resolve 'global' dependencies for the state, i.e. those not specific to a view.
3538
+ // We're also including $stateParams in this; that way the parameters are restricted
3539
+ // to the set that should be visible to the state, and are independent of when we update
3540
+ // the global $state and $stateParams values.
3541
+ dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
3542
+ var promises = [dst.resolve.then(function (globals) {
3543
+ dst.globals = globals;
3544
+ })];
3545
+ if (inherited) promises.push(inherited);
3546
+
3547
+ function resolveViews() {
3548
+ var viewsPromises = [];
3549
+
3550
+ // Resolve template and dependencies for all views.
3551
+ forEach(state.views, function (view, name) {
3552
+ var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {});
3553
+ injectables.$template = [ function () {
3554
+ return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || '';
3555
+ }];
3556
+
3557
+ viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) {
3558
+ // References to the controller (only instantiated at link time)
3559
+ if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) {
3560
+ var injectLocals = angular.extend({}, injectables, dst.globals);
3561
+ result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals);
3562
+ } else {
3563
+ result.$$controller = view.controller;
3564
+ }
3565
+ // Provide access to the state itself for internal use
3566
+ result.$$state = state;
3567
+ result.$$controllerAs = view.controllerAs;
3568
+ dst[name] = result;
3569
+ }));
3570
+ });
3571
+
3572
+ return $q.all(viewsPromises).then(function(){
3573
+ return dst.globals;
3574
+ });
3575
+ }
3576
+
3577
+ // Wait for all the promises and then return the activation object
3578
+ return $q.all(promises).then(resolveViews).then(function (values) {
3579
+ return dst;
3580
+ });
3581
+ }
3582
+
3583
+ return $state;
3584
+ }
3585
+
3586
+ function shouldSkipReload(to, toParams, from, fromParams, locals, options) {
3587
+ // Return true if there are no differences in non-search (path/object) params, false if there are differences
3588
+ function nonSearchParamsEqual(fromAndToState, fromParams, toParams) {
3589
+ // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params.
3590
+ function notSearchParam(key) {
3591
+ return fromAndToState.params[key].location != "search";
3592
+ }
3593
+ var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam);
3594
+ var nonQueryParams = pick.apply({}, [fromAndToState.params].concat(nonQueryParamKeys));
3595
+ var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams);
3596
+ return nonQueryParamSet.$$equals(fromParams, toParams);
3597
+ }
3598
+
3599
+ // If reload was not explicitly requested
3600
+ // and we're transitioning to the same state we're already in
3601
+ // and the locals didn't change
3602
+ // or they changed in a way that doesn't merit reloading
3603
+ // (reloadOnParams:false, or reloadOnSearch.false and only search params changed)
3604
+ // Then return true.
3605
+ if (!options.reload && to === from &&
3606
+ (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) {
3607
+ return true;
3608
+ }
3609
+ }
3610
+ }
3611
+
3612
+ angular.module('ui.router.state')
3613
+ .value('$stateParams', {})
3614
+ .provider('$state', $StateProvider);
3615
+
3616
+
3617
+ $ViewProvider.$inject = [];
3618
+ function $ViewProvider() {
3619
+
3620
+ this.$get = $get;
3621
+ /**
3622
+ * @ngdoc object
3623
+ * @name ui.router.state.$view
3624
+ *
3625
+ * @requires ui.router.util.$templateFactory
3626
+ * @requires $rootScope
3627
+ *
3628
+ * @description
3629
+ *
3630
+ */
3631
+ $get.$inject = ['$rootScope', '$templateFactory'];
3632
+ function $get( $rootScope, $templateFactory) {
3633
+ return {
3634
+ // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... })
3635
+ /**
3636
+ * @ngdoc function
3637
+ * @name ui.router.state.$view#load
3638
+ * @methodOf ui.router.state.$view
3639
+ *
3640
+ * @description
3641
+ *
3642
+ * @param {string} name name
3643
+ * @param {object} options option object.
3644
+ */
3645
+ load: function load(name, options) {
3646
+ var result, defaults = {
3647
+ template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {}
3648
+ };
3649
+ options = extend(defaults, options);
3650
+
3651
+ if (options.view) {
3652
+ result = $templateFactory.fromConfig(options.view, options.params, options.locals);
3653
+ }
3654
+ if (result && options.notify) {
3655
+ /**
3656
+ * @ngdoc event
3657
+ * @name ui.router.state.$state#$viewContentLoading
3658
+ * @eventOf ui.router.state.$view
3659
+ * @eventType broadcast on root scope
3660
+ * @description
3661
+ *
3662
+ * Fired once the view **begins loading**, *before* the DOM is rendered.
3663
+ *
3664
+ * @param {Object} event Event object.
3665
+ * @param {Object} viewConfig The view config properties (template, controller, etc).
3666
+ *
3667
+ * @example
3668
+ *
3669
+ * <pre>
3670
+ * $scope.$on('$viewContentLoading',
3671
+ * function(event, viewConfig){
3672
+ * // Access to all the view config properties.
3673
+ * // and one special property 'targetView'
3674
+ * // viewConfig.targetView
3675
+ * });
3676
+ * </pre>
3677
+ */
3678
+ $rootScope.$broadcast('$viewContentLoading', options);
3679
+ }
3680
+ return result;
3681
+ }
3682
+ };
3683
+ }
3684
+ }
3685
+
3686
+ angular.module('ui.router.state').provider('$view', $ViewProvider);
3687
+
3688
+ /**
3689
+ * @ngdoc object
3690
+ * @name ui.router.state.$uiViewScrollProvider
3691
+ *
3692
+ * @description
3693
+ * Provider that returns the {@link ui.router.state.$uiViewScroll} service function.
3694
+ */
3695
+ function $ViewScrollProvider() {
3696
+
3697
+ var useAnchorScroll = false;
3698
+
3699
+ /**
3700
+ * @ngdoc function
3701
+ * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll
3702
+ * @methodOf ui.router.state.$uiViewScrollProvider
3703
+ *
3704
+ * @description
3705
+ * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for
3706
+ * scrolling based on the url anchor.
3707
+ */
3708
+ this.useAnchorScroll = function () {
3709
+ useAnchorScroll = true;
3710
+ };
3711
+
3712
+ /**
3713
+ * @ngdoc object
3714
+ * @name ui.router.state.$uiViewScroll
3715
+ *
3716
+ * @requires $anchorScroll
3717
+ * @requires $timeout
3718
+ *
3719
+ * @description
3720
+ * When called with a jqLite element, it scrolls the element into view (after a
3721
+ * `$timeout` so the DOM has time to refresh).
3722
+ *
3723
+ * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor,
3724
+ * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}.
3725
+ */
3726
+ this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) {
3727
+ if (useAnchorScroll) {
3728
+ return $anchorScroll;
3729
+ }
3730
+
3731
+ return function ($element) {
3732
+ return $timeout(function () {
3733
+ $element[0].scrollIntoView();
3734
+ }, 0, false);
3735
+ };
3736
+ }];
3737
+ }
3738
+
3739
+ angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider);
3740
+
3741
+ /**
3742
+ * @ngdoc directive
3743
+ * @name ui.router.state.directive:ui-view
3744
+ *
3745
+ * @requires ui.router.state.$state
3746
+ * @requires $compile
3747
+ * @requires $controller
3748
+ * @requires $injector
3749
+ * @requires ui.router.state.$uiViewScroll
3750
+ * @requires $document
3751
+ *
3752
+ * @restrict ECA
3753
+ *
3754
+ * @description
3755
+ * The ui-view directive tells $state where to place your templates.
3756
+ *
3757
+ * @param {string=} name A view name. The name should be unique amongst the other views in the
3758
+ * same state. You can have views of the same name that live in different states.
3759
+ *
3760
+ * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window
3761
+ * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll
3762
+ * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you
3763
+ * scroll ui-view elements into view when they are populated during a state activation.
3764
+ *
3765
+ * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll)
3766
+ * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.*
3767
+ *
3768
+ * @param {string=} onload Expression to evaluate whenever the view updates.
3769
+ *
3770
+ * @example
3771
+ * A view can be unnamed or named.
3772
+ * <pre>
3773
+ * <!-- Unnamed -->
3774
+ * <div ui-view></div>
3775
+ *
3776
+ * <!-- Named -->
3777
+ * <div ui-view="viewName"></div>
3778
+ * </pre>
3779
+ *
3780
+ * You can only have one unnamed view within any template (or root html). If you are only using a
3781
+ * single view and it is unnamed then you can populate it like so:
3782
+ * <pre>
3783
+ * <div ui-view></div>
3784
+ * $stateProvider.state("home", {
3785
+ * template: "<h1>HELLO!</h1>"
3786
+ * })
3787
+ * </pre>
3788
+ *
3789
+ * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`}
3790
+ * config property, by name, in this case an empty name:
3791
+ * <pre>
3792
+ * $stateProvider.state("home", {
3793
+ * views: {
3794
+ * "": {
3795
+ * template: "<h1>HELLO!</h1>"
3796
+ * }
3797
+ * }
3798
+ * })
3799
+ * </pre>
3800
+ *
3801
+ * But typically you'll only use the views property if you name your view or have more than one view
3802
+ * in the same template. There's not really a compelling reason to name a view if its the only one,
3803
+ * but you could if you wanted, like so:
3804
+ * <pre>
3805
+ * <div ui-view="main"></div>
3806
+ * </pre>
3807
+ * <pre>
3808
+ * $stateProvider.state("home", {
3809
+ * views: {
3810
+ * "main": {
3811
+ * template: "<h1>HELLO!</h1>"
3812
+ * }
3813
+ * }
3814
+ * })
3815
+ * </pre>
3816
+ *
3817
+ * Really though, you'll use views to set up multiple views:
3818
+ * <pre>
3819
+ * <div ui-view></div>
3820
+ * <div ui-view="chart"></div>
3821
+ * <div ui-view="data"></div>
3822
+ * </pre>
3823
+ *
3824
+ * <pre>
3825
+ * $stateProvider.state("home", {
3826
+ * views: {
3827
+ * "": {
3828
+ * template: "<h1>HELLO!</h1>"
3829
+ * },
3830
+ * "chart": {
3831
+ * template: "<chart_thing/>"
3832
+ * },
3833
+ * "data": {
3834
+ * template: "<data_thing/>"
3835
+ * }
3836
+ * }
3837
+ * })
3838
+ * </pre>
3839
+ *
3840
+ * Examples for `autoscroll`:
3841
+ *
3842
+ * <pre>
3843
+ * <!-- If autoscroll present with no expression,
3844
+ * then scroll ui-view into view -->
3845
+ * <ui-view autoscroll/>
3846
+ *
3847
+ * <!-- If autoscroll present with valid expression,
3848
+ * then scroll ui-view into view if expression evaluates to true -->
3849
+ * <ui-view autoscroll='true'/>
3850
+ * <ui-view autoscroll='false'/>
3851
+ * <ui-view autoscroll='scopeVariable'/>
3852
+ * </pre>
3853
+ */
3854
+ $ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate'];
3855
+ function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) {
3856
+
3857
+ function getService() {
3858
+ return ($injector.has) ? function(service) {
3859
+ return $injector.has(service) ? $injector.get(service) : null;
3860
+ } : function(service) {
3861
+ try {
3862
+ return $injector.get(service);
3863
+ } catch (e) {
3864
+ return null;
3865
+ }
3866
+ };
3867
+ }
3868
+
3869
+ var service = getService(),
3870
+ $animator = service('$animator'),
3871
+ $animate = service('$animate');
3872
+
3873
+ // Returns a set of DOM manipulation functions based on which Angular version
3874
+ // it should use
3875
+ function getRenderer(attrs, scope) {
3876
+ var statics = function() {
3877
+ return {
3878
+ enter: function (element, target, cb) { target.after(element); cb(); },
3879
+ leave: function (element, cb) { element.remove(); cb(); }
3880
+ };
3881
+ };
3882
+
3883
+ if ($animate) {
3884
+ return {
3885
+ enter: function(element, target, cb) {
3886
+ var promise = $animate.enter(element, null, target, cb);
3887
+ if (promise && promise.then) promise.then(cb);
3888
+ },
3889
+ leave: function(element, cb) {
3890
+ var promise = $animate.leave(element, cb);
3891
+ if (promise && promise.then) promise.then(cb);
3892
+ }
3893
+ };
3894
+ }
3895
+
3896
+ if ($animator) {
3897
+ var animate = $animator && $animator(scope, attrs);
3898
+
3899
+ return {
3900
+ enter: function(element, target, cb) {animate.enter(element, null, target); cb(); },
3901
+ leave: function(element, cb) { animate.leave(element); cb(); }
3902
+ };
3903
+ }
3904
+
3905
+ return statics();
3906
+ }
3907
+
3908
+ var directive = {
3909
+ restrict: 'ECA',
3910
+ terminal: true,
3911
+ priority: 400,
3912
+ transclude: 'element',
3913
+ compile: function (tElement, tAttrs, $transclude) {
3914
+ return function (scope, $element, attrs) {
3915
+ var previousEl, currentEl, currentScope, latestLocals,
3916
+ onloadExp = attrs.onload || '',
3917
+ autoScrollExp = attrs.autoscroll,
3918
+ renderer = getRenderer(attrs, scope);
3919
+
3920
+ scope.$on('$stateChangeSuccess', function() {
3921
+ updateView(false);
3922
+ });
3923
+ scope.$on('$viewContentLoading', function() {
3924
+ updateView(false);
3925
+ });
3926
+
3927
+ updateView(true);
3928
+
3929
+ function cleanupLastView() {
3930
+ if (previousEl) {
3931
+ previousEl.remove();
3932
+ previousEl = null;
3933
+ }
3934
+
3935
+ if (currentScope) {
3936
+ currentScope.$destroy();
3937
+ currentScope = null;
3938
+ }
3939
+
3940
+ if (currentEl) {
3941
+ renderer.leave(currentEl, function() {
3942
+ previousEl = null;
3943
+ });
3944
+
3945
+ previousEl = currentEl;
3946
+ currentEl = null;
3947
+ }
3948
+ }
3949
+
3950
+ function updateView(firstTime) {
3951
+ var newScope,
3952
+ name = getUiViewName(scope, attrs, $element, $interpolate),
3953
+ previousLocals = name && $state.$current && $state.$current.locals[name];
3954
+
3955
+ if (!firstTime && previousLocals === latestLocals) return; // nothing to do
3956
+ newScope = scope.$new();
3957
+ latestLocals = $state.$current.locals[name];
3958
+
3959
+ var clone = $transclude(newScope, function(clone) {
3960
+ renderer.enter(clone, $element, function onUiViewEnter() {
3961
+ if(currentScope) {
3962
+ currentScope.$emit('$viewContentAnimationEnded');
3963
+ }
3964
+
3965
+ if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) {
3966
+ $uiViewScroll(clone);
3967
+ }
3968
+ });
3969
+ cleanupLastView();
3970
+ });
3971
+
3972
+ currentEl = clone;
3973
+ currentScope = newScope;
3974
+ /**
3975
+ * @ngdoc event
3976
+ * @name ui.router.state.directive:ui-view#$viewContentLoaded
3977
+ * @eventOf ui.router.state.directive:ui-view
3978
+ * @eventType emits on ui-view directive scope
3979
+ * @description *
3980
+ * Fired once the view is **loaded**, *after* the DOM is rendered.
3981
+ *
3982
+ * @param {Object} event Event object.
3983
+ */
3984
+ currentScope.$emit('$viewContentLoaded');
3985
+ currentScope.$eval(onloadExp);
3986
+ }
3987
+ };
3988
+ }
3989
+ };
3990
+
3991
+ return directive;
3992
+ }
3993
+
3994
+ $ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate'];
3995
+ function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate) {
3996
+ return {
3997
+ restrict: 'ECA',
3998
+ priority: -400,
3999
+ compile: function (tElement) {
4000
+ var initial = tElement.html();
4001
+ return function (scope, $element, attrs) {
4002
+ var current = $state.$current,
4003
+ name = getUiViewName(scope, attrs, $element, $interpolate),
4004
+ locals = current && current.locals[name];
4005
+
4006
+ if (! locals) {
4007
+ return;
4008
+ }
4009
+
4010
+ $element.data('$uiView', { name: name, state: locals.$$state });
4011
+ $element.html(locals.$template ? locals.$template : initial);
4012
+
4013
+ var link = $compile($element.contents());
4014
+
4015
+ if (locals.$$controller) {
4016
+ locals.$scope = scope;
4017
+ locals.$element = $element;
4018
+ var controller = $controller(locals.$$controller, locals);
4019
+ if (locals.$$controllerAs) {
4020
+ scope[locals.$$controllerAs] = controller;
4021
+ }
4022
+ $element.data('$ngControllerController', controller);
4023
+ $element.children().data('$ngControllerController', controller);
4024
+ }
4025
+
4026
+ link(scope);
4027
+ };
4028
+ }
4029
+ };
4030
+ }
4031
+
4032
+ /**
4033
+ * Shared ui-view code for both directives:
4034
+ * Given scope, element, and its attributes, return the view's name
4035
+ */
4036
+ function getUiViewName(scope, attrs, element, $interpolate) {
4037
+ var name = $interpolate(attrs.uiView || attrs.name || '')(scope);
4038
+ var inherited = element.inheritedData('$uiView');
4039
+ return name.indexOf('@') >= 0 ? name : (name + '@' + (inherited ? inherited.state.name : ''));
4040
+ }
4041
+
4042
+ angular.module('ui.router.state').directive('uiView', $ViewDirective);
4043
+ angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill);
4044
+
4045
+ function parseStateRef(ref, current) {
4046
+ var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
4047
+ if (preparsed) ref = current + '(' + preparsed[1] + ')';
4048
+ parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
4049
+ if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
4050
+ return { state: parsed[1], paramExpr: parsed[3] || null };
4051
+ }
4052
+
4053
+ function stateContext(el) {
4054
+ var stateData = el.parent().inheritedData('$uiView');
4055
+
4056
+ if (stateData && stateData.state && stateData.state.name) {
4057
+ return stateData.state;
4058
+ }
4059
+ }
4060
+
4061
+ /**
4062
+ * @ngdoc directive
4063
+ * @name ui.router.state.directive:ui-sref
4064
+ *
4065
+ * @requires ui.router.state.$state
4066
+ * @requires $timeout
4067
+ *
4068
+ * @restrict A
4069
+ *
4070
+ * @description
4071
+ * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
4072
+ * URL, the directive will automatically generate & update the `href` attribute via
4073
+ * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
4074
+ * the link will trigger a state transition with optional parameters.
4075
+ *
4076
+ * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
4077
+ * handled natively by the browser.
4078
+ *
4079
+ * You can also use relative state paths within ui-sref, just like the relative
4080
+ * paths passed to `$state.go()`. You just need to be aware that the path is relative
4081
+ * to the state that the link lives in, in other words the state that loaded the
4082
+ * template containing the link.
4083
+ *
4084
+ * You can specify options to pass to {@link ui.router.state.$state#go $state.go()}
4085
+ * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`,
4086
+ * and `reload`.
4087
+ *
4088
+ * @example
4089
+ * Here's an example of how you'd use ui-sref and how it would compile. If you have the
4090
+ * following template:
4091
+ * <pre>
4092
+ * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
4093
+ *
4094
+ * <ul>
4095
+ * <li ng-repeat="contact in contacts">
4096
+ * <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
4097
+ * </li>
4098
+ * </ul>
4099
+ * </pre>
4100
+ *
4101
+ * Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
4102
+ * <pre>
4103
+ * <a href="#/home" ui-sref="home">Home</a> | <a href="#/about" ui-sref="about">About</a> | <a href="#/contacts?page=2" ui-sref="{page: 2}">Next page</a>
4104
+ *
4105
+ * <ul>
4106
+ * <li ng-repeat="contact in contacts">
4107
+ * <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
4108
+ * </li>
4109
+ * <li ng-repeat="contact in contacts">
4110
+ * <a href="#/contacts/2" ui-sref="contacts.detail({ id: contact.id })">Alice</a>
4111
+ * </li>
4112
+ * <li ng-repeat="contact in contacts">
4113
+ * <a href="#/contacts/3" ui-sref="contacts.detail({ id: contact.id })">Bob</a>
4114
+ * </li>
4115
+ * </ul>
4116
+ *
4117
+ * <a ui-sref="home" ui-sref-opts="{reload: true}">Home</a>
4118
+ * </pre>
4119
+ *
4120
+ * @param {string} ui-sref 'stateName' can be any valid absolute or relative state
4121
+ * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#go $state.go()}
4122
+ */
4123
+ $StateRefDirective.$inject = ['$state', '$timeout'];
4124
+ function $StateRefDirective($state, $timeout) {
4125
+ var allowedOptions = ['location', 'inherit', 'reload', 'absolute'];
4126
+
4127
+ return {
4128
+ restrict: 'A',
4129
+ require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
4130
+ link: function(scope, element, attrs, uiSrefActive) {
4131
+ var ref = parseStateRef(attrs.uiSref, $state.current.name);
4132
+ var params = null, url = null, base = stateContext(element) || $state.$current;
4133
+ // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
4134
+ var hrefKind = Object.prototype.toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
4135
+ 'xlink:href' : 'href';
4136
+ var newHref = null, isAnchor = element.prop("tagName").toUpperCase() === "A";
4137
+ var isForm = element[0].nodeName === "FORM";
4138
+ var attr = isForm ? "action" : hrefKind, nav = true;
4139
+
4140
+ var options = { relative: base, inherit: true };
4141
+ var optionsOverride = scope.$eval(attrs.uiSrefOpts) || {};
4142
+
4143
+ angular.forEach(allowedOptions, function(option) {
4144
+ if (option in optionsOverride) {
4145
+ options[option] = optionsOverride[option];
4146
+ }
4147
+ });
4148
+
4149
+ var update = function(newVal) {
4150
+ if (newVal) params = angular.copy(newVal);
4151
+ if (!nav) return;
4152
+
4153
+ newHref = $state.href(ref.state, params, options);
4154
+
4155
+ var activeDirective = uiSrefActive[1] || uiSrefActive[0];
4156
+ if (activeDirective) {
4157
+ activeDirective.$$addStateInfo(ref.state, params);
4158
+ }
4159
+ if (newHref === null) {
4160
+ nav = false;
4161
+ return false;
4162
+ }
4163
+ attrs.$set(attr, newHref);
4164
+ };
4165
+
4166
+ if (ref.paramExpr) {
4167
+ scope.$watch(ref.paramExpr, function(newVal, oldVal) {
4168
+ if (newVal !== params) update(newVal);
4169
+ }, true);
4170
+ params = angular.copy(scope.$eval(ref.paramExpr));
4171
+ }
4172
+ update();
4173
+
4174
+ if (isForm) return;
4175
+
4176
+ element.bind("click", function(e) {
4177
+ var button = e.which || e.button;
4178
+ if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) {
4179
+ // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
4180
+ var transition = $timeout(function() {
4181
+ $state.go(ref.state, params, options);
4182
+ });
4183
+ e.preventDefault();
4184
+
4185
+ // if the state has no URL, ignore one preventDefault from the <a> directive.
4186
+ var ignorePreventDefaultCount = isAnchor && !newHref ? 1: 0;
4187
+ e.preventDefault = function() {
4188
+ if (ignorePreventDefaultCount-- <= 0)
4189
+ $timeout.cancel(transition);
4190
+ };
4191
+ }
4192
+ });
4193
+ }
4194
+ };
4195
+ }
4196
+
4197
+ /**
4198
+ * @ngdoc directive
4199
+ * @name ui.router.state.directive:ui-sref-active
4200
+ *
4201
+ * @requires ui.router.state.$state
4202
+ * @requires ui.router.state.$stateParams
4203
+ * @requires $interpolate
4204
+ *
4205
+ * @restrict A
4206
+ *
4207
+ * @description
4208
+ * A directive working alongside ui-sref to add classes to an element when the
4209
+ * related ui-sref directive's state is active, and removing them when it is inactive.
4210
+ * The primary use-case is to simplify the special appearance of navigation menus
4211
+ * relying on `ui-sref`, by having the "active" state's menu button appear different,
4212
+ * distinguishing it from the inactive menu items.
4213
+ *
4214
+ * ui-sref-active can live on the same element as ui-sref or on a parent element. The first
4215
+ * ui-sref-active found at the same level or above the ui-sref will be used.
4216
+ *
4217
+ * Will activate when the ui-sref's target state or any child state is active. If you
4218
+ * need to activate only when the ui-sref target state is active and *not* any of
4219
+ * it's children, then you will use
4220
+ * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq}
4221
+ *
4222
+ * @example
4223
+ * Given the following template:
4224
+ * <pre>
4225
+ * <ul>
4226
+ * <li ui-sref-active="active" class="item">
4227
+ * <a href ui-sref="app.user({user: 'bilbobaggins'})">@bilbobaggins</a>
4228
+ * </li>
4229
+ * </ul>
4230
+ * </pre>
4231
+ *
4232
+ *
4233
+ * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins",
4234
+ * the resulting HTML will appear as (note the 'active' class):
4235
+ * <pre>
4236
+ * <ul>
4237
+ * <li ui-sref-active="active" class="item active">
4238
+ * <a ui-sref="app.user({user: 'bilbobaggins'})" href="/users/bilbobaggins">@bilbobaggins</a>
4239
+ * </li>
4240
+ * </ul>
4241
+ * </pre>
4242
+ *
4243
+ * The class name is interpolated **once** during the directives link time (any further changes to the
4244
+ * interpolated value are ignored).
4245
+ *
4246
+ * Multiple classes may be specified in a space-separated format:
4247
+ * <pre>
4248
+ * <ul>
4249
+ * <li ui-sref-active='class1 class2 class3'>
4250
+ * <a ui-sref="app.user">link</a>
4251
+ * </li>
4252
+ * </ul>
4253
+ * </pre>
4254
+ */
4255
+
4256
+ /**
4257
+ * @ngdoc directive
4258
+ * @name ui.router.state.directive:ui-sref-active-eq
4259
+ *
4260
+ * @requires ui.router.state.$state
4261
+ * @requires ui.router.state.$stateParams
4262
+ * @requires $interpolate
4263
+ *
4264
+ * @restrict A
4265
+ *
4266
+ * @description
4267
+ * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate
4268
+ * when the exact target state used in the `ui-sref` is active; no child states.
4269
+ *
4270
+ */
4271
+ $StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
4272
+ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
4273
+ return {
4274
+ restrict: "A",
4275
+ controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
4276
+ var states = [], activeClass;
4277
+
4278
+ // There probably isn't much point in $observing this
4279
+ // uiSrefActive and uiSrefActiveEq share the same directive object with some
4280
+ // slight difference in logic routing
4281
+ activeClass = $interpolate($attrs.uiSrefActiveEq || $attrs.uiSrefActive || '', false)($scope);
4282
+
4283
+ // Allow uiSref to communicate with uiSrefActive[Equals]
4284
+ this.$$addStateInfo = function (newState, newParams) {
4285
+ var state = $state.get(newState, stateContext($element));
4286
+
4287
+ states.push({
4288
+ state: state || { name: newState },
4289
+ params: newParams
4290
+ });
4291
+
4292
+ update();
4293
+ };
4294
+
4295
+ $scope.$on('$stateChangeSuccess', update);
4296
+
4297
+ // Update route state
4298
+ function update() {
4299
+ if (anyMatch()) {
4300
+ $element.addClass(activeClass);
4301
+ } else {
4302
+ $element.removeClass(activeClass);
4303
+ }
4304
+ }
4305
+
4306
+ function anyMatch() {
4307
+ for (var i = 0; i < states.length; i++) {
4308
+ if (isMatch(states[i].state, states[i].params)) {
4309
+ return true;
4310
+ }
4311
+ }
4312
+ return false;
4313
+ }
4314
+
4315
+ function isMatch(state, params) {
4316
+ if (typeof $attrs.uiSrefActiveEq !== 'undefined') {
4317
+ return $state.is(state.name, params);
4318
+ } else {
4319
+ return $state.includes(state.name, params);
4320
+ }
4321
+ }
4322
+ }]
4323
+ };
4324
+ }
4325
+
4326
+ angular.module('ui.router.state')
4327
+ .directive('uiSref', $StateRefDirective)
4328
+ .directive('uiSrefActive', $StateRefActiveDirective)
4329
+ .directive('uiSrefActiveEq', $StateRefActiveDirective);
4330
+
4331
+ /**
4332
+ * @ngdoc filter
4333
+ * @name ui.router.state.filter:isState
4334
+ *
4335
+ * @requires ui.router.state.$state
4336
+ *
4337
+ * @description
4338
+ * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}.
4339
+ */
4340
+ $IsStateFilter.$inject = ['$state'];
4341
+ function $IsStateFilter($state) {
4342
+ var isFilter = function (state) {
4343
+ return $state.is(state);
4344
+ };
4345
+ isFilter.$stateful = true;
4346
+ return isFilter;
4347
+ }
4348
+
4349
+ /**
4350
+ * @ngdoc filter
4351
+ * @name ui.router.state.filter:includedByState
4352
+ *
4353
+ * @requires ui.router.state.$state
4354
+ *
4355
+ * @description
4356
+ * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}.
4357
+ */
4358
+ $IncludedByStateFilter.$inject = ['$state'];
4359
+ function $IncludedByStateFilter($state) {
4360
+ var includesFilter = function (state) {
4361
+ return $state.includes(state);
4362
+ };
4363
+ includesFilter.$stateful = true;
4364
+ return includesFilter;
4365
+ }
4366
+
4367
+ angular.module('ui.router.state')
4368
+ .filter('isState', $IsStateFilter)
4369
+ .filter('includedByState', $IncludedByStateFilter);
4370
+ })(window, window.angular);