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