voltar 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/fonts/FontAwesome.otf +0 -0
  6. data/app/assets/fonts/Simple-Line-Icons.eot +0 -0
  7. data/app/assets/fonts/Simple-Line-Icons.svg +1369 -0
  8. data/app/assets/fonts/Simple-Line-Icons.ttf +0 -0
  9. data/app/assets/fonts/Simple-Line-Icons.woff +0 -0
  10. data/app/assets/fonts/fontawesome-webfont.eot +0 -0
  11. data/app/assets/fonts/fontawesome-webfont.svg +520 -0
  12. data/app/assets/fonts/fontawesome-webfont.ttf +0 -0
  13. data/app/assets/fonts/fontawesome-webfont.woff +0 -0
  14. data/app/assets/fonts/glyphicons-halflings-regular.eot +0 -0
  15. data/app/assets/fonts/glyphicons-halflings-regular.svg +229 -0
  16. data/app/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
  17. data/app/assets/fonts/glyphicons-halflings-regular.woff +0 -0
  18. data/app/assets/fonts/sourcesanspro/sourcesanspro-bold.woff +0 -0
  19. data/app/assets/fonts/sourcesanspro/sourcesanspro-light.woff +0 -0
  20. data/app/assets/fonts/sourcesanspro/sourcesanspro.woff +0 -0
  21. data/app/assets/javascripts/voltar/app.js +203 -0
  22. data/app/assets/javascripts/voltar/application.js +33 -0
  23. data/app/assets/javascripts/voltar/controllers/app_ctrl.js.erb +673 -0
  24. data/app/assets/javascripts/voltar/directives/app_directives.js +345 -0
  25. data/app/assets/javascripts/voltar/factories/app_services.js +255 -0
  26. data/app/assets/javascripts/voltar/stripe.js.coffee +2 -0
  27. data/app/assets/stylesheets/voltar/app.css +4990 -0
  28. data/app/assets/stylesheets/voltar/application.css +23 -0
  29. data/app/assets/stylesheets/voltar/voltar.css.scss +93 -0
  30. data/app/controllers/voltar/application_controller.rb +4 -0
  31. data/app/controllers/voltar/dashboard_controller.rb +11 -0
  32. data/app/helpers/voltar/application_helper.rb +4 -0
  33. data/app/views/layouts/voltar/application.html.erb +40 -0
  34. data/app/views/voltar/account/_billing.html.erb +552 -0
  35. data/app/views/voltar/account/_locations.html.erb +135 -0
  36. data/app/views/voltar/account/_managers.html +134 -0
  37. data/app/views/voltar/account/_password.html.erb +57 -0
  38. data/app/views/voltar/account/_profile.html.erb +84 -0
  39. data/app/views/voltar/dashboard/index.html.erb +0 -0
  40. data/app/views/voltar/inventory/_delete_dialog.html.erb +16 -0
  41. data/app/views/voltar/inventory/_edit.html.erb +244 -0
  42. data/app/views/voltar/inventory/_index.html.erb +160 -0
  43. data/app/views/voltar/inventory/_mark_as_sold_dialog.html.erb +26 -0
  44. data/app/views/voltar/shared/_keen_js.html.haml +11 -0
  45. data/app/views/voltar/shared/_voltar_app.html.erb +83 -0
  46. data/app/views/voltar/shared/_voltar_aside.html.erb +71 -0
  47. data/app/views/voltar/shared/_voltar_footer.html.erb +13 -0
  48. data/app/views/voltar/shared/_voltar_header.html.erb +156 -0
  49. data/app/views/voltar/shared/app/_country_province_select.html +19 -0
  50. data/app/views/voltar/shared/app/_dashboard.html.erb +243 -0
  51. data/app/views/voltar/shared/app/_notifications.html.erb +13 -0
  52. data/app/views/voltar/shared/app/_spinner.html.erb +8 -0
  53. data/config/routes.rb +4 -0
  54. data/lib/tasks/voltar_tasks.rake +4 -0
  55. data/lib/voltar.rb +5 -0
  56. data/lib/voltar/engine.rb +15 -0
  57. data/lib/voltar/version.rb +3 -0
  58. data/test/dummy/README.rdoc +28 -0
  59. data/test/dummy/Rakefile +6 -0
  60. data/test/dummy/app/assets/javascripts/application.js +13 -0
  61. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  62. data/test/dummy/app/controllers/application_controller.rb +5 -0
  63. data/test/dummy/app/helpers/application_helper.rb +2 -0
  64. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  65. data/test/dummy/bin/bundle +3 -0
  66. data/test/dummy/bin/rails +4 -0
  67. data/test/dummy/bin/rake +4 -0
  68. data/test/dummy/bin/setup +29 -0
  69. data/test/dummy/config.ru +4 -0
  70. data/test/dummy/config/application.rb +26 -0
  71. data/test/dummy/config/boot.rb +5 -0
  72. data/test/dummy/config/database.yml +25 -0
  73. data/test/dummy/config/environment.rb +5 -0
  74. data/test/dummy/config/environments/development.rb +41 -0
  75. data/test/dummy/config/environments/production.rb +76 -0
  76. data/test/dummy/config/environments/test.rb +39 -0
  77. data/test/dummy/config/initializers/assets.rb +11 -0
  78. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  79. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  80. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  81. data/test/dummy/config/initializers/inflections.rb +16 -0
  82. data/test/dummy/config/initializers/mime_types.rb +4 -0
  83. data/test/dummy/config/initializers/session_store.rb +3 -0
  84. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  85. data/test/dummy/config/locales/en.yml +23 -0
  86. data/test/dummy/config/routes.rb +4 -0
  87. data/test/dummy/config/secrets.yml +22 -0
  88. data/test/dummy/log/development.log +0 -0
  89. data/test/dummy/public/404.html +67 -0
  90. data/test/dummy/public/422.html +67 -0
  91. data/test/dummy/public/500.html +66 -0
  92. data/test/dummy/public/favicon.ico +0 -0
  93. data/test/integration/navigation_test.rb +10 -0
  94. data/test/test_helper.rb +15 -0
  95. data/test/voltar_test.rb +7 -0
  96. data/vendor/assets/images/voltar/a0.jpg +0 -0
  97. data/vendor/assets/images/voltar/a1.jpg +0 -0
  98. data/vendor/assets/images/voltar/a10.jpg +0 -0
  99. data/vendor/assets/images/voltar/a2.jpg +0 -0
  100. data/vendor/assets/images/voltar/a3.jpg +0 -0
  101. data/vendor/assets/images/voltar/a4.jpg +0 -0
  102. data/vendor/assets/images/voltar/a5.jpg +0 -0
  103. data/vendor/assets/images/voltar/a6.jpg +0 -0
  104. data/vendor/assets/images/voltar/a7.jpg +0 -0
  105. data/vendor/assets/images/voltar/a8.jpg +0 -0
  106. data/vendor/assets/images/voltar/a9.jpg +0 -0
  107. data/vendor/assets/images/voltar/b0.jpg +0 -0
  108. data/vendor/assets/images/voltar/b1.jpg +0 -0
  109. data/vendor/assets/images/voltar/b2.jpg +0 -0
  110. data/vendor/assets/images/voltar/b3.jpg +0 -0
  111. data/vendor/assets/images/voltar/b4.jpg +0 -0
  112. data/vendor/assets/images/voltar/b5.jpg +0 -0
  113. data/vendor/assets/images/voltar/c0.jpg +0 -0
  114. data/vendor/assets/images/voltar/c1.jpg +0 -0
  115. data/vendor/assets/images/voltar/c2.jpg +0 -0
  116. data/vendor/assets/images/voltar/c3.jpg +0 -0
  117. data/vendor/assets/images/voltar/c4.jpg +0 -0
  118. data/vendor/assets/images/voltar/c5.jpg +0 -0
  119. data/vendor/assets/images/voltar/chosen-sprite.png +0 -0
  120. data/vendor/assets/images/voltar/chosen-sprite@2x.png +0 -0
  121. data/vendor/assets/images/voltar/logo.png +0 -0
  122. data/vendor/assets/images/voltar/p0.jpg +0 -0
  123. data/vendor/assets/javascripts/voltar/angular-animate.js +1689 -0
  124. data/vendor/assets/javascripts/voltar/angular-contenteditable.js +98 -0
  125. data/vendor/assets/javascripts/voltar/angular-cookies.js +206 -0
  126. data/vendor/assets/javascripts/voltar/angular-sanitize.js +647 -0
  127. data/vendor/assets/javascripts/voltar/angular-ui-router.js +3658 -0
  128. data/vendor/assets/javascripts/voltar/angular.js +22024 -0
  129. data/vendor/assets/javascripts/voltar/chosen.jquery.min.js +2 -0
  130. data/vendor/assets/javascripts/voltar/easypiechart/jquery.easy-pie-chart.js +209 -0
  131. data/vendor/assets/javascripts/voltar/flot/jquery.flot.min.js +29 -0
  132. data/vendor/assets/javascripts/voltar/flot/jquery.flot.orderBars.js +187 -0
  133. data/vendor/assets/javascripts/voltar/flot/jquery.flot.pie.min.js +56 -0
  134. data/vendor/assets/javascripts/voltar/flot/jquery.flot.resize.js +60 -0
  135. data/vendor/assets/javascripts/voltar/flot/jquery.flot.spline.js +212 -0
  136. data/vendor/assets/javascripts/voltar/flot/jquery.flot.tooltip.min.js +12 -0
  137. data/vendor/assets/javascripts/voltar/jquery.min.js +5 -0
  138. data/vendor/assets/javascripts/voltar/moment.js +2856 -0
  139. data/vendor/assets/javascripts/voltar/ngStorage.js +103 -0
  140. data/vendor/assets/javascripts/voltar/ocLazyLoad.js +906 -0
  141. data/vendor/assets/javascripts/voltar/smart-table.min.js +1 -0
  142. data/vendor/assets/javascripts/voltar/sparkline/jquery.sparkline.min.js +2 -0
  143. data/vendor/assets/javascripts/voltar/toaster.js +185 -0
  144. data/vendor/assets/javascripts/voltar/ui-bootstrap-tpls.js +4116 -0
  145. data/vendor/assets/javascripts/voltar/ui-jq.js +86 -0
  146. data/vendor/assets/javascripts/voltar/ui-load.js +93 -0
  147. data/vendor/assets/javascripts/voltar/ui-validate.js +119 -0
  148. data/vendor/assets/stylesheets/voltar/animate.css +1098 -0
  149. data/vendor/assets/stylesheets/voltar/bootstrap.css +6202 -0
  150. data/vendor/assets/stylesheets/voltar/chosen.css +399 -0
  151. data/vendor/assets/stylesheets/voltar/font-awesome.min.css +4 -0
  152. data/vendor/assets/stylesheets/voltar/font.css +18 -0
  153. data/vendor/assets/stylesheets/voltar/simple-line-icons.css +526 -0
  154. data/vendor/assets/stylesheets/voltar/toaster.css +213 -0
  155. metadata +333 -0
@@ -0,0 +1,3658 @@
1
+ /**
2
+ * State-based routing for AngularJS
3
+ * @version v0.2.11
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
+ angular.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 arraySearch(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 (arraySearch(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
+ * @ngdoc overview
164
+ * @name ui.router.util
165
+ *
166
+ * @description
167
+ * # ui.router.util sub-module
168
+ *
169
+ * This module is a dependency of other sub-modules. Do not include this module as a dependency
170
+ * in your angular app (use {@link ui.router} module instead).
171
+ *
172
+ */
173
+ angular.module('ui.router.util', ['ng']);
174
+
175
+ /**
176
+ * @ngdoc overview
177
+ * @name ui.router.router
178
+ *
179
+ * @requires ui.router.util
180
+ *
181
+ * @description
182
+ * # ui.router.router sub-module
183
+ *
184
+ * This module is a dependency of other sub-modules. Do not include this module as a dependency
185
+ * in your angular app (use {@link ui.router} module instead).
186
+ */
187
+ angular.module('ui.router.router', ['ui.router.util']);
188
+
189
+ /**
190
+ * @ngdoc overview
191
+ * @name ui.router.state
192
+ *
193
+ * @requires ui.router.router
194
+ * @requires ui.router.util
195
+ *
196
+ * @description
197
+ * # ui.router.state sub-module
198
+ *
199
+ * This module is a dependency of the main ui.router module. Do not include this module as a dependency
200
+ * in your angular app (use {@link ui.router} module instead).
201
+ *
202
+ */
203
+ angular.module('ui.router.state', ['ui.router.router', 'ui.router.util']);
204
+
205
+ /**
206
+ * @ngdoc overview
207
+ * @name ui.router
208
+ *
209
+ * @requires ui.router.state
210
+ *
211
+ * @description
212
+ * # ui.router
213
+ *
214
+ * ## The main module for ui.router
215
+ * There are several sub-modules included with the ui.router module, however only this module is needed
216
+ * as a dependency within your angular app. The other modules are for organization purposes.
217
+ *
218
+ * The modules are:
219
+ * * ui.router - the main "umbrella" module
220
+ * * ui.router.router -
221
+ *
222
+ * *You'll need to include **only** this module as the dependency within your angular app.*
223
+ *
224
+ * <pre>
225
+ * <!doctype html>
226
+ * <html ng-app="myApp">
227
+ * <head>
228
+ * <script src="js/angular.js"></script>
229
+ * <!-- Include the ui-router script -->
230
+ * <script src="js/angular-ui-router.min.js"></script>
231
+ * <script>
232
+ * // ...and add 'ui.router' as a dependency
233
+ * var myApp = angular.module('myApp', ['ui.router']);
234
+ * </script>
235
+ * </head>
236
+ * <body>
237
+ * </body>
238
+ * </html>
239
+ * </pre>
240
+ */
241
+ angular.module('ui.router', ['ui.router.state']);
242
+
243
+ angular.module('ui.router.compat', ['ui.router']);
244
+
245
+ /**
246
+ * @ngdoc object
247
+ * @name ui.router.util.$resolve
248
+ *
249
+ * @requires $q
250
+ * @requires $injector
251
+ *
252
+ * @description
253
+ * Manages resolution of (acyclic) graphs of promises.
254
+ */
255
+ $Resolve.$inject = ['$q', '$injector'];
256
+ function $Resolve( $q, $injector) {
257
+
258
+ var VISIT_IN_PROGRESS = 1,
259
+ VISIT_DONE = 2,
260
+ NOTHING = {},
261
+ NO_DEPENDENCIES = [],
262
+ NO_LOCALS = NOTHING,
263
+ NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING });
264
+
265
+
266
+ /**
267
+ * @ngdoc function
268
+ * @name ui.router.util.$resolve#study
269
+ * @methodOf ui.router.util.$resolve
270
+ *
271
+ * @description
272
+ * Studies a set of invocables that are likely to be used multiple times.
273
+ * <pre>
274
+ * $resolve.study(invocables)(locals, parent, self)
275
+ * </pre>
276
+ * is equivalent to
277
+ * <pre>
278
+ * $resolve.resolve(invocables, locals, parent, self)
279
+ * </pre>
280
+ * but the former is more efficient (in fact `resolve` just calls `study`
281
+ * internally).
282
+ *
283
+ * @param {object} invocables Invocable objects
284
+ * @return {function} a function to pass in locals, parent and self
285
+ */
286
+ this.study = function (invocables) {
287
+ if (!isObject(invocables)) throw new Error("'invocables' must be an object");
288
+
289
+ // Perform a topological sort of invocables to build an ordered plan
290
+ var plan = [], cycle = [], visited = {};
291
+ function visit(value, key) {
292
+ if (visited[key] === VISIT_DONE) return;
293
+
294
+ cycle.push(key);
295
+ if (visited[key] === VISIT_IN_PROGRESS) {
296
+ cycle.splice(0, cycle.indexOf(key));
297
+ throw new Error("Cyclic dependency: " + cycle.join(" -> "));
298
+ }
299
+ visited[key] = VISIT_IN_PROGRESS;
300
+
301
+ if (isString(value)) {
302
+ plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES);
303
+ } else {
304
+ var params = $injector.annotate(value);
305
+ forEach(params, function (param) {
306
+ if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param);
307
+ });
308
+ plan.push(key, value, params);
309
+ }
310
+
311
+ cycle.pop();
312
+ visited[key] = VISIT_DONE;
313
+ }
314
+ forEach(invocables, visit);
315
+ invocables = cycle = visited = null; // plan is all that's required
316
+
317
+ function isResolve(value) {
318
+ return isObject(value) && value.then && value.$$promises;
319
+ }
320
+
321
+ return function (locals, parent, self) {
322
+ if (isResolve(locals) && self === undefined) {
323
+ self = parent; parent = locals; locals = null;
324
+ }
325
+ if (!locals) locals = NO_LOCALS;
326
+ else if (!isObject(locals)) {
327
+ throw new Error("'locals' must be an object");
328
+ }
329
+ if (!parent) parent = NO_PARENT;
330
+ else if (!isResolve(parent)) {
331
+ throw new Error("'parent' must be a promise returned by $resolve.resolve()");
332
+ }
333
+
334
+ // To complete the overall resolution, we have to wait for the parent
335
+ // promise and for the promise for each invokable in our plan.
336
+ var resolution = $q.defer(),
337
+ result = resolution.promise,
338
+ promises = result.$$promises = {},
339
+ values = extend({}, locals),
340
+ wait = 1 + plan.length/3,
341
+ merged = false;
342
+
343
+ function done() {
344
+ // Merge parent values we haven't got yet and publish our own $$values
345
+ if (!--wait) {
346
+ if (!merged) merge(values, parent.$$values);
347
+ result.$$values = values;
348
+ result.$$promises = true; // keep for isResolve()
349
+ delete result.$$inheritedValues;
350
+ resolution.resolve(values);
351
+ }
352
+ }
353
+
354
+ function fail(reason) {
355
+ result.$$failure = reason;
356
+ resolution.reject(reason);
357
+ }
358
+
359
+ // Short-circuit if parent has already failed
360
+ if (isDefined(parent.$$failure)) {
361
+ fail(parent.$$failure);
362
+ return result;
363
+ }
364
+
365
+ if (parent.$$inheritedValues) {
366
+ merge(values, parent.$$inheritedValues);
367
+ }
368
+
369
+ // Merge parent values if the parent has already resolved, or merge
370
+ // parent promises and wait if the parent resolve is still in progress.
371
+ if (parent.$$values) {
372
+ merged = merge(values, parent.$$values);
373
+ result.$$inheritedValues = parent.$$values;
374
+ done();
375
+ } else {
376
+ if (parent.$$inheritedValues) {
377
+ result.$$inheritedValues = parent.$$inheritedValues;
378
+ }
379
+ extend(promises, parent.$$promises);
380
+ parent.then(done, fail);
381
+ }
382
+
383
+ // Process each invocable in the plan, but ignore any where a local of the same name exists.
384
+ for (var i=0, ii=plan.length; i<ii; i+=3) {
385
+ if (locals.hasOwnProperty(plan[i])) done();
386
+ else invoke(plan[i], plan[i+1], plan[i+2]);
387
+ }
388
+
389
+ function invoke(key, invocable, params) {
390
+ // Create a deferred for this invocation. Failures will propagate to the resolution as well.
391
+ var invocation = $q.defer(), waitParams = 0;
392
+ function onfailure(reason) {
393
+ invocation.reject(reason);
394
+ fail(reason);
395
+ }
396
+ // Wait for any parameter that we have a promise for (either from parent or from this
397
+ // resolve; in that case study() will have made sure it's ordered before us in the plan).
398
+ forEach(params, function (dep) {
399
+ if (promises.hasOwnProperty(dep) && !locals.hasOwnProperty(dep)) {
400
+ waitParams++;
401
+ promises[dep].then(function (result) {
402
+ values[dep] = result;
403
+ if (!(--waitParams)) proceed();
404
+ }, onfailure);
405
+ }
406
+ });
407
+ if (!waitParams) proceed();
408
+ function proceed() {
409
+ if (isDefined(result.$$failure)) return;
410
+ try {
411
+ invocation.resolve($injector.invoke(invocable, self, values));
412
+ invocation.promise.then(function (result) {
413
+ values[key] = result;
414
+ done();
415
+ }, onfailure);
416
+ } catch (e) {
417
+ onfailure(e);
418
+ }
419
+ }
420
+ // Publish promise synchronously; invocations further down in the plan may depend on it.
421
+ promises[key] = invocation.promise;
422
+ }
423
+
424
+ return result;
425
+ };
426
+ };
427
+
428
+ /**
429
+ * @ngdoc function
430
+ * @name ui.router.util.$resolve#resolve
431
+ * @methodOf ui.router.util.$resolve
432
+ *
433
+ * @description
434
+ * Resolves a set of invocables. An invocable is a function to be invoked via
435
+ * `$injector.invoke()`, and can have an arbitrary number of dependencies.
436
+ * An invocable can either return a value directly,
437
+ * or a `$q` promise. If a promise is returned it will be resolved and the
438
+ * resulting value will be used instead. Dependencies of invocables are resolved
439
+ * (in this order of precedence)
440
+ *
441
+ * - from the specified `locals`
442
+ * - from another invocable that is part of this `$resolve` call
443
+ * - from an invocable that is inherited from a `parent` call to `$resolve`
444
+ * (or recursively
445
+ * - from any ancestor `$resolve` of that parent).
446
+ *
447
+ * The return value of `$resolve` is a promise for an object that contains
448
+ * (in this order of precedence)
449
+ *
450
+ * - any `locals` (if specified)
451
+ * - the resolved return values of all injectables
452
+ * - any values inherited from a `parent` call to `$resolve` (if specified)
453
+ *
454
+ * The promise will resolve after the `parent` promise (if any) and all promises
455
+ * returned by injectables have been resolved. If any invocable
456
+ * (or `$injector.invoke`) throws an exception, or if a promise returned by an
457
+ * invocable is rejected, the `$resolve` promise is immediately rejected with the
458
+ * same error. A rejection of a `parent` promise (if specified) will likewise be
459
+ * propagated immediately. Once the `$resolve` promise has been rejected, no
460
+ * further invocables will be called.
461
+ *
462
+ * Cyclic dependencies between invocables are not permitted and will caues `$resolve`
463
+ * to throw an error. As a special case, an injectable can depend on a parameter
464
+ * with the same name as the injectable, which will be fulfilled from the `parent`
465
+ * injectable of the same name. This allows inherited values to be decorated.
466
+ * Note that in this case any other injectable in the same `$resolve` with the same
467
+ * dependency would see the decorated value, not the inherited value.
468
+ *
469
+ * Note that missing dependencies -- unlike cyclic dependencies -- will cause an
470
+ * (asynchronous) rejection of the `$resolve` promise rather than a (synchronous)
471
+ * exception.
472
+ *
473
+ * Invocables are invoked eagerly as soon as all dependencies are available.
474
+ * This is true even for dependencies inherited from a `parent` call to `$resolve`.
475
+ *
476
+ * As a special case, an invocable can be a string, in which case it is taken to
477
+ * be a service name to be passed to `$injector.get()`. This is supported primarily
478
+ * for backwards-compatibility with the `resolve` property of `$routeProvider`
479
+ * routes.
480
+ *
481
+ * @param {object} invocables functions to invoke or
482
+ * `$injector` services to fetch.
483
+ * @param {object} locals values to make available to the injectables
484
+ * @param {object} parent a promise returned by another call to `$resolve`.
485
+ * @param {object} self the `this` for the invoked methods
486
+ * @return {object} Promise for an object that contains the resolved return value
487
+ * of all invocables, as well as any inherited and local values.
488
+ */
489
+ this.resolve = function (invocables, locals, parent, self) {
490
+ return this.study(invocables)(locals, parent, self);
491
+ };
492
+ }
493
+
494
+ angular.module('ui.router.util').service('$resolve', $Resolve);
495
+
496
+
497
+ /**
498
+ * @ngdoc object
499
+ * @name ui.router.util.$templateFactory
500
+ *
501
+ * @requires $http
502
+ * @requires $templateCache
503
+ * @requires $injector
504
+ *
505
+ * @description
506
+ * Service. Manages loading of templates.
507
+ */
508
+ $TemplateFactory.$inject = ['$http', '$templateCache', '$injector'];
509
+ function $TemplateFactory( $http, $templateCache, $injector) {
510
+
511
+ /**
512
+ * @ngdoc function
513
+ * @name ui.router.util.$templateFactory#fromConfig
514
+ * @methodOf ui.router.util.$templateFactory
515
+ *
516
+ * @description
517
+ * Creates a template from a configuration object.
518
+ *
519
+ * @param {object} config Configuration object for which to load a template.
520
+ * The following properties are search in the specified order, and the first one
521
+ * that is defined is used to create the template:
522
+ *
523
+ * @param {string|object} config.template html string template or function to
524
+ * load via {@link ui.router.util.$templateFactory#fromString fromString}.
525
+ * @param {string|object} config.templateUrl url to load or a function returning
526
+ * the url to load via {@link ui.router.util.$templateFactory#fromUrl fromUrl}.
527
+ * @param {Function} config.templateProvider function to invoke via
528
+ * {@link ui.router.util.$templateFactory#fromProvider fromProvider}.
529
+ * @param {object} params Parameters to pass to the template function.
530
+ * @param {object} locals Locals to pass to `invoke` if the template is loaded
531
+ * via a `templateProvider`. Defaults to `{ params: params }`.
532
+ *
533
+ * @return {string|object} The template html as a string, or a promise for
534
+ * that string,or `null` if no template is configured.
535
+ */
536
+ this.fromConfig = function (config, params, locals) {
537
+ return (
538
+ isDefined(config.template) ? this.fromString(config.template, params) :
539
+ isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) :
540
+ isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) :
541
+ null
542
+ );
543
+ };
544
+
545
+ /**
546
+ * @ngdoc function
547
+ * @name ui.router.util.$templateFactory#fromString
548
+ * @methodOf ui.router.util.$templateFactory
549
+ *
550
+ * @description
551
+ * Creates a template from a string or a function returning a string.
552
+ *
553
+ * @param {string|object} template html template as a string or function that
554
+ * returns an html template as a string.
555
+ * @param {object} params Parameters to pass to the template function.
556
+ *
557
+ * @return {string|object} The template html as a string, or a promise for that
558
+ * string.
559
+ */
560
+ this.fromString = function (template, params) {
561
+ return isFunction(template) ? template(params) : template;
562
+ };
563
+
564
+ /**
565
+ * @ngdoc function
566
+ * @name ui.router.util.$templateFactory#fromUrl
567
+ * @methodOf ui.router.util.$templateFactory
568
+ *
569
+ * @description
570
+ * Loads a template from the a URL via `$http` and `$templateCache`.
571
+ *
572
+ * @param {string|Function} url url of the template to load, or a function
573
+ * that returns a url.
574
+ * @param {Object} params Parameters to pass to the url function.
575
+ * @return {string|Promise.<string>} The template html as a string, or a promise
576
+ * for that string.
577
+ */
578
+ this.fromUrl = function (url, params) {
579
+ if (isFunction(url)) url = url(params);
580
+ if (url == null) return null;
581
+ else return $http
582
+ .get(url, { cache: $templateCache })
583
+ .then(function(response) { return response.data; });
584
+ };
585
+
586
+ /**
587
+ * @ngdoc function
588
+ * @name ui.router.util.$templateFactory#fromProvider
589
+ * @methodOf ui.router.util.$templateFactory
590
+ *
591
+ * @description
592
+ * Creates a template by invoking an injectable provider function.
593
+ *
594
+ * @param {Function} provider Function to invoke via `$injector.invoke`
595
+ * @param {Object} params Parameters for the template.
596
+ * @param {Object} locals Locals to pass to `invoke`. Defaults to
597
+ * `{ params: params }`.
598
+ * @return {string|Promise.<string>} The template html as a string, or a promise
599
+ * for that string.
600
+ */
601
+ this.fromProvider = function (provider, params, locals) {
602
+ return $injector.invoke(provider, null, locals || { params: params });
603
+ };
604
+ }
605
+
606
+ angular.module('ui.router.util').service('$templateFactory', $TemplateFactory);
607
+
608
+ /**
609
+ * @ngdoc object
610
+ * @name ui.router.util.type:UrlMatcher
611
+ *
612
+ * @description
613
+ * Matches URLs against patterns and extracts named parameters from the path or the search
614
+ * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list
615
+ * of search parameters. Multiple search parameter names are separated by '&'. Search parameters
616
+ * do not influence whether or not a URL is matched, but their values are passed through into
617
+ * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}.
618
+ *
619
+ * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace
620
+ * syntax, which optionally allows a regular expression for the parameter to be specified:
621
+ *
622
+ * * `':'` name - colon placeholder
623
+ * * `'*'` name - catch-all placeholder
624
+ * * `'{' name '}'` - curly placeholder
625
+ * * `'{' name ':' regexp '}'` - curly placeholder with regexp. Should the regexp itself contain
626
+ * curly braces, they must be in matched pairs or escaped with a backslash.
627
+ *
628
+ * Parameter names may contain only word characters (latin letters, digits, and underscore) and
629
+ * must be unique within the pattern (across both path and search parameters). For colon
630
+ * placeholders or curly placeholders without an explicit regexp, a path parameter matches any
631
+ * number of characters other than '/'. For catch-all placeholders the path parameter matches
632
+ * any number of characters.
633
+ *
634
+ * Examples:
635
+ *
636
+ * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for
637
+ * trailing slashes, and patterns have to match the entire path, not just a prefix.
638
+ * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or
639
+ * '/user/bob/details'. The second path segment will be captured as the parameter 'id'.
640
+ * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax.
641
+ * * `'/user/{id:[^/]*}'` - Same as the previous example.
642
+ * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id
643
+ * parameter consists of 1 to 8 hex digits.
644
+ * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the
645
+ * path into the parameter 'path'.
646
+ * * `'/files/*path'` - ditto.
647
+ *
648
+ * @param {string} pattern The pattern to compile into a matcher.
649
+ * @param {Object} config A configuration object hash:
650
+ *
651
+ * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`.
652
+ * * `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`.
653
+ *
654
+ * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any
655
+ * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns
656
+ * non-null) will start with this prefix.
657
+ *
658
+ * @property {string} source The pattern that was passed into the constructor
659
+ *
660
+ * @property {string} sourcePath The path portion of the source property
661
+ *
662
+ * @property {string} sourceSearch The search portion of the source property
663
+ *
664
+ * @property {string} regex The constructed regex that will be used to match against the url when
665
+ * it is time to determine which url will match.
666
+ *
667
+ * @returns {Object} New `UrlMatcher` object
668
+ */
669
+ function UrlMatcher(pattern, config) {
670
+ config = angular.isObject(config) ? config : {};
671
+
672
+ // Find all placeholders and create a compiled pattern, using either classic or curly syntax:
673
+ // '*' name
674
+ // ':' name
675
+ // '{' name '}'
676
+ // '{' name ':' regexp '}'
677
+ // The regular expression is somewhat complicated due to the need to allow curly braces
678
+ // inside the regular expression. The placeholder regexp breaks down as follows:
679
+ // ([:*])(\w+) classic placeholder ($1 / $2)
680
+ // \{(\w+)(?:\:( ... ))?\} curly brace placeholder ($3) with optional regexp ... ($4)
681
+ // (?: ... | ... | ... )+ the regexp consists of any number of atoms, an atom being either
682
+ // [^{}\\]+ - anything other than curly braces or backslash
683
+ // \\. - a backslash escape
684
+ // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms
685
+ var placeholder = /([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
686
+ compiled = '^', last = 0, m,
687
+ segments = this.segments = [],
688
+ params = this.params = {};
689
+
690
+ /**
691
+ * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the
692
+ * default value, which may be the result of an injectable function.
693
+ */
694
+ function $value(value) {
695
+ /*jshint validthis: true */
696
+ return isDefined(value) ? this.type.decode(value) : $UrlMatcherFactory.$$getDefaultValue(this);
697
+ }
698
+
699
+ function addParameter(id, type, config) {
700
+ if (!/^\w+(-+\w+)*$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
701
+ if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
702
+ params[id] = extend({ type: type || new Type(), $value: $value }, config);
703
+ }
704
+
705
+ function quoteRegExp(string, pattern, isOptional) {
706
+ var result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
707
+ if (!pattern) return result;
708
+ var flag = isOptional ? '?' : '';
709
+ return result + flag + '(' + pattern + ')' + flag;
710
+ }
711
+
712
+ function paramConfig(param) {
713
+ if (!config.params || !config.params[param]) return {};
714
+ var cfg = config.params[param];
715
+ return isObject(cfg) ? cfg : { value: cfg };
716
+ }
717
+
718
+ this.source = pattern;
719
+
720
+ // Split into static segments separated by path parameter placeholders.
721
+ // The number of segments is always 1 more than the number of parameters.
722
+ var id, regexp, segment, type, cfg;
723
+
724
+ while ((m = placeholder.exec(pattern))) {
725
+ id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
726
+ regexp = m[4] || (m[1] == '*' ? '.*' : '[^/]*');
727
+ segment = pattern.substring(last, m.index);
728
+ type = this.$types[regexp] || new Type({ pattern: new RegExp(regexp) });
729
+ cfg = paramConfig(id);
730
+
731
+ if (segment.indexOf('?') >= 0) break; // we're into the search part
732
+
733
+ compiled += quoteRegExp(segment, type.$subPattern(), isDefined(cfg.value));
734
+ addParameter(id, type, cfg);
735
+ segments.push(segment);
736
+ last = placeholder.lastIndex;
737
+ }
738
+ segment = pattern.substring(last);
739
+
740
+ // Find any search parameter names and remove them from the last segment
741
+ var i = segment.indexOf('?');
742
+
743
+ if (i >= 0) {
744
+ var search = this.sourceSearch = segment.substring(i);
745
+ segment = segment.substring(0, i);
746
+ this.sourcePath = pattern.substring(0, last + i);
747
+
748
+ // Allow parameters to be separated by '?' as well as '&' to make concat() easier
749
+ forEach(search.substring(1).split(/[&?]/), function(key) {
750
+ addParameter(key, null, paramConfig(key));
751
+ });
752
+ } else {
753
+ this.sourcePath = pattern;
754
+ this.sourceSearch = '';
755
+ }
756
+
757
+ compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$';
758
+ segments.push(segment);
759
+
760
+ this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
761
+ this.prefix = segments[0];
762
+ }
763
+
764
+ /**
765
+ * @ngdoc function
766
+ * @name ui.router.util.type:UrlMatcher#concat
767
+ * @methodOf ui.router.util.type:UrlMatcher
768
+ *
769
+ * @description
770
+ * Returns a new matcher for a pattern constructed by appending the path part and adding the
771
+ * search parameters of the specified pattern to this pattern. The current pattern is not
772
+ * modified. This can be understood as creating a pattern for URLs that are relative to (or
773
+ * suffixes of) the current pattern.
774
+ *
775
+ * @example
776
+ * The following two matchers are equivalent:
777
+ * <pre>
778
+ * new UrlMatcher('/user/{id}?q').concat('/details?date');
779
+ * new UrlMatcher('/user/{id}/details?q&date');
780
+ * </pre>
781
+ *
782
+ * @param {string} pattern The pattern to append.
783
+ * @param {Object} config An object hash of the configuration for the matcher.
784
+ * @returns {UrlMatcher} A matcher for the concatenated pattern.
785
+ */
786
+ UrlMatcher.prototype.concat = function (pattern, config) {
787
+ // Because order of search parameters is irrelevant, we can add our own search
788
+ // parameters to the end of the new pattern. Parse the new pattern by itself
789
+ // and then join the bits together, but it's much easier to do this on a string level.
790
+ return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, config);
791
+ };
792
+
793
+ UrlMatcher.prototype.toString = function () {
794
+ return this.source;
795
+ };
796
+
797
+ /**
798
+ * @ngdoc function
799
+ * @name ui.router.util.type:UrlMatcher#exec
800
+ * @methodOf ui.router.util.type:UrlMatcher
801
+ *
802
+ * @description
803
+ * Tests the specified path against this matcher, and returns an object containing the captured
804
+ * parameter values, or null if the path does not match. The returned object contains the values
805
+ * of any search parameters that are mentioned in the pattern, but their value may be null if
806
+ * they are not present in `searchParams`. This means that search parameters are always treated
807
+ * as optional.
808
+ *
809
+ * @example
810
+ * <pre>
811
+ * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {
812
+ * x: '1', q: 'hello'
813
+ * });
814
+ * // returns { id: 'bob', q: 'hello', r: null }
815
+ * </pre>
816
+ *
817
+ * @param {string} path The URL path to match, e.g. `$location.path()`.
818
+ * @param {Object} searchParams URL search parameters, e.g. `$location.search()`.
819
+ * @returns {Object} The captured parameter values.
820
+ */
821
+ UrlMatcher.prototype.exec = function (path, searchParams) {
822
+ var m = this.regexp.exec(path);
823
+ if (!m) return null;
824
+ searchParams = searchParams || {};
825
+
826
+ var params = this.parameters(), nTotal = params.length,
827
+ nPath = this.segments.length - 1,
828
+ values = {}, i, cfg, param;
829
+
830
+ if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'");
831
+
832
+ for (i = 0; i < nPath; i++) {
833
+ param = params[i];
834
+ cfg = this.params[param];
835
+ values[param] = cfg.$value(m[i + 1]);
836
+ }
837
+ for (/**/; i < nTotal; i++) {
838
+ param = params[i];
839
+ cfg = this.params[param];
840
+ values[param] = cfg.$value(searchParams[param]);
841
+ }
842
+
843
+ return values;
844
+ };
845
+
846
+ /**
847
+ * @ngdoc function
848
+ * @name ui.router.util.type:UrlMatcher#parameters
849
+ * @methodOf ui.router.util.type:UrlMatcher
850
+ *
851
+ * @description
852
+ * Returns the names of all path and search parameters of this pattern in an unspecified order.
853
+ *
854
+ * @returns {Array.<string>} An array of parameter names. Must be treated as read-only. If the
855
+ * pattern has no parameters, an empty array is returned.
856
+ */
857
+ UrlMatcher.prototype.parameters = function (param) {
858
+ if (!isDefined(param)) return objectKeys(this.params);
859
+ return this.params[param] || null;
860
+ };
861
+
862
+ /**
863
+ * @ngdoc function
864
+ * @name ui.router.util.type:UrlMatcher#validate
865
+ * @methodOf ui.router.util.type:UrlMatcher
866
+ *
867
+ * @description
868
+ * Checks an object hash of parameters to validate their correctness according to the parameter
869
+ * types of this `UrlMatcher`.
870
+ *
871
+ * @param {Object} params The object hash of parameters to validate.
872
+ * @returns {boolean} Returns `true` if `params` validates, otherwise `false`.
873
+ */
874
+ UrlMatcher.prototype.validates = function (params) {
875
+ var result = true, isOptional, cfg, self = this;
876
+
877
+ forEach(params, function(val, key) {
878
+ if (!self.params[key]) return;
879
+ cfg = self.params[key];
880
+ isOptional = !val && isDefined(cfg.value);
881
+ result = result && (isOptional || cfg.type.is(val));
882
+ });
883
+ return result;
884
+ };
885
+
886
+ /**
887
+ * @ngdoc function
888
+ * @name ui.router.util.type:UrlMatcher#format
889
+ * @methodOf ui.router.util.type:UrlMatcher
890
+ *
891
+ * @description
892
+ * Creates a URL that matches this pattern by substituting the specified values
893
+ * for the path and search parameters. Null values for path parameters are
894
+ * treated as empty strings.
895
+ *
896
+ * @example
897
+ * <pre>
898
+ * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
899
+ * // returns '/user/bob?q=yes'
900
+ * </pre>
901
+ *
902
+ * @param {Object} values the values to substitute for the parameters in this pattern.
903
+ * @returns {string} the formatted URL (path and optionally search part).
904
+ */
905
+ UrlMatcher.prototype.format = function (values) {
906
+ var segments = this.segments, params = this.parameters();
907
+
908
+ if (!values) return segments.join('').replace('//', '/');
909
+
910
+ var nPath = segments.length - 1, nTotal = params.length,
911
+ result = segments[0], i, search, value, param, cfg, array;
912
+
913
+ if (!this.validates(values)) return null;
914
+
915
+ for (i = 0; i < nPath; i++) {
916
+ param = params[i];
917
+ value = values[param];
918
+ cfg = this.params[param];
919
+
920
+ if (!isDefined(value) && (segments[i] === '/' || segments[i + 1] === '/')) continue;
921
+ if (value != null) result += encodeURIComponent(cfg.type.encode(value));
922
+ result += segments[i + 1];
923
+ }
924
+
925
+ for (/**/; i < nTotal; i++) {
926
+ param = params[i];
927
+ value = values[param];
928
+ if (value == null) continue;
929
+ array = isArray(value);
930
+
931
+ if (array) {
932
+ value = value.map(encodeURIComponent).join('&' + param + '=');
933
+ }
934
+ result += (search ? '&' : '?') + param + '=' + (array ? value : encodeURIComponent(value));
935
+ search = true;
936
+ }
937
+ return result;
938
+ };
939
+
940
+ UrlMatcher.prototype.$types = {};
941
+
942
+ /**
943
+ * @ngdoc object
944
+ * @name ui.router.util.type:Type
945
+ *
946
+ * @description
947
+ * Implements an interface to define custom parameter types that can be decoded from and encoded to
948
+ * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`}
949
+ * objects when matching or formatting URLs, or comparing or validating parameter values.
950
+ *
951
+ * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more
952
+ * information on registering custom types.
953
+ *
954
+ * @param {Object} config A configuration object hash that includes any method in `Type`'s public
955
+ * interface, and/or `pattern`, which should contain a custom regular expression used to match
956
+ * string parameters originating from a URL.
957
+ *
958
+ * @property {RegExp} pattern The regular expression pattern used to match values of this type when
959
+ * coming from a substring of a URL.
960
+ *
961
+ * @returns {Object} Returns a new `Type` object.
962
+ */
963
+ function Type(config) {
964
+ extend(this, config);
965
+ }
966
+
967
+ /**
968
+ * @ngdoc function
969
+ * @name ui.router.util.type:Type#is
970
+ * @methodOf ui.router.util.type:Type
971
+ *
972
+ * @description
973
+ * Detects whether a value is of a particular type. Accepts a native (decoded) value
974
+ * and determines whether it matches the current `Type` object.
975
+ *
976
+ * @param {*} val The value to check.
977
+ * @param {string} key Optional. If the type check is happening in the context of a specific
978
+ * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the
979
+ * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects.
980
+ * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`.
981
+ */
982
+ Type.prototype.is = function(val, key) {
983
+ return true;
984
+ };
985
+
986
+ /**
987
+ * @ngdoc function
988
+ * @name ui.router.util.type:Type#encode
989
+ * @methodOf ui.router.util.type:Type
990
+ *
991
+ * @description
992
+ * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the
993
+ * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it
994
+ * only needs to be a representation of `val` that has been coerced to a string.
995
+ *
996
+ * @param {*} val The value to encode.
997
+ * @param {string} key The name of the parameter in which `val` is stored. Can be used for
998
+ * meta-programming of `Type` objects.
999
+ * @returns {string} Returns a string representation of `val` that can be encoded in a URL.
1000
+ */
1001
+ Type.prototype.encode = function(val, key) {
1002
+ return val;
1003
+ };
1004
+
1005
+ /**
1006
+ * @ngdoc function
1007
+ * @name ui.router.util.type:Type#decode
1008
+ * @methodOf ui.router.util.type:Type
1009
+ *
1010
+ * @description
1011
+ * Converts a string URL parameter value to a custom/native value.
1012
+ *
1013
+ * @param {string} val The URL parameter value to decode.
1014
+ * @param {string} key The name of the parameter in which `val` is stored. Can be used for
1015
+ * meta-programming of `Type` objects.
1016
+ * @returns {*} Returns a custom representation of the URL parameter value.
1017
+ */
1018
+ Type.prototype.decode = function(val, key) {
1019
+ return val;
1020
+ };
1021
+
1022
+ /**
1023
+ * @ngdoc function
1024
+ * @name ui.router.util.type:Type#equals
1025
+ * @methodOf ui.router.util.type:Type
1026
+ *
1027
+ * @description
1028
+ * Determines whether two decoded values are equivalent.
1029
+ *
1030
+ * @param {*} a A value to compare against.
1031
+ * @param {*} b A value to compare against.
1032
+ * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`.
1033
+ */
1034
+ Type.prototype.equals = function(a, b) {
1035
+ return a == b;
1036
+ };
1037
+
1038
+ Type.prototype.$subPattern = function() {
1039
+ var sub = this.pattern.toString();
1040
+ return sub.substr(1, sub.length - 2);
1041
+ };
1042
+
1043
+ Type.prototype.pattern = /.*/;
1044
+
1045
+ /**
1046
+ * @ngdoc object
1047
+ * @name ui.router.util.$urlMatcherFactory
1048
+ *
1049
+ * @description
1050
+ * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory
1051
+ * is also available to providers under the name `$urlMatcherFactoryProvider`.
1052
+ */
1053
+ function $UrlMatcherFactory() {
1054
+
1055
+ var isCaseInsensitive = false, isStrictMode = true;
1056
+
1057
+ var enqueue = true, typeQueue = [], injector, defaultTypes = {
1058
+ int: {
1059
+ decode: function(val) {
1060
+ return parseInt(val, 10);
1061
+ },
1062
+ is: function(val) {
1063
+ if (!isDefined(val)) return false;
1064
+ return this.decode(val.toString()) === val;
1065
+ },
1066
+ pattern: /\d+/
1067
+ },
1068
+ bool: {
1069
+ encode: function(val) {
1070
+ return val ? 1 : 0;
1071
+ },
1072
+ decode: function(val) {
1073
+ return parseInt(val, 10) === 0 ? false : true;
1074
+ },
1075
+ is: function(val) {
1076
+ return val === true || val === false;
1077
+ },
1078
+ pattern: /0|1/
1079
+ },
1080
+ string: {
1081
+ pattern: /[^\/]*/
1082
+ },
1083
+ date: {
1084
+ equals: function (a, b) {
1085
+ return a.toISOString() === b.toISOString();
1086
+ },
1087
+ decode: function (val) {
1088
+ return new Date(val);
1089
+ },
1090
+ encode: function (val) {
1091
+ return [
1092
+ val.getFullYear(),
1093
+ ('0' + (val.getMonth() + 1)).slice(-2),
1094
+ ('0' + val.getDate()).slice(-2)
1095
+ ].join("-");
1096
+ },
1097
+ pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/
1098
+ }
1099
+ };
1100
+
1101
+ function getDefaultConfig() {
1102
+ return {
1103
+ strict: isStrictMode,
1104
+ caseInsensitive: isCaseInsensitive
1105
+ };
1106
+ }
1107
+
1108
+ function isInjectable(value) {
1109
+ return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1])));
1110
+ }
1111
+
1112
+ /**
1113
+ * [Internal] Get the default value of a parameter, which may be an injectable function.
1114
+ */
1115
+ $UrlMatcherFactory.$$getDefaultValue = function(config) {
1116
+ if (!isInjectable(config.value)) return config.value;
1117
+ if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
1118
+ return injector.invoke(config.value);
1119
+ };
1120
+
1121
+ /**
1122
+ * @ngdoc function
1123
+ * @name ui.router.util.$urlMatcherFactory#caseInsensitive
1124
+ * @methodOf ui.router.util.$urlMatcherFactory
1125
+ *
1126
+ * @description
1127
+ * Defines whether URL matching should be case sensitive (the default behavior), or not.
1128
+ *
1129
+ * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`;
1130
+ */
1131
+ this.caseInsensitive = function(value) {
1132
+ isCaseInsensitive = value;
1133
+ };
1134
+
1135
+ /**
1136
+ * @ngdoc function
1137
+ * @name ui.router.util.$urlMatcherFactory#strictMode
1138
+ * @methodOf ui.router.util.$urlMatcherFactory
1139
+ *
1140
+ * @description
1141
+ * Defines whether URLs should match trailing slashes, or not (the default behavior).
1142
+ *
1143
+ * @param {boolean} value `false` to match trailing slashes in URLs, otherwise `true`.
1144
+ */
1145
+ this.strictMode = function(value) {
1146
+ isStrictMode = value;
1147
+ };
1148
+
1149
+ /**
1150
+ * @ngdoc function
1151
+ * @name ui.router.util.$urlMatcherFactory#compile
1152
+ * @methodOf ui.router.util.$urlMatcherFactory
1153
+ *
1154
+ * @description
1155
+ * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern.
1156
+ *
1157
+ * @param {string} pattern The URL pattern.
1158
+ * @param {Object} config The config object hash.
1159
+ * @returns {UrlMatcher} The UrlMatcher.
1160
+ */
1161
+ this.compile = function (pattern, config) {
1162
+ return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
1163
+ };
1164
+
1165
+ /**
1166
+ * @ngdoc function
1167
+ * @name ui.router.util.$urlMatcherFactory#isMatcher
1168
+ * @methodOf ui.router.util.$urlMatcherFactory
1169
+ *
1170
+ * @description
1171
+ * Returns true if the specified object is a `UrlMatcher`, or false otherwise.
1172
+ *
1173
+ * @param {Object} object The object to perform the type check against.
1174
+ * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by
1175
+ * implementing all the same methods.
1176
+ */
1177
+ this.isMatcher = function (o) {
1178
+ if (!isObject(o)) return false;
1179
+ var result = true;
1180
+
1181
+ forEach(UrlMatcher.prototype, function(val, name) {
1182
+ if (isFunction(val)) {
1183
+ result = result && (isDefined(o[name]) && isFunction(o[name]));
1184
+ }
1185
+ });
1186
+ return result;
1187
+ };
1188
+
1189
+ /**
1190
+ * @ngdoc function
1191
+ * @name ui.router.util.$urlMatcherFactory#type
1192
+ * @methodOf ui.router.util.$urlMatcherFactory
1193
+ *
1194
+ * @description
1195
+ * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to
1196
+ * generate URLs with typed parameters.
1197
+ *
1198
+ * @param {string} name The type name.
1199
+ * @param {Object|Function} def The type definition. See
1200
+ * {@link ui.router.util.type:Type `Type`} for information on the values accepted.
1201
+ *
1202
+ * @returns {Object} Returns `$urlMatcherFactoryProvider`.
1203
+ *
1204
+ * @example
1205
+ * This is a simple example of a custom type that encodes and decodes items from an
1206
+ * array, using the array index as the URL-encoded value:
1207
+ *
1208
+ * <pre>
1209
+ * var list = ['John', 'Paul', 'George', 'Ringo'];
1210
+ *
1211
+ * $urlMatcherFactoryProvider.type('listItem', {
1212
+ * encode: function(item) {
1213
+ * // Represent the list item in the URL using its corresponding index
1214
+ * return list.indexOf(item);
1215
+ * },
1216
+ * decode: function(item) {
1217
+ * // Look up the list item by index
1218
+ * return list[parseInt(item, 10)];
1219
+ * },
1220
+ * is: function(item) {
1221
+ * // Ensure the item is valid by checking to see that it appears
1222
+ * // in the list
1223
+ * return list.indexOf(item) > -1;
1224
+ * }
1225
+ * });
1226
+ *
1227
+ * $stateProvider.state('list', {
1228
+ * url: "/list/{item:listItem}",
1229
+ * controller: function($scope, $stateParams) {
1230
+ * console.log($stateParams.item);
1231
+ * }
1232
+ * });
1233
+ *
1234
+ * // ...
1235
+ *
1236
+ * // Changes URL to '/list/3', logs "Ringo" to the console
1237
+ * $state.go('list', { item: "Ringo" });
1238
+ * </pre>
1239
+ *
1240
+ * This is a more complex example of a type that relies on dependency injection to
1241
+ * interact with services, and uses the parameter name from the URL to infer how to
1242
+ * handle encoding and decoding parameter values:
1243
+ *
1244
+ * <pre>
1245
+ * // Defines a custom type that gets a value from a service,
1246
+ * // where each service gets different types of values from
1247
+ * // a backend API:
1248
+ * $urlMatcherFactoryProvider.type('dbObject', function(Users, Posts) {
1249
+ *
1250
+ * // Matches up services to URL parameter names
1251
+ * var services = {
1252
+ * user: Users,
1253
+ * post: Posts
1254
+ * };
1255
+ *
1256
+ * return {
1257
+ * encode: function(object) {
1258
+ * // Represent the object in the URL using its unique ID
1259
+ * return object.id;
1260
+ * },
1261
+ * decode: function(value, key) {
1262
+ * // Look up the object by ID, using the parameter
1263
+ * // name (key) to call the correct service
1264
+ * return services[key].findById(value);
1265
+ * },
1266
+ * is: function(object, key) {
1267
+ * // Check that object is a valid dbObject
1268
+ * return angular.isObject(object) && object.id && services[key];
1269
+ * }
1270
+ * equals: function(a, b) {
1271
+ * // Check the equality of decoded objects by comparing
1272
+ * // their unique IDs
1273
+ * return a.id === b.id;
1274
+ * }
1275
+ * };
1276
+ * });
1277
+ *
1278
+ * // In a config() block, you can then attach URLs with
1279
+ * // type-annotated parameters:
1280
+ * $stateProvider.state('users', {
1281
+ * url: "/users",
1282
+ * // ...
1283
+ * }).state('users.item', {
1284
+ * url: "/{user:dbObject}",
1285
+ * controller: function($scope, $stateParams) {
1286
+ * // $stateParams.user will now be an object returned from
1287
+ * // the Users service
1288
+ * },
1289
+ * // ...
1290
+ * });
1291
+ * </pre>
1292
+ */
1293
+ this.type = function (name, def) {
1294
+ if (!isDefined(def)) return UrlMatcher.prototype.$types[name];
1295
+ typeQueue.push({ name: name, def: def });
1296
+ if (!enqueue) flushTypeQueue();
1297
+ return this;
1298
+ };
1299
+
1300
+ /* No need to document $get, since it returns this */
1301
+ this.$get = ['$injector', function ($injector) {
1302
+ injector = $injector;
1303
+ enqueue = false;
1304
+ UrlMatcher.prototype.$types = {};
1305
+ flushTypeQueue();
1306
+
1307
+ forEach(defaultTypes, function(type, name) {
1308
+ if (!UrlMatcher.prototype.$types[name]) UrlMatcher.prototype.$types[name] = new Type(type);
1309
+ });
1310
+ return this;
1311
+ }];
1312
+
1313
+ // To ensure proper order of operations in object configuration, and to allow internal
1314
+ // types to be overridden, `flushTypeQueue()` waits until `$urlMatcherFactory` is injected
1315
+ // before actually wiring up and assigning type definitions
1316
+ function flushTypeQueue() {
1317
+ forEach(typeQueue, function(type) {
1318
+ if (UrlMatcher.prototype.$types[type.name]) {
1319
+ throw new Error("A type named '" + type.name + "' has already been defined.");
1320
+ }
1321
+ var def = new Type(isInjectable(type.def) ? injector.invoke(type.def) : type.def);
1322
+ UrlMatcher.prototype.$types[type.name] = def;
1323
+ });
1324
+ }
1325
+ }
1326
+
1327
+ // Register as a provider so it's available to other providers
1328
+ angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory);
1329
+
1330
+ /**
1331
+ * @ngdoc object
1332
+ * @name ui.router.router.$urlRouterProvider
1333
+ *
1334
+ * @requires ui.router.util.$urlMatcherFactoryProvider
1335
+ * @requires $locationProvider
1336
+ *
1337
+ * @description
1338
+ * `$urlRouterProvider` has the responsibility of watching `$location`.
1339
+ * When `$location` changes it runs through a list of rules one by one until a
1340
+ * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify
1341
+ * a url in a state configuration. All urls are compiled into a UrlMatcher object.
1342
+ *
1343
+ * There are several methods on `$urlRouterProvider` that make it useful to use directly
1344
+ * in your module config.
1345
+ */
1346
+ $UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
1347
+ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
1348
+ var rules = [], otherwise = null, interceptDeferred = false, listener;
1349
+
1350
+ // Returns a string that is a prefix of all strings matching the RegExp
1351
+ function regExpPrefix(re) {
1352
+ var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source);
1353
+ return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : '';
1354
+ }
1355
+
1356
+ // Interpolates matched values into a String.replace()-style pattern
1357
+ function interpolate(pattern, match) {
1358
+ return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) {
1359
+ return match[what === '$' ? 0 : Number(what)];
1360
+ });
1361
+ }
1362
+
1363
+ /**
1364
+ * @ngdoc function
1365
+ * @name ui.router.router.$urlRouterProvider#rule
1366
+ * @methodOf ui.router.router.$urlRouterProvider
1367
+ *
1368
+ * @description
1369
+ * Defines rules that are used by `$urlRouterProvider` to find matches for
1370
+ * specific URLs.
1371
+ *
1372
+ * @example
1373
+ * <pre>
1374
+ * var app = angular.module('app', ['ui.router.router']);
1375
+ *
1376
+ * app.config(function ($urlRouterProvider) {
1377
+ * // Here's an example of how you might allow case insensitive urls
1378
+ * $urlRouterProvider.rule(function ($injector, $location) {
1379
+ * var path = $location.path(),
1380
+ * normalized = path.toLowerCase();
1381
+ *
1382
+ * if (path !== normalized) {
1383
+ * return normalized;
1384
+ * }
1385
+ * });
1386
+ * });
1387
+ * </pre>
1388
+ *
1389
+ * @param {object} rule Handler function that takes `$injector` and `$location`
1390
+ * services as arguments. You can use them to return a valid path as a string.
1391
+ *
1392
+ * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
1393
+ */
1394
+ this.rule = function (rule) {
1395
+ if (!isFunction(rule)) throw new Error("'rule' must be a function");
1396
+ rules.push(rule);
1397
+ return this;
1398
+ };
1399
+
1400
+ /**
1401
+ * @ngdoc object
1402
+ * @name ui.router.router.$urlRouterProvider#otherwise
1403
+ * @methodOf ui.router.router.$urlRouterProvider
1404
+ *
1405
+ * @description
1406
+ * Defines a path that is used when an invalid route is requested.
1407
+ *
1408
+ * @example
1409
+ * <pre>
1410
+ * var app = angular.module('app', ['ui.router.router']);
1411
+ *
1412
+ * app.config(function ($urlRouterProvider) {
1413
+ * // if the path doesn't match any of the urls you configured
1414
+ * // otherwise will take care of routing the user to the
1415
+ * // specified url
1416
+ * $urlRouterProvider.otherwise('/index');
1417
+ *
1418
+ * // Example of using function rule as param
1419
+ * $urlRouterProvider.otherwise(function ($injector, $location) {
1420
+ * return '/a/valid/url';
1421
+ * });
1422
+ * });
1423
+ * </pre>
1424
+ *
1425
+ * @param {string|object} rule The url path you want to redirect to or a function
1426
+ * rule that returns the url path. The function version is passed two params:
1427
+ * `$injector` and `$location` services, and must return a url string.
1428
+ *
1429
+ * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
1430
+ */
1431
+ this.otherwise = function (rule) {
1432
+ if (isString(rule)) {
1433
+ var redirect = rule;
1434
+ rule = function () { return redirect; };
1435
+ }
1436
+ else if (!isFunction(rule)) throw new Error("'rule' must be a function");
1437
+ otherwise = rule;
1438
+ return this;
1439
+ };
1440
+
1441
+
1442
+ function handleIfMatch($injector, handler, match) {
1443
+ if (!match) return false;
1444
+ var result = $injector.invoke(handler, handler, { $match: match });
1445
+ return isDefined(result) ? result : true;
1446
+ }
1447
+
1448
+ /**
1449
+ * @ngdoc function
1450
+ * @name ui.router.router.$urlRouterProvider#when
1451
+ * @methodOf ui.router.router.$urlRouterProvider
1452
+ *
1453
+ * @description
1454
+ * Registers a handler for a given url matching. if handle is a string, it is
1455
+ * treated as a redirect, and is interpolated according to the syntax of match
1456
+ * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
1457
+ *
1458
+ * If the handler is a function, it is injectable. It gets invoked if `$location`
1459
+ * matches. You have the option of inject the match object as `$match`.
1460
+ *
1461
+ * The handler can return
1462
+ *
1463
+ * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter`
1464
+ * will continue trying to find another one that matches.
1465
+ * - **string** which is treated as a redirect and passed to `$location.url()`
1466
+ * - **void** or any **truthy** value tells `$urlRouter` that the url was handled.
1467
+ *
1468
+ * @example
1469
+ * <pre>
1470
+ * var app = angular.module('app', ['ui.router.router']);
1471
+ *
1472
+ * app.config(function ($urlRouterProvider) {
1473
+ * $urlRouterProvider.when($state.url, function ($match, $stateParams) {
1474
+ * if ($state.$current.navigable !== state ||
1475
+ * !equalForKeys($match, $stateParams) {
1476
+ * $state.transitionTo(state, $match, false);
1477
+ * }
1478
+ * });
1479
+ * });
1480
+ * </pre>
1481
+ *
1482
+ * @param {string|object} what The incoming path that you want to redirect.
1483
+ * @param {string|object} handler The path you want to redirect your user to.
1484
+ */
1485
+ this.when = function (what, handler) {
1486
+ var redirect, handlerIsString = isString(handler);
1487
+ if (isString(what)) what = $urlMatcherFactory.compile(what);
1488
+
1489
+ if (!handlerIsString && !isFunction(handler) && !isArray(handler))
1490
+ throw new Error("invalid 'handler' in when()");
1491
+
1492
+ var strategies = {
1493
+ matcher: function (what, handler) {
1494
+ if (handlerIsString) {
1495
+ redirect = $urlMatcherFactory.compile(handler);
1496
+ handler = ['$match', function ($match) { return redirect.format($match); }];
1497
+ }
1498
+ return extend(function ($injector, $location) {
1499
+ return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));
1500
+ }, {
1501
+ prefix: isString(what.prefix) ? what.prefix : ''
1502
+ });
1503
+ },
1504
+ regex: function (what, handler) {
1505
+ if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky");
1506
+
1507
+ if (handlerIsString) {
1508
+ redirect = handler;
1509
+ handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
1510
+ }
1511
+ return extend(function ($injector, $location) {
1512
+ return handleIfMatch($injector, handler, what.exec($location.path()));
1513
+ }, {
1514
+ prefix: regExpPrefix(what)
1515
+ });
1516
+ }
1517
+ };
1518
+
1519
+ var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };
1520
+
1521
+ for (var n in check) {
1522
+ if (check[n]) return this.rule(strategies[n](what, handler));
1523
+ }
1524
+
1525
+ throw new Error("invalid 'what' in when()");
1526
+ };
1527
+
1528
+ /**
1529
+ * @ngdoc function
1530
+ * @name ui.router.router.$urlRouterProvider#deferIntercept
1531
+ * @methodOf ui.router.router.$urlRouterProvider
1532
+ *
1533
+ * @description
1534
+ * Disables (or enables) deferring location change interception.
1535
+ *
1536
+ * If you wish to customize the behavior of syncing the URL (for example, if you wish to
1537
+ * defer a transition but maintain the current URL), call this method at configuration time.
1538
+ * Then, at run time, call `$urlRouter.listen()` after you have configured your own
1539
+ * `$locationChangeSuccess` event handler.
1540
+ *
1541
+ * @example
1542
+ * <pre>
1543
+ * var app = angular.module('app', ['ui.router.router']);
1544
+ *
1545
+ * app.config(function ($urlRouterProvider) {
1546
+ *
1547
+ * // Prevent $urlRouter from automatically intercepting URL changes;
1548
+ * // this allows you to configure custom behavior in between
1549
+ * // location changes and route synchronization:
1550
+ * $urlRouterProvider.deferIntercept();
1551
+ *
1552
+ * }).run(function ($rootScope, $urlRouter, UserService) {
1553
+ *
1554
+ * $rootScope.$on('$locationChangeSuccess', function(e) {
1555
+ * // UserService is an example service for managing user state
1556
+ * if (UserService.isLoggedIn()) return;
1557
+ *
1558
+ * // Prevent $urlRouter's default handler from firing
1559
+ * e.preventDefault();
1560
+ *
1561
+ * UserService.handleLogin().then(function() {
1562
+ * // Once the user has logged in, sync the current URL
1563
+ * // to the router:
1564
+ * $urlRouter.sync();
1565
+ * });
1566
+ * });
1567
+ *
1568
+ * // Configures $urlRouter's listener *after* your custom listener
1569
+ * $urlRouter.listen();
1570
+ * });
1571
+ * </pre>
1572
+ *
1573
+ * @param {boolean} defer Indicates whether to defer location change interception. Passing
1574
+ no parameter is equivalent to `true`.
1575
+ */
1576
+ this.deferIntercept = function (defer) {
1577
+ if (defer === undefined) defer = true;
1578
+ interceptDeferred = defer;
1579
+ };
1580
+
1581
+ /**
1582
+ * @ngdoc object
1583
+ * @name ui.router.router.$urlRouter
1584
+ *
1585
+ * @requires $location
1586
+ * @requires $rootScope
1587
+ * @requires $injector
1588
+ * @requires $browser
1589
+ *
1590
+ * @description
1591
+ *
1592
+ */
1593
+ this.$get = $get;
1594
+ $get.$inject = ['$location', '$rootScope', '$injector', '$browser'];
1595
+ function $get( $location, $rootScope, $injector, $browser) {
1596
+
1597
+ var baseHref = $browser.baseHref(), location = $location.url();
1598
+
1599
+ function appendBasePath(url, isHtml5, absolute) {
1600
+ if (baseHref === '/') return url;
1601
+ if (isHtml5) return baseHref.slice(0, -1) + url;
1602
+ if (absolute) return baseHref.slice(1) + url;
1603
+ return url;
1604
+ }
1605
+
1606
+ // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
1607
+ function update(evt) {
1608
+ if (evt && evt.defaultPrevented) return;
1609
+
1610
+ function check(rule) {
1611
+ var handled = rule($injector, $location);
1612
+
1613
+ if (!handled) return false;
1614
+ if (isString(handled)) $location.replace().url(handled);
1615
+ return true;
1616
+ }
1617
+ var n = rules.length, i;
1618
+
1619
+ for (i = 0; i < n; i++) {
1620
+ if (check(rules[i])) return;
1621
+ }
1622
+ // always check otherwise last to allow dynamic updates to the set of rules
1623
+ if (otherwise) check(otherwise);
1624
+ }
1625
+
1626
+ function listen() {
1627
+ listener = listener || $rootScope.$on('$locationChangeSuccess', update);
1628
+ return listener;
1629
+ }
1630
+
1631
+ if (!interceptDeferred) listen();
1632
+
1633
+ return {
1634
+ /**
1635
+ * @ngdoc function
1636
+ * @name ui.router.router.$urlRouter#sync
1637
+ * @methodOf ui.router.router.$urlRouter
1638
+ *
1639
+ * @description
1640
+ * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.
1641
+ * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,
1642
+ * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed
1643
+ * with the transition by calling `$urlRouter.sync()`.
1644
+ *
1645
+ * @example
1646
+ * <pre>
1647
+ * angular.module('app', ['ui.router'])
1648
+ * .run(function($rootScope, $urlRouter) {
1649
+ * $rootScope.$on('$locationChangeSuccess', function(evt) {
1650
+ * // Halt state change from even starting
1651
+ * evt.preventDefault();
1652
+ * // Perform custom logic
1653
+ * var meetsRequirement = ...
1654
+ * // Continue with the update and state transition if logic allows
1655
+ * if (meetsRequirement) $urlRouter.sync();
1656
+ * });
1657
+ * });
1658
+ * </pre>
1659
+ */
1660
+ sync: function() {
1661
+ update();
1662
+ },
1663
+
1664
+ listen: function() {
1665
+ return listen();
1666
+ },
1667
+
1668
+ update: function(read) {
1669
+ if (read) {
1670
+ location = $location.url();
1671
+ return;
1672
+ }
1673
+ if ($location.url() === location) return;
1674
+
1675
+ $location.url(location);
1676
+ $location.replace();
1677
+ },
1678
+
1679
+ push: function(urlMatcher, params, options) {
1680
+ $location.url(urlMatcher.format(params || {}));
1681
+ if (options && options.replace) $location.replace();
1682
+ },
1683
+
1684
+ /**
1685
+ * @ngdoc function
1686
+ * @name ui.router.router.$urlRouter#href
1687
+ * @methodOf ui.router.router.$urlRouter
1688
+ *
1689
+ * @description
1690
+ * A URL generation method that returns the compiled URL for a given
1691
+ * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters.
1692
+ *
1693
+ * @example
1694
+ * <pre>
1695
+ * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
1696
+ * person: "bob"
1697
+ * });
1698
+ * // $bob == "/about/bob";
1699
+ * </pre>
1700
+ *
1701
+ * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate.
1702
+ * @param {object=} params An object of parameter values to fill the matcher's required parameters.
1703
+ * @param {object=} options Options object. The options are:
1704
+ *
1705
+ * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
1706
+ *
1707
+ * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`
1708
+ */
1709
+ href: function(urlMatcher, params, options) {
1710
+ if (!urlMatcher.validates(params)) return null;
1711
+
1712
+ var isHtml5 = $locationProvider.html5Mode();
1713
+ var url = urlMatcher.format(params);
1714
+ options = options || {};
1715
+
1716
+ if (!isHtml5 && url !== null) {
1717
+ url = "#" + $locationProvider.hashPrefix() + url;
1718
+ }
1719
+ url = appendBasePath(url, isHtml5, options.absolute);
1720
+
1721
+ if (!options.absolute || !url) {
1722
+ return url;
1723
+ }
1724
+
1725
+ var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();
1726
+ port = (port === 80 || port === 443 ? '' : ':' + port);
1727
+
1728
+ return [$location.protocol(), '://', $location.host(), port, slash, url].join('');
1729
+ }
1730
+ };
1731
+ }
1732
+ }
1733
+
1734
+ angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);
1735
+
1736
+ /**
1737
+ * @ngdoc object
1738
+ * @name ui.router.state.$stateProvider
1739
+ *
1740
+ * @requires ui.router.router.$urlRouterProvider
1741
+ * @requires ui.router.util.$urlMatcherFactoryProvider
1742
+ *
1743
+ * @description
1744
+ * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely
1745
+ * on state.
1746
+ *
1747
+ * A state corresponds to a "place" in the application in terms of the overall UI and
1748
+ * navigation. A state describes (via the controller / template / view properties) what
1749
+ * the UI looks like and does at that place.
1750
+ *
1751
+ * States often have things in common, and the primary way of factoring out these
1752
+ * commonalities in this model is via the state hierarchy, i.e. parent/child states aka
1753
+ * nested states.
1754
+ *
1755
+ * The `$stateProvider` provides interfaces to declare these states for your app.
1756
+ */
1757
+ $StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];
1758
+ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
1759
+
1760
+ var root, states = {}, $state, queue = {}, abstractKey = 'abstract';
1761
+
1762
+ // Builds state properties from definition passed to registerState()
1763
+ var stateBuilder = {
1764
+
1765
+ // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.
1766
+ // state.children = [];
1767
+ // if (parent) parent.children.push(state);
1768
+ parent: function(state) {
1769
+ if (isDefined(state.parent) && state.parent) return findState(state.parent);
1770
+ // regex matches any valid composite state name
1771
+ // would match "contact.list" but not "contacts"
1772
+ var compositeName = /^(.+)\.[^.]+$/.exec(state.name);
1773
+ return compositeName ? findState(compositeName[1]) : root;
1774
+ },
1775
+
1776
+ // inherit 'data' from parent and override by own values (if any)
1777
+ data: function(state) {
1778
+ if (state.parent && state.parent.data) {
1779
+ state.data = state.self.data = extend({}, state.parent.data, state.data);
1780
+ }
1781
+ return state.data;
1782
+ },
1783
+
1784
+ // Build a URLMatcher if necessary, either via a relative or absolute URL
1785
+ url: function(state) {
1786
+ var url = state.url, config = { params: state.params || {} };
1787
+
1788
+ if (isString(url)) {
1789
+ if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config);
1790
+ return (state.parent.navigable || root).url.concat(url, config);
1791
+ }
1792
+
1793
+ if (!url || $urlMatcherFactory.isMatcher(url)) return url;
1794
+ throw new Error("Invalid url '" + url + "' in state '" + state + "'");
1795
+ },
1796
+
1797
+ // Keep track of the closest ancestor state that has a URL (i.e. is navigable)
1798
+ navigable: function(state) {
1799
+ return state.url ? state : (state.parent ? state.parent.navigable : null);
1800
+ },
1801
+
1802
+ // Derive parameters for this state and ensure they're a super-set of parent's parameters
1803
+ params: function(state) {
1804
+ if (!state.params) {
1805
+ return state.url ? state.url.params : state.parent.params;
1806
+ }
1807
+ return state.params;
1808
+ },
1809
+
1810
+ // If there is no explicit multi-view configuration, make one up so we don't have
1811
+ // to handle both cases in the view directive later. Note that having an explicit
1812
+ // 'views' property will mean the default unnamed view properties are ignored. This
1813
+ // is also a good time to resolve view names to absolute names, so everything is a
1814
+ // straight lookup at link time.
1815
+ views: function(state) {
1816
+ var views = {};
1817
+
1818
+ forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) {
1819
+ if (name.indexOf('@') < 0) name += '@' + state.parent.name;
1820
+ views[name] = view;
1821
+ });
1822
+ return views;
1823
+ },
1824
+
1825
+ ownParams: function(state) {
1826
+ state.params = state.params || {};
1827
+
1828
+ if (!state.parent) {
1829
+ return objectKeys(state.params);
1830
+ }
1831
+ var paramNames = {}; forEach(state.params, function (v, k) { paramNames[k] = true; });
1832
+
1833
+ forEach(state.parent.params, function (v, k) {
1834
+ if (!paramNames[k]) {
1835
+ throw new Error("Missing required parameter '" + k + "' in state '" + state.name + "'");
1836
+ }
1837
+ paramNames[k] = false;
1838
+ });
1839
+ var ownParams = [];
1840
+
1841
+ forEach(paramNames, function (own, p) {
1842
+ if (own) ownParams.push(p);
1843
+ });
1844
+ return ownParams;
1845
+ },
1846
+
1847
+ // Keep a full path from the root down to this state as this is needed for state activation.
1848
+ path: function(state) {
1849
+ return state.parent ? state.parent.path.concat(state) : []; // exclude root from path
1850
+ },
1851
+
1852
+ // Speed up $state.contains() as it's used a lot
1853
+ includes: function(state) {
1854
+ var includes = state.parent ? extend({}, state.parent.includes) : {};
1855
+ includes[state.name] = true;
1856
+ return includes;
1857
+ },
1858
+
1859
+ $delegates: {}
1860
+ };
1861
+
1862
+ function isRelative(stateName) {
1863
+ return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0;
1864
+ }
1865
+
1866
+ function findState(stateOrName, base) {
1867
+ if (!stateOrName) return undefined;
1868
+
1869
+ var isStr = isString(stateOrName),
1870
+ name = isStr ? stateOrName : stateOrName.name,
1871
+ path = isRelative(name);
1872
+
1873
+ if (path) {
1874
+ if (!base) throw new Error("No reference point given for path '" + name + "'");
1875
+ var rel = name.split("."), i = 0, pathLength = rel.length, current = base;
1876
+
1877
+ for (; i < pathLength; i++) {
1878
+ if (rel[i] === "" && i === 0) {
1879
+ current = base;
1880
+ continue;
1881
+ }
1882
+ if (rel[i] === "^") {
1883
+ if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'");
1884
+ current = current.parent;
1885
+ continue;
1886
+ }
1887
+ break;
1888
+ }
1889
+ rel = rel.slice(i).join(".");
1890
+ name = current.name + (current.name && rel ? "." : "") + rel;
1891
+ }
1892
+ var state = states[name];
1893
+
1894
+ if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) {
1895
+ return state;
1896
+ }
1897
+ return undefined;
1898
+ }
1899
+
1900
+ function queueState(parentName, state) {
1901
+ if (!queue[parentName]) {
1902
+ queue[parentName] = [];
1903
+ }
1904
+ queue[parentName].push(state);
1905
+ }
1906
+
1907
+ function registerState(state) {
1908
+ // Wrap a new object around the state so we can store our private details easily.
1909
+ state = inherit(state, {
1910
+ self: state,
1911
+ resolve: state.resolve || {},
1912
+ toString: function() { return this.name; }
1913
+ });
1914
+
1915
+ var name = state.name;
1916
+ if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name");
1917
+ if (states.hasOwnProperty(name)) throw new Error("State '" + name + "'' is already defined");
1918
+
1919
+ // Get parent name
1920
+ var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.'))
1921
+ : (isString(state.parent)) ? state.parent
1922
+ : '';
1923
+
1924
+ // If parent is not registered yet, add state to queue and register later
1925
+ if (parentName && !states[parentName]) {
1926
+ return queueState(parentName, state.self);
1927
+ }
1928
+
1929
+ for (var key in stateBuilder) {
1930
+ if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]);
1931
+ }
1932
+ states[name] = state;
1933
+
1934
+ // Register the state in the global state list and with $urlRouter if necessary.
1935
+ if (!state[abstractKey] && state.url) {
1936
+ $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {
1937
+ if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {
1938
+ $state.transitionTo(state, $match, { location: false });
1939
+ }
1940
+ }]);
1941
+ }
1942
+
1943
+ // Register any queued children
1944
+ if (queue[name]) {
1945
+ for (var i = 0; i < queue[name].length; i++) {
1946
+ registerState(queue[name][i]);
1947
+ }
1948
+ }
1949
+
1950
+ return state;
1951
+ }
1952
+
1953
+ // Checks text to see if it looks like a glob.
1954
+ function isGlob (text) {
1955
+ return text.indexOf('*') > -1;
1956
+ }
1957
+
1958
+ // Returns true if glob matches current $state name.
1959
+ function doesStateMatchGlob (glob) {
1960
+ var globSegments = glob.split('.'),
1961
+ segments = $state.$current.name.split('.');
1962
+
1963
+ //match greedy starts
1964
+ if (globSegments[0] === '**') {
1965
+ segments = segments.slice(segments.indexOf(globSegments[1]));
1966
+ segments.unshift('**');
1967
+ }
1968
+ //match greedy ends
1969
+ if (globSegments[globSegments.length - 1] === '**') {
1970
+ segments.splice(segments.indexOf(globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);
1971
+ segments.push('**');
1972
+ }
1973
+
1974
+ if (globSegments.length != segments.length) {
1975
+ return false;
1976
+ }
1977
+
1978
+ //match single stars
1979
+ for (var i = 0, l = globSegments.length; i < l; i++) {
1980
+ if (globSegments[i] === '*') {
1981
+ segments[i] = '*';
1982
+ }
1983
+ }
1984
+
1985
+ return segments.join('') === globSegments.join('');
1986
+ }
1987
+
1988
+
1989
+ // Implicit root state that is always active
1990
+ root = registerState({
1991
+ name: '',
1992
+ url: '^',
1993
+ views: null,
1994
+ 'abstract': true
1995
+ });
1996
+ root.navigable = null;
1997
+
1998
+
1999
+ /**
2000
+ * @ngdoc function
2001
+ * @name ui.router.state.$stateProvider#decorator
2002
+ * @methodOf ui.router.state.$stateProvider
2003
+ *
2004
+ * @description
2005
+ * Allows you to extend (carefully) or override (at your own peril) the
2006
+ * `stateBuilder` object used internally by `$stateProvider`. This can be used
2007
+ * to add custom functionality to ui-router, for example inferring templateUrl
2008
+ * based on the state name.
2009
+ *
2010
+ * When passing only a name, it returns the current (original or decorated) builder
2011
+ * function that matches `name`.
2012
+ *
2013
+ * The builder functions that can be decorated are listed below. Though not all
2014
+ * necessarily have a good use case for decoration, that is up to you to decide.
2015
+ *
2016
+ * In addition, users can attach custom decorators, which will generate new
2017
+ * properties within the state's internal definition. There is currently no clear
2018
+ * use-case for this beyond accessing internal states (i.e. $state.$current),
2019
+ * however, expect this to become increasingly relevant as we introduce additional
2020
+ * meta-programming features.
2021
+ *
2022
+ * **Warning**: Decorators should not be interdependent because the order of
2023
+ * execution of the builder functions in non-deterministic. Builder functions
2024
+ * should only be dependent on the state definition object and super function.
2025
+ *
2026
+ *
2027
+ * Existing builder functions and current return values:
2028
+ *
2029
+ * - **parent** `{object}` - returns the parent state object.
2030
+ * - **data** `{object}` - returns state data, including any inherited data that is not
2031
+ * overridden by own values (if any).
2032
+ * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher}
2033
+ * or `null`.
2034
+ * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is
2035
+ * navigable).
2036
+ * - **params** `{object}` - returns an array of state params that are ensured to
2037
+ * be a super-set of parent's params.
2038
+ * - **views** `{object}` - returns a views object where each key is an absolute view
2039
+ * name (i.e. "viewName@stateName") and each value is the config object
2040
+ * (template, controller) for the view. Even when you don't use the views object
2041
+ * explicitly on a state config, one is still created for you internally.
2042
+ * So by decorating this builder function you have access to decorating template
2043
+ * and controller properties.
2044
+ * - **ownParams** `{object}` - returns an array of params that belong to the state,
2045
+ * not including any params defined by ancestor states.
2046
+ * - **path** `{string}` - returns the full path from the root down to this state.
2047
+ * Needed for state activation.
2048
+ * - **includes** `{object}` - returns an object that includes every state that
2049
+ * would pass a `$state.includes()` test.
2050
+ *
2051
+ * @example
2052
+ * <pre>
2053
+ * // Override the internal 'views' builder with a function that takes the state
2054
+ * // definition, and a reference to the internal function being overridden:
2055
+ * $stateProvider.decorator('views', function (state, parent) {
2056
+ * var result = {},
2057
+ * views = parent(state);
2058
+ *
2059
+ * angular.forEach(views, function (config, name) {
2060
+ * var autoName = (state.name + '.' + name).replace('.', '/');
2061
+ * config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
2062
+ * result[name] = config;
2063
+ * });
2064
+ * return result;
2065
+ * });
2066
+ *
2067
+ * $stateProvider.state('home', {
2068
+ * views: {
2069
+ * 'contact.list': { controller: 'ListController' },
2070
+ * 'contact.item': { controller: 'ItemController' }
2071
+ * }
2072
+ * });
2073
+ *
2074
+ * // ...
2075
+ *
2076
+ * $state.go('home');
2077
+ * // Auto-populates list and item views with /partials/home/contact/list.html,
2078
+ * // and /partials/home/contact/item.html, respectively.
2079
+ * </pre>
2080
+ *
2081
+ * @param {string} name The name of the builder function to decorate.
2082
+ * @param {object} func A function that is responsible for decorating the original
2083
+ * builder function. The function receives two parameters:
2084
+ *
2085
+ * - `{object}` - state - The state config object.
2086
+ * - `{object}` - super - The original builder function.
2087
+ *
2088
+ * @return {object} $stateProvider - $stateProvider instance
2089
+ */
2090
+ this.decorator = decorator;
2091
+ function decorator(name, func) {
2092
+ /*jshint validthis: true */
2093
+ if (isString(name) && !isDefined(func)) {
2094
+ return stateBuilder[name];
2095
+ }
2096
+ if (!isFunction(func) || !isString(name)) {
2097
+ return this;
2098
+ }
2099
+ if (stateBuilder[name] && !stateBuilder.$delegates[name]) {
2100
+ stateBuilder.$delegates[name] = stateBuilder[name];
2101
+ }
2102
+ stateBuilder[name] = func;
2103
+ return this;
2104
+ }
2105
+
2106
+ /**
2107
+ * @ngdoc function
2108
+ * @name ui.router.state.$stateProvider#state
2109
+ * @methodOf ui.router.state.$stateProvider
2110
+ *
2111
+ * @description
2112
+ * Registers a state configuration under a given state name. The stateConfig object
2113
+ * has the following acceptable properties.
2114
+ *
2115
+ * <a id='template'></a>
2116
+ *
2117
+ * - **`template`** - {string|function=} - html template as a string or a function that returns
2118
+ * an html template as a string which should be used by the uiView directives. This property
2119
+ * takes precedence over templateUrl.
2120
+ *
2121
+ * If `template` is a function, it will be called with the following parameters:
2122
+ *
2123
+ * - {array.&lt;object&gt;} - state parameters extracted from the current $location.path() by
2124
+ * applying the current state
2125
+ *
2126
+ * <a id='templateUrl'></a>
2127
+ *
2128
+ * - **`templateUrl`** - {string|function=} - path or function that returns a path to an html
2129
+ * template that should be used by uiView.
2130
+ *
2131
+ * If `templateUrl` is a function, it will be called with the following parameters:
2132
+ *
2133
+ * - {array.&lt;object&gt;} - state parameters extracted from the current $location.path() by
2134
+ * applying the current state
2135
+ *
2136
+ * <a id='templateProvider'></a>
2137
+ *
2138
+ * - **`templateProvider`** - {function=} - Provider function that returns HTML content
2139
+ * string.
2140
+ *
2141
+ * <a id='controller'></a>
2142
+ *
2143
+ * - **`controller`** - {string|function=} - Controller fn that should be associated with newly
2144
+ * related scope or the name of a registered controller if passed as a string.
2145
+ *
2146
+ * <a id='controllerProvider'></a>
2147
+ *
2148
+ * - **`controllerProvider`** - {function=} - Injectable provider function that returns
2149
+ * the actual controller or string.
2150
+ *
2151
+ * <a id='controllerAs'></a>
2152
+ *
2153
+ * - **`controllerAs`** – {string=} – A controller alias name. If present the controller will be
2154
+ * published to scope under the controllerAs name.
2155
+ *
2156
+ * <a id='resolve'></a>
2157
+ *
2158
+ * - **`resolve`** - {object.&lt;string, function&gt;=} - An optional map of dependencies which
2159
+ * should be injected into the controller. If any of these dependencies are promises,
2160
+ * the router will wait for them all to be resolved or one to be rejected before the
2161
+ * controller is instantiated. If all the promises are resolved successfully, the values
2162
+ * of the resolved promises are injected and $stateChangeSuccess event is fired. If any
2163
+ * of the promises are rejected the $stateChangeError event is fired. The map object is:
2164
+ *
2165
+ * - key - {string}: name of dependency to be injected into controller
2166
+ * - factory - {string|function}: If string then it is alias for service. Otherwise if function,
2167
+ * it is injected and return value it treated as dependency. If result is a promise, it is
2168
+ * resolved before its value is injected into controller.
2169
+ *
2170
+ * <a id='url'></a>
2171
+ *
2172
+ * - **`url`** - {string=} - A url with optional parameters. When a state is navigated or
2173
+ * transitioned to, the `$stateParams` service will be populated with any
2174
+ * parameters that were passed.
2175
+ *
2176
+ * <a id='params'></a>
2177
+ *
2178
+ * - **`params`** - {object=} - An array of parameter names or regular expressions. Only
2179
+ * use this within a state if you are not using url. Otherwise you can specify your
2180
+ * parameters within the url. When a state is navigated or transitioned to, the
2181
+ * $stateParams service will be populated with any parameters that were passed.
2182
+ *
2183
+ * <a id='views'></a>
2184
+ *
2185
+ * - **`views`** - {object=} - Use the views property to set up multiple views or to target views
2186
+ * manually/explicitly.
2187
+ *
2188
+ * <a id='abstract'></a>
2189
+ *
2190
+ * - **`abstract`** - {boolean=} - An abstract state will never be directly activated,
2191
+ * but can provide inherited properties to its common children states.
2192
+ *
2193
+ * <a id='onEnter'></a>
2194
+ *
2195
+ * - **`onEnter`** - {object=} - Callback function for when a state is entered. Good way
2196
+ * to trigger an action or dispatch an event, such as opening a dialog.
2197
+ * If minifying your scripts, make sure to use the `['injection1', 'injection2', function(injection1, injection2){}]` syntax.
2198
+ *
2199
+ * <a id='onExit'></a>
2200
+ *
2201
+ * - **`onExit`** - {object=} - Callback function for when a state is exited. Good way to
2202
+ * trigger an action or dispatch an event, such as opening a dialog.
2203
+ * If minifying your scripts, make sure to use the `['injection1', 'injection2', function(injection1, injection2){}]` syntax.
2204
+ *
2205
+ * <a id='reloadOnSearch'></a>
2206
+ *
2207
+ * - **`reloadOnSearch = true`** - {boolean=} - If `false`, will not retrigger the same state
2208
+ * just because a search/query parameter has changed (via $location.search() or $location.hash()).
2209
+ * Useful for when you'd like to modify $location.search() without triggering a reload.
2210
+ *
2211
+ * <a id='data'></a>
2212
+ *
2213
+ * - **`data`** - {object=} - Arbitrary data object, useful for custom configuration.
2214
+ *
2215
+ * @example
2216
+ * <pre>
2217
+ * // Some state name examples
2218
+ *
2219
+ * // stateName can be a single top-level name (must be unique).
2220
+ * $stateProvider.state("home", {});
2221
+ *
2222
+ * // Or it can be a nested state name. This state is a child of the
2223
+ * // above "home" state.
2224
+ * $stateProvider.state("home.newest", {});
2225
+ *
2226
+ * // Nest states as deeply as needed.
2227
+ * $stateProvider.state("home.newest.abc.xyz.inception", {});
2228
+ *
2229
+ * // state() returns $stateProvider, so you can chain state declarations.
2230
+ * $stateProvider
2231
+ * .state("home", {})
2232
+ * .state("about", {})
2233
+ * .state("contacts", {});
2234
+ * </pre>
2235
+ *
2236
+ * @param {string} name A unique state name, e.g. "home", "about", "contacts".
2237
+ * To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
2238
+ * @param {object} definition State configuration object.
2239
+ */
2240
+ this.state = state;
2241
+ function state(name, definition) {
2242
+ /*jshint validthis: true */
2243
+ if (isObject(name)) definition = name;
2244
+ else definition.name = name;
2245
+ registerState(definition);
2246
+ return this;
2247
+ }
2248
+
2249
+ /**
2250
+ * @ngdoc object
2251
+ * @name ui.router.state.$state
2252
+ *
2253
+ * @requires $rootScope
2254
+ * @requires $q
2255
+ * @requires ui.router.state.$view
2256
+ * @requires $injector
2257
+ * @requires ui.router.util.$resolve
2258
+ * @requires ui.router.state.$stateParams
2259
+ * @requires ui.router.router.$urlRouter
2260
+ *
2261
+ * @property {object} params A param object, e.g. {sectionId: section.id)}, that
2262
+ * you'd like to test against the current active state.
2263
+ * @property {object} current A reference to the state's config object. However
2264
+ * you passed it in. Useful for accessing custom data.
2265
+ * @property {object} transition Currently pending transition. A promise that'll
2266
+ * resolve or reject.
2267
+ *
2268
+ * @description
2269
+ * `$state` service is responsible for representing states as well as transitioning
2270
+ * between them. It also provides interfaces to ask for current state or even states
2271
+ * you're coming from.
2272
+ */
2273
+ this.$get = $get;
2274
+ $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter'];
2275
+ function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter) {
2276
+
2277
+ var TransitionSuperseded = $q.reject(new Error('transition superseded'));
2278
+ var TransitionPrevented = $q.reject(new Error('transition prevented'));
2279
+ var TransitionAborted = $q.reject(new Error('transition aborted'));
2280
+ var TransitionFailed = $q.reject(new Error('transition failed'));
2281
+
2282
+ // Handles the case where a state which is the target of a transition is not found, and the user
2283
+ // can optionally retry or defer the transition
2284
+ function handleRedirect(redirect, state, params, options) {
2285
+ /**
2286
+ * @ngdoc event
2287
+ * @name ui.router.state.$state#$stateNotFound
2288
+ * @eventOf ui.router.state.$state
2289
+ * @eventType broadcast on root scope
2290
+ * @description
2291
+ * Fired when a requested state **cannot be found** using the provided state name during transition.
2292
+ * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
2293
+ * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler,
2294
+ * you can see its three properties in the example. You can use `event.preventDefault()` to abort the
2295
+ * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value.
2296
+ *
2297
+ * @param {Object} event Event object.
2298
+ * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties.
2299
+ * @param {State} fromState Current state object.
2300
+ * @param {Object} fromParams Current state params.
2301
+ *
2302
+ * @example
2303
+ *
2304
+ * <pre>
2305
+ * // somewhere, assume lazy.state has not been defined
2306
+ * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
2307
+ *
2308
+ * // somewhere else
2309
+ * $scope.$on('$stateNotFound',
2310
+ * function(event, unfoundState, fromState, fromParams){
2311
+ * console.log(unfoundState.to); // "lazy.state"
2312
+ * console.log(unfoundState.toParams); // {a:1, b:2}
2313
+ * console.log(unfoundState.options); // {inherit:false} + default options
2314
+ * })
2315
+ * </pre>
2316
+ */
2317
+ var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params);
2318
+
2319
+ if (evt.defaultPrevented) {
2320
+ $urlRouter.update();
2321
+ return TransitionAborted;
2322
+ }
2323
+
2324
+ if (!evt.retry) {
2325
+ return null;
2326
+ }
2327
+
2328
+ // Allow the handler to return a promise to defer state lookup retry
2329
+ if (options.$retry) {
2330
+ $urlRouter.update();
2331
+ return TransitionFailed;
2332
+ }
2333
+ var retryTransition = $state.transition = $q.when(evt.retry);
2334
+
2335
+ retryTransition.then(function() {
2336
+ if (retryTransition !== $state.transition) return TransitionSuperseded;
2337
+ redirect.options.$retry = true;
2338
+ return $state.transitionTo(redirect.to, redirect.toParams, redirect.options);
2339
+ }, function() {
2340
+ return TransitionAborted;
2341
+ });
2342
+ $urlRouter.update();
2343
+
2344
+ return retryTransition;
2345
+ }
2346
+
2347
+ root.locals = { resolve: null, globals: { $stateParams: {} } };
2348
+
2349
+ $state = {
2350
+ params: {},
2351
+ current: root.self,
2352
+ $current: root,
2353
+ transition: null
2354
+ };
2355
+
2356
+ /**
2357
+ * @ngdoc function
2358
+ * @name ui.router.state.$state#reload
2359
+ * @methodOf ui.router.state.$state
2360
+ *
2361
+ * @description
2362
+ * A method that force reloads the current state. All resolves are re-resolved, events are not re-fired,
2363
+ * and controllers reinstantiated (bug with controllers reinstantiating right now, fixing soon).
2364
+ *
2365
+ * @example
2366
+ * <pre>
2367
+ * var app angular.module('app', ['ui.router']);
2368
+ *
2369
+ * app.controller('ctrl', function ($scope, $state) {
2370
+ * $scope.reload = function(){
2371
+ * $state.reload();
2372
+ * }
2373
+ * });
2374
+ * </pre>
2375
+ *
2376
+ * `reload()` is just an alias for:
2377
+ * <pre>
2378
+ * $state.transitionTo($state.current, $stateParams, {
2379
+ * reload: true, inherit: false, notify: false
2380
+ * });
2381
+ * </pre>
2382
+ */
2383
+ $state.reload = function reload() {
2384
+ $state.transitionTo($state.current, $stateParams, { reload: true, inherit: false, notify: false });
2385
+ };
2386
+
2387
+ /**
2388
+ * @ngdoc function
2389
+ * @name ui.router.state.$state#go
2390
+ * @methodOf ui.router.state.$state
2391
+ *
2392
+ * @description
2393
+ * Convenience method for transitioning to a new state. `$state.go` calls
2394
+ * `$state.transitionTo` internally but automatically sets options to
2395
+ * `{ location: true, inherit: true, relative: $state.$current, notify: true }`.
2396
+ * This allows you to easily use an absolute or relative to path and specify
2397
+ * only the parameters you'd like to update (while letting unspecified parameters
2398
+ * inherit from the currently active ancestor states).
2399
+ *
2400
+ * @example
2401
+ * <pre>
2402
+ * var app = angular.module('app', ['ui.router']);
2403
+ *
2404
+ * app.controller('ctrl', function ($scope, $state) {
2405
+ * $scope.changeState = function () {
2406
+ * $state.go('contact.detail');
2407
+ * };
2408
+ * });
2409
+ * </pre>
2410
+ * <img src='../ngdoc_assets/StateGoExamples.png'/>
2411
+ *
2412
+ * @param {string} to Absolute state name or relative state path. Some examples:
2413
+ *
2414
+ * - `$state.go('contact.detail')` - will go to the `contact.detail` state
2415
+ * - `$state.go('^')` - will go to a parent state
2416
+ * - `$state.go('^.sibling')` - will go to a sibling state
2417
+ * - `$state.go('.child.grandchild')` - will go to grandchild state
2418
+ *
2419
+ * @param {object=} params A map of the parameters that will be sent to the state,
2420
+ * will populate $stateParams. Any parameters that are not specified will be inherited from currently
2421
+ * defined parameters. This allows, for example, going to a sibling state that shares parameters
2422
+ * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e.
2423
+ * transitioning to a sibling will get you the parameters for all parents, transitioning to a child
2424
+ * will get you all current parameters, etc.
2425
+ * @param {object=} options Options object. The options are:
2426
+ *
2427
+ * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
2428
+ * will not. If string, must be `"replace"`, which will update url and also replace last history record.
2429
+ * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
2430
+ * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
2431
+ * defines which state to be relative from.
2432
+ * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
2433
+ * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params
2434
+ * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
2435
+ * use this when you want to force a reload when *everything* is the same, including search params.
2436
+ *
2437
+ * @returns {promise} A promise representing the state of the new transition.
2438
+ *
2439
+ * Possible success values:
2440
+ *
2441
+ * - $state.current
2442
+ *
2443
+ * <br/>Possible rejection values:
2444
+ *
2445
+ * - 'transition superseded' - when a newer transition has been started after this one
2446
+ * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener
2447
+ * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or
2448
+ * when a `$stateNotFound` `event.retry` promise errors.
2449
+ * - 'transition failed' - when a state has been unsuccessfully found after 2 tries.
2450
+ * - *resolve error* - when an error has occurred with a `resolve`
2451
+ *
2452
+ */
2453
+ $state.go = function go(to, params, options) {
2454
+ return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options));
2455
+ };
2456
+
2457
+ /**
2458
+ * @ngdoc function
2459
+ * @name ui.router.state.$state#transitionTo
2460
+ * @methodOf ui.router.state.$state
2461
+ *
2462
+ * @description
2463
+ * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go}
2464
+ * uses `transitionTo` internally. `$state.go` is recommended in most situations.
2465
+ *
2466
+ * @example
2467
+ * <pre>
2468
+ * var app = angular.module('app', ['ui.router']);
2469
+ *
2470
+ * app.controller('ctrl', function ($scope, $state) {
2471
+ * $scope.changeState = function () {
2472
+ * $state.transitionTo('contact.detail');
2473
+ * };
2474
+ * });
2475
+ * </pre>
2476
+ *
2477
+ * @param {string} to State name.
2478
+ * @param {object=} toParams A map of the parameters that will be sent to the state,
2479
+ * will populate $stateParams.
2480
+ * @param {object=} options Options object. The options are:
2481
+ *
2482
+ * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
2483
+ * will not. If string, must be `"replace"`, which will update url and also replace last history record.
2484
+ * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url.
2485
+ * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'),
2486
+ * defines which state to be relative from.
2487
+ * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
2488
+ * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params
2489
+ * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
2490
+ * use this when you want to force a reload when *everything* is the same, including search params.
2491
+ *
2492
+ * @returns {promise} A promise representing the state of the new transition. See
2493
+ * {@link ui.router.state.$state#methods_go $state.go}.
2494
+ */
2495
+ $state.transitionTo = function transitionTo(to, toParams, options) {
2496
+ toParams = toParams || {};
2497
+ options = extend({
2498
+ location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false
2499
+ }, options || {});
2500
+
2501
+ var from = $state.$current, fromParams = $state.params, fromPath = from.path;
2502
+ var evt, toState = findState(to, options.relative);
2503
+
2504
+ if (!isDefined(toState)) {
2505
+ var redirect = { to: to, toParams: toParams, options: options };
2506
+ var redirectResult = handleRedirect(redirect, from.self, fromParams, options);
2507
+
2508
+ if (redirectResult) {
2509
+ return redirectResult;
2510
+ }
2511
+
2512
+ // Always retry once if the $stateNotFound was not prevented
2513
+ // (handles either redirect changed or state lazy-definition)
2514
+ to = redirect.to;
2515
+ toParams = redirect.toParams;
2516
+ options = redirect.options;
2517
+ toState = findState(to, options.relative);
2518
+
2519
+ if (!isDefined(toState)) {
2520
+ if (!options.relative) throw new Error("No such state '" + to + "'");
2521
+ throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'");
2522
+ }
2523
+ }
2524
+ if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'");
2525
+ if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState);
2526
+ to = toState;
2527
+
2528
+ var toPath = to.path;
2529
+
2530
+ // Starting from the root of the path, keep all levels that haven't changed
2531
+ var keep = 0, state = toPath[keep], locals = root.locals, toLocals = [];
2532
+
2533
+ if (!options.reload) {
2534
+ while (state && state === fromPath[keep] && equalForKeys(toParams, fromParams, state.ownParams)) {
2535
+ locals = toLocals[keep] = state.locals;
2536
+ keep++;
2537
+ state = toPath[keep];
2538
+ }
2539
+ }
2540
+
2541
+ // If we're going to the same state and all locals are kept, we've got nothing to do.
2542
+ // But clear 'transition', as we still want to cancel any other pending transitions.
2543
+ // TODO: We may not want to bump 'transition' if we're called from a location change
2544
+ // that we've initiated ourselves, because we might accidentally abort a legitimate
2545
+ // transition initiated from code?
2546
+ if (shouldTriggerReload(to, from, locals, options)) {
2547
+ if (to.self.reloadOnSearch !== false) $urlRouter.update();
2548
+ $state.transition = null;
2549
+ return $q.when($state.current);
2550
+ }
2551
+
2552
+ // Filter parameters before we pass them to event handlers etc.
2553
+ toParams = filterByKeys(objectKeys(to.params), toParams || {});
2554
+
2555
+ // Broadcast start event and cancel the transition if requested
2556
+ if (options.notify) {
2557
+ /**
2558
+ * @ngdoc event
2559
+ * @name ui.router.state.$state#$stateChangeStart
2560
+ * @eventOf ui.router.state.$state
2561
+ * @eventType broadcast on root scope
2562
+ * @description
2563
+ * Fired when the state transition **begins**. You can use `event.preventDefault()`
2564
+ * to prevent the transition from happening and then the transition promise will be
2565
+ * rejected with a `'transition prevented'` value.
2566
+ *
2567
+ * @param {Object} event Event object.
2568
+ * @param {State} toState The state being transitioned to.
2569
+ * @param {Object} toParams The params supplied to the `toState`.
2570
+ * @param {State} fromState The current state, pre-transition.
2571
+ * @param {Object} fromParams The params supplied to the `fromState`.
2572
+ *
2573
+ * @example
2574
+ *
2575
+ * <pre>
2576
+ * $rootScope.$on('$stateChangeStart',
2577
+ * function(event, toState, toParams, fromState, fromParams){
2578
+ * event.preventDefault();
2579
+ * // transitionTo() promise will be rejected with
2580
+ * // a 'transition prevented' error
2581
+ * })
2582
+ * </pre>
2583
+ */
2584
+ if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams).defaultPrevented) {
2585
+ $urlRouter.update();
2586
+ return TransitionPrevented;
2587
+ }
2588
+ }
2589
+
2590
+ // Resolve locals for the remaining states, but don't update any global state just
2591
+ // yet -- if anything fails to resolve the current state needs to remain untouched.
2592
+ // We also set up an inheritance chain for the locals here. This allows the view directive
2593
+ // to quickly look up the correct definition for each view in the current state. Even
2594
+ // though we create the locals object itself outside resolveState(), it is initially
2595
+ // empty and gets filled asynchronously. We need to keep track of the promise for the
2596
+ // (fully resolved) current locals, and pass this down the chain.
2597
+ var resolved = $q.when(locals);
2598
+
2599
+ for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
2600
+ locals = toLocals[l] = inherit(locals);
2601
+ resolved = resolveState(state, toParams, state === to, resolved, locals);
2602
+ }
2603
+
2604
+ // Once everything is resolved, we are ready to perform the actual transition
2605
+ // and return a promise for the new state. We also keep track of what the
2606
+ // current promise is, so that we can detect overlapping transitions and
2607
+ // keep only the outcome of the last transition.
2608
+ var transition = $state.transition = resolved.then(function () {
2609
+ var l, entering, exiting;
2610
+
2611
+ if ($state.transition !== transition) return TransitionSuperseded;
2612
+
2613
+ // Exit 'from' states not kept
2614
+ for (l = fromPath.length - 1; l >= keep; l--) {
2615
+ exiting = fromPath[l];
2616
+ if (exiting.self.onExit) {
2617
+ $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals);
2618
+ }
2619
+ exiting.locals = null;
2620
+ }
2621
+
2622
+ // Enter 'to' states not kept
2623
+ for (l = keep; l < toPath.length; l++) {
2624
+ entering = toPath[l];
2625
+ entering.locals = toLocals[l];
2626
+ if (entering.self.onEnter) {
2627
+ $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals);
2628
+ }
2629
+ }
2630
+
2631
+ // Run it again, to catch any transitions in callbacks
2632
+ if ($state.transition !== transition) return TransitionSuperseded;
2633
+
2634
+ // Update globals in $state
2635
+ $state.$current = to;
2636
+ $state.current = to.self;
2637
+ $state.params = toParams;
2638
+ copy($state.params, $stateParams);
2639
+ $state.transition = null;
2640
+
2641
+ if (options.location && to.navigable) {
2642
+ $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, {
2643
+ replace: options.location === 'replace'
2644
+ });
2645
+ }
2646
+
2647
+ if (options.notify) {
2648
+ /**
2649
+ * @ngdoc event
2650
+ * @name ui.router.state.$state#$stateChangeSuccess
2651
+ * @eventOf ui.router.state.$state
2652
+ * @eventType broadcast on root scope
2653
+ * @description
2654
+ * Fired once the state transition is **complete**.
2655
+ *
2656
+ * @param {Object} event Event object.
2657
+ * @param {State} toState The state being transitioned to.
2658
+ * @param {Object} toParams The params supplied to the `toState`.
2659
+ * @param {State} fromState The current state, pre-transition.
2660
+ * @param {Object} fromParams The params supplied to the `fromState`.
2661
+ */
2662
+ $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
2663
+ }
2664
+ $urlRouter.update(true);
2665
+
2666
+ return $state.current;
2667
+ }, function (error) {
2668
+ if ($state.transition !== transition) return TransitionSuperseded;
2669
+
2670
+ $state.transition = null;
2671
+ /**
2672
+ * @ngdoc event
2673
+ * @name ui.router.state.$state#$stateChangeError
2674
+ * @eventOf ui.router.state.$state
2675
+ * @eventType broadcast on root scope
2676
+ * @description
2677
+ * Fired when an **error occurs** during transition. It's important to note that if you
2678
+ * have any errors in your resolve functions (javascript errors, non-existent services, etc)
2679
+ * they will not throw traditionally. You must listen for this $stateChangeError event to
2680
+ * catch **ALL** errors.
2681
+ *
2682
+ * @param {Object} event Event object.
2683
+ * @param {State} toState The state being transitioned to.
2684
+ * @param {Object} toParams The params supplied to the `toState`.
2685
+ * @param {State} fromState The current state, pre-transition.
2686
+ * @param {Object} fromParams The params supplied to the `fromState`.
2687
+ * @param {Error} error The resolve error object.
2688
+ */
2689
+ evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);
2690
+
2691
+ if (!evt.defaultPrevented) {
2692
+ $urlRouter.update();
2693
+ }
2694
+
2695
+ return $q.reject(error);
2696
+ });
2697
+
2698
+ return transition;
2699
+ };
2700
+
2701
+ /**
2702
+ * @ngdoc function
2703
+ * @name ui.router.state.$state#is
2704
+ * @methodOf ui.router.state.$state
2705
+ *
2706
+ * @description
2707
+ * Similar to {@link ui.router.state.$state#methods_includes $state.includes},
2708
+ * but only checks for the full state name. If params is supplied then it will be
2709
+ * tested for strict equality against the current active params object, so all params
2710
+ * must match with none missing and no extras.
2711
+ *
2712
+ * @example
2713
+ * <pre>
2714
+ * $state.$current.name = 'contacts.details.item';
2715
+ *
2716
+ * // absolute name
2717
+ * $state.is('contact.details.item'); // returns true
2718
+ * $state.is(contactDetailItemStateObject); // returns true
2719
+ *
2720
+ * // relative name (. and ^), typically from a template
2721
+ * // E.g. from the 'contacts.details' template
2722
+ * <div ng-class="{highlighted: $state.is('.item')}">Item</div>
2723
+ * </pre>
2724
+ *
2725
+ * @param {string|object} stateName The state name (absolute or relative) or state object you'd like to check.
2726
+ * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like
2727
+ * to test against the current active state.
2728
+ * @returns {boolean} Returns true if it is the state.
2729
+ */
2730
+ $state.is = function is(stateOrName, params) {
2731
+ var state = findState(stateOrName);
2732
+
2733
+ if (!isDefined(state)) {
2734
+ return undefined;
2735
+ }
2736
+
2737
+ if ($state.$current !== state) {
2738
+ return false;
2739
+ }
2740
+
2741
+ return isDefined(params) && params !== null ? angular.equals($stateParams, params) : true;
2742
+ };
2743
+
2744
+ /**
2745
+ * @ngdoc function
2746
+ * @name ui.router.state.$state#includes
2747
+ * @methodOf ui.router.state.$state
2748
+ *
2749
+ * @description
2750
+ * A method to determine if the current active state is equal to or is the child of the
2751
+ * state stateName. If any params are passed then they will be tested for a match as well.
2752
+ * Not all the parameters need to be passed, just the ones you'd like to test for equality.
2753
+ *
2754
+ * @example
2755
+ * Partial and relative names
2756
+ * <pre>
2757
+ * $state.$current.name = 'contacts.details.item';
2758
+ *
2759
+ * // Using partial names
2760
+ * $state.includes("contacts"); // returns true
2761
+ * $state.includes("contacts.details"); // returns true
2762
+ * $state.includes("contacts.details.item"); // returns true
2763
+ * $state.includes("contacts.list"); // returns false
2764
+ * $state.includes("about"); // returns false
2765
+ *
2766
+ * // Using relative names (. and ^), typically from a template
2767
+ * // E.g. from the 'contacts.details' template
2768
+ * <div ng-class="{highlighted: $state.includes('.item')}">Item</div>
2769
+ * </pre>
2770
+ *
2771
+ * Basic globbing patterns
2772
+ * <pre>
2773
+ * $state.$current.name = 'contacts.details.item.url';
2774
+ *
2775
+ * $state.includes("*.details.*.*"); // returns true
2776
+ * $state.includes("*.details.**"); // returns true
2777
+ * $state.includes("**.item.**"); // returns true
2778
+ * $state.includes("*.details.item.url"); // returns true
2779
+ * $state.includes("*.details.*.url"); // returns true
2780
+ * $state.includes("*.details.*"); // returns false
2781
+ * $state.includes("item.**"); // returns false
2782
+ * </pre>
2783
+ *
2784
+ * @param {string} stateOrName A partial name, relative name, or glob pattern
2785
+ * to be searched for within the current state name.
2786
+ * @param {object} params A param object, e.g. `{sectionId: section.id}`,
2787
+ * that you'd like to test against the current active state.
2788
+ * @returns {boolean} Returns true if it does include the state
2789
+ */
2790
+ $state.includes = function includes(stateOrName, params) {
2791
+ if (isString(stateOrName) && isGlob(stateOrName)) {
2792
+ if (!doesStateMatchGlob(stateOrName)) {
2793
+ return false;
2794
+ }
2795
+ stateOrName = $state.$current.name;
2796
+ }
2797
+ var state = findState(stateOrName);
2798
+
2799
+ if (!isDefined(state)) {
2800
+ return undefined;
2801
+ }
2802
+ if (!isDefined($state.$current.includes[state.name])) {
2803
+ return false;
2804
+ }
2805
+ return equalForKeys(params, $stateParams);
2806
+ };
2807
+
2808
+
2809
+ /**
2810
+ * @ngdoc function
2811
+ * @name ui.router.state.$state#href
2812
+ * @methodOf ui.router.state.$state
2813
+ *
2814
+ * @description
2815
+ * A url generation method that returns the compiled url for the given state populated with the given params.
2816
+ *
2817
+ * @example
2818
+ * <pre>
2819
+ * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
2820
+ * </pre>
2821
+ *
2822
+ * @param {string|object} stateOrName The state name or state object you'd like to generate a url from.
2823
+ * @param {object=} params An object of parameter values to fill the state's required parameters.
2824
+ * @param {object=} options Options object. The options are:
2825
+ *
2826
+ * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the
2827
+ * first parameter, then the constructed href url will be built from the first navigable ancestor (aka
2828
+ * ancestor with a valid url).
2829
+ * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
2830
+ * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
2831
+ * defines which state to be relative from.
2832
+ * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
2833
+ *
2834
+ * @returns {string} compiled state url
2835
+ */
2836
+ $state.href = function href(stateOrName, params, options) {
2837
+ options = extend({
2838
+ lossy: true,
2839
+ inherit: true,
2840
+ absolute: false,
2841
+ relative: $state.$current
2842
+ }, options || {});
2843
+
2844
+ var state = findState(stateOrName, options.relative);
2845
+
2846
+ if (!isDefined(state)) return null;
2847
+ if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state);
2848
+
2849
+ var nav = (state && options.lossy) ? state.navigable : state;
2850
+
2851
+ if (!nav || !nav.url) {
2852
+ return null;
2853
+ }
2854
+ return $urlRouter.href(nav.url, filterByKeys(objectKeys(state.params), params || {}), {
2855
+ absolute: options.absolute
2856
+ });
2857
+ };
2858
+
2859
+ /**
2860
+ * @ngdoc function
2861
+ * @name ui.router.state.$state#get
2862
+ * @methodOf ui.router.state.$state
2863
+ *
2864
+ * @description
2865
+ * Returns the state configuration object for any specific state or all states.
2866
+ *
2867
+ * @param {string|Sbject=} stateOrName (absolute or relative) If provided, will only get the config for
2868
+ * the requested state. If not provided, returns an array of ALL state configs.
2869
+ * @returns {Object|Array} State configuration object or array of all objects.
2870
+ */
2871
+ $state.get = function (stateOrName, context) {
2872
+ if (arguments.length === 0) return objectKeys(states).map(function(name) { return states[name].self; });
2873
+ var state = findState(stateOrName, context);
2874
+ return (state && state.self) ? state.self : null;
2875
+ };
2876
+
2877
+ function resolveState(state, params, paramsAreFiltered, inherited, dst) {
2878
+ // Make a restricted $stateParams with only the parameters that apply to this state if
2879
+ // necessary. In addition to being available to the controller and onEnter/onExit callbacks,
2880
+ // we also need $stateParams to be available for any $injector calls we make during the
2881
+ // dependency resolution process.
2882
+ var $stateParams = (paramsAreFiltered) ? params : filterByKeys(objectKeys(state.params), params);
2883
+ var locals = { $stateParams: $stateParams };
2884
+
2885
+ // Resolve 'global' dependencies for the state, i.e. those not specific to a view.
2886
+ // We're also including $stateParams in this; that way the parameters are restricted
2887
+ // to the set that should be visible to the state, and are independent of when we update
2888
+ // the global $state and $stateParams values.
2889
+ dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
2890
+ var promises = [dst.resolve.then(function (globals) {
2891
+ dst.globals = globals;
2892
+ })];
2893
+ if (inherited) promises.push(inherited);
2894
+
2895
+ // Resolve template and dependencies for all views.
2896
+ forEach(state.views, function (view, name) {
2897
+ var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {});
2898
+ injectables.$template = [ function () {
2899
+ return $view.load(name, { view: view, locals: locals, params: $stateParams }) || '';
2900
+ }];
2901
+
2902
+ promises.push($resolve.resolve(injectables, locals, dst.resolve, state).then(function (result) {
2903
+ // References to the controller (only instantiated at link time)
2904
+ if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) {
2905
+ var injectLocals = angular.extend({}, injectables, locals);
2906
+ result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals);
2907
+ } else {
2908
+ result.$$controller = view.controller;
2909
+ }
2910
+ // Provide access to the state itself for internal use
2911
+ result.$$state = state;
2912
+ result.$$controllerAs = view.controllerAs;
2913
+ dst[name] = result;
2914
+ }));
2915
+ });
2916
+
2917
+ // Wait for all the promises and then return the activation object
2918
+ return $q.all(promises).then(function (values) {
2919
+ return dst;
2920
+ });
2921
+ }
2922
+
2923
+ return $state;
2924
+ }
2925
+
2926
+ function shouldTriggerReload(to, from, locals, options) {
2927
+ if (to === from && ((locals === from.locals && !options.reload) || (to.self.reloadOnSearch === false))) {
2928
+ return true;
2929
+ }
2930
+ }
2931
+ }
2932
+
2933
+ angular.module('ui.router.state')
2934
+ .value('$stateParams', {})
2935
+ .provider('$state', $StateProvider);
2936
+
2937
+
2938
+ $ViewProvider.$inject = [];
2939
+ function $ViewProvider() {
2940
+
2941
+ this.$get = $get;
2942
+ /**
2943
+ * @ngdoc object
2944
+ * @name ui.router.state.$view
2945
+ *
2946
+ * @requires ui.router.util.$templateFactory
2947
+ * @requires $rootScope
2948
+ *
2949
+ * @description
2950
+ *
2951
+ */
2952
+ $get.$inject = ['$rootScope', '$templateFactory'];
2953
+ function $get( $rootScope, $templateFactory) {
2954
+ return {
2955
+ // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... })
2956
+ /**
2957
+ * @ngdoc function
2958
+ * @name ui.router.state.$view#load
2959
+ * @methodOf ui.router.state.$view
2960
+ *
2961
+ * @description
2962
+ *
2963
+ * @param {string} name name
2964
+ * @param {object} options option object.
2965
+ */
2966
+ load: function load(name, options) {
2967
+ var result, defaults = {
2968
+ template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {}
2969
+ };
2970
+ options = extend(defaults, options);
2971
+
2972
+ if (options.view) {
2973
+ result = $templateFactory.fromConfig(options.view, options.params, options.locals);
2974
+ }
2975
+ if (result && options.notify) {
2976
+ /**
2977
+ * @ngdoc event
2978
+ * @name ui.router.state.$state#$viewContentLoading
2979
+ * @eventOf ui.router.state.$view
2980
+ * @eventType broadcast on root scope
2981
+ * @description
2982
+ *
2983
+ * Fired once the view **begins loading**, *before* the DOM is rendered.
2984
+ *
2985
+ * @param {Object} event Event object.
2986
+ * @param {Object} viewConfig The view config properties (template, controller, etc).
2987
+ *
2988
+ * @example
2989
+ *
2990
+ * <pre>
2991
+ * $scope.$on('$viewContentLoading',
2992
+ * function(event, viewConfig){
2993
+ * // Access to all the view config properties.
2994
+ * // and one special property 'targetView'
2995
+ * // viewConfig.targetView
2996
+ * });
2997
+ * </pre>
2998
+ */
2999
+ $rootScope.$broadcast('$viewContentLoading', options);
3000
+ }
3001
+ return result;
3002
+ }
3003
+ };
3004
+ }
3005
+ }
3006
+
3007
+ angular.module('ui.router.state').provider('$view', $ViewProvider);
3008
+
3009
+ /**
3010
+ * @ngdoc object
3011
+ * @name ui.router.state.$uiViewScrollProvider
3012
+ *
3013
+ * @description
3014
+ * Provider that returns the {@link ui.router.state.$uiViewScroll} service function.
3015
+ */
3016
+ function $ViewScrollProvider() {
3017
+
3018
+ var useAnchorScroll = false;
3019
+
3020
+ /**
3021
+ * @ngdoc function
3022
+ * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll
3023
+ * @methodOf ui.router.state.$uiViewScrollProvider
3024
+ *
3025
+ * @description
3026
+ * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for
3027
+ * scrolling based on the url anchor.
3028
+ */
3029
+ this.useAnchorScroll = function () {
3030
+ useAnchorScroll = true;
3031
+ };
3032
+
3033
+ /**
3034
+ * @ngdoc object
3035
+ * @name ui.router.state.$uiViewScroll
3036
+ *
3037
+ * @requires $anchorScroll
3038
+ * @requires $timeout
3039
+ *
3040
+ * @description
3041
+ * When called with a jqLite element, it scrolls the element into view (after a
3042
+ * `$timeout` so the DOM has time to refresh).
3043
+ *
3044
+ * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor,
3045
+ * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}.
3046
+ */
3047
+ this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) {
3048
+ if (useAnchorScroll) {
3049
+ return $anchorScroll;
3050
+ }
3051
+
3052
+ return function ($element) {
3053
+ $timeout(function () {
3054
+ $element[0].scrollIntoView();
3055
+ }, 0, false);
3056
+ };
3057
+ }];
3058
+ }
3059
+
3060
+ angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider);
3061
+
3062
+ /**
3063
+ * @ngdoc directive
3064
+ * @name ui.router.state.directive:ui-view
3065
+ *
3066
+ * @requires ui.router.state.$state
3067
+ * @requires $compile
3068
+ * @requires $controller
3069
+ * @requires $injector
3070
+ * @requires ui.router.state.$uiViewScroll
3071
+ * @requires $document
3072
+ *
3073
+ * @restrict ECA
3074
+ *
3075
+ * @description
3076
+ * The ui-view directive tells $state where to place your templates.
3077
+ *
3078
+ * @param {string=} ui-view A view name. The name should be unique amongst the other views in the
3079
+ * same state. You can have views of the same name that live in different states.
3080
+ *
3081
+ * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window
3082
+ * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll
3083
+ * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you
3084
+ * scroll ui-view elements into view when they are populated during a state activation.
3085
+ *
3086
+ * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll)
3087
+ * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.*
3088
+ *
3089
+ * @param {string=} onload Expression to evaluate whenever the view updates.
3090
+ *
3091
+ * @example
3092
+ * A view can be unnamed or named.
3093
+ * <pre>
3094
+ * <!-- Unnamed -->
3095
+ * <div ui-view></div>
3096
+ *
3097
+ * <!-- Named -->
3098
+ * <div ui-view="viewName"></div>
3099
+ * </pre>
3100
+ *
3101
+ * You can only have one unnamed view within any template (or root html). If you are only using a
3102
+ * single view and it is unnamed then you can populate it like so:
3103
+ * <pre>
3104
+ * <div ui-view></div>
3105
+ * $stateProvider.state("home", {
3106
+ * template: "<h1>HELLO!</h1>"
3107
+ * })
3108
+ * </pre>
3109
+ *
3110
+ * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`}
3111
+ * config property, by name, in this case an empty name:
3112
+ * <pre>
3113
+ * $stateProvider.state("home", {
3114
+ * views: {
3115
+ * "": {
3116
+ * template: "<h1>HELLO!</h1>"
3117
+ * }
3118
+ * }
3119
+ * })
3120
+ * </pre>
3121
+ *
3122
+ * But typically you'll only use the views property if you name your view or have more than one view
3123
+ * in the same template. There's not really a compelling reason to name a view if its the only one,
3124
+ * but you could if you wanted, like so:
3125
+ * <pre>
3126
+ * <div ui-view="main"></div>
3127
+ * </pre>
3128
+ * <pre>
3129
+ * $stateProvider.state("home", {
3130
+ * views: {
3131
+ * "main": {
3132
+ * template: "<h1>HELLO!</h1>"
3133
+ * }
3134
+ * }
3135
+ * })
3136
+ * </pre>
3137
+ *
3138
+ * Really though, you'll use views to set up multiple views:
3139
+ * <pre>
3140
+ * <div ui-view></div>
3141
+ * <div ui-view="chart"></div>
3142
+ * <div ui-view="data"></div>
3143
+ * </pre>
3144
+ *
3145
+ * <pre>
3146
+ * $stateProvider.state("home", {
3147
+ * views: {
3148
+ * "": {
3149
+ * template: "<h1>HELLO!</h1>"
3150
+ * },
3151
+ * "chart": {
3152
+ * template: "<chart_thing/>"
3153
+ * },
3154
+ * "data": {
3155
+ * template: "<data_thing/>"
3156
+ * }
3157
+ * }
3158
+ * })
3159
+ * </pre>
3160
+ *
3161
+ * Examples for `autoscroll`:
3162
+ *
3163
+ * <pre>
3164
+ * <!-- If autoscroll present with no expression,
3165
+ * then scroll ui-view into view -->
3166
+ * <ui-view autoscroll/>
3167
+ *
3168
+ * <!-- If autoscroll present with valid expression,
3169
+ * then scroll ui-view into view if expression evaluates to true -->
3170
+ * <ui-view autoscroll='true'/>
3171
+ * <ui-view autoscroll='false'/>
3172
+ * <ui-view autoscroll='scopeVariable'/>
3173
+ * </pre>
3174
+ */
3175
+ $ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll'];
3176
+ function $ViewDirective( $state, $injector, $uiViewScroll) {
3177
+
3178
+ function getService() {
3179
+ return ($injector.has) ? function(service) {
3180
+ return $injector.has(service) ? $injector.get(service) : null;
3181
+ } : function(service) {
3182
+ try {
3183
+ return $injector.get(service);
3184
+ } catch (e) {
3185
+ return null;
3186
+ }
3187
+ };
3188
+ }
3189
+
3190
+ var service = getService(),
3191
+ $animator = service('$animator'),
3192
+ $animate = service('$animate');
3193
+
3194
+ // Returns a set of DOM manipulation functions based on which Angular version
3195
+ // it should use
3196
+ function getRenderer(attrs, scope) {
3197
+ var statics = function() {
3198
+ return {
3199
+ enter: function (element, target, cb) { target.after(element); cb(); },
3200
+ leave: function (element, cb) { element.remove(); cb(); }
3201
+ };
3202
+ };
3203
+
3204
+ if ($animate) {
3205
+ return {
3206
+ enter: function(element, target, cb) { $animate.enter(element, null, target, cb); },
3207
+ leave: function(element, cb) { $animate.leave(element, cb); }
3208
+ };
3209
+ }
3210
+
3211
+ if ($animator) {
3212
+ var animate = $animator && $animator(scope, attrs);
3213
+
3214
+ return {
3215
+ enter: function(element, target, cb) {animate.enter(element, null, target); cb(); },
3216
+ leave: function(element, cb) { animate.leave(element); cb(); }
3217
+ };
3218
+ }
3219
+
3220
+ return statics();
3221
+ }
3222
+
3223
+ var directive = {
3224
+ restrict: 'ECA',
3225
+ terminal: true,
3226
+ priority: 400,
3227
+ transclude: 'element',
3228
+ compile: function (tElement, tAttrs, $transclude) {
3229
+ return function (scope, $element, attrs) {
3230
+ var previousEl, currentEl, currentScope, latestLocals,
3231
+ onloadExp = attrs.onload || '',
3232
+ autoScrollExp = attrs.autoscroll,
3233
+ renderer = getRenderer(attrs, scope);
3234
+
3235
+ scope.$on('$stateChangeSuccess', function() {
3236
+ updateView(false);
3237
+ });
3238
+ scope.$on('$viewContentLoading', function() {
3239
+ updateView(false);
3240
+ });
3241
+
3242
+ updateView(true);
3243
+
3244
+ function cleanupLastView() {
3245
+ if (previousEl) {
3246
+ previousEl.remove();
3247
+ previousEl = null;
3248
+ }
3249
+
3250
+ if (currentScope) {
3251
+ currentScope.$destroy();
3252
+ currentScope = null;
3253
+ }
3254
+
3255
+ if (currentEl) {
3256
+ renderer.leave(currentEl, function() {
3257
+ previousEl = null;
3258
+ });
3259
+
3260
+ previousEl = currentEl;
3261
+ currentEl = null;
3262
+ }
3263
+ }
3264
+
3265
+ function updateView(firstTime) {
3266
+ var newScope,
3267
+ name = getUiViewName(attrs, $element.inheritedData('$uiView')),
3268
+ previousLocals = name && $state.$current && $state.$current.locals[name];
3269
+
3270
+ if (!firstTime && previousLocals === latestLocals) return; // nothing to do
3271
+ newScope = scope.$new();
3272
+ latestLocals = $state.$current.locals[name];
3273
+
3274
+ var clone = $transclude(newScope, function(clone) {
3275
+ renderer.enter(clone, $element, function onUiViewEnter() {
3276
+ if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) {
3277
+ $uiViewScroll(clone);
3278
+ }
3279
+ });
3280
+ cleanupLastView();
3281
+ });
3282
+
3283
+ currentEl = clone;
3284
+ currentScope = newScope;
3285
+ /**
3286
+ * @ngdoc event
3287
+ * @name ui.router.state.directive:ui-view#$viewContentLoaded
3288
+ * @eventOf ui.router.state.directive:ui-view
3289
+ * @eventType emits on ui-view directive scope
3290
+ * @description *
3291
+ * Fired once the view is **loaded**, *after* the DOM is rendered.
3292
+ *
3293
+ * @param {Object} event Event object.
3294
+ */
3295
+ currentScope.$emit('$viewContentLoaded');
3296
+ currentScope.$eval(onloadExp);
3297
+ }
3298
+ };
3299
+ }
3300
+ };
3301
+
3302
+ return directive;
3303
+ }
3304
+
3305
+ $ViewDirectiveFill.$inject = ['$compile', '$controller', '$state'];
3306
+ function $ViewDirectiveFill ($compile, $controller, $state) {
3307
+ return {
3308
+ restrict: 'ECA',
3309
+ priority: -400,
3310
+ compile: function (tElement) {
3311
+ var initial = tElement.html();
3312
+ return function (scope, $element, attrs) {
3313
+ var current = $state.$current,
3314
+ name = getUiViewName(attrs, $element.inheritedData('$uiView')),
3315
+ locals = current && current.locals[name];
3316
+
3317
+ if (! locals) {
3318
+ return;
3319
+ }
3320
+
3321
+ $element.data('$uiView', { name: name, state: locals.$$state });
3322
+ $element.html(locals.$template ? locals.$template : initial);
3323
+
3324
+ var link = $compile($element.contents());
3325
+
3326
+ if (locals.$$controller) {
3327
+ locals.$scope = scope;
3328
+ var controller = $controller(locals.$$controller, locals);
3329
+ if (locals.$$controllerAs) {
3330
+ scope[locals.$$controllerAs] = controller;
3331
+ }
3332
+ $element.data('$ngControllerController', controller);
3333
+ $element.children().data('$ngControllerController', controller);
3334
+ }
3335
+
3336
+ link(scope);
3337
+ };
3338
+ }
3339
+ };
3340
+ }
3341
+
3342
+ /**
3343
+ * Shared ui-view code for both directives:
3344
+ * Given attributes and inherited $uiView data, return the view's name
3345
+ */
3346
+ function getUiViewName(attrs, inherited) {
3347
+ var name = attrs.uiView || attrs.name || '';
3348
+ return name.indexOf('@') >= 0 ? name : (name + '@' + (inherited ? inherited.state.name : ''));
3349
+ }
3350
+
3351
+ angular.module('ui.router.state').directive('uiView', $ViewDirective);
3352
+ angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill);
3353
+
3354
+ function parseStateRef(ref, current) {
3355
+ var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
3356
+ if (preparsed) ref = current + '(' + preparsed[1] + ')';
3357
+ parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
3358
+ if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
3359
+ return { state: parsed[1], paramExpr: parsed[3] || null };
3360
+ }
3361
+
3362
+ function stateContext(el) {
3363
+ var stateData = el.parent().inheritedData('$uiView');
3364
+
3365
+ if (stateData && stateData.state && stateData.state.name) {
3366
+ return stateData.state;
3367
+ }
3368
+ }
3369
+
3370
+ /**
3371
+ * @ngdoc directive
3372
+ * @name ui.router.state.directive:ui-sref
3373
+ *
3374
+ * @requires ui.router.state.$state
3375
+ * @requires $timeout
3376
+ *
3377
+ * @restrict A
3378
+ *
3379
+ * @description
3380
+ * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
3381
+ * URL, the directive will automatically generate & update the `href` attribute via
3382
+ * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
3383
+ * the link will trigger a state transition with optional parameters.
3384
+ *
3385
+ * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
3386
+ * handled natively by the browser.
3387
+ *
3388
+ * You can also use relative state paths within ui-sref, just like the relative
3389
+ * paths passed to `$state.go()`. You just need to be aware that the path is relative
3390
+ * to the state that the link lives in, in other words the state that loaded the
3391
+ * template containing the link.
3392
+ *
3393
+ * You can specify options to pass to {@link ui.router.state.$state#go $state.go()}
3394
+ * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`,
3395
+ * and `reload`.
3396
+ *
3397
+ * @example
3398
+ * Here's an example of how you'd use ui-sref and how it would compile. If you have the
3399
+ * following template:
3400
+ * <pre>
3401
+ * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
3402
+ *
3403
+ * <ul>
3404
+ * <li ng-repeat="contact in contacts">
3405
+ * <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
3406
+ * </li>
3407
+ * </ul>
3408
+ * </pre>
3409
+ *
3410
+ * Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
3411
+ * <pre>
3412
+ * <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>
3413
+ *
3414
+ * <ul>
3415
+ * <li ng-repeat="contact in contacts">
3416
+ * <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
3417
+ * </li>
3418
+ * <li ng-repeat="contact in contacts">
3419
+ * <a href="#/contacts/2" ui-sref="contacts.detail({ id: contact.id })">Alice</a>
3420
+ * </li>
3421
+ * <li ng-repeat="contact in contacts">
3422
+ * <a href="#/contacts/3" ui-sref="contacts.detail({ id: contact.id })">Bob</a>
3423
+ * </li>
3424
+ * </ul>
3425
+ *
3426
+ * <a ui-sref="home" ui-sref-opts="{reload: true}">Home</a>
3427
+ * </pre>
3428
+ *
3429
+ * @param {string} ui-sref 'stateName' can be any valid absolute or relative state
3430
+ * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#go $state.go()}
3431
+ */
3432
+ $StateRefDirective.$inject = ['$state', '$timeout'];
3433
+ function $StateRefDirective($state, $timeout) {
3434
+ var allowedOptions = ['location', 'inherit', 'reload'];
3435
+
3436
+ return {
3437
+ restrict: 'A',
3438
+ require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
3439
+ link: function(scope, element, attrs, uiSrefActive) {
3440
+ var ref = parseStateRef(attrs.uiSref, $state.current.name);
3441
+ var params = null, url = null, base = stateContext(element) || $state.$current;
3442
+ var isForm = element[0].nodeName === "FORM";
3443
+ var attr = isForm ? "action" : "href", nav = true;
3444
+
3445
+ var options = { relative: base, inherit: true };
3446
+ var optionsOverride = scope.$eval(attrs.uiSrefOpts) || {};
3447
+
3448
+ angular.forEach(allowedOptions, function(option) {
3449
+ if (option in optionsOverride) {
3450
+ options[option] = optionsOverride[option];
3451
+ }
3452
+ });
3453
+
3454
+ var update = function(newVal) {
3455
+ if (newVal) params = newVal;
3456
+ if (!nav) return;
3457
+
3458
+ var newHref = $state.href(ref.state, params, options);
3459
+
3460
+ var activeDirective = uiSrefActive[1] || uiSrefActive[0];
3461
+ if (activeDirective) {
3462
+ activeDirective.$$setStateInfo(ref.state, params);
3463
+ }
3464
+ if (newHref === null) {
3465
+ nav = false;
3466
+ return false;
3467
+ }
3468
+ element[0][attr] = newHref;
3469
+ };
3470
+
3471
+ if (ref.paramExpr) {
3472
+ scope.$watch(ref.paramExpr, function(newVal, oldVal) {
3473
+ if (newVal !== params) update(newVal);
3474
+ }, true);
3475
+ params = scope.$eval(ref.paramExpr);
3476
+ }
3477
+ update();
3478
+
3479
+ if (isForm) return;
3480
+
3481
+ element.bind("click", function(e) {
3482
+ var button = e.which || e.button;
3483
+ if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) {
3484
+ // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
3485
+ var transition = $timeout(function() {
3486
+ $state.go(ref.state, params, options);
3487
+ });
3488
+ e.preventDefault();
3489
+
3490
+ e.preventDefault = function() {
3491
+ $timeout.cancel(transition);
3492
+ };
3493
+ }
3494
+ });
3495
+ }
3496
+ };
3497
+ }
3498
+
3499
+ /**
3500
+ * @ngdoc directive
3501
+ * @name ui.router.state.directive:ui-sref-active
3502
+ *
3503
+ * @requires ui.router.state.$state
3504
+ * @requires ui.router.state.$stateParams
3505
+ * @requires $interpolate
3506
+ *
3507
+ * @restrict A
3508
+ *
3509
+ * @description
3510
+ * A directive working alongside ui-sref to add classes to an element when the
3511
+ * related ui-sref directive's state is active, and removing them when it is inactive.
3512
+ * The primary use-case is to simplify the special appearance of navigation menus
3513
+ * relying on `ui-sref`, by having the "active" state's menu button appear different,
3514
+ * distinguishing it from the inactive menu items.
3515
+ *
3516
+ * ui-sref-active can live on the same element as ui-sref or on a parent element. The first
3517
+ * ui-sref-active found at the same level or above the ui-sref will be used.
3518
+ *
3519
+ * Will activate when the ui-sref's target state or any child state is active. If you
3520
+ * need to activate only when the ui-sref target state is active and *not* any of
3521
+ * it's children, then you will use
3522
+ * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq}
3523
+ *
3524
+ * @example
3525
+ * Given the following template:
3526
+ * <pre>
3527
+ * <ul>
3528
+ * <li ui-sref-active="active" class="item">
3529
+ * <a href ui-sref="app.user({user: 'bilbobaggins'})">@bilbobaggins</a>
3530
+ * </li>
3531
+ * </ul>
3532
+ * </pre>
3533
+ *
3534
+ *
3535
+ * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins",
3536
+ * the resulting HTML will appear as (note the 'active' class):
3537
+ * <pre>
3538
+ * <ul>
3539
+ * <li ui-sref-active="active" class="item active">
3540
+ * <a ui-sref="app.user({user: 'bilbobaggins'})" href="/users/bilbobaggins">@bilbobaggins</a>
3541
+ * </li>
3542
+ * </ul>
3543
+ * </pre>
3544
+ *
3545
+ * The class name is interpolated **once** during the directives link time (any further changes to the
3546
+ * interpolated value are ignored).
3547
+ *
3548
+ * Multiple classes may be specified in a space-separated format:
3549
+ * <pre>
3550
+ * <ul>
3551
+ * <li ui-sref-active='class1 class2 class3'>
3552
+ * <a ui-sref="app.user">link</a>
3553
+ * </li>
3554
+ * </ul>
3555
+ * </pre>
3556
+ */
3557
+
3558
+ /**
3559
+ * @ngdoc directive
3560
+ * @name ui.router.state.directive:ui-sref-active-eq
3561
+ *
3562
+ * @requires ui.router.state.$state
3563
+ * @requires ui.router.state.$stateParams
3564
+ * @requires $interpolate
3565
+ *
3566
+ * @restrict A
3567
+ *
3568
+ * @description
3569
+ * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will will only activate
3570
+ * when the exact target state used in the `ui-sref` is active; no child states.
3571
+ *
3572
+ */
3573
+ $StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
3574
+ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
3575
+ return {
3576
+ restrict: "A",
3577
+ controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
3578
+ var state, params, activeClass;
3579
+
3580
+ // There probably isn't much point in $observing this
3581
+ // uiSrefActive and uiSrefActiveEq share the same directive object with some
3582
+ // slight difference in logic routing
3583
+ activeClass = $interpolate($attrs.uiSrefActiveEq || $attrs.uiSrefActive || '', false)($scope);
3584
+
3585
+ // Allow uiSref to communicate with uiSrefActive[Equals]
3586
+ this.$$setStateInfo = function (newState, newParams) {
3587
+ state = $state.get(newState, stateContext($element));
3588
+ params = newParams;
3589
+ update();
3590
+ };
3591
+
3592
+ $scope.$on('$stateChangeSuccess', update);
3593
+
3594
+ // Update route state
3595
+ function update() {
3596
+ if (isMatch()) {
3597
+ $element.addClass(activeClass);
3598
+ } else {
3599
+ $element.removeClass(activeClass);
3600
+ }
3601
+ }
3602
+
3603
+ function isMatch() {
3604
+ if (typeof $attrs.uiSrefActiveEq !== 'undefined') {
3605
+ return $state.$current.self === state && matchesParams();
3606
+ } else {
3607
+ return $state.includes(state.name) && matchesParams();
3608
+ }
3609
+ }
3610
+
3611
+ function matchesParams() {
3612
+ return !params || equalForKeys(params, $stateParams);
3613
+ }
3614
+ }]
3615
+ };
3616
+ }
3617
+
3618
+ angular.module('ui.router.state')
3619
+ .directive('uiSref', $StateRefDirective)
3620
+ .directive('uiSrefActive', $StateRefActiveDirective)
3621
+ .directive('uiSrefActiveEq', $StateRefActiveDirective);
3622
+
3623
+ /**
3624
+ * @ngdoc filter
3625
+ * @name ui.router.state.filter:isState
3626
+ *
3627
+ * @requires ui.router.state.$state
3628
+ *
3629
+ * @description
3630
+ * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}.
3631
+ */
3632
+ $IsStateFilter.$inject = ['$state'];
3633
+ function $IsStateFilter($state) {
3634
+ return function(state) {
3635
+ return $state.is(state);
3636
+ };
3637
+ }
3638
+
3639
+ /**
3640
+ * @ngdoc filter
3641
+ * @name ui.router.state.filter:includedByState
3642
+ *
3643
+ * @requires ui.router.state.$state
3644
+ *
3645
+ * @description
3646
+ * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}.
3647
+ */
3648
+ $IncludedByStateFilter.$inject = ['$state'];
3649
+ function $IncludedByStateFilter($state) {
3650
+ return function(state) {
3651
+ return $state.includes(state);
3652
+ };
3653
+ }
3654
+
3655
+ angular.module('ui.router.state')
3656
+ .filter('isState', $IsStateFilter)
3657
+ .filter('includedByState', $IncludedByStateFilter);
3658
+ })(window, window.angular);