bastion 3.3.4 → 3.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * State-based routing for AngularJS
3
- * @version v0.2.10
3
+ * @version v0.3.1
4
4
  * @link http://angular-ui.github.com/
5
5
  * @license MIT License, http://www.opensource.org/licenses/MIT
6
6
  */
@@ -22,7 +22,8 @@ var isDefined = angular.isDefined,
22
22
  isArray = angular.isArray,
23
23
  forEach = angular.forEach,
24
24
  extend = angular.extend,
25
- copy = angular.copy;
25
+ copy = angular.copy,
26
+ toJson = angular.toJson;
26
27
 
27
28
  function inherit(parent, extra) {
28
29
  return extend(new (extend(function() {}, { prototype: parent }))(), extra);
@@ -62,13 +63,13 @@ function ancestors(first, second) {
62
63
  * @param {Object} object A JavaScript object.
63
64
  * @return {Array} Returns the keys of the object as an array.
64
65
  */
65
- function keys(object) {
66
+ function objectKeys(object) {
66
67
  if (Object.keys) {
67
68
  return Object.keys(object);
68
69
  }
69
70
  var result = [];
70
71
 
71
- angular.forEach(object, function(val, key) {
72
+ forEach(object, function(val, key) {
72
73
  result.push(key);
73
74
  });
74
75
  return result;
@@ -81,7 +82,7 @@ function keys(object) {
81
82
  * @param {*} value A value to search the array for.
82
83
  * @return {Number} Returns the array index value of `value`, or `-1` if not present.
83
84
  */
84
- function arraySearch(array, value) {
85
+ function indexOf(array, value) {
85
86
  if (Array.prototype.indexOf) {
86
87
  return array.indexOf(value, Number(arguments[2]) || 0);
87
88
  }
@@ -109,11 +110,12 @@ function inheritParams(currentParams, newParams, $current, $to) {
109
110
  var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = [];
110
111
 
111
112
  for (var i in parents) {
112
- if (!parents[i].params || !parents[i].params.length) continue;
113
- parentParams = parents[i].params;
113
+ if (!parents[i] || !parents[i].params) continue;
114
+ parentParams = objectKeys(parents[i].params);
115
+ if (!parentParams.length) continue;
114
116
 
115
117
  for (var j in parentParams) {
116
- if (arraySearch(inheritList, parentParams[j]) >= 0) continue;
118
+ if (indexOf(inheritList, parentParams[j]) >= 0) continue;
117
119
  inheritList.push(parentParams[j]);
118
120
  inherited[parentParams[j]] = currentParams[parentParams[j]];
119
121
  }
@@ -121,23 +123,6 @@ function inheritParams(currentParams, newParams, $current, $to) {
121
123
  return extend({}, inherited, newParams);
122
124
  }
123
125
 
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
126
  /**
142
127
  * Performs a non-strict comparison of the subset of two objects, defined by a list of keys.
143
128
  *
@@ -175,6 +160,68 @@ function filterByKeys(keys, values) {
175
160
  });
176
161
  return filtered;
177
162
  }
163
+
164
+ // like _.indexBy
165
+ // when you know that your index values will be unique, or you want last-one-in to win
166
+ function indexBy(array, propName) {
167
+ var result = {};
168
+ forEach(array, function(item) {
169
+ result[item[propName]] = item;
170
+ });
171
+ return result;
172
+ }
173
+
174
+ // extracted from underscore.js
175
+ // Return a copy of the object only containing the whitelisted properties.
176
+ function pick(obj) {
177
+ var copy = {};
178
+ var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
179
+ forEach(keys, function(key) {
180
+ if (key in obj) copy[key] = obj[key];
181
+ });
182
+ return copy;
183
+ }
184
+
185
+ // extracted from underscore.js
186
+ // Return a copy of the object omitting the blacklisted properties.
187
+ function omit(obj) {
188
+ var copy = {};
189
+ var keys = Array.prototype.concat.apply(Array.prototype, Array.prototype.slice.call(arguments, 1));
190
+ for (var key in obj) {
191
+ if (indexOf(keys, key) == -1) copy[key] = obj[key];
192
+ }
193
+ return copy;
194
+ }
195
+
196
+ function pluck(collection, key) {
197
+ var result = isArray(collection) ? [] : {};
198
+
199
+ forEach(collection, function(val, i) {
200
+ result[i] = isFunction(key) ? key(val) : val[key];
201
+ });
202
+ return result;
203
+ }
204
+
205
+ function filter(collection, callback) {
206
+ var array = isArray(collection);
207
+ var result = array ? [] : {};
208
+ forEach(collection, function(val, i) {
209
+ if (callback(val, i)) {
210
+ result[array ? result.length : i] = val;
211
+ }
212
+ });
213
+ return result;
214
+ }
215
+
216
+ function map(collection, callback) {
217
+ var result = isArray(collection) ? [] : {};
218
+
219
+ forEach(collection, function(val, i) {
220
+ result[i] = callback(val, i);
221
+ });
222
+ return result;
223
+ }
224
+
178
225
  /**
179
226
  * @ngdoc overview
180
227
  * @name ui.router.util
@@ -301,6 +348,7 @@ function $Resolve( $q, $injector) {
301
348
  */
302
349
  this.study = function (invocables) {
303
350
  if (!isObject(invocables)) throw new Error("'invocables' must be an object");
351
+ var invocableKeys = objectKeys(invocables || {});
304
352
 
305
353
  // Perform a topological sort of invocables to build an ordered plan
306
354
  var plan = [], cycle = [], visited = {};
@@ -309,7 +357,7 @@ function $Resolve( $q, $injector) {
309
357
 
310
358
  cycle.push(key);
311
359
  if (visited[key] === VISIT_IN_PROGRESS) {
312
- cycle.splice(0, cycle.indexOf(key));
360
+ cycle.splice(0, indexOf(cycle, key));
313
361
  throw new Error("Cyclic dependency: " + cycle.join(" -> "));
314
362
  }
315
363
  visited[key] = VISIT_IN_PROGRESS;
@@ -361,7 +409,8 @@ function $Resolve( $q, $injector) {
361
409
  if (!--wait) {
362
410
  if (!merged) merge(values, parent.$$values);
363
411
  result.$$values = values;
364
- result.$$promises = true; // keep for isResolve()
412
+ result.$$promises = result.$$promises || true; // keep for isResolve()
413
+ delete result.$$inheritedValues;
365
414
  resolution.resolve(values);
366
415
  }
367
416
  }
@@ -370,20 +419,28 @@ function $Resolve( $q, $injector) {
370
419
  result.$$failure = reason;
371
420
  resolution.reject(reason);
372
421
  }
373
-
422
+
374
423
  // Short-circuit if parent has already failed
375
424
  if (isDefined(parent.$$failure)) {
376
425
  fail(parent.$$failure);
377
426
  return result;
378
427
  }
379
428
 
429
+ if (parent.$$inheritedValues) {
430
+ merge(values, omit(parent.$$inheritedValues, invocableKeys));
431
+ }
432
+
380
433
  // Merge parent values if the parent has already resolved, or merge
381
434
  // parent promises and wait if the parent resolve is still in progress.
435
+ extend(promises, parent.$$promises);
382
436
  if (parent.$$values) {
383
- merged = merge(values, parent.$$values);
437
+ merged = merge(values, omit(parent.$$values, invocableKeys));
438
+ result.$$inheritedValues = omit(parent.$$values, invocableKeys);
384
439
  done();
385
440
  } else {
386
- extend(promises, parent.$$promises);
441
+ if (parent.$$inheritedValues) {
442
+ result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys);
443
+ }
387
444
  parent.then(done, fail);
388
445
  }
389
446
 
@@ -466,7 +523,7 @@ function $Resolve( $q, $injector) {
466
523
  * propagated immediately. Once the `$resolve` promise has been rejected, no
467
524
  * further invocables will be called.
468
525
  *
469
- * Cyclic dependencies between invocables are not permitted and will caues `$resolve`
526
+ * Cyclic dependencies between invocables are not permitted and will cause `$resolve`
470
527
  * to throw an error. As a special case, an injectable can depend on a parameter
471
528
  * with the same name as the injectable, which will be fulfilled from the `parent`
472
529
  * injectable of the same name. This allows inherited values to be decorated.
@@ -586,13 +643,13 @@ function $TemplateFactory( $http, $templateCache, $injector) {
586
643
  if (isFunction(url)) url = url(params);
587
644
  if (url == null) return null;
588
645
  else return $http
589
- .get(url, { cache: $templateCache })
646
+ .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }})
590
647
  .then(function(response) { return response.data; });
591
648
  };
592
649
 
593
650
  /**
594
651
  * @ngdoc function
595
- * @name ui.router.util.$templateFactory#fromUrl
652
+ * @name ui.router.util.$templateFactory#fromProvider
596
653
  * @methodOf ui.router.util.$templateFactory
597
654
  *
598
655
  * @description
@@ -612,6 +669,8 @@ function $TemplateFactory( $http, $templateCache, $injector) {
612
669
 
613
670
  angular.module('ui.router.util').service('$templateFactory', $TemplateFactory);
614
671
 
672
+ var $$UMFP; // reference to $UrlMatcherFactoryProvider
673
+
615
674
  /**
616
675
  * @ngdoc object
617
676
  * @name ui.router.util.type:UrlMatcher
@@ -622,24 +681,24 @@ angular.module('ui.router.util').service('$templateFactory', $TemplateFactory);
622
681
  * of search parameters. Multiple search parameter names are separated by '&'. Search parameters
623
682
  * do not influence whether or not a URL is matched, but their values are passed through into
624
683
  * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}.
625
- *
684
+ *
626
685
  * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace
627
686
  * syntax, which optionally allows a regular expression for the parameter to be specified:
628
687
  *
629
688
  * * `':'` name - colon placeholder
630
689
  * * `'*'` name - catch-all placeholder
631
690
  * * `'{' 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.
691
+ * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the
692
+ * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash.
634
693
  *
635
694
  * 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
695
+ * must be unique within the pattern (across both path and search parameters). For colon
637
696
  * placeholders or curly placeholders without an explicit regexp, a path parameter matches any
638
697
  * number of characters other than '/'. For catch-all placeholders the path parameter matches
639
698
  * any number of characters.
640
- *
699
+ *
641
700
  * Examples:
642
- *
701
+ *
643
702
  * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for
644
703
  * trailing slashes, and patterns have to match the entire path, not just a prefix.
645
704
  * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or
@@ -651,25 +710,34 @@ angular.module('ui.router.util').service('$templateFactory', $TemplateFactory);
651
710
  * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the
652
711
  * path into the parameter 'path'.
653
712
  * * `'/files/*path'` - ditto.
713
+ * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined
714
+ * in the built-in `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start
654
715
  *
655
- * @param {string} pattern the pattern to compile into a matcher.
716
+ * @param {string} pattern The pattern to compile into a matcher.
717
+ * @param {Object} config A configuration object hash:
718
+ * @param {Object=} parentMatcher Used to concatenate the pattern/config onto
719
+ * an existing UrlMatcher
720
+ *
721
+ * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`.
722
+ * * `strict` - `false` if matching against a URL with a trailing slash should be treated as equivalent to a URL without a trailing slash, the default value is `true`.
656
723
  *
657
724
  * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any
658
725
  * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns
659
726
  * non-null) will start with this prefix.
660
727
  *
661
- * @property {string} source The pattern that was passed into the contructor
728
+ * @property {string} source The pattern that was passed into the constructor
662
729
  *
663
730
  * @property {string} sourcePath The path portion of the source property
664
731
  *
665
732
  * @property {string} sourceSearch The search portion of the source property
666
733
  *
667
- * @property {string} regex The constructed regex that will be used to match against the url when
734
+ * @property {string} regex The constructed regex that will be used to match against the url when
668
735
  * it is time to determine which url will match.
669
736
  *
670
- * @returns {Object} New UrlMatcher object
737
+ * @returns {Object} New `UrlMatcher` object
671
738
  */
672
- function UrlMatcher(pattern) {
739
+ function UrlMatcher(pattern, config, parentMatcher) {
740
+ config = extend({ params: {} }, isObject(config) ? config : {});
673
741
 
674
742
  // Find all placeholders and create a compiled pattern, using either classic or curly syntax:
675
743
  // '*' name
@@ -678,63 +746,103 @@ function UrlMatcher(pattern) {
678
746
  // '{' name ':' regexp '}'
679
747
  // The regular expression is somewhat complicated due to the need to allow curly braces
680
748
  // 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,
749
+ // ([:*])([\w\[\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case)
750
+ // \{([\w\[\]]+)(?:\:\s*( ... ))?\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case
751
+ // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either
752
+ // [^{}\\]+ - anything other than curly braces or backslash
753
+ // \\. - a backslash escape
754
+ // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms
755
+ var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
756
+ searchPlaceholder = /([:]?)([\w\[\].-]+)|\{([\w\[\].-]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
757
+ compiled = '^', last = 0, m,
689
758
  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);
759
+ parentParams = parentMatcher ? parentMatcher.params : {},
760
+ params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(),
761
+ paramNames = [];
762
+
763
+ function addParameter(id, type, config, location) {
764
+ paramNames.push(id);
765
+ if (parentParams[id]) return parentParams[id];
766
+ if (!/^\w+([-.]+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
767
+ if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
768
+ params[id] = new $$UMFP.Param(id, type, config, location);
769
+ return params[id];
697
770
  }
698
771
 
699
- function quoteRegExp(string) {
700
- return string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
772
+ function quoteRegExp(string, pattern, squash, optional) {
773
+ var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
774
+ if (!pattern) return result;
775
+ switch(squash) {
776
+ case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break;
777
+ case true:
778
+ result = result.replace(/\/$/, '');
779
+ surroundPattern = ['(?:\/(', ')|\/)?'];
780
+ break;
781
+ default: surroundPattern = ['(' + squash + "|", ')?']; break;
782
+ }
783
+ return result + surroundPattern[0] + pattern + surroundPattern[1];
701
784
  }
702
785
 
703
786
  this.source = pattern;
704
787
 
705
788
  // Split into static segments separated by path parameter placeholders.
706
789
  // The number of segments is always 1 more than the number of parameters.
707
- var id, regexp, segment;
790
+ function matchDetails(m, isSearch) {
791
+ var id, regexp, segment, type, cfg, arrayMode;
792
+ id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
793
+ cfg = config.params[id];
794
+ segment = pattern.substring(last, m.index);
795
+ regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null);
796
+
797
+ if (regexp) {
798
+ type = $$UMFP.type(regexp) || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) });
799
+ }
800
+
801
+ return {
802
+ id: id, regexp: regexp, segment: segment, type: type, cfg: cfg
803
+ };
804
+ }
805
+
806
+ var p, param, segment;
708
807
  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);
808
+ p = matchDetails(m, false);
809
+ if (p.segment.indexOf('?') >= 0) break; // we're into the search part
810
+
811
+ param = addParameter(p.id, p.type, p.cfg, "path");
812
+ compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional);
813
+ segments.push(p.segment);
716
814
  last = placeholder.lastIndex;
717
815
  }
718
816
  segment = pattern.substring(last);
719
817
 
720
818
  // Find any search parameter names and remove them from the last segment
721
819
  var i = segment.indexOf('?');
820
+
722
821
  if (i >= 0) {
723
822
  var search = this.sourceSearch = segment.substring(i);
724
823
  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);
824
+ this.sourcePath = pattern.substring(0, last + i);
825
+
826
+ if (search.length > 0) {
827
+ last = 0;
828
+ while ((m = searchPlaceholder.exec(search))) {
829
+ p = matchDetails(m, true);
830
+ param = addParameter(p.id, p.type, p.cfg, "search");
831
+ last = placeholder.lastIndex;
832
+ // check if ?&
833
+ }
834
+ }
729
835
  } else {
730
836
  this.sourcePath = pattern;
731
837
  this.sourceSearch = '';
732
838
  }
733
839
 
734
- compiled += quoteRegExp(segment) + '$';
840
+ compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$';
735
841
  segments.push(segment);
736
- this.regexp = new RegExp(compiled);
842
+
843
+ this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
737
844
  this.prefix = segments[0];
845
+ this.$$paramNames = paramNames;
738
846
  }
739
847
 
740
848
  /**
@@ -750,19 +858,25 @@ function UrlMatcher(pattern) {
750
858
  *
751
859
  * @example
752
860
  * The following two matchers are equivalent:
753
- * ```
861
+ * <pre>
754
862
  * new UrlMatcher('/user/{id}?q').concat('/details?date');
755
863
  * new UrlMatcher('/user/{id}/details?q&date');
756
- * ```
864
+ * </pre>
757
865
  *
758
866
  * @param {string} pattern The pattern to append.
759
- * @returns {ui.router.util.type:UrlMatcher} A matcher for the concatenated pattern.
867
+ * @param {Object} config An object hash of the configuration for the matcher.
868
+ * @returns {UrlMatcher} A matcher for the concatenated pattern.
760
869
  */
761
- UrlMatcher.prototype.concat = function (pattern) {
870
+ UrlMatcher.prototype.concat = function (pattern, config) {
762
871
  // Because order of search parameters is irrelevant, we can add our own search
763
872
  // parameters to the end of the new pattern. Parse the new pattern by itself
764
873
  // 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);
874
+ var defaultConfig = {
875
+ caseInsensitive: $$UMFP.caseInsensitive(),
876
+ strict: $$UMFP.strictMode(),
877
+ squash: $$UMFP.defaultSquashPolicy()
878
+ };
879
+ return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this);
766
880
  };
767
881
 
768
882
  UrlMatcher.prototype.toString = function () {
@@ -782,10 +896,12 @@ UrlMatcher.prototype.toString = function () {
782
896
  * as optional.
783
897
  *
784
898
  * @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
- * ```
899
+ * <pre>
900
+ * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {
901
+ * x: '1', q: 'hello'
902
+ * });
903
+ * // returns { id: 'bob', q: 'hello', r: null }
904
+ * </pre>
789
905
  *
790
906
  * @param {string} path The URL path to match, e.g. `$location.path()`.
791
907
  * @param {Object} searchParams URL search parameters, e.g. `$location.search()`.
@@ -794,15 +910,47 @@ UrlMatcher.prototype.toString = function () {
794
910
  UrlMatcher.prototype.exec = function (path, searchParams) {
795
911
  var m = this.regexp.exec(path);
796
912
  if (!m) return null;
913
+ searchParams = searchParams || {};
797
914
 
798
- var params = this.params, nTotal = params.length,
799
- nPath = this.segments.length-1,
800
- values = {}, i;
915
+ var paramNames = this.parameters(), nTotal = paramNames.length,
916
+ nPath = this.segments.length - 1,
917
+ values = {}, i, j, cfg, paramName;
801
918
 
802
919
  if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'");
803
920
 
804
- for (i=0; i<nPath; i++) values[params[i]] = m[i+1];
805
- for (/**/; i<nTotal; i++) values[params[i]] = searchParams[params[i]];
921
+ function decodePathArray(string) {
922
+ function reverseString(str) { return str.split("").reverse().join(""); }
923
+ function unquoteDashes(str) { return str.replace(/\\-/g, "-"); }
924
+
925
+ var split = reverseString(string).split(/-(?!\\)/);
926
+ var allReversed = map(split, reverseString);
927
+ return map(allReversed, unquoteDashes).reverse();
928
+ }
929
+
930
+ var param, paramVal;
931
+ for (i = 0; i < nPath; i++) {
932
+ paramName = paramNames[i];
933
+ param = this.params[paramName];
934
+ paramVal = m[i+1];
935
+ // if the param value matches a pre-replace pair, replace the value before decoding.
936
+ for (j = 0; j < param.replace.length; j++) {
937
+ if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
938
+ }
939
+ if (paramVal && param.array === true) paramVal = decodePathArray(paramVal);
940
+ if (isDefined(paramVal)) paramVal = param.type.decode(paramVal);
941
+ values[paramName] = param.value(paramVal);
942
+ }
943
+ for (/**/; i < nTotal; i++) {
944
+ paramName = paramNames[i];
945
+ values[paramName] = this.params[paramName].value(searchParams[paramName]);
946
+ param = this.params[paramName];
947
+ paramVal = searchParams[paramName];
948
+ for (j = 0; j < param.replace.length; j++) {
949
+ if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
950
+ }
951
+ if (isDefined(paramVal)) paramVal = param.type.decode(paramVal);
952
+ values[paramName] = param.value(paramVal);
953
+ }
806
954
 
807
955
  return values;
808
956
  };
@@ -814,12 +962,29 @@ UrlMatcher.prototype.exec = function (path, searchParams) {
814
962
  *
815
963
  * @description
816
964
  * Returns the names of all path and search parameters of this pattern in an unspecified order.
817
- *
965
+ *
818
966
  * @returns {Array.<string>} An array of parameter names. Must be treated as read-only. If the
819
967
  * pattern has no parameters, an empty array is returned.
820
968
  */
821
- UrlMatcher.prototype.parameters = function () {
822
- return this.params;
969
+ UrlMatcher.prototype.parameters = function (param) {
970
+ if (!isDefined(param)) return this.$$paramNames;
971
+ return this.params[param] || null;
972
+ };
973
+
974
+ /**
975
+ * @ngdoc function
976
+ * @name ui.router.util.type:UrlMatcher#validates
977
+ * @methodOf ui.router.util.type:UrlMatcher
978
+ *
979
+ * @description
980
+ * Checks an object hash of parameters to validate their correctness according to the parameter
981
+ * types of this `UrlMatcher`.
982
+ *
983
+ * @param {Object} params The object hash of parameters to validate.
984
+ * @returns {boolean} Returns `true` if `params` validates, otherwise `false`.
985
+ */
986
+ UrlMatcher.prototype.validates = function (params) {
987
+ return this.params.$$validates(params);
823
988
  };
824
989
 
825
990
  /**
@@ -833,31 +998,59 @@ UrlMatcher.prototype.parameters = function () {
833
998
  * treated as empty strings.
834
999
  *
835
1000
  * @example
836
- * ```
1001
+ * <pre>
837
1002
  * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
838
1003
  * // returns '/user/bob?q=yes'
839
- * ```
1004
+ * </pre>
840
1005
  *
841
1006
  * @param {Object} values the values to substitute for the parameters in this pattern.
842
1007
  * @returns {string} the formatted URL (path and optionally search part).
843
1008
  */
844
1009
  UrlMatcher.prototype.format = function (values) {
845
- var segments = this.segments, params = this.params;
846
- if (!values) return segments.join('');
1010
+ values = values || {};
1011
+ var segments = this.segments, params = this.parameters(), paramset = this.params;
1012
+ if (!this.validates(values)) return null;
847
1013
 
848
- var nPath = segments.length-1, nTotal = params.length,
849
- result = segments[0], i, search, value;
1014
+ var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0];
850
1015
 
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];
1016
+ function encodeDashes(str) { // Replace dashes with encoded "\-"
1017
+ return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); });
856
1018
  }
857
- for (/**/; i<nTotal; i++) {
858
- value = values[params[i]];
859
- if (value != null) {
860
- result += (search ? '&' : '?') + params[i] + '=' + encodeURIComponent(value);
1019
+
1020
+ for (i = 0; i < nTotal; i++) {
1021
+ var isPathParam = i < nPath;
1022
+ var name = params[i], param = paramset[name], value = param.value(values[name]);
1023
+ var isDefaultValue = param.isOptional && param.type.equals(param.value(), value);
1024
+ var squash = isDefaultValue ? param.squash : false;
1025
+ var encoded = param.type.encode(value);
1026
+
1027
+ if (isPathParam) {
1028
+ var nextSegment = segments[i + 1];
1029
+ var isFinalPathParam = i + 1 === nPath;
1030
+
1031
+ if (squash === false) {
1032
+ if (encoded != null) {
1033
+ if (isArray(encoded)) {
1034
+ result += map(encoded, encodeDashes).join("-");
1035
+ } else {
1036
+ result += encodeURIComponent(encoded);
1037
+ }
1038
+ }
1039
+ result += nextSegment;
1040
+ } else if (squash === true) {
1041
+ var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/;
1042
+ result += nextSegment.match(capture)[1];
1043
+ } else if (isString(squash)) {
1044
+ result += squash + nextSegment;
1045
+ }
1046
+
1047
+ if (isFinalPathParam && param.squash === true && result.slice(-1) === '/') result = result.slice(0, -1);
1048
+ } else {
1049
+ if (encoded == null || (isDefaultValue && squash !== false)) continue;
1050
+ if (!isArray(encoded)) encoded = [ encoded ];
1051
+ if (encoded.length === 0) continue;
1052
+ encoded = map(encoded, encodeURIComponent).join('&' + name + '=');
1053
+ result += (search ? '&' : '?') + (name + '=' + encoded);
861
1054
  search = true;
862
1055
  }
863
1056
  }
@@ -865,6 +1058,195 @@ UrlMatcher.prototype.format = function (values) {
865
1058
  return result;
866
1059
  };
867
1060
 
1061
+ /**
1062
+ * @ngdoc object
1063
+ * @name ui.router.util.type:Type
1064
+ *
1065
+ * @description
1066
+ * Implements an interface to define custom parameter types that can be decoded from and encoded to
1067
+ * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`}
1068
+ * objects when matching or formatting URLs, or comparing or validating parameter values.
1069
+ *
1070
+ * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more
1071
+ * information on registering custom types.
1072
+ *
1073
+ * @param {Object} config A configuration object which contains the custom type definition. The object's
1074
+ * properties will override the default methods and/or pattern in `Type`'s public interface.
1075
+ * @example
1076
+ * <pre>
1077
+ * {
1078
+ * decode: function(val) { return parseInt(val, 10); },
1079
+ * encode: function(val) { return val && val.toString(); },
1080
+ * equals: function(a, b) { return this.is(a) && a === b; },
1081
+ * is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; },
1082
+ * pattern: /\d+/
1083
+ * }
1084
+ * </pre>
1085
+ *
1086
+ * @property {RegExp} pattern The regular expression pattern used to match values of this type when
1087
+ * coming from a substring of a URL.
1088
+ *
1089
+ * @returns {Object} Returns a new `Type` object.
1090
+ */
1091
+ function Type(config) {
1092
+ extend(this, config);
1093
+ }
1094
+
1095
+ /**
1096
+ * @ngdoc function
1097
+ * @name ui.router.util.type:Type#is
1098
+ * @methodOf ui.router.util.type:Type
1099
+ *
1100
+ * @description
1101
+ * Detects whether a value is of a particular type. Accepts a native (decoded) value
1102
+ * and determines whether it matches the current `Type` object.
1103
+ *
1104
+ * @param {*} val The value to check.
1105
+ * @param {string} key Optional. If the type check is happening in the context of a specific
1106
+ * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the
1107
+ * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects.
1108
+ * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`.
1109
+ */
1110
+ Type.prototype.is = function(val, key) {
1111
+ return true;
1112
+ };
1113
+
1114
+ /**
1115
+ * @ngdoc function
1116
+ * @name ui.router.util.type:Type#encode
1117
+ * @methodOf ui.router.util.type:Type
1118
+ *
1119
+ * @description
1120
+ * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the
1121
+ * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it
1122
+ * only needs to be a representation of `val` that has been coerced to a string.
1123
+ *
1124
+ * @param {*} val The value to encode.
1125
+ * @param {string} key The name of the parameter in which `val` is stored. Can be used for
1126
+ * meta-programming of `Type` objects.
1127
+ * @returns {string} Returns a string representation of `val` that can be encoded in a URL.
1128
+ */
1129
+ Type.prototype.encode = function(val, key) {
1130
+ return val;
1131
+ };
1132
+
1133
+ /**
1134
+ * @ngdoc function
1135
+ * @name ui.router.util.type:Type#decode
1136
+ * @methodOf ui.router.util.type:Type
1137
+ *
1138
+ * @description
1139
+ * Converts a parameter value (from URL string or transition param) to a custom/native value.
1140
+ *
1141
+ * @param {string} val The URL parameter value to decode.
1142
+ * @param {string} key The name of the parameter in which `val` is stored. Can be used for
1143
+ * meta-programming of `Type` objects.
1144
+ * @returns {*} Returns a custom representation of the URL parameter value.
1145
+ */
1146
+ Type.prototype.decode = function(val, key) {
1147
+ return val;
1148
+ };
1149
+
1150
+ /**
1151
+ * @ngdoc function
1152
+ * @name ui.router.util.type:Type#equals
1153
+ * @methodOf ui.router.util.type:Type
1154
+ *
1155
+ * @description
1156
+ * Determines whether two decoded values are equivalent.
1157
+ *
1158
+ * @param {*} a A value to compare against.
1159
+ * @param {*} b A value to compare against.
1160
+ * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`.
1161
+ */
1162
+ Type.prototype.equals = function(a, b) {
1163
+ return a == b;
1164
+ };
1165
+
1166
+ Type.prototype.$subPattern = function() {
1167
+ var sub = this.pattern.toString();
1168
+ return sub.substr(1, sub.length - 2);
1169
+ };
1170
+
1171
+ Type.prototype.pattern = /.*/;
1172
+
1173
+ Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };
1174
+
1175
+ /** Given an encoded string, or a decoded object, returns a decoded object */
1176
+ Type.prototype.$normalize = function(val) {
1177
+ return this.is(val) ? val : this.decode(val);
1178
+ };
1179
+
1180
+ /*
1181
+ * Wraps an existing custom Type as an array of Type, depending on 'mode'.
1182
+ * e.g.:
1183
+ * - urlmatcher pattern "/path?{queryParam[]:int}"
1184
+ * - url: "/path?queryParam=1&queryParam=2
1185
+ * - $stateParams.queryParam will be [1, 2]
1186
+ * if `mode` is "auto", then
1187
+ * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1
1188
+ * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2]
1189
+ */
1190
+ Type.prototype.$asArray = function(mode, isSearch) {
1191
+ if (!mode) return this;
1192
+ if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only");
1193
+
1194
+ function ArrayType(type, mode) {
1195
+ function bindTo(type, callbackName) {
1196
+ return function() {
1197
+ return type[callbackName].apply(type, arguments);
1198
+ };
1199
+ }
1200
+
1201
+ // Wrap non-array value as array
1202
+ function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); }
1203
+ // Unwrap array value for "auto" mode. Return undefined for empty array.
1204
+ function arrayUnwrap(val) {
1205
+ switch(val.length) {
1206
+ case 0: return undefined;
1207
+ case 1: return mode === "auto" ? val[0] : val;
1208
+ default: return val;
1209
+ }
1210
+ }
1211
+ function falsey(val) { return !val; }
1212
+
1213
+ // Wraps type (.is/.encode/.decode) functions to operate on each value of an array
1214
+ function arrayHandler(callback, allTruthyMode) {
1215
+ return function handleArray(val) {
1216
+ if (isArray(val) && val.length === 0) return val;
1217
+ val = arrayWrap(val);
1218
+ var result = map(val, callback);
1219
+ if (allTruthyMode === true)
1220
+ return filter(result, falsey).length === 0;
1221
+ return arrayUnwrap(result);
1222
+ };
1223
+ }
1224
+
1225
+ // Wraps type (.equals) functions to operate on each value of an array
1226
+ function arrayEqualsHandler(callback) {
1227
+ return function handleArray(val1, val2) {
1228
+ var left = arrayWrap(val1), right = arrayWrap(val2);
1229
+ if (left.length !== right.length) return false;
1230
+ for (var i = 0; i < left.length; i++) {
1231
+ if (!callback(left[i], right[i])) return false;
1232
+ }
1233
+ return true;
1234
+ };
1235
+ }
1236
+
1237
+ this.encode = arrayHandler(bindTo(type, 'encode'));
1238
+ this.decode = arrayHandler(bindTo(type, 'decode'));
1239
+ this.is = arrayHandler(bindTo(type, 'is'), true);
1240
+ this.equals = arrayEqualsHandler(bindTo(type, 'equals'));
1241
+ this.pattern = type.pattern;
1242
+ this.$normalize = arrayHandler(bindTo(type, '$normalize'));
1243
+ this.name = type.name;
1244
+ this.$arrayMode = mode;
1245
+ }
1246
+
1247
+ return new ArrayType(this, mode);
1248
+ };
1249
+
868
1250
 
869
1251
 
870
1252
  /**
@@ -872,10 +1254,152 @@ UrlMatcher.prototype.format = function (values) {
872
1254
  * @name ui.router.util.$urlMatcherFactory
873
1255
  *
874
1256
  * @description
875
- * Factory for {@link ui.router.util.type:UrlMatcher} instances. The factory is also available to providers
876
- * under the name `$urlMatcherFactoryProvider`.
1257
+ * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory
1258
+ * is also available to providers under the name `$urlMatcherFactoryProvider`.
877
1259
  */
878
1260
  function $UrlMatcherFactory() {
1261
+ $$UMFP = this;
1262
+
1263
+ var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false;
1264
+
1265
+ // Use tildes to pre-encode slashes.
1266
+ // If the slashes are simply URLEncoded, the browser can choose to pre-decode them,
1267
+ // and bidirectional encoding/decoding fails.
1268
+ // Tilde was chosen because it's not a RFC 3986 section 2.2 Reserved Character
1269
+ function valToString(val) { return val != null ? val.toString().replace(/~/g, "~~").replace(/\//g, "~2F") : val; }
1270
+ function valFromString(val) { return val != null ? val.toString().replace(/~2F/g, "/").replace(/~~/g, "~") : val; }
1271
+
1272
+ var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = {
1273
+ "string": {
1274
+ encode: valToString,
1275
+ decode: valFromString,
1276
+ // TODO: in 1.0, make string .is() return false if value is undefined/null by default.
1277
+ // In 0.2.x, string params are optional by default for backwards compat
1278
+ is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; },
1279
+ pattern: /[^/]*/
1280
+ },
1281
+ "int": {
1282
+ encode: valToString,
1283
+ decode: function(val) { return parseInt(val, 10); },
1284
+ is: function(val) { return isDefined(val) && this.decode(val.toString()) === val; },
1285
+ pattern: /\d+/
1286
+ },
1287
+ "bool": {
1288
+ encode: function(val) { return val ? 1 : 0; },
1289
+ decode: function(val) { return parseInt(val, 10) !== 0; },
1290
+ is: function(val) { return val === true || val === false; },
1291
+ pattern: /0|1/
1292
+ },
1293
+ "date": {
1294
+ encode: function (val) {
1295
+ if (!this.is(val))
1296
+ return undefined;
1297
+ return [ val.getFullYear(),
1298
+ ('0' + (val.getMonth() + 1)).slice(-2),
1299
+ ('0' + val.getDate()).slice(-2)
1300
+ ].join("-");
1301
+ },
1302
+ decode: function (val) {
1303
+ if (this.is(val)) return val;
1304
+ var match = this.capture.exec(val);
1305
+ return match ? new Date(match[1], match[2] - 1, match[3]) : undefined;
1306
+ },
1307
+ is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); },
1308
+ equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); },
1309
+ pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,
1310
+ capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/
1311
+ },
1312
+ "json": {
1313
+ encode: angular.toJson,
1314
+ decode: angular.fromJson,
1315
+ is: angular.isObject,
1316
+ equals: angular.equals,
1317
+ pattern: /[^/]*/
1318
+ },
1319
+ "any": { // does not encode/decode
1320
+ encode: angular.identity,
1321
+ decode: angular.identity,
1322
+ equals: angular.equals,
1323
+ pattern: /.*/
1324
+ }
1325
+ };
1326
+
1327
+ function getDefaultConfig() {
1328
+ return {
1329
+ strict: isStrictMode,
1330
+ caseInsensitive: isCaseInsensitive
1331
+ };
1332
+ }
1333
+
1334
+ function isInjectable(value) {
1335
+ return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1])));
1336
+ }
1337
+
1338
+ /**
1339
+ * [Internal] Get the default value of a parameter, which may be an injectable function.
1340
+ */
1341
+ $UrlMatcherFactory.$$getDefaultValue = function(config) {
1342
+ if (!isInjectable(config.value)) return config.value;
1343
+ if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
1344
+ return injector.invoke(config.value);
1345
+ };
1346
+
1347
+ /**
1348
+ * @ngdoc function
1349
+ * @name ui.router.util.$urlMatcherFactory#caseInsensitive
1350
+ * @methodOf ui.router.util.$urlMatcherFactory
1351
+ *
1352
+ * @description
1353
+ * Defines whether URL matching should be case sensitive (the default behavior), or not.
1354
+ *
1355
+ * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`;
1356
+ * @returns {boolean} the current value of caseInsensitive
1357
+ */
1358
+ this.caseInsensitive = function(value) {
1359
+ if (isDefined(value))
1360
+ isCaseInsensitive = value;
1361
+ return isCaseInsensitive;
1362
+ };
1363
+
1364
+ /**
1365
+ * @ngdoc function
1366
+ * @name ui.router.util.$urlMatcherFactory#strictMode
1367
+ * @methodOf ui.router.util.$urlMatcherFactory
1368
+ *
1369
+ * @description
1370
+ * Defines whether URLs should match trailing slashes, or not (the default behavior).
1371
+ *
1372
+ * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`.
1373
+ * @returns {boolean} the current value of strictMode
1374
+ */
1375
+ this.strictMode = function(value) {
1376
+ if (isDefined(value))
1377
+ isStrictMode = value;
1378
+ return isStrictMode;
1379
+ };
1380
+
1381
+ /**
1382
+ * @ngdoc function
1383
+ * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy
1384
+ * @methodOf ui.router.util.$urlMatcherFactory
1385
+ *
1386
+ * @description
1387
+ * Sets the default behavior when generating or matching URLs with default parameter values.
1388
+ *
1389
+ * @param {string} value A string that defines the default parameter URL squashing behavior.
1390
+ * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL
1391
+ * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the
1392
+ * parameter is surrounded by slashes, squash (remove) one slash from the URL
1393
+ * any other string, e.g. "~": When generating an href with a default parameter value, squash (remove)
1394
+ * the parameter value from the URL and replace it with this string.
1395
+ */
1396
+ this.defaultSquashPolicy = function(value) {
1397
+ if (!isDefined(value)) return defaultSquashPolicy;
1398
+ if (value !== true && value !== false && !isString(value))
1399
+ throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string");
1400
+ defaultSquashPolicy = value;
1401
+ return value;
1402
+ };
879
1403
 
880
1404
  /**
881
1405
  * @ngdoc function
@@ -883,13 +1407,14 @@ function $UrlMatcherFactory() {
883
1407
  * @methodOf ui.router.util.$urlMatcherFactory
884
1408
  *
885
1409
  * @description
886
- * Creates a {@link ui.router.util.type:UrlMatcher} for the specified pattern.
887
- *
1410
+ * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern.
1411
+ *
888
1412
  * @param {string} pattern The URL pattern.
889
- * @returns {ui.router.util.type:UrlMatcher} The UrlMatcher.
1413
+ * @param {Object} config The config object hash.
1414
+ * @returns {UrlMatcher} The UrlMatcher.
890
1415
  */
891
- this.compile = function (pattern) {
892
- return new UrlMatcher(pattern);
1416
+ this.compile = function (pattern, config) {
1417
+ return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
893
1418
  };
894
1419
 
895
1420
  /**
@@ -898,69 +1423,379 @@ function $UrlMatcherFactory() {
898
1423
  * @methodOf ui.router.util.$urlMatcherFactory
899
1424
  *
900
1425
  * @description
901
- * Returns true if the specified object is a UrlMatcher, or false otherwise.
1426
+ * Returns true if the specified object is a `UrlMatcher`, or false otherwise.
902
1427
  *
903
1428
  * @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`.
1429
+ * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by
1430
+ * implementing all the same methods.
905
1431
  */
906
1432
  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
- }
1433
+ if (!isObject(o)) return false;
1434
+ var result = true;
944
1435
 
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)];
1436
+ forEach(UrlMatcher.prototype, function(val, name) {
1437
+ if (isFunction(val)) {
1438
+ result = result && (isDefined(o[name]) && isFunction(o[name]));
1439
+ }
949
1440
  });
950
- }
1441
+ return result;
1442
+ };
951
1443
 
952
1444
  /**
953
1445
  * @ngdoc function
954
- * @name ui.router.router.$urlRouterProvider#rule
955
- * @methodOf ui.router.router.$urlRouterProvider
1446
+ * @name ui.router.util.$urlMatcherFactory#type
1447
+ * @methodOf ui.router.util.$urlMatcherFactory
956
1448
  *
957
1449
  * @description
958
- * Defines rules that are used by `$urlRouterProvider to find matches for
959
- * specific URLs.
1450
+ * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to
1451
+ * generate URLs with typed parameters.
1452
+ *
1453
+ * @param {string} name The type name.
1454
+ * @param {Object|Function} definition The type definition. See
1455
+ * {@link ui.router.util.type:Type `Type`} for information on the values accepted.
1456
+ * @param {Object|Function} definitionFn (optional) A function that is injected before the app
1457
+ * runtime starts. The result of this function is merged into the existing `definition`.
1458
+ * See {@link ui.router.util.type:Type `Type`} for information on the values accepted.
1459
+ *
1460
+ * @returns {Object} Returns `$urlMatcherFactoryProvider`.
960
1461
  *
961
1462
  * @example
1463
+ * This is a simple example of a custom type that encodes and decodes items from an
1464
+ * array, using the array index as the URL-encoded value:
1465
+ *
962
1466
  * <pre>
963
- * var app = angular.module('app', ['ui.router.router']);
1467
+ * var list = ['John', 'Paul', 'George', 'Ringo'];
1468
+ *
1469
+ * $urlMatcherFactoryProvider.type('listItem', {
1470
+ * encode: function(item) {
1471
+ * // Represent the list item in the URL using its corresponding index
1472
+ * return list.indexOf(item);
1473
+ * },
1474
+ * decode: function(item) {
1475
+ * // Look up the list item by index
1476
+ * return list[parseInt(item, 10)];
1477
+ * },
1478
+ * is: function(item) {
1479
+ * // Ensure the item is valid by checking to see that it appears
1480
+ * // in the list
1481
+ * return list.indexOf(item) > -1;
1482
+ * }
1483
+ * });
1484
+ *
1485
+ * $stateProvider.state('list', {
1486
+ * url: "/list/{item:listItem}",
1487
+ * controller: function($scope, $stateParams) {
1488
+ * console.log($stateParams.item);
1489
+ * }
1490
+ * });
1491
+ *
1492
+ * // ...
1493
+ *
1494
+ * // Changes URL to '/list/3', logs "Ringo" to the console
1495
+ * $state.go('list', { item: "Ringo" });
1496
+ * </pre>
1497
+ *
1498
+ * This is a more complex example of a type that relies on dependency injection to
1499
+ * interact with services, and uses the parameter name from the URL to infer how to
1500
+ * handle encoding and decoding parameter values:
1501
+ *
1502
+ * <pre>
1503
+ * // Defines a custom type that gets a value from a service,
1504
+ * // where each service gets different types of values from
1505
+ * // a backend API:
1506
+ * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {
1507
+ *
1508
+ * // Matches up services to URL parameter names
1509
+ * var services = {
1510
+ * user: Users,
1511
+ * post: Posts
1512
+ * };
1513
+ *
1514
+ * return {
1515
+ * encode: function(object) {
1516
+ * // Represent the object in the URL using its unique ID
1517
+ * return object.id;
1518
+ * },
1519
+ * decode: function(value, key) {
1520
+ * // Look up the object by ID, using the parameter
1521
+ * // name (key) to call the correct service
1522
+ * return services[key].findById(value);
1523
+ * },
1524
+ * is: function(object, key) {
1525
+ * // Check that object is a valid dbObject
1526
+ * return angular.isObject(object) && object.id && services[key];
1527
+ * }
1528
+ * equals: function(a, b) {
1529
+ * // Check the equality of decoded objects by comparing
1530
+ * // their unique IDs
1531
+ * return a.id === b.id;
1532
+ * }
1533
+ * };
1534
+ * });
1535
+ *
1536
+ * // In a config() block, you can then attach URLs with
1537
+ * // type-annotated parameters:
1538
+ * $stateProvider.state('users', {
1539
+ * url: "/users",
1540
+ * // ...
1541
+ * }).state('users.item', {
1542
+ * url: "/{user:dbObject}",
1543
+ * controller: function($scope, $stateParams) {
1544
+ * // $stateParams.user will now be an object returned from
1545
+ * // the Users service
1546
+ * },
1547
+ * // ...
1548
+ * });
1549
+ * </pre>
1550
+ */
1551
+ this.type = function (name, definition, definitionFn) {
1552
+ if (!isDefined(definition)) return $types[name];
1553
+ if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined.");
1554
+
1555
+ $types[name] = new Type(extend({ name: name }, definition));
1556
+ if (definitionFn) {
1557
+ typeQueue.push({ name: name, def: definitionFn });
1558
+ if (!enqueue) flushTypeQueue();
1559
+ }
1560
+ return this;
1561
+ };
1562
+
1563
+ // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s
1564
+ function flushTypeQueue() {
1565
+ while(typeQueue.length) {
1566
+ var type = typeQueue.shift();
1567
+ if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime.");
1568
+ angular.extend($types[type.name], injector.invoke(type.def));
1569
+ }
1570
+ }
1571
+
1572
+ // Register default types. Store them in the prototype of $types.
1573
+ forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); });
1574
+ $types = inherit($types, {});
1575
+
1576
+ /* No need to document $get, since it returns this */
1577
+ this.$get = ['$injector', function ($injector) {
1578
+ injector = $injector;
1579
+ enqueue = false;
1580
+ flushTypeQueue();
1581
+
1582
+ forEach(defaultTypes, function(type, name) {
1583
+ if (!$types[name]) $types[name] = new Type(type);
1584
+ });
1585
+ return this;
1586
+ }];
1587
+
1588
+ this.Param = function Param(id, type, config, location) {
1589
+ var self = this;
1590
+ config = unwrapShorthand(config);
1591
+ type = getType(config, type, location);
1592
+ var arrayMode = getArrayMode();
1593
+ type = arrayMode ? type.$asArray(arrayMode, location === "search") : type;
1594
+ if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined)
1595
+ config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to ""
1596
+ var isOptional = config.value !== undefined;
1597
+ var squash = getSquashPolicy(config, isOptional);
1598
+ var replace = getReplace(config, arrayMode, isOptional, squash);
1599
+
1600
+ function unwrapShorthand(config) {
1601
+ var keys = isObject(config) ? objectKeys(config) : [];
1602
+ var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 &&
1603
+ indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1;
1604
+ if (isShorthand) config = { value: config };
1605
+ config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; };
1606
+ return config;
1607
+ }
1608
+
1609
+ function getType(config, urlType, location) {
1610
+ if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations.");
1611
+ if (urlType) return urlType;
1612
+ if (!config.type) return (location === "config" ? $types.any : $types.string);
1613
+
1614
+ if (angular.isString(config.type))
1615
+ return $types[config.type];
1616
+ if (config.type instanceof Type)
1617
+ return config.type;
1618
+ return new Type(config.type);
1619
+ }
1620
+
1621
+ // array config: param name (param[]) overrides default settings. explicit config overrides param name.
1622
+ function getArrayMode() {
1623
+ var arrayDefaults = { array: (location === "search" ? "auto" : false) };
1624
+ var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {};
1625
+ return extend(arrayDefaults, arrayParamNomenclature, config).array;
1626
+ }
1627
+
1628
+ /**
1629
+ * returns false, true, or the squash value to indicate the "default parameter url squash policy".
1630
+ */
1631
+ function getSquashPolicy(config, isOptional) {
1632
+ var squash = config.squash;
1633
+ if (!isOptional || squash === false) return false;
1634
+ if (!isDefined(squash) || squash == null) return defaultSquashPolicy;
1635
+ if (squash === true || isString(squash)) return squash;
1636
+ throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string");
1637
+ }
1638
+
1639
+ function getReplace(config, arrayMode, isOptional, squash) {
1640
+ var replace, configuredKeys, defaultPolicy = [
1641
+ { from: "", to: (isOptional || arrayMode ? undefined : "") },
1642
+ { from: null, to: (isOptional || arrayMode ? undefined : "") }
1643
+ ];
1644
+ replace = isArray(config.replace) ? config.replace : [];
1645
+ if (isString(squash))
1646
+ replace.push({ from: squash, to: undefined });
1647
+ configuredKeys = map(replace, function(item) { return item.from; } );
1648
+ return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace);
1649
+ }
1650
+
1651
+ /**
1652
+ * [Internal] Get the default value of a parameter, which may be an injectable function.
1653
+ */
1654
+ function $$getDefaultValue() {
1655
+ if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
1656
+ var defaultValue = injector.invoke(config.$$fn);
1657
+ if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue))
1658
+ throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")");
1659
+ return defaultValue;
1660
+ }
1661
+
1662
+ /**
1663
+ * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the
1664
+ * default value, which may be the result of an injectable function.
1665
+ */
1666
+ function $value(value) {
1667
+ function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; }
1668
+ function $replace(value) {
1669
+ var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; });
1670
+ return replacement.length ? replacement[0] : value;
1671
+ }
1672
+ value = $replace(value);
1673
+ return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value);
1674
+ }
1675
+
1676
+ function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; }
1677
+
1678
+ extend(this, {
1679
+ id: id,
1680
+ type: type,
1681
+ location: location,
1682
+ array: arrayMode,
1683
+ squash: squash,
1684
+ replace: replace,
1685
+ isOptional: isOptional,
1686
+ value: $value,
1687
+ dynamic: undefined,
1688
+ config: config,
1689
+ toString: toString
1690
+ });
1691
+ };
1692
+
1693
+ function ParamSet(params) {
1694
+ extend(this, params || {});
1695
+ }
1696
+
1697
+ ParamSet.prototype = {
1698
+ $$new: function() {
1699
+ return inherit(this, extend(new ParamSet(), { $$parent: this}));
1700
+ },
1701
+ $$keys: function () {
1702
+ var keys = [], chain = [], parent = this,
1703
+ ignore = objectKeys(ParamSet.prototype);
1704
+ while (parent) { chain.push(parent); parent = parent.$$parent; }
1705
+ chain.reverse();
1706
+ forEach(chain, function(paramset) {
1707
+ forEach(objectKeys(paramset), function(key) {
1708
+ if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key);
1709
+ });
1710
+ });
1711
+ return keys;
1712
+ },
1713
+ $$values: function(paramValues) {
1714
+ var values = {}, self = this;
1715
+ forEach(self.$$keys(), function(key) {
1716
+ values[key] = self[key].value(paramValues && paramValues[key]);
1717
+ });
1718
+ return values;
1719
+ },
1720
+ $$equals: function(paramValues1, paramValues2) {
1721
+ var equal = true, self = this;
1722
+ forEach(self.$$keys(), function(key) {
1723
+ var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key];
1724
+ if (!self[key].type.equals(left, right)) equal = false;
1725
+ });
1726
+ return equal;
1727
+ },
1728
+ $$validates: function $$validate(paramValues) {
1729
+ var keys = this.$$keys(), i, param, rawVal, normalized, encoded;
1730
+ for (i = 0; i < keys.length; i++) {
1731
+ param = this[keys[i]];
1732
+ rawVal = paramValues[keys[i]];
1733
+ if ((rawVal === undefined || rawVal === null) && param.isOptional)
1734
+ break; // There was no parameter value, but the param is optional
1735
+ normalized = param.type.$normalize(rawVal);
1736
+ if (!param.type.is(normalized))
1737
+ return false; // The value was not of the correct Type, and could not be decoded to the correct Type
1738
+ encoded = param.type.encode(normalized);
1739
+ if (angular.isString(encoded) && !param.type.pattern.exec(encoded))
1740
+ return false; // The value was of the correct type, but when encoded, did not match the Type's regexp
1741
+ }
1742
+ return true;
1743
+ },
1744
+ $$parent: undefined
1745
+ };
1746
+
1747
+ this.ParamSet = ParamSet;
1748
+ }
1749
+
1750
+ // Register as a provider so it's available to other providers
1751
+ angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory);
1752
+ angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]);
1753
+
1754
+ /**
1755
+ * @ngdoc object
1756
+ * @name ui.router.router.$urlRouterProvider
1757
+ *
1758
+ * @requires ui.router.util.$urlMatcherFactoryProvider
1759
+ * @requires $locationProvider
1760
+ *
1761
+ * @description
1762
+ * `$urlRouterProvider` has the responsibility of watching `$location`.
1763
+ * When `$location` changes it runs through a list of rules one by one until a
1764
+ * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify
1765
+ * a url in a state configuration. All urls are compiled into a UrlMatcher object.
1766
+ *
1767
+ * There are several methods on `$urlRouterProvider` that make it useful to use directly
1768
+ * in your module config.
1769
+ */
1770
+ $UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
1771
+ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
1772
+ var rules = [], otherwise = null, interceptDeferred = false, listener;
1773
+
1774
+ // Returns a string that is a prefix of all strings matching the RegExp
1775
+ function regExpPrefix(re) {
1776
+ var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source);
1777
+ return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : '';
1778
+ }
1779
+
1780
+ // Interpolates matched values into a String.replace()-style pattern
1781
+ function interpolate(pattern, match) {
1782
+ return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) {
1783
+ return match[what === '$' ? 0 : Number(what)];
1784
+ });
1785
+ }
1786
+
1787
+ /**
1788
+ * @ngdoc function
1789
+ * @name ui.router.router.$urlRouterProvider#rule
1790
+ * @methodOf ui.router.router.$urlRouterProvider
1791
+ *
1792
+ * @description
1793
+ * Defines rules that are used by `$urlRouterProvider` to find matches for
1794
+ * specific URLs.
1795
+ *
1796
+ * @example
1797
+ * <pre>
1798
+ * var app = angular.module('app', ['ui.router.router']);
964
1799
  *
965
1800
  * app.config(function ($urlRouterProvider) {
966
1801
  * // Here's an example of how you might allow case insensitive urls
@@ -975,17 +1810,16 @@ function $UrlRouterProvider( $urlMatcherFactory) {
975
1810
  * });
976
1811
  * </pre>
977
1812
  *
978
- * @param {object} rule Handler function that takes `$injector` and `$location`
1813
+ * @param {function} rule Handler function that takes `$injector` and `$location`
979
1814
  * services as arguments. You can use them to return a valid path as a string.
980
1815
  *
981
- * @return {object} $urlRouterProvider - $urlRouterProvider instance
1816
+ * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
982
1817
  */
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
- };
1818
+ this.rule = function (rule) {
1819
+ if (!isFunction(rule)) throw new Error("'rule' must be a function");
1820
+ rules.push(rule);
1821
+ return this;
1822
+ };
989
1823
 
990
1824
  /**
991
1825
  * @ngdoc object
@@ -993,7 +1827,7 @@ function $UrlRouterProvider( $urlMatcherFactory) {
993
1827
  * @methodOf ui.router.router.$urlRouterProvider
994
1828
  *
995
1829
  * @description
996
- * Defines a path that is used when an invalied route is requested.
1830
+ * Defines a path that is used when an invalid route is requested.
997
1831
  *
998
1832
  * @example
999
1833
  * <pre>
@@ -1007,27 +1841,26 @@ function $UrlRouterProvider( $urlMatcherFactory) {
1007
1841
  *
1008
1842
  * // Example of using function rule as param
1009
1843
  * $urlRouterProvider.otherwise(function ($injector, $location) {
1010
- * ...
1844
+ * return '/a/valid/url';
1011
1845
  * });
1012
1846
  * });
1013
1847
  * </pre>
1014
1848
  *
1015
- * @param {string|object} rule The url path you want to redirect to or a function
1849
+ * @param {string|function} rule The url path you want to redirect to or a function
1016
1850
  * rule that returns the url path. The function version is passed two params:
1017
- * `$injector` and `$location` services.
1851
+ * `$injector` and `$location` services, and must return a url string.
1018
1852
  *
1019
- * @return {object} $urlRouterProvider - $urlRouterProvider instance
1853
+ * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
1020
1854
  */
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
- };
1855
+ this.otherwise = function (rule) {
1856
+ if (isString(rule)) {
1857
+ var redirect = rule;
1858
+ rule = function () { return redirect; };
1859
+ }
1860
+ else if (!isFunction(rule)) throw new Error("'rule' must be a function");
1861
+ otherwise = rule;
1862
+ return this;
1863
+ };
1031
1864
 
1032
1865
 
1033
1866
  function handleIfMatch($injector, handler, match) {
@@ -1042,9 +1875,11 @@ function $UrlRouterProvider( $urlMatcherFactory) {
1042
1875
  * @methodOf ui.router.router.$urlRouterProvider
1043
1876
  *
1044
1877
  * @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).
1878
+ * Registers a handler for a given url matching.
1879
+ *
1880
+ * If the handler is a string, it is
1881
+ * treated as a redirect, and is interpolated according to the syntax of match
1882
+ * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
1048
1883
  *
1049
1884
  * If the handler is a function, it is injectable. It gets invoked if `$location`
1050
1885
  * matches. You have the option of inject the match object as `$match`.
@@ -1071,54 +1906,104 @@ function $UrlRouterProvider( $urlMatcherFactory) {
1071
1906
  * </pre>
1072
1907
  *
1073
1908
  * @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.
1909
+ * @param {string|function} handler The path you want to redirect your user to.
1075
1910
  */
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
- });
1911
+ this.when = function (what, handler) {
1912
+ var redirect, handlerIsString = isString(handler);
1913
+ if (isString(what)) what = $urlMatcherFactory.compile(what);
1914
+
1915
+ if (!handlerIsString && !isFunction(handler) && !isArray(handler))
1916
+ throw new Error("invalid 'handler' in when()");
1917
+
1918
+ var strategies = {
1919
+ matcher: function (what, handler) {
1920
+ if (handlerIsString) {
1921
+ redirect = $urlMatcherFactory.compile(handler);
1922
+ handler = ['$match', function ($match) { return redirect.format($match); }];
1108
1923
  }
1109
- };
1110
-
1111
- var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };
1924
+ return extend(function ($injector, $location) {
1925
+ return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));
1926
+ }, {
1927
+ prefix: isString(what.prefix) ? what.prefix : ''
1928
+ });
1929
+ },
1930
+ regex: function (what, handler) {
1931
+ if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky");
1112
1932
 
1113
- for (var n in check) {
1114
- if (check[n]) {
1115
- return this.rule(strategies[n](what, handler));
1933
+ if (handlerIsString) {
1934
+ redirect = handler;
1935
+ handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
1116
1936
  }
1937
+ return extend(function ($injector, $location) {
1938
+ return handleIfMatch($injector, handler, what.exec($location.path()));
1939
+ }, {
1940
+ prefix: regExpPrefix(what)
1941
+ });
1117
1942
  }
1118
-
1119
- throw new Error("invalid 'what' in when()");
1120
1943
  };
1121
1944
 
1945
+ var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };
1946
+
1947
+ for (var n in check) {
1948
+ if (check[n]) return this.rule(strategies[n](what, handler));
1949
+ }
1950
+
1951
+ throw new Error("invalid 'what' in when()");
1952
+ };
1953
+
1954
+ /**
1955
+ * @ngdoc function
1956
+ * @name ui.router.router.$urlRouterProvider#deferIntercept
1957
+ * @methodOf ui.router.router.$urlRouterProvider
1958
+ *
1959
+ * @description
1960
+ * Disables (or enables) deferring location change interception.
1961
+ *
1962
+ * If you wish to customize the behavior of syncing the URL (for example, if you wish to
1963
+ * defer a transition but maintain the current URL), call this method at configuration time.
1964
+ * Then, at run time, call `$urlRouter.listen()` after you have configured your own
1965
+ * `$locationChangeSuccess` event handler.
1966
+ *
1967
+ * @example
1968
+ * <pre>
1969
+ * var app = angular.module('app', ['ui.router.router']);
1970
+ *
1971
+ * app.config(function ($urlRouterProvider) {
1972
+ *
1973
+ * // Prevent $urlRouter from automatically intercepting URL changes;
1974
+ * // this allows you to configure custom behavior in between
1975
+ * // location changes and route synchronization:
1976
+ * $urlRouterProvider.deferIntercept();
1977
+ *
1978
+ * }).run(function ($rootScope, $urlRouter, UserService) {
1979
+ *
1980
+ * $rootScope.$on('$locationChangeSuccess', function(e) {
1981
+ * // UserService is an example service for managing user state
1982
+ * if (UserService.isLoggedIn()) return;
1983
+ *
1984
+ * // Prevent $urlRouter's default handler from firing
1985
+ * e.preventDefault();
1986
+ *
1987
+ * UserService.handleLogin().then(function() {
1988
+ * // Once the user has logged in, sync the current URL
1989
+ * // to the router:
1990
+ * $urlRouter.sync();
1991
+ * });
1992
+ * });
1993
+ *
1994
+ * // Configures $urlRouter's listener *after* your custom listener
1995
+ * $urlRouter.listen();
1996
+ * });
1997
+ * </pre>
1998
+ *
1999
+ * @param {boolean} defer Indicates whether to defer location change interception. Passing
2000
+ no parameter is equivalent to `true`.
2001
+ */
2002
+ this.deferIntercept = function (defer) {
2003
+ if (defer === undefined) defer = true;
2004
+ interceptDeferred = defer;
2005
+ };
2006
+
1122
2007
  /**
1123
2008
  * @ngdoc object
1124
2009
  * @name ui.router.router.$urlRouter
@@ -1126,66 +2011,174 @@ function $UrlRouterProvider( $urlMatcherFactory) {
1126
2011
  * @requires $location
1127
2012
  * @requires $rootScope
1128
2013
  * @requires $injector
2014
+ * @requires $browser
1129
2015
  *
1130
2016
  * @description
1131
2017
  *
1132
2018
  */
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;
2019
+ this.$get = $get;
2020
+ $get.$inject = ['$location', '$rootScope', '$injector', '$browser', '$sniffer'];
2021
+ function $get( $location, $rootScope, $injector, $browser, $sniffer) {
2022
+
2023
+ var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl;
2024
+
2025
+ function appendBasePath(url, isHtml5, absolute) {
2026
+ if (baseHref === '/') return url;
2027
+ if (isHtml5) return baseHref.slice(0, -1) + url;
2028
+ if (absolute) return baseHref.slice(1) + url;
2029
+ return url;
2030
+ }
2031
+
2032
+ // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
2033
+ function update(evt) {
2034
+ if (evt && evt.defaultPrevented) return;
2035
+ var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;
2036
+ lastPushedUrl = undefined;
2037
+ // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573
2038
+ //if (ignoreUpdate) return true;
2039
+
2040
+ function check(rule) {
2041
+ var handled = rule($injector, $location);
2042
+
2043
+ if (!handled) return false;
2044
+ if (isString(handled)) $location.replace().url(handled);
2045
+ return true;
2046
+ }
2047
+ var n = rules.length, i;
2048
+
2049
+ for (i = 0; i < n; i++) {
2050
+ if (check(rules[i])) return;
2051
+ }
2052
+ // always check otherwise last to allow dynamic updates to the set of rules
2053
+ if (otherwise) check(otherwise);
2054
+ }
2055
+
2056
+ function listen() {
2057
+ listener = listener || $rootScope.$on('$locationChangeSuccess', update);
2058
+ return listener;
2059
+ }
2060
+
2061
+ if (!interceptDeferred) listen();
2062
+
2063
+ return {
2064
+ /**
2065
+ * @ngdoc function
2066
+ * @name ui.router.router.$urlRouter#sync
2067
+ * @methodOf ui.router.router.$urlRouter
2068
+ *
2069
+ * @description
2070
+ * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.
2071
+ * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,
2072
+ * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed
2073
+ * with the transition by calling `$urlRouter.sync()`.
2074
+ *
2075
+ * @example
2076
+ * <pre>
2077
+ * angular.module('app', ['ui.router'])
2078
+ * .run(function($rootScope, $urlRouter) {
2079
+ * $rootScope.$on('$locationChangeSuccess', function(evt) {
2080
+ * // Halt state change from even starting
2081
+ * evt.preventDefault();
2082
+ * // Perform custom logic
2083
+ * var meetsRequirement = ...
2084
+ * // Continue with the update and state transition if logic allows
2085
+ * if (meetsRequirement) $urlRouter.sync();
2086
+ * });
2087
+ * });
2088
+ * </pre>
2089
+ */
2090
+ sync: function() {
2091
+ update();
2092
+ },
2093
+
2094
+ listen: function() {
2095
+ return listen();
2096
+ },
2097
+
2098
+ update: function(read) {
2099
+ if (read) {
2100
+ location = $location.url();
2101
+ return;
1146
2102
  }
1147
- var n=rules.length, i;
1148
- for (i=0; i<n; i++) {
1149
- if (check(rules[i])) return;
2103
+ if ($location.url() === location) return;
2104
+
2105
+ $location.url(location);
2106
+ $location.replace();
2107
+ },
2108
+
2109
+ push: function(urlMatcher, params, options) {
2110
+ var url = urlMatcher.format(params || {});
2111
+
2112
+ // Handle the special hash param, if needed
2113
+ if (url !== null && params && params['#']) {
2114
+ url += '#' + params['#'];
1150
2115
  }
1151
- // always check otherwise last to allow dynamic updates to the set of rules
1152
- if (otherwise) check(otherwise);
1153
- }
1154
2116
 
1155
- $rootScope.$on('$locationChangeSuccess', update);
2117
+ $location.url(url);
2118
+ lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;
2119
+ if (options && options.replace) $location.replace();
2120
+ },
1156
2121
 
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();
2122
+ /**
2123
+ * @ngdoc function
2124
+ * @name ui.router.router.$urlRouter#href
2125
+ * @methodOf ui.router.router.$urlRouter
2126
+ *
2127
+ * @description
2128
+ * A URL generation method that returns the compiled URL for a given
2129
+ * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters.
2130
+ *
2131
+ * @example
2132
+ * <pre>
2133
+ * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
2134
+ * person: "bob"
2135
+ * });
2136
+ * // $bob == "/about/bob";
2137
+ * </pre>
2138
+ *
2139
+ * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate.
2140
+ * @param {object=} params An object of parameter values to fill the matcher's required parameters.
2141
+ * @param {object=} options Options object. The options are:
2142
+ *
2143
+ * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
2144
+ *
2145
+ * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`
2146
+ */
2147
+ href: function(urlMatcher, params, options) {
2148
+ if (!urlMatcher.validates(params)) return null;
2149
+
2150
+ var isHtml5 = $locationProvider.html5Mode();
2151
+ if (angular.isObject(isHtml5)) {
2152
+ isHtml5 = isHtml5.enabled;
1186
2153
  }
1187
- };
1188
- }];
2154
+
2155
+ isHtml5 = isHtml5 && $sniffer.history;
2156
+
2157
+ var url = urlMatcher.format(params);
2158
+ options = options || {};
2159
+
2160
+ if (!isHtml5 && url !== null) {
2161
+ url = "#" + $locationProvider.hashPrefix() + url;
2162
+ }
2163
+
2164
+ // Handle special hash param, if needed
2165
+ if (url !== null && params && params['#']) {
2166
+ url += '#' + params['#'];
2167
+ }
2168
+
2169
+ url = appendBasePath(url, isHtml5, options.absolute);
2170
+
2171
+ if (!options.absolute || !url) {
2172
+ return url;
2173
+ }
2174
+
2175
+ var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();
2176
+ port = (port === 80 || port === 443 ? '' : ':' + port);
2177
+
2178
+ return [$location.protocol(), '://', $location.host(), port, slash, url].join('');
2179
+ }
2180
+ };
2181
+ }
1189
2182
  }
1190
2183
 
1191
2184
  angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);
@@ -1196,7 +2189,6 @@ angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);
1196
2189
  *
1197
2190
  * @requires ui.router.router.$urlRouterProvider
1198
2191
  * @requires ui.router.util.$urlMatcherFactoryProvider
1199
- * @requires $locationProvider
1200
2192
  *
1201
2193
  * @description
1202
2194
  * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely
@@ -1212,8 +2204,8 @@ angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);
1212
2204
  *
1213
2205
  * The `$stateProvider` provides interfaces to declare these states for your app.
1214
2206
  */
1215
- $StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider', '$locationProvider'];
1216
- function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $locationProvider) {
2207
+ $StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];
2208
+ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
1217
2209
 
1218
2210
  var root, states = {}, $state, queue = {}, abstractKey = 'abstract';
1219
2211
 
@@ -1234,25 +2226,21 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1234
2226
  // inherit 'data' from parent and override by own values (if any)
1235
2227
  data: function(state) {
1236
2228
  if (state.parent && state.parent.data) {
1237
- state.data = state.self.data = extend({}, state.parent.data, state.data);
2229
+ state.data = state.self.data = inherit(state.parent.data, state.data);
1238
2230
  }
1239
2231
  return state.data;
1240
2232
  },
1241
2233
 
1242
2234
  // Build a URLMatcher if necessary, either via a relative or absolute URL
1243
2235
  url: function(state) {
1244
- var url = state.url;
2236
+ var url = state.url, config = { params: state.params || {} };
1245
2237
 
1246
2238
  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);
2239
+ if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config);
2240
+ return (state.parent.navigable || root).url.concat(url, config);
1251
2241
  }
1252
2242
 
1253
- if ($urlMatcherFactory.isMatcher(url) || url == null) {
1254
- return url;
1255
- }
2243
+ if (!url || $urlMatcherFactory.isMatcher(url)) return url;
1256
2244
  throw new Error("Invalid url '" + url + "' in state '" + state + "'");
1257
2245
  },
1258
2246
 
@@ -1261,14 +2249,19 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1261
2249
  return state.url ? state : (state.parent ? state.parent.navigable : null);
1262
2250
  },
1263
2251
 
2252
+ // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params
2253
+ ownParams: function(state) {
2254
+ var params = state.url && state.url.params || new $$UMFP.ParamSet();
2255
+ forEach(state.params || {}, function(config, id) {
2256
+ if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config");
2257
+ });
2258
+ return params;
2259
+ },
2260
+
1264
2261
  // Derive parameters for this state and ensure they're a super-set of parent's parameters
1265
2262
  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;
2263
+ var ownParams = pick(state.ownParams, state.ownParams.$$keys());
2264
+ return state.parent && state.parent.params ? extend(state.parent.params.$$new(), ownParams) : new $$UMFP.ParamSet();
1272
2265
  },
1273
2266
 
1274
2267
  // If there is no explicit multi-view configuration, make one up so we don't have
@@ -1281,31 +2274,12 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1281
2274
 
1282
2275
  forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) {
1283
2276
  if (name.indexOf('@') < 0) name += '@' + state.parent.name;
2277
+ view.resolveAs = view.resolveAs || state.resolveAs || '$resolve';
1284
2278
  views[name] = view;
1285
2279
  });
1286
2280
  return views;
1287
2281
  },
1288
2282
 
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
2283
  // Keep a full path from the root down to this state as this is needed for state activation.
1310
2284
  path: function(state) {
1311
2285
  return state.parent ? state.parent.path.concat(state) : []; // exclude root from path
@@ -1326,12 +2300,16 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1326
2300
  }
1327
2301
 
1328
2302
  function findState(stateOrName, base) {
2303
+ if (!stateOrName) return undefined;
2304
+
1329
2305
  var isStr = isString(stateOrName),
1330
2306
  name = isStr ? stateOrName : stateOrName.name,
1331
2307
  path = isRelative(name);
1332
2308
 
1333
2309
  if (path) {
1334
2310
  if (!base) throw new Error("No reference point given for path '" + name + "'");
2311
+ base = findState(base);
2312
+
1335
2313
  var rel = name.split("."), i = 0, pathLength = rel.length, current = base;
1336
2314
 
1337
2315
  for (; i < pathLength; i++) {
@@ -1364,6 +2342,13 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1364
2342
  queue[parentName].push(state);
1365
2343
  }
1366
2344
 
2345
+ function flushQueuedChildren(parentName) {
2346
+ var queued = queue[parentName] || [];
2347
+ while(queued.length) {
2348
+ registerState(queued.shift());
2349
+ }
2350
+ }
2351
+
1367
2352
  function registerState(state) {
1368
2353
  // Wrap a new object around the state so we can store our private details easily.
1369
2354
  state = inherit(state, {
@@ -1374,11 +2359,12 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1374
2359
 
1375
2360
  var name = state.name;
1376
2361
  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");
2362
+ if (states.hasOwnProperty(name)) throw new Error("State '" + name + "' is already defined");
1378
2363
 
1379
2364
  // Get parent name
1380
2365
  var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.'))
1381
2366
  : (isString(state.parent)) ? state.parent
2367
+ : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name
1382
2368
  : '';
1383
2369
 
1384
2370
  // If parent is not registered yet, add state to queue and register later
@@ -1395,17 +2381,13 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1395
2381
  if (!state[abstractKey] && state.url) {
1396
2382
  $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {
1397
2383
  if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {
1398
- $state.transitionTo(state, $match, { location: false });
2384
+ $state.transitionTo(state, $match, { inherit: true, location: false });
1399
2385
  }
1400
2386
  }]);
1401
2387
  }
1402
2388
 
1403
2389
  // 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
- }
2390
+ flushQueuedChildren(name);
1409
2391
 
1410
2392
  return state;
1411
2393
  }
@@ -1420,14 +2402,21 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1420
2402
  var globSegments = glob.split('.'),
1421
2403
  segments = $state.$current.name.split('.');
1422
2404
 
2405
+ //match single stars
2406
+ for (var i = 0, l = globSegments.length; i < l; i++) {
2407
+ if (globSegments[i] === '*') {
2408
+ segments[i] = '*';
2409
+ }
2410
+ }
2411
+
1423
2412
  //match greedy starts
1424
2413
  if (globSegments[0] === '**') {
1425
- segments = segments.slice(segments.indexOf(globSegments[1]));
2414
+ segments = segments.slice(indexOf(segments, globSegments[1]));
1426
2415
  segments.unshift('**');
1427
2416
  }
1428
2417
  //match greedy ends
1429
2418
  if (globSegments[globSegments.length - 1] === '**') {
1430
- segments.splice(segments.indexOf(globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);
2419
+ segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);
1431
2420
  segments.push('**');
1432
2421
  }
1433
2422
 
@@ -1435,13 +2424,6 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1435
2424
  return false;
1436
2425
  }
1437
2426
 
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
2427
  return segments.join('') === globSegments.join('');
1446
2428
  }
1447
2429
 
@@ -1489,7 +2471,8 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1489
2471
  * - **parent** `{object}` - returns the parent state object.
1490
2472
  * - **data** `{object}` - returns state data, including any inherited data that is not
1491
2473
  * overridden by own values (if any).
1492
- * - **url** `{object}` - returns a {link ui.router.util.type:UrlMatcher} or null.
2474
+ * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher}
2475
+ * or `null`.
1493
2476
  * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is
1494
2477
  * navigable).
1495
2478
  * - **params** `{object}` - returns an array of state params that are ensured to
@@ -1505,17 +2488,17 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1505
2488
  * - **path** `{string}` - returns the full path from the root down to this state.
1506
2489
  * Needed for state activation.
1507
2490
  * - **includes** `{object}` - returns an object that includes every state that
1508
- * would pass a '$state.includes()' test.
2491
+ * would pass a `$state.includes()` test.
1509
2492
  *
1510
2493
  * @example
1511
2494
  * <pre>
1512
2495
  * // Override the internal 'views' builder with a function that takes the state
1513
2496
  * // definition, and a reference to the internal function being overridden:
1514
- * $stateProvider.decorator('views', function ($state, parent) {
2497
+ * $stateProvider.decorator('views', function (state, parent) {
1515
2498
  * var result = {},
1516
2499
  * views = parent(state);
1517
2500
  *
1518
- * angular.forEach(view, function (config, name) {
2501
+ * angular.forEach(views, function (config, name) {
1519
2502
  * var autoName = (state.name + '.' + name).replace('.', '/');
1520
2503
  * config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
1521
2504
  * result[name] = config;
@@ -1571,9 +2554,12 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1571
2554
  * Registers a state configuration under a given state name. The stateConfig object
1572
2555
  * has the following acceptable properties.
1573
2556
  *
2557
+ * @param {string} name A unique state name, e.g. "home", "about", "contacts".
2558
+ * To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
2559
+ * @param {object} stateConfig State configuration object.
2560
+ * @param {string|function=} stateConfig.template
1574
2561
  * <a id='template'></a>
1575
- *
1576
- * - **`template`** - {string|function=} - html template as a string or a function that returns
2562
+ * html template as a string or a function that returns
1577
2563
  * an html template as a string which should be used by the uiView directives. This property
1578
2564
  * takes precedence over templateUrl.
1579
2565
  *
@@ -1582,9 +2568,17 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1582
2568
  * - {array.&lt;object&gt;} - state parameters extracted from the current $location.path() by
1583
2569
  * applying the current state
1584
2570
  *
2571
+ * <pre>template:
2572
+ * "<h1>inline template definition</h1>" +
2573
+ * "<div ui-view></div>"</pre>
2574
+ * <pre>template: function(params) {
2575
+ * return "<h1>generated template</h1>"; }</pre>
2576
+ * </div>
2577
+ *
2578
+ * @param {string|function=} stateConfig.templateUrl
1585
2579
  * <a id='templateUrl'></a>
1586
2580
  *
1587
- * - **`templateUrl`** - {string|function=} - path or function that returns a path to an html
2581
+ * path or function that returns a path to an html
1588
2582
  * template that should be used by uiView.
1589
2583
  *
1590
2584
  * If `templateUrl` is a function, it will be called with the following parameters:
@@ -1592,82 +2586,261 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1592
2586
  * - {array.&lt;object&gt;} - state parameters extracted from the current $location.path() by
1593
2587
  * applying the current state
1594
2588
  *
1595
- * <a id='templateProvider'></a>
2589
+ * <pre>templateUrl: "home.html"</pre>
2590
+ * <pre>templateUrl: function(params) {
2591
+ * return myTemplates[params.pageId]; }</pre>
1596
2592
  *
1597
- * - **`templateProvider`** - {function=} - Provider function that returns HTML content
1598
- * string.
2593
+ * @param {function=} stateConfig.templateProvider
2594
+ * <a id='templateProvider'></a>
2595
+ * Provider function that returns HTML content string.
2596
+ * <pre> templateProvider:
2597
+ * function(MyTemplateService, params) {
2598
+ * return MyTemplateService.getTemplate(params.pageId);
2599
+ * }</pre>
1599
2600
  *
2601
+ * @param {string|function=} stateConfig.controller
1600
2602
  * <a id='controller'></a>
1601
2603
  *
1602
- * - **`controller`** - {string|function=} - Controller fn that should be associated with newly
2604
+ * Controller fn that should be associated with newly
1603
2605
  * related scope or the name of a registered controller if passed as a string.
2606
+ * Optionally, the ControllerAs may be declared here.
2607
+ * <pre>controller: "MyRegisteredController"</pre>
2608
+ * <pre>controller:
2609
+ * "MyRegisteredController as fooCtrl"}</pre>
2610
+ * <pre>controller: function($scope, MyService) {
2611
+ * $scope.data = MyService.getData(); }</pre>
1604
2612
  *
2613
+ * @param {function=} stateConfig.controllerProvider
1605
2614
  * <a id='controllerProvider'></a>
1606
2615
  *
1607
- * - **`controllerProvider`** - {function=} - Injectable provider function that returns
1608
- * the actual controller or string.
2616
+ * Injectable provider function that returns the actual controller or string.
2617
+ * <pre>controllerProvider:
2618
+ * function(MyResolveData) {
2619
+ * if (MyResolveData.foo)
2620
+ * return "FooCtrl"
2621
+ * else if (MyResolveData.bar)
2622
+ * return "BarCtrl";
2623
+ * else return function($scope) {
2624
+ * $scope.baz = "Qux";
2625
+ * }
2626
+ * }</pre>
1609
2627
  *
2628
+ * @param {string=} stateConfig.controllerAs
1610
2629
  * <a id='controllerAs'></a>
1611
2630
  *
1612
- * - **`controllerAs`** – {string=} – A controller alias name. If present the controller will be
2631
+ * A controller alias name. If present the controller will be
1613
2632
  * published to scope under the controllerAs name.
2633
+ * <pre>controllerAs: "myCtrl"</pre>
2634
+ *
2635
+ * @param {string|object=} stateConfig.parent
2636
+ * <a id='parent'></a>
2637
+ * Optionally specifies the parent state of this state.
1614
2638
  *
2639
+ * <pre>parent: 'parentState'</pre>
2640
+ * <pre>parent: parentState // JS variable</pre>
2641
+ *
2642
+ * @param {object=} stateConfig.resolve
1615
2643
  * <a id='resolve'></a>
1616
2644
  *
1617
- * - **`resolve`** - {object.&lt;string, function&gt;=} - An optional map of dependencies which
2645
+ * An optional map&lt;string, function&gt; of dependencies which
1618
2646
  * 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:
2647
+ * the router will wait for them all to be resolved before the controller is instantiated.
2648
+ * If all the promises are resolved successfully, the $stateChangeSuccess event is fired
2649
+ * and the values of the resolved promises are injected into any controllers that reference them.
2650
+ * If any of the promises are rejected the $stateChangeError event is fired.
2651
+ *
2652
+ * The map object is:
1623
2653
  *
1624
2654
  * - key - {string}: name of dependency to be injected into controller
1625
2655
  * - factory - {string|function}: If string then it is alias for service. Otherwise if function,
1626
2656
  * it is injected and return value it treated as dependency. If result is a promise, it is
1627
2657
  * resolved before its value is injected into controller.
1628
2658
  *
2659
+ * <pre>resolve: {
2660
+ * myResolve1:
2661
+ * function($http, $stateParams) {
2662
+ * return $http.get("/api/foos/"+stateParams.fooID);
2663
+ * }
2664
+ * }</pre>
2665
+ *
2666
+ * @param {string=} stateConfig.url
1629
2667
  * <a id='url'></a>
1630
2668
  *
1631
- * - **`url`** - {string=} - A url with optional parameters. When a state is navigated or
2669
+ * A url fragment with optional parameters. When a state is navigated or
1632
2670
  * transitioned to, the `$stateParams` service will be populated with any
1633
2671
  * parameters that were passed.
1634
2672
  *
1635
- * <a id='params'></a>
2673
+ * (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for
2674
+ * more details on acceptable patterns )
1636
2675
  *
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.
2676
+ * examples:
2677
+ * <pre>url: "/home"
2678
+ * url: "/users/:userid"
2679
+ * url: "/books/{bookid:[a-zA-Z_-]}"
2680
+ * url: "/books/{categoryid:int}"
2681
+ * url: "/books/{publishername:string}/{categoryid:int}"
2682
+ * url: "/messages?before&after"
2683
+ * url: "/messages?{before:date}&{after:date}"
2684
+ * url: "/messages/:mailboxid?{before:date}&{after:date}"
2685
+ * </pre>
1641
2686
  *
2687
+ * @param {object=} stateConfig.views
1642
2688
  * <a id='views'></a>
2689
+ * an optional map&lt;string, object&gt; which defined multiple views, or targets views
2690
+ * manually/explicitly.
1643
2691
  *
1644
- * - **`views`** - {object=} - Use the views property to set up multiple views or to target views
1645
- * manually/explicitly.
2692
+ * Examples:
1646
2693
  *
1647
- * <a id='abstract'></a>
2694
+ * Targets three named `ui-view`s in the parent state's template
2695
+ * <pre>views: {
2696
+ * header: {
2697
+ * controller: "headerCtrl",
2698
+ * templateUrl: "header.html"
2699
+ * }, body: {
2700
+ * controller: "bodyCtrl",
2701
+ * templateUrl: "body.html"
2702
+ * }, footer: {
2703
+ * controller: "footCtrl",
2704
+ * templateUrl: "footer.html"
2705
+ * }
2706
+ * }</pre>
2707
+ *
2708
+ * Targets named `ui-view="header"` from grandparent state 'top''s template, and named `ui-view="body" from parent state's template.
2709
+ * <pre>views: {
2710
+ * 'header@top': {
2711
+ * controller: "msgHeaderCtrl",
2712
+ * templateUrl: "msgHeader.html"
2713
+ * }, 'body': {
2714
+ * controller: "messagesCtrl",
2715
+ * templateUrl: "messages.html"
2716
+ * }
2717
+ * }</pre>
1648
2718
  *
1649
- * - **`abstract`** - {boolean=} - An abstract state will never be directly activated,
2719
+ * @param {boolean=} [stateConfig.abstract=false]
2720
+ * <a id='abstract'></a>
2721
+ * An abstract state will never be directly activated,
1650
2722
  * but can provide inherited properties to its common children states.
2723
+ * <pre>abstract: true</pre>
1651
2724
  *
2725
+ * @param {function=} stateConfig.onEnter
1652
2726
  * <a id='onEnter'></a>
1653
2727
  *
1654
- * - **`onEnter`** - {object=} - Callback function for when a state is entered. Good way
2728
+ * Callback function for when a state is entered. Good way
1655
2729
  * to trigger an action or dispatch an event, such as opening a dialog.
2730
+ * If minifying your scripts, make sure to explicitly annotate this function,
2731
+ * because it won't be automatically annotated by your build tools.
2732
+ *
2733
+ * <pre>onEnter: function(MyService, $stateParams) {
2734
+ * MyService.foo($stateParams.myParam);
2735
+ * }</pre>
1656
2736
  *
2737
+ * @param {function=} stateConfig.onExit
1657
2738
  * <a id='onExit'></a>
1658
2739
  *
1659
- * - **`onExit`** - {object=} - Callback function for when a state is exited. Good way to
2740
+ * Callback function for when a state is exited. Good way to
1660
2741
  * trigger an action or dispatch an event, such as opening a dialog.
2742
+ * If minifying your scripts, make sure to explicitly annotate this function,
2743
+ * because it won't be automatically annotated by your build tools.
1661
2744
  *
2745
+ * <pre>onExit: function(MyService, $stateParams) {
2746
+ * MyService.cleanup($stateParams.myParam);
2747
+ * }</pre>
2748
+ *
2749
+ * @param {boolean=} [stateConfig.reloadOnSearch=true]
1662
2750
  * <a id='reloadOnSearch'></a>
1663
2751
  *
1664
- * - **`reloadOnSearch = true`** - {boolean=} - If `false`, will not retrigger the same state
2752
+ * If `false`, will not retrigger the same state
1665
2753
  * just because a search/query parameter has changed (via $location.search() or $location.hash()).
1666
2754
  * Useful for when you'd like to modify $location.search() without triggering a reload.
2755
+ * <pre>reloadOnSearch: false</pre>
1667
2756
  *
2757
+ * @param {object=} stateConfig.data
1668
2758
  * <a id='data'></a>
1669
2759
  *
1670
- * - **`data`** - {object=} - Arbitrary data object, useful for custom configuration.
2760
+ * Arbitrary data object, useful for custom configuration. The parent state's `data` is
2761
+ * prototypally inherited. In other words, adding a data property to a state adds it to
2762
+ * the entire subtree via prototypal inheritance.
2763
+ *
2764
+ * <pre>data: {
2765
+ * requiredRole: 'foo'
2766
+ * } </pre>
2767
+ *
2768
+ * @param {object=} stateConfig.params
2769
+ * <a id='params'></a>
2770
+ *
2771
+ * A map which optionally configures parameters declared in the `url`, or
2772
+ * defines additional non-url parameters. For each parameter being
2773
+ * configured, add a configuration object keyed to the name of the parameter.
2774
+ *
2775
+ * Each parameter configuration object may contain the following properties:
2776
+ *
2777
+ * - ** value ** - {object|function=}: specifies the default value for this
2778
+ * parameter. This implicitly sets this parameter as optional.
2779
+ *
2780
+ * When UI-Router routes to a state and no value is
2781
+ * specified for this parameter in the URL or transition, the
2782
+ * default value will be used instead. If `value` is a function,
2783
+ * it will be injected and invoked, and the return value used.
2784
+ *
2785
+ * *Note*: `undefined` is treated as "no default value" while `null`
2786
+ * is treated as "the default value is `null`".
2787
+ *
2788
+ * *Shorthand*: If you only need to configure the default value of the
2789
+ * parameter, you may use a shorthand syntax. In the **`params`**
2790
+ * map, instead mapping the param name to a full parameter configuration
2791
+ * object, simply set map it to the default parameter value, e.g.:
2792
+ *
2793
+ * <pre>// define a parameter's default value
2794
+ * params: {
2795
+ * param1: { value: "defaultValue" }
2796
+ * }
2797
+ * // shorthand default values
2798
+ * params: {
2799
+ * param1: "defaultValue",
2800
+ * param2: "param2Default"
2801
+ * }</pre>
2802
+ *
2803
+ * - ** array ** - {boolean=}: *(default: false)* If true, the param value will be
2804
+ * treated as an array of values. If you specified a Type, the value will be
2805
+ * treated as an array of the specified Type. Note: query parameter values
2806
+ * default to a special `"auto"` mode.
2807
+ *
2808
+ * For query parameters in `"auto"` mode, if multiple values for a single parameter
2809
+ * are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values
2810
+ * are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`). However, if
2811
+ * only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single
2812
+ * value (e.g.: `{ foo: '1' }`).
2813
+ *
2814
+ * <pre>params: {
2815
+ * param1: { array: true }
2816
+ * }</pre>
2817
+ *
2818
+ * - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when
2819
+ * the current parameter value is the same as the default value. If `squash` is not set, it uses the
2820
+ * configured default squash policy.
2821
+ * (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`})
2822
+ *
2823
+ * There are three squash settings:
2824
+ *
2825
+ * - false: The parameter's default value is not squashed. It is encoded and included in the URL
2826
+ * - true: The parameter's default value is omitted from the URL. If the parameter is preceeded and followed
2827
+ * by slashes in the state's `url` declaration, then one of those slashes are omitted.
2828
+ * This can allow for cleaner looking URLs.
2829
+ * - `"<arbitrary string>"`: The parameter's default value is replaced with an arbitrary placeholder of your choice.
2830
+ *
2831
+ * <pre>params: {
2832
+ * param1: {
2833
+ * value: "defaultId",
2834
+ * squash: true
2835
+ * } }
2836
+ * // squash "defaultValue" to "~"
2837
+ * params: {
2838
+ * param1: {
2839
+ * value: "defaultValue",
2840
+ * squash: "~"
2841
+ * } }
2842
+ * </pre>
2843
+ *
1671
2844
  *
1672
2845
  * @example
1673
2846
  * <pre>
@@ -1676,7 +2849,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1676
2849
  * // stateName can be a single top-level name (must be unique).
1677
2850
  * $stateProvider.state("home", {});
1678
2851
  *
1679
- * // Or it can be a nested state name. This state is a child of the
2852
+ * // Or it can be a nested state name. This state is a child of the
1680
2853
  * // above "home" state.
1681
2854
  * $stateProvider.state("home.newest", {});
1682
2855
  *
@@ -1690,9 +2863,6 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1690
2863
  * .state("contacts", {});
1691
2864
  * </pre>
1692
2865
  *
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
2866
  */
1697
2867
  this.state = state;
1698
2868
  function state(name, definition) {
@@ -1713,6 +2883,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1713
2883
  * @requires $injector
1714
2884
  * @requires ui.router.util.$resolve
1715
2885
  * @requires ui.router.state.$stateParams
2886
+ * @requires ui.router.router.$urlRouter
1716
2887
  *
1717
2888
  * @property {object} params A param object, e.g. {sectionId: section.id)}, that
1718
2889
  * you'd like to test against the current active state.
@@ -1726,26 +2897,82 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1726
2897
  * between them. It also provides interfaces to ask for current state or even states
1727
2898
  * you're coming from.
1728
2899
  */
1729
- // $urlRouter is injected just to ensure it gets instantiated
1730
2900
  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) {
2901
+ $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory'];
2902
+ function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) {
1733
2903
 
1734
2904
  var TransitionSuperseded = $q.reject(new Error('transition superseded'));
1735
2905
  var TransitionPrevented = $q.reject(new Error('transition prevented'));
1736
2906
  var TransitionAborted = $q.reject(new Error('transition aborted'));
1737
2907
  var TransitionFailed = $q.reject(new Error('transition failed'));
1738
- var currentLocation = $location.url();
1739
- var baseHref = $browser.baseHref();
1740
2908
 
1741
- function syncUrl() {
1742
- if ($location.url() !== currentLocation) {
1743
- $location.url(currentLocation);
1744
- $location.replace();
2909
+ // Handles the case where a state which is the target of a transition is not found, and the user
2910
+ // can optionally retry or defer the transition
2911
+ function handleRedirect(redirect, state, params, options) {
2912
+ /**
2913
+ * @ngdoc event
2914
+ * @name ui.router.state.$state#$stateNotFound
2915
+ * @eventOf ui.router.state.$state
2916
+ * @eventType broadcast on root scope
2917
+ * @description
2918
+ * Fired when a requested state **cannot be found** using the provided state name during transition.
2919
+ * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
2920
+ * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler,
2921
+ * you can see its three properties in the example. You can use `event.preventDefault()` to abort the
2922
+ * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value.
2923
+ *
2924
+ * @param {Object} event Event object.
2925
+ * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties.
2926
+ * @param {State} fromState Current state object.
2927
+ * @param {Object} fromParams Current state params.
2928
+ *
2929
+ * @example
2930
+ *
2931
+ * <pre>
2932
+ * // somewhere, assume lazy.state has not been defined
2933
+ * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
2934
+ *
2935
+ * // somewhere else
2936
+ * $scope.$on('$stateNotFound',
2937
+ * function(event, unfoundState, fromState, fromParams){
2938
+ * console.log(unfoundState.to); // "lazy.state"
2939
+ * console.log(unfoundState.toParams); // {a:1, b:2}
2940
+ * console.log(unfoundState.options); // {inherit:false} + default options
2941
+ * })
2942
+ * </pre>
2943
+ */
2944
+ var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params);
2945
+
2946
+ if (evt.defaultPrevented) {
2947
+ $urlRouter.update();
2948
+ return TransitionAborted;
2949
+ }
2950
+
2951
+ if (!evt.retry) {
2952
+ return null;
2953
+ }
2954
+
2955
+ // Allow the handler to return a promise to defer state lookup retry
2956
+ if (options.$retry) {
2957
+ $urlRouter.update();
2958
+ return TransitionFailed;
1745
2959
  }
2960
+ var retryTransition = $state.transition = $q.when(evt.retry);
2961
+
2962
+ retryTransition.then(function() {
2963
+ if (retryTransition !== $state.transition) return TransitionSuperseded;
2964
+ redirect.options.$retry = true;
2965
+ return $state.transitionTo(redirect.to, redirect.toParams, redirect.options);
2966
+ }, function() {
2967
+ return TransitionAborted;
2968
+ });
2969
+ $urlRouter.update();
2970
+
2971
+ return retryTransition;
1746
2972
  }
1747
2973
 
1748
2974
  root.locals = { resolve: null, globals: { $stateParams: {} } };
2975
+
1749
2976
  $state = {
1750
2977
  params: {},
1751
2978
  current: root.self,
@@ -1759,8 +2986,8 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1759
2986
  * @methodOf ui.router.state.$state
1760
2987
  *
1761
2988
  * @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).
2989
+ * A method that force reloads the current state. All resolves are re-resolved,
2990
+ * controllers reinstantiated, and events re-fired.
1764
2991
  *
1765
2992
  * @example
1766
2993
  * <pre>
@@ -1776,12 +3003,37 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1776
3003
  * `reload()` is just an alias for:
1777
3004
  * <pre>
1778
3005
  * $state.transitionTo($state.current, $stateParams, {
1779
- * reload: true, inherit: false, notify: false
3006
+ * reload: true, inherit: false, notify: true
3007
+ * });
3008
+ * </pre>
3009
+ *
3010
+ * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved.
3011
+ * @example
3012
+ * <pre>
3013
+ * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item'
3014
+ * //and current state is 'contacts.detail.item'
3015
+ * var app angular.module('app', ['ui.router']);
3016
+ *
3017
+ * app.controller('ctrl', function ($scope, $state) {
3018
+ * $scope.reload = function(){
3019
+ * //will reload 'contact.detail' and 'contact.detail.item' states
3020
+ * $state.reload('contact.detail');
3021
+ * }
3022
+ * });
3023
+ * </pre>
3024
+ *
3025
+ * `reload()` is just an alias for:
3026
+ * <pre>
3027
+ * $state.transitionTo($state.current, $stateParams, {
3028
+ * reload: true, inherit: false, notify: true
1780
3029
  * });
1781
3030
  * </pre>
3031
+
3032
+ * @returns {promise} A promise representing the state of the new transition. See
3033
+ * {@link ui.router.state.$state#methods_go $state.go}.
1782
3034
  */
1783
- $state.reload = function reload() {
1784
- $state.transitionTo($state.current, $stateParams, { reload: true, inherit: false, notify: false });
3035
+ $state.reload = function reload(state) {
3036
+ return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true});
1785
3037
  };
1786
3038
 
1787
3039
  /**
@@ -1818,7 +3070,8 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1818
3070
  *
1819
3071
  * @param {object=} params A map of the parameters that will be sent to the state,
1820
3072
  * 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
3073
+ * defined parameters. Only parameters specified in the state definition can be overridden, new
3074
+ * parameters will be ignored. This allows, for example, going to a sibling state that shares parameters
1822
3075
  * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e.
1823
3076
  * transitioning to a sibling will get you the parameters for all parents, transitioning to a child
1824
3077
  * will get you all current parameters, etc.
@@ -1830,9 +3083,10 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1830
3083
  * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
1831
3084
  * defines which state to be relative from.
1832
3085
  * - **`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.
3086
+ * - **`reload`** (v0.2.5) - {boolean=false|string|object}, If `true` will force transition even if no state or params
3087
+ * have changed. It will reload the resolves and views of the current state and parent states.
3088
+ * If `reload` is a string (or state object), the state object is fetched (by name, or object reference); and \
3089
+ * the transition reloads the resolves and views for that matched state, and all its children states.
1836
3090
  *
1837
3091
  * @returns {promise} A promise representing the state of the new transition.
1838
3092
  *
@@ -1851,7 +3105,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1851
3105
  *
1852
3106
  */
1853
3107
  $state.go = function go(to, params, options) {
1854
- return this.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options));
3108
+ return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options));
1855
3109
  };
1856
3110
 
1857
3111
  /**
@@ -1885,9 +3139,11 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1885
3139
  * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'),
1886
3140
  * defines which state to be relative from.
1887
3141
  * - **`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
3142
+ * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params
1889
3143
  * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
1890
3144
  * use this when you want to force a reload when *everything* is the same, including search params.
3145
+ * if String, then will reload the state with the name given in reload, and any children.
3146
+ * if Object, then a stateObj is expected, will reload the state found in stateObj, and any children.
1891
3147
  *
1892
3148
  * @returns {promise} A promise representing the state of the new transition. See
1893
3149
  * {@link ui.router.state.$state#methods_go $state.go}.
@@ -1901,64 +3157,15 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1901
3157
  var from = $state.$current, fromParams = $state.params, fromPath = from.path;
1902
3158
  var evt, toState = findState(to, options.relative);
1903
3159
 
3160
+ // Store the hash param for later (since it will be stripped out by various methods)
3161
+ var hash = toParams['#'];
3162
+
1904
3163
  if (!isDefined(toState)) {
1905
- // Broadcast not found event and abort the transition if prevented
1906
3164
  var redirect = { to: to, toParams: toParams, options: options };
3165
+ var redirectResult = handleRedirect(redirect, from.self, fromParams, options);
1907
3166
 
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;
3167
+ if (redirectResult) {
3168
+ return redirectResult;
1962
3169
  }
1963
3170
 
1964
3171
  // Always retry once if the $stateNotFound was not prevented
@@ -1967,39 +3174,73 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
1967
3174
  toParams = redirect.toParams;
1968
3175
  options = redirect.options;
1969
3176
  toState = findState(to, options.relative);
3177
+
1970
3178
  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 + "'");
3179
+ if (!options.relative) throw new Error("No such state '" + to + "'");
3180
+ throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'");
1973
3181
  }
1974
3182
  }
1975
3183
  if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'");
1976
3184
  if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState);
3185
+ if (!toState.params.$$validates(toParams)) return TransitionFailed;
3186
+
3187
+ toParams = toState.params.$$values(toParams);
1977
3188
  to = toState;
1978
3189
 
1979
3190
  var toPath = to.path;
1980
3191
 
1981
3192
  // 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;
3193
+ var keep = 0, state = toPath[keep], locals = root.locals, toLocals = [];
3194
+
3195
+ if (!options.reload) {
3196
+ while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) {
3197
+ locals = toLocals[keep] = state.locals;
3198
+ keep++;
3199
+ state = toPath[keep];
3200
+ }
3201
+ } else if (isString(options.reload) || isObject(options.reload)) {
3202
+ if (isObject(options.reload) && !options.reload.name) {
3203
+ throw new Error('Invalid reload state object');
3204
+ }
3205
+
3206
+ var reloadState = options.reload === true ? fromPath[0] : findState(options.reload);
3207
+ if (options.reload && !reloadState) {
3208
+ throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'");
3209
+ }
3210
+
3211
+ while (state && state === fromPath[keep] && state !== reloadState) {
3212
+ locals = toLocals[keep] = state.locals;
3213
+ keep++;
3214
+ state = toPath[keep];
3215
+ }
1987
3216
  }
1988
3217
 
1989
3218
  // If we're going to the same state and all locals are kept, we've got nothing to do.
1990
3219
  // 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();
3220
+ // TODO: We may not want to bump 'transition' if we're called from a location change
3221
+ // that we've initiated ourselves, because we might accidentally abort a legitimate
3222
+ // transition initiated from code?
3223
+ if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) {
3224
+ if (hash) toParams['#'] = hash;
3225
+ $state.params = toParams;
3226
+ copy($state.params, $stateParams);
3227
+ copy(filterByKeys(to.params.$$keys(), $stateParams), to.locals.globals.$stateParams);
3228
+ if (options.location && to.navigable && to.navigable.url) {
3229
+ $urlRouter.push(to.navigable.url, toParams, {
3230
+ $$avoidResync: true, replace: options.location === 'replace'
3231
+ });
3232
+ $urlRouter.update(true);
3233
+ }
1996
3234
  $state.transition = null;
1997
3235
  return $q.when($state.current);
1998
3236
  }
1999
3237
 
2000
- // Normalize/filter parameters before we pass them to event handlers etc.
2001
- toParams = normalize(to.params, toParams || {});
2002
-
3238
+ // Filter parameters before we pass them to event handlers etc.
3239
+ toParams = filterByKeys(to.params.$$keys(), toParams || {});
3240
+
3241
+ // Re-add the saved hash before we start returning things or broadcasting $stateChangeStart
3242
+ if (hash) toParams['#'] = hash;
3243
+
2003
3244
  // Broadcast start event and cancel the transition if requested
2004
3245
  if (options.notify) {
2005
3246
  /**
@@ -2029,9 +3270,10 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2029
3270
  * })
2030
3271
  * </pre>
2031
3272
  */
2032
- evt = $rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams);
2033
- if (evt.defaultPrevented) {
2034
- syncUrl();
3273
+ if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams, options).defaultPrevented) {
3274
+ $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams);
3275
+ //Don't update and resync url if there's been a new transition started. see issue #2238, #600
3276
+ if ($state.transition == null) $urlRouter.update();
2035
3277
  return TransitionPrevented;
2036
3278
  }
2037
3279
  }
@@ -2044,9 +3286,10 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2044
3286
  // empty and gets filled asynchronously. We need to keep track of the promise for the
2045
3287
  // (fully resolved) current locals, and pass this down the chain.
2046
3288
  var resolved = $q.when(locals);
2047
- for (var l=keep; l<toPath.length; l++, state=toPath[l]) {
3289
+
3290
+ for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
2048
3291
  locals = toLocals[l] = inherit(locals);
2049
- resolved = resolveState(state, toParams, state===to, resolved, locals);
3292
+ resolved = resolveState(state, toParams, state === to, resolved, locals, options);
2050
3293
  }
2051
3294
 
2052
3295
  // Once everything is resolved, we are ready to perform the actual transition
@@ -2059,7 +3302,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2059
3302
  if ($state.transition !== transition) return TransitionSuperseded;
2060
3303
 
2061
3304
  // Exit 'from' states not kept
2062
- for (l=fromPath.length-1; l>=keep; l--) {
3305
+ for (l = fromPath.length - 1; l >= keep; l--) {
2063
3306
  exiting = fromPath[l];
2064
3307
  if (exiting.self.onExit) {
2065
3308
  $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals);
@@ -2068,7 +3311,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2068
3311
  }
2069
3312
 
2070
3313
  // Enter 'to' states not kept
2071
- for (l=keep; l<toPath.length; l++) {
3314
+ for (l = keep; l < toPath.length; l++) {
2072
3315
  entering = toPath[l];
2073
3316
  entering.locals = toLocals[l];
2074
3317
  if (entering.self.onEnter) {
@@ -2086,14 +3329,10 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2086
3329
  copy($state.params, $stateParams);
2087
3330
  $state.transition = null;
2088
3331
 
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
- }
3332
+ if (options.location && to.navigable) {
3333
+ $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, {
3334
+ $$avoidResync: true, replace: options.location === 'replace'
3335
+ });
2097
3336
  }
2098
3337
 
2099
3338
  if (options.notify) {
@@ -2113,10 +3352,10 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2113
3352
  */
2114
3353
  $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
2115
3354
  }
2116
- currentLocation = $location.url();
3355
+ $urlRouter.update(true);
2117
3356
 
2118
3357
  return $state.current;
2119
- }, function (error) {
3358
+ }).then(null, function (error) {
2120
3359
  if ($state.transition !== transition) return TransitionSuperseded;
2121
3360
 
2122
3361
  $state.transition = null;
@@ -2138,8 +3377,11 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2138
3377
  * @param {Object} fromParams The params supplied to the `fromState`.
2139
3378
  * @param {Error} error The resolve error object.
2140
3379
  */
2141
- $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);
2142
- syncUrl();
3380
+ evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);
3381
+
3382
+ if (!evt.defaultPrevented) {
3383
+ $urlRouter.update();
3384
+ }
2143
3385
 
2144
3386
  return $q.reject(error);
2145
3387
  });
@@ -2154,35 +3396,40 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2154
3396
  *
2155
3397
  * @description
2156
3398
  * 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
3399
+ * but only checks for the full state name. If params is supplied then it will be
3400
+ * tested for strict equality against the current active params object, so all params
2159
3401
  * must match with none missing and no extras.
2160
3402
  *
2161
3403
  * @example
2162
3404
  * <pre>
3405
+ * $state.$current.name = 'contacts.details.item';
3406
+ *
3407
+ * // absolute name
2163
3408
  * $state.is('contact.details.item'); // returns true
2164
3409
  * $state.is(contactDetailItemStateObject); // returns true
2165
3410
  *
2166
- * // everything else would return false
3411
+ * // relative name (. and ^), typically from a template
3412
+ * // E.g. from the 'contacts.details' template
3413
+ * <div ng-class="{highlighted: $state.is('.item')}">Item</div>
2167
3414
  * </pre>
2168
3415
  *
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
3416
+ * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check.
3417
+ * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like
2171
3418
  * to test against the current active state.
3419
+ * @param {object=} options An options object. The options are:
3420
+ *
3421
+ * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will
3422
+ * test relative to `options.relative` state (or name).
3423
+ *
2172
3424
  * @returns {boolean} Returns true if it is the state.
2173
3425
  */
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
- }
3426
+ $state.is = function is(stateOrName, params, options) {
3427
+ options = extend({ relative: $state.$current }, options || {});
3428
+ var state = findState(stateOrName, options.relative);
2184
3429
 
2185
- return isDefined(params) && params !== null ? angular.equals($stateParams, params) : true;
3430
+ if (!isDefined(state)) { return undefined; }
3431
+ if ($state.$current !== state) { return false; }
3432
+ return params ? equalForKeys(state.params.$$values(params), $stateParams) : true;
2186
3433
  };
2187
3434
 
2188
3435
  /**
@@ -2191,25 +3438,28 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2191
3438
  * @methodOf ui.router.state.$state
2192
3439
  *
2193
3440
  * @description
2194
- * A method to determine if the current active state is equal to or is the child of the
3441
+ * A method to determine if the current active state is equal to or is the child of the
2195
3442
  * state stateName. If any params are passed then they will be tested for a match as well.
2196
3443
  * Not all the parameters need to be passed, just the ones you'd like to test for equality.
2197
3444
  *
2198
3445
  * @example
3446
+ * Partial and relative names
2199
3447
  * <pre>
2200
3448
  * $state.$current.name = 'contacts.details.item';
2201
3449
  *
3450
+ * // Using partial names
2202
3451
  * $state.includes("contacts"); // returns true
2203
3452
  * $state.includes("contacts.details"); // returns true
2204
3453
  * $state.includes("contacts.details.item"); // returns true
2205
3454
  * $state.includes("contacts.list"); // returns false
2206
3455
  * $state.includes("about"); // returns false
2207
- * </pre>
2208
3456
  *
2209
- * @description
2210
- * Basic globing patterns will also work.
3457
+ * // Using relative names (. and ^), typically from a template
3458
+ * // E.g. from the 'contacts.details' template
3459
+ * <div ng-class="{highlighted: $state.includes('.item')}">Item</div>
3460
+ * </pre>
2211
3461
  *
2212
- * @example
3462
+ * Basic globbing patterns
2213
3463
  * <pre>
2214
3464
  * $state.$current.name = 'contacts.details.item.url';
2215
3465
  *
@@ -2222,37 +3472,30 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2222
3472
  * $state.includes("item.**"); // returns false
2223
3473
  * </pre>
2224
3474
  *
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}`,
3475
+ * @param {string} stateOrName A partial name, relative name, or glob pattern
3476
+ * to be searched for within the current state name.
3477
+ * @param {object=} params A param object, e.g. `{sectionId: section.id}`,
2227
3478
  * that you'd like to test against the current active state.
3479
+ * @param {object=} options An options object. The options are:
3480
+ *
3481
+ * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set,
3482
+ * .includes will test relative to `options.relative` state (or name).
3483
+ *
2228
3484
  * @returns {boolean} Returns true if it does include the state
2229
3485
  */
2230
-
2231
- $state.includes = function includes(stateOrName, params) {
3486
+ $state.includes = function includes(stateOrName, params, options) {
3487
+ options = extend({ relative: $state.$current }, options || {});
2232
3488
  if (isString(stateOrName) && isGlob(stateOrName)) {
2233
- if (doesStateMatchGlob(stateOrName)) {
2234
- stateOrName = $state.$current.name;
2235
- } else {
3489
+ if (!doesStateMatchGlob(stateOrName)) {
2236
3490
  return false;
2237
3491
  }
3492
+ stateOrName = $state.$current.name;
2238
3493
  }
2239
3494
 
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;
3495
+ var state = findState(stateOrName, options.relative);
3496
+ if (!isDefined(state)) { return undefined; }
3497
+ if (!isDefined($state.$current.includes[state.name])) { return false; }
3498
+ return params ? equalForKeys(state.params.$$values(params), $stateParams, objectKeys(params)) : true;
2256
3499
  };
2257
3500
 
2258
3501
 
@@ -2276,7 +3519,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2276
3519
  * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the
2277
3520
  * first parameter, then the constructed href url will be built from the first navigable ancestor (aka
2278
3521
  * ancestor with a valid url).
2279
- * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url.
3522
+ * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
2280
3523
  * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
2281
3524
  * defines which state to be relative from.
2282
3525
  * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
@@ -2284,33 +3527,26 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2284
3527
  * @returns {string} compiled state url
2285
3528
  */
2286
3529
  $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;
3530
+ options = extend({
3531
+ lossy: true,
3532
+ inherit: true,
3533
+ absolute: false,
3534
+ relative: $state.$current
3535
+ }, options || {});
2290
3536
 
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
- }
3537
+ var state = findState(stateOrName, options.relative);
2297
3538
 
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
- }
3539
+ if (!isDefined(state)) return null;
3540
+ if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state);
3541
+
3542
+ var nav = (state && options.lossy) ? state.navigable : state;
2305
3543
 
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;
3544
+ if (!nav || nav.url === undefined || nav.url === null) {
3545
+ return null;
2312
3546
  }
2313
- return url;
3547
+ return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), {
3548
+ absolute: options.absolute
3549
+ });
2314
3550
  };
2315
3551
 
2316
3552
  /**
@@ -2321,26 +3557,23 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2321
3557
  * @description
2322
3558
  * Returns the state configuration object for any specific state or all states.
2323
3559
  *
2324
- * @param {string|object=} stateOrName If provided, will only get the config for
3560
+ * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for
2325
3561
  * 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.
3562
+ * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context.
3563
+ * @returns {Object|Array} State configuration object or array of all objects.
2327
3564
  */
2328
3565
  $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);
3566
+ if (arguments.length === 0) return map(objectKeys(states), function(name) { return states[name].self; });
3567
+ var state = findState(stateOrName, context || $state.$current);
2335
3568
  return (state && state.self) ? state.self : null;
2336
3569
  };
2337
3570
 
2338
- function resolveState(state, params, paramsAreFiltered, inherited, dst) {
3571
+ function resolveState(state, params, paramsAreFiltered, inherited, dst, options) {
2339
3572
  // Make a restricted $stateParams with only the parameters that apply to this state if
2340
3573
  // necessary. In addition to being available to the controller and onEnter/onExit callbacks,
2341
3574
  // we also need $stateParams to be available for any $injector calls we make during the
2342
3575
  // dependency resolution process.
2343
- var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params, params);
3576
+ var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
2344
3577
  var locals = { $stateParams: $stateParams };
2345
3578
 
2346
3579
  // Resolve 'global' dependencies for the state, i.e. those not specific to a view.
@@ -2348,35 +3581,44 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2348
3581
  // to the set that should be visible to the state, and are independent of when we update
2349
3582
  // the global $state and $stateParams values.
2350
3583
  dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
2351
- var promises = [ dst.resolve.then(function (globals) {
3584
+ var promises = [dst.resolve.then(function (globals) {
2352
3585
  dst.globals = globals;
2353
- }) ];
3586
+ })];
2354
3587
  if (inherited) promises.push(inherited);
2355
3588
 
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
- });
3589
+ function resolveViews() {
3590
+ var viewsPromises = [];
3591
+
3592
+ // Resolve template and dependencies for all views.
3593
+ forEach(state.views, function (view, name) {
3594
+ var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {});
3595
+ injectables.$template = [ function () {
3596
+ return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || '';
3597
+ }];
3598
+
3599
+ viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) {
3600
+ // References to the controller (only instantiated at link time)
3601
+ if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) {
3602
+ var injectLocals = angular.extend({}, injectables, dst.globals);
3603
+ result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals);
3604
+ } else {
3605
+ result.$$controller = view.controller;
3606
+ }
3607
+ // Provide access to the state itself for internal use
3608
+ result.$$state = state;
3609
+ result.$$controllerAs = view.controllerAs;
3610
+ result.$$resolveAs = view.resolveAs;
3611
+ dst[name] = result;
3612
+ }));
3613
+ });
3614
+
3615
+ return $q.all(viewsPromises).then(function(){
3616
+ return dst.globals;
3617
+ });
3618
+ }
2377
3619
 
2378
3620
  // Wait for all the promises and then return the activation object
2379
- return $q.all(promises).then(function (values) {
3621
+ return $q.all(promises).then(resolveViews).then(function (values) {
2380
3622
  return dst;
2381
3623
  });
2382
3624
  }
@@ -2384,16 +3626,43 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
2384
3626
  return $state;
2385
3627
  }
2386
3628
 
2387
- function shouldTriggerReload(to, from, locals, options) {
2388
- if ( to === from && ((locals === from.locals && !options.reload) || (to.self.reloadOnSearch === false)) ) {
3629
+ function shouldSkipReload(to, toParams, from, fromParams, locals, options) {
3630
+ // Return true if there are no differences in non-search (path/object) params, false if there are differences
3631
+ function nonSearchParamsEqual(fromAndToState, fromParams, toParams) {
3632
+ // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params.
3633
+ function notSearchParam(key) {
3634
+ return fromAndToState.params[key].location != "search";
3635
+ }
3636
+ var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam);
3637
+ var nonQueryParams = pick.apply({}, [fromAndToState.params].concat(nonQueryParamKeys));
3638
+ var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams);
3639
+ return nonQueryParamSet.$$equals(fromParams, toParams);
3640
+ }
3641
+
3642
+ // If reload was not explicitly requested
3643
+ // and we're transitioning to the same state we're already in
3644
+ // and the locals didn't change
3645
+ // or they changed in a way that doesn't merit reloading
3646
+ // (reloadOnParams:false, or reloadOnSearch.false and only search params changed)
3647
+ // Then return true.
3648
+ if (!options.reload && to === from &&
3649
+ (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) {
2389
3650
  return true;
2390
3651
  }
2391
3652
  }
2392
3653
  }
2393
3654
 
2394
3655
  angular.module('ui.router.state')
2395
- .value('$stateParams', {})
2396
- .provider('$state', $StateProvider);
3656
+ .factory('$stateParams', function () { return {}; })
3657
+ .constant("$state.runtime", { autoinject: true })
3658
+ .provider('$state', $StateProvider)
3659
+ // Inject $state to initialize when entering runtime. #2574
3660
+ .run(['$injector', function ($injector) {
3661
+ // Allow tests (stateSpec.js) to turn this off by defining this constant
3662
+ if ($injector.get("$state.runtime").autoinject) {
3663
+ $injector.get('$state');
3664
+ }
3665
+ }]);
2397
3666
 
2398
3667
 
2399
3668
  $ViewProvider.$inject = [];
@@ -2433,32 +3702,6 @@ function $ViewProvider() {
2433
3702
  if (options.view) {
2434
3703
  result = $templateFactory.fromConfig(options.view, options.params, options.locals);
2435
3704
  }
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
3705
  return result;
2463
3706
  }
2464
3707
  };
@@ -2511,7 +3754,7 @@ function $ViewScrollProvider() {
2511
3754
  }
2512
3755
 
2513
3756
  return function ($element) {
2514
- $timeout(function () {
3757
+ return $timeout(function () {
2515
3758
  $element[0].scrollIntoView();
2516
3759
  }, 0, false);
2517
3760
  };
@@ -2536,7 +3779,7 @@ angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider)
2536
3779
  * @description
2537
3780
  * The ui-view directive tells $state where to place your templates.
2538
3781
  *
2539
- * @param {string=} ui-view A view name. The name should be unique amongst the other views in the
3782
+ * @param {string=} name A view name. The name should be unique amongst the other views in the
2540
3783
  * same state. You can have views of the same name that live in different states.
2541
3784
  *
2542
3785
  * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window
@@ -2548,27 +3791,27 @@ angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider)
2548
3791
  * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.*
2549
3792
  *
2550
3793
  * @param {string=} onload Expression to evaluate whenever the view updates.
2551
- *
3794
+ *
2552
3795
  * @example
2553
- * A view can be unnamed or named.
3796
+ * A view can be unnamed or named.
2554
3797
  * <pre>
2555
3798
  * <!-- Unnamed -->
2556
- * <div ui-view></div>
2557
- *
3799
+ * <div ui-view></div>
3800
+ *
2558
3801
  * <!-- Named -->
2559
3802
  * <div ui-view="viewName"></div>
2560
3803
  * </pre>
2561
3804
  *
2562
- * You can only have one unnamed view within any template (or root html). If you are only using a
3805
+ * You can only have one unnamed view within any template (or root html). If you are only using a
2563
3806
  * single view and it is unnamed then you can populate it like so:
2564
3807
  * <pre>
2565
- * <div ui-view></div>
3808
+ * <div ui-view></div>
2566
3809
  * $stateProvider.state("home", {
2567
3810
  * template: "<h1>HELLO!</h1>"
2568
3811
  * })
2569
3812
  * </pre>
2570
- *
2571
- * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`}
3813
+ *
3814
+ * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#methods_state `views`}
2572
3815
  * config property, by name, in this case an empty name:
2573
3816
  * <pre>
2574
3817
  * $stateProvider.state("home", {
@@ -2579,13 +3822,13 @@ angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider)
2579
3822
  * }
2580
3823
  * })
2581
3824
  * </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,
3825
+ *
3826
+ * But typically you'll only use the views property if you name your view or have more than one view
3827
+ * in the same template. There's not really a compelling reason to name a view if its the only one,
2585
3828
  * but you could if you wanted, like so:
2586
3829
  * <pre>
2587
3830
  * <div ui-view="main"></div>
2588
- * </pre>
3831
+ * </pre>
2589
3832
  * <pre>
2590
3833
  * $stateProvider.state("home", {
2591
3834
  * views: {
@@ -2595,14 +3838,14 @@ angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider)
2595
3838
  * }
2596
3839
  * })
2597
3840
  * </pre>
2598
- *
3841
+ *
2599
3842
  * Really though, you'll use views to set up multiple views:
2600
3843
  * <pre>
2601
3844
  * <div ui-view></div>
2602
- * <div ui-view="chart"></div>
2603
- * <div ui-view="data"></div>
3845
+ * <div ui-view="chart"></div>
3846
+ * <div ui-view="data"></div>
2604
3847
  * </pre>
2605
- *
3848
+ *
2606
3849
  * <pre>
2607
3850
  * $stateProvider.state("home", {
2608
3851
  * views: {
@@ -2632,9 +3875,28 @@ angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider)
2632
3875
  * <ui-view autoscroll='false'/>
2633
3876
  * <ui-view autoscroll='scopeVariable'/>
2634
3877
  * </pre>
3878
+ *
3879
+ * Resolve data:
3880
+ *
3881
+ * The resolved data from the state's `resolve` block is placed on the scope as `$resolve` (this
3882
+ * can be customized using [[ViewDeclaration.resolveAs]]). This can be then accessed from the template.
3883
+ *
3884
+ * Note that when `controllerAs` is being used, `$resolve` is set on the controller instance *after* the
3885
+ * controller is instantiated. The `$onInit()` hook can be used to perform initialization code which
3886
+ * depends on `$resolve` data.
3887
+ *
3888
+ * Example usage of $resolve in a view template
3889
+ * <pre>
3890
+ * $stateProvider.state('home', {
3891
+ * template: '<my-component user="$resolve.user"></my-component>',
3892
+ * resolve: {
3893
+ * user: function(UserService) { return UserService.fetchUser(); }
3894
+ * }
3895
+ * });
3896
+ * </pre>
2635
3897
  */
2636
- $ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll'];
2637
- function $ViewDirective( $state, $injector, $uiViewScroll) {
3898
+ $ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate', '$q'];
3899
+ function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate, $q) {
2638
3900
 
2639
3901
  function getService() {
2640
3902
  return ($injector.has) ? function(service) {
@@ -2664,8 +3926,20 @@ function $ViewDirective( $state, $injector, $uiViewScroll) {
2664
3926
 
2665
3927
  if ($animate) {
2666
3928
  return {
2667
- enter: function(element, target, cb) { $animate.enter(element, null, target, cb); },
2668
- leave: function(element, cb) { $animate.leave(element, cb); }
3929
+ enter: function(element, target, cb) {
3930
+ if (angular.version.minor > 2) {
3931
+ $animate.enter(element, null, target).then(cb);
3932
+ } else {
3933
+ $animate.enter(element, null, target, cb);
3934
+ }
3935
+ },
3936
+ leave: function(element, cb) {
3937
+ if (angular.version.minor > 2) {
3938
+ $animate.leave(element).then(cb);
3939
+ } else {
3940
+ $animate.leave(element, cb);
3941
+ }
3942
+ }
2669
3943
  };
2670
3944
  }
2671
3945
 
@@ -2691,14 +3965,12 @@ function $ViewDirective( $state, $injector, $uiViewScroll) {
2691
3965
  var previousEl, currentEl, currentScope, latestLocals,
2692
3966
  onloadExp = attrs.onload || '',
2693
3967
  autoScrollExp = attrs.autoscroll,
2694
- renderer = getRenderer(attrs, scope);
3968
+ renderer = getRenderer(attrs, scope),
3969
+ inherited = $element.inheritedData('$uiView');
2695
3970
 
2696
3971
  scope.$on('$stateChangeSuccess', function() {
2697
3972
  updateView(false);
2698
3973
  });
2699
- scope.$on('$viewContentLoading', function() {
2700
- updateView(false);
2701
- });
2702
3974
 
2703
3975
  updateView(true);
2704
3976
 
@@ -2714,7 +3986,9 @@ function $ViewDirective( $state, $injector, $uiViewScroll) {
2714
3986
  }
2715
3987
 
2716
3988
  if (currentEl) {
3989
+ var $uiViewData = currentEl.data('$uiViewAnim');
2717
3990
  renderer.leave(currentEl, function() {
3991
+ $uiViewData.$$animLeave.resolve();
2718
3992
  previousEl = null;
2719
3993
  });
2720
3994
 
@@ -2724,14 +3998,43 @@ function $ViewDirective( $state, $injector, $uiViewScroll) {
2724
3998
  }
2725
3999
 
2726
4000
  function updateView(firstTime) {
2727
- var newScope = scope.$new(),
2728
- name = currentEl && currentEl.data('$uiViewName'),
4001
+ var newScope,
4002
+ name = getUiViewName(scope, attrs, $element, $interpolate),
2729
4003
  previousLocals = name && $state.$current && $state.$current.locals[name];
2730
4004
 
2731
4005
  if (!firstTime && previousLocals === latestLocals) return; // nothing to do
4006
+ newScope = scope.$new();
4007
+ latestLocals = $state.$current.locals[name];
4008
+
4009
+ /**
4010
+ * @ngdoc event
4011
+ * @name ui.router.state.directive:ui-view#$viewContentLoading
4012
+ * @eventOf ui.router.state.directive:ui-view
4013
+ * @eventType emits on ui-view directive scope
4014
+ * @description
4015
+ *
4016
+ * Fired once the view **begins loading**, *before* the DOM is rendered.
4017
+ *
4018
+ * @param {Object} event Event object.
4019
+ * @param {string} viewName Name of the view.
4020
+ */
4021
+ newScope.$emit('$viewContentLoading', name);
2732
4022
 
2733
4023
  var clone = $transclude(newScope, function(clone) {
4024
+ var animEnter = $q.defer(), animLeave = $q.defer();
4025
+ var viewAnimData = {
4026
+ $animEnter: animEnter.promise,
4027
+ $animLeave: animLeave.promise,
4028
+ $$animLeave: animLeave
4029
+ };
4030
+
4031
+ clone.data('$uiViewAnim', viewAnimData);
2734
4032
  renderer.enter(clone, $element, function onUiViewEnter() {
4033
+ animEnter.resolve();
4034
+ if(currentScope) {
4035
+ currentScope.$emit('$viewContentAnimationEnded');
4036
+ }
4037
+
2735
4038
  if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) {
2736
4039
  $uiViewScroll(clone);
2737
4040
  }
@@ -2739,8 +4042,6 @@ function $ViewDirective( $state, $injector, $uiViewScroll) {
2739
4042
  cleanupLastView();
2740
4043
  });
2741
4044
 
2742
- latestLocals = $state.$current.locals[clone.data('$uiViewName')];
2743
-
2744
4045
  currentEl = clone;
2745
4046
  currentScope = newScope;
2746
4047
  /**
@@ -2748,12 +4049,13 @@ function $ViewDirective( $state, $injector, $uiViewScroll) {
2748
4049
  * @name ui.router.state.directive:ui-view#$viewContentLoaded
2749
4050
  * @eventOf ui.router.state.directive:ui-view
2750
4051
  * @eventType emits on ui-view directive scope
2751
- * @description *
4052
+ * @description
2752
4053
  * Fired once the view is **loaded**, *after* the DOM is rendered.
2753
4054
  *
2754
4055
  * @param {Object} event Event object.
4056
+ * @param {string} viewName Name of the view.
2755
4057
  */
2756
- currentScope.$emit('$viewContentLoaded');
4058
+ currentScope.$emit('$viewContentLoaded', name);
2757
4059
  currentScope.$eval(onloadExp);
2758
4060
  }
2759
4061
  };
@@ -2763,24 +4065,16 @@ function $ViewDirective( $state, $injector, $uiViewScroll) {
2763
4065
  return directive;
2764
4066
  }
2765
4067
 
2766
- $ViewDirectiveFill.$inject = ['$compile', '$controller', '$state'];
2767
- function $ViewDirectiveFill ($compile, $controller, $state) {
4068
+ $ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate'];
4069
+ function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate) {
2768
4070
  return {
2769
4071
  restrict: 'ECA',
2770
4072
  priority: -400,
2771
4073
  compile: function (tElement) {
2772
4074
  var initial = tElement.html();
2773
4075
  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
4076
  var current = $state.$current,
4077
+ name = getUiViewName(scope, attrs, $element, $interpolate),
2784
4078
  locals = current && current.locals[name];
2785
4079
 
2786
4080
  if (! locals) {
@@ -2790,14 +4084,20 @@ function $ViewDirectiveFill ($compile, $controller, $state) {
2790
4084
  $element.data('$uiView', { name: name, state: locals.$$state });
2791
4085
  $element.html(locals.$template ? locals.$template : initial);
2792
4086
 
4087
+ var resolveData = angular.extend({}, locals);
4088
+ scope[locals.$$resolveAs] = resolveData;
4089
+
2793
4090
  var link = $compile($element.contents());
2794
4091
 
2795
4092
  if (locals.$$controller) {
2796
4093
  locals.$scope = scope;
4094
+ locals.$element = $element;
2797
4095
  var controller = $controller(locals.$$controller, locals);
2798
4096
  if (locals.$$controllerAs) {
2799
4097
  scope[locals.$$controllerAs] = controller;
4098
+ scope[locals.$$controllerAs][locals.$$resolveAs] = resolveData;
2800
4099
  }
4100
+ if (isFunction(controller.$onInit)) controller.$onInit();
2801
4101
  $element.data('$ngControllerController', controller);
2802
4102
  $element.children().data('$ngControllerController', controller);
2803
4103
  }
@@ -2808,11 +4108,23 @@ function $ViewDirectiveFill ($compile, $controller, $state) {
2808
4108
  };
2809
4109
  }
2810
4110
 
4111
+ /**
4112
+ * Shared ui-view code for both directives:
4113
+ * Given scope, element, and its attributes, return the view's name
4114
+ */
4115
+ function getUiViewName(scope, attrs, element, $interpolate) {
4116
+ var name = $interpolate(attrs.uiView || attrs.name || '')(scope);
4117
+ var uiViewCreatedBy = element.inheritedData('$uiView');
4118
+ return name.indexOf('@') >= 0 ? name : (name + '@' + (uiViewCreatedBy ? uiViewCreatedBy.state.name : ''));
4119
+ }
4120
+
2811
4121
  angular.module('ui.router.state').directive('uiView', $ViewDirective);
2812
4122
  angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill);
2813
4123
 
2814
- function parseStateRef(ref) {
2815
- var parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
4124
+ function parseStateRef(ref, current) {
4125
+ var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
4126
+ if (preparsed) ref = current + '(' + preparsed[1] + ')';
4127
+ parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
2816
4128
  if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'");
2817
4129
  return { state: parsed[1], paramExpr: parsed[3] || null };
2818
4130
  }
@@ -2825,6 +4137,43 @@ function stateContext(el) {
2825
4137
  }
2826
4138
  }
2827
4139
 
4140
+ function getTypeInfo(el) {
4141
+ // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
4142
+ var isSvg = Object.prototype.toString.call(el.prop('href')) === '[object SVGAnimatedString]';
4143
+ var isForm = el[0].nodeName === "FORM";
4144
+
4145
+ return {
4146
+ attr: isForm ? "action" : (isSvg ? 'xlink:href' : 'href'),
4147
+ isAnchor: el.prop("tagName").toUpperCase() === "A",
4148
+ clickable: !isForm
4149
+ };
4150
+ }
4151
+
4152
+ function clickHook(el, $state, $timeout, type, current) {
4153
+ return function(e) {
4154
+ var button = e.which || e.button, target = current();
4155
+
4156
+ if (!(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || el.attr('target'))) {
4157
+ // HACK: This is to allow ng-clicks to be processed before the transition is initiated:
4158
+ var transition = $timeout(function() {
4159
+ $state.go(target.state, target.params, target.options);
4160
+ });
4161
+ e.preventDefault();
4162
+
4163
+ // if the state has no URL, ignore one preventDefault from the <a> directive.
4164
+ var ignorePreventDefaultCount = type.isAnchor && !target.href ? 1: 0;
4165
+
4166
+ e.preventDefault = function() {
4167
+ if (ignorePreventDefaultCount-- <= 0) $timeout.cancel(transition);
4168
+ };
4169
+ }
4170
+ };
4171
+ }
4172
+
4173
+ function defaultOpts(el, $state) {
4174
+ return { relative: stateContext(el) || $state.$current, inherit: true };
4175
+ }
4176
+
2828
4177
  /**
2829
4178
  * @ngdoc directive
2830
4179
  * @name ui.router.state.directive:ui-sref
@@ -2835,40 +4184,40 @@ function stateContext(el) {
2835
4184
  * @restrict A
2836
4185
  *
2837
4186
  * @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.
4187
+ * A directive that binds a link (`<a>` tag) to a state. If the state has an associated
4188
+ * URL, the directive will automatically generate & update the `href` attribute via
4189
+ * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
4190
+ * the link will trigger a state transition with optional parameters.
2842
4191
  *
2843
- * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
4192
+ * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
2844
4193
  * handled natively by the browser.
2845
4194
  *
2846
- * You can also use relative state paths within ui-sref, just like the relative
4195
+ * You can also use relative state paths within ui-sref, just like the relative
2847
4196
  * 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
4197
+ * to the state that the link lives in, in other words the state that loaded the
2849
4198
  * template containing the link.
2850
4199
  *
2851
- * You can specify options to pass to {@link ui.router.state.$state#go $state.go()}
4200
+ * You can specify options to pass to {@link ui.router.state.$state#methods_go $state.go()}
2852
4201
  * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`,
2853
4202
  * and `reload`.
2854
4203
  *
2855
4204
  * @example
2856
- * Here's an example of how you'd use ui-sref and how it would compile. If you have the
4205
+ * Here's an example of how you'd use ui-sref and how it would compile. If you have the
2857
4206
  * following template:
2858
4207
  * <pre>
2859
- * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a>
2860
- *
4208
+ * <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
4209
+ *
2861
4210
  * <ul>
2862
4211
  * <li ng-repeat="contact in contacts">
2863
4212
  * <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
2864
4213
  * </li>
2865
4214
  * </ul>
2866
4215
  * </pre>
2867
- *
2868
- * Then the compiled html would be (assuming Html5Mode is off):
4216
+ *
4217
+ * Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
2869
4218
  * <pre>
2870
- * <a href="#/home" ui-sref="home">Home</a> | <a href="#/about" ui-sref="about">About</a>
2871
- *
4219
+ * <a href="#/home" ui-sref="home">Home</a> | <a href="#/about" ui-sref="about">About</a> | <a href="#/contacts?page=2" ui-sref="{page: 2}">Next page</a>
4220
+ *
2872
4221
  * <ul>
2873
4222
  * <li ng-repeat="contact in contacts">
2874
4223
  * <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
@@ -2885,71 +4234,101 @@ function stateContext(el) {
2885
4234
  * </pre>
2886
4235
  *
2887
4236
  * @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()}
4237
+ * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#methods_go $state.go()}
2889
4238
  */
2890
4239
  $StateRefDirective.$inject = ['$state', '$timeout'];
2891
4240
  function $StateRefDirective($state, $timeout) {
2892
- var allowedOptions = ['location', 'inherit', 'reload'];
2893
-
2894
4241
  return {
2895
4242
  restrict: 'A',
2896
- require: '?^uiSrefActive',
4243
+ require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
2897
4244
  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;
4245
+ var ref = parseStateRef(attrs.uiSref, $state.current.name);
4246
+ var def = { state: ref.state, href: null, params: null };
4247
+ var type = getTypeInfo(element);
4248
+ var active = uiSrefActive[1] || uiSrefActive[0];
4249
+ var unlinkInfoFn = null;
4250
+ var hookFn;
4251
+
4252
+ def.options = extend(defaultOpts(element, $state), attrs.uiSrefOpts ? scope.$eval(attrs.uiSrefOpts) : {});
4253
+
4254
+ var update = function(val) {
4255
+ if (val) def.params = angular.copy(val);
4256
+ def.href = $state.href(ref.state, def.params, def.options);
4257
+
4258
+ if (unlinkInfoFn) unlinkInfoFn();
4259
+ if (active) unlinkInfoFn = active.$$addStateInfo(ref.state, def.params);
4260
+ if (def.href !== null) attrs.$set(type.attr, def.href);
2927
4261
  };
2928
4262
 
2929
4263
  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);
4264
+ scope.$watch(ref.paramExpr, function(val) { if (val !== def.params) update(val); }, true);
4265
+ def.params = angular.copy(scope.$eval(ref.paramExpr));
2934
4266
  }
2935
4267
  update();
2936
4268
 
2937
- if (isForm) return;
4269
+ if (!type.clickable) return;
4270
+ hookFn = clickHook(element, $state, $timeout, type, function() { return def; });
4271
+ element.bind("click", hookFn);
4272
+ scope.$on('$destroy', function() {
4273
+ element.unbind("click", hookFn);
4274
+ });
4275
+ }
4276
+ };
4277
+ }
2938
4278
 
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
- }
4279
+ /**
4280
+ * @ngdoc directive
4281
+ * @name ui.router.state.directive:ui-state
4282
+ *
4283
+ * @requires ui.router.state.uiSref
4284
+ *
4285
+ * @restrict A
4286
+ *
4287
+ * @description
4288
+ * Much like ui-sref, but will accept named $scope properties to evaluate for a state definition,
4289
+ * params and override options.
4290
+ *
4291
+ * @param {string} ui-state 'stateName' can be any valid absolute or relative state
4292
+ * @param {Object} ui-state-params params to pass to {@link ui.router.state.$state#methods_href $state.href()}
4293
+ * @param {Object} ui-state-opts options to pass to {@link ui.router.state.$state#methods_go $state.go()}
4294
+ */
4295
+ $StateRefDynamicDirective.$inject = ['$state', '$timeout'];
4296
+ function $StateRefDynamicDirective($state, $timeout) {
4297
+ return {
4298
+ restrict: 'A',
4299
+ require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
4300
+ link: function(scope, element, attrs, uiSrefActive) {
4301
+ var type = getTypeInfo(element);
4302
+ var active = uiSrefActive[1] || uiSrefActive[0];
4303
+ var group = [attrs.uiState, attrs.uiStateParams || null, attrs.uiStateOpts || null];
4304
+ var watch = '[' + group.map(function(val) { return val || 'null'; }).join(', ') + ']';
4305
+ var def = { state: null, params: null, options: null, href: null };
4306
+ var unlinkInfoFn = null;
4307
+ var hookFn;
4308
+
4309
+ function runStateRefLink (group) {
4310
+ def.state = group[0]; def.params = group[1]; def.options = group[2];
4311
+ def.href = $state.href(def.state, def.params, def.options);
4312
+
4313
+ if (unlinkInfoFn) unlinkInfoFn();
4314
+ if (active) unlinkInfoFn = active.$$addStateInfo(def.state, def.params);
4315
+ if (def.href) attrs.$set(type.attr, def.href);
4316
+ }
4317
+
4318
+ scope.$watch(watch, runStateRefLink, true);
4319
+ runStateRefLink(scope.$eval(watch));
4320
+
4321
+ if (!type.clickable) return;
4322
+ hookFn = clickHook(element, $state, $timeout, type, function() { return def; });
4323
+ element.bind("click", hookFn);
4324
+ scope.$on('$destroy', function() {
4325
+ element.unbind("click", hookFn);
2948
4326
  });
2949
4327
  }
2950
4328
  };
2951
4329
  }
2952
4330
 
4331
+
2953
4332
  /**
2954
4333
  * @ngdoc directive
2955
4334
  * @name ui.router.state.directive:ui-sref-active
@@ -2961,12 +4340,20 @@ function $StateRefDirective($state, $timeout) {
2961
4340
  * @restrict A
2962
4341
  *
2963
4342
  * @description
2964
- * A directive working alongside ui-sref to add classes to an element when the
4343
+ * A directive working alongside ui-sref to add classes to an element when the
2965
4344
  * 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
4345
+ * The primary use-case is to simplify the special appearance of navigation menus
2967
4346
  * relying on `ui-sref`, by having the "active" state's menu button appear different,
2968
4347
  * distinguishing it from the inactive menu items.
2969
4348
  *
4349
+ * ui-sref-active can live on the same element as ui-sref or on a parent element. The first
4350
+ * ui-sref-active found at the same level or above the ui-sref will be used.
4351
+ *
4352
+ * Will activate when the ui-sref's target state or any child state is active. If you
4353
+ * need to activate only when the ui-sref target state is active and *not* any of
4354
+ * it's children, then you will use
4355
+ * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq}
4356
+ *
2970
4357
  * @example
2971
4358
  * Given the following template:
2972
4359
  * <pre>
@@ -2976,8 +4363,9 @@ function $StateRefDirective($state, $timeout) {
2976
4363
  * </li>
2977
4364
  * </ul>
2978
4365
  * </pre>
2979
- *
2980
- * When the app state is "app.user", and contains the state parameter "user" with value "bilbobaggins",
4366
+ *
4367
+ *
4368
+ * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins",
2981
4369
  * the resulting HTML will appear as (note the 'active' class):
2982
4370
  * <pre>
2983
4371
  * <ul>
@@ -2986,10 +4374,10 @@ function $StateRefDirective($state, $timeout) {
2986
4374
  * </li>
2987
4375
  * </ul>
2988
4376
  * </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
- *
4377
+ *
4378
+ * The class name is interpolated **once** during the directives link time (any further changes to the
4379
+ * interpolated value are ignored).
4380
+ *
2993
4381
  * Multiple classes may be specified in a space-separated format:
2994
4382
  * <pre>
2995
4383
  * <ul>
@@ -2998,45 +4386,153 @@ function $StateRefDirective($state, $timeout) {
2998
4386
  * </li>
2999
4387
  * </ul>
3000
4388
  * </pre>
4389
+ *
4390
+ * It is also possible to pass ui-sref-active an expression that evaluates
4391
+ * to an object hash, whose keys represent active class names and whose
4392
+ * values represent the respective state names/globs.
4393
+ * ui-sref-active will match if the current active state **includes** any of
4394
+ * the specified state names/globs, even the abstract ones.
4395
+ *
4396
+ * @Example
4397
+ * Given the following template, with "admin" being an abstract state:
4398
+ * <pre>
4399
+ * <div ui-sref-active="{'active': 'admin.*'}">
4400
+ * <a ui-sref-active="active" ui-sref="admin.roles">Roles</a>
4401
+ * </div>
4402
+ * </pre>
4403
+ *
4404
+ * When the current state is "admin.roles" the "active" class will be applied
4405
+ * to both the <div> and <a> elements. It is important to note that the state
4406
+ * names/globs passed to ui-sref-active shadow the state provided by ui-sref.
3001
4407
  */
3002
- $StateActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
3003
- function $StateActiveDirective($state, $stateParams, $interpolate) {
3004
- return {
4408
+
4409
+ /**
4410
+ * @ngdoc directive
4411
+ * @name ui.router.state.directive:ui-sref-active-eq
4412
+ *
4413
+ * @requires ui.router.state.$state
4414
+ * @requires ui.router.state.$stateParams
4415
+ * @requires $interpolate
4416
+ *
4417
+ * @restrict A
4418
+ *
4419
+ * @description
4420
+ * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate
4421
+ * when the exact target state used in the `ui-sref` is active; no child states.
4422
+ *
4423
+ */
4424
+ $StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
4425
+ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
4426
+ return {
3005
4427
  restrict: "A",
3006
- controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
3007
- var state, params, activeClass;
4428
+ controller: ['$scope', '$element', '$attrs', '$timeout', function ($scope, $element, $attrs, $timeout) {
4429
+ var states = [], activeClasses = {}, activeEqClass, uiSrefActive;
3008
4430
 
3009
4431
  // There probably isn't much point in $observing this
3010
- activeClass = $interpolate($attrs.uiSrefActive || '', false)($scope);
4432
+ // uiSrefActive and uiSrefActiveEq share the same directive object with some
4433
+ // slight difference in logic routing
4434
+ activeEqClass = $interpolate($attrs.uiSrefActiveEq || '', false)($scope);
4435
+
4436
+ try {
4437
+ uiSrefActive = $scope.$eval($attrs.uiSrefActive);
4438
+ } catch (e) {
4439
+ // Do nothing. uiSrefActive is not a valid expression.
4440
+ // Fall back to using $interpolate below
4441
+ }
4442
+ uiSrefActive = uiSrefActive || $interpolate($attrs.uiSrefActive || '', false)($scope);
4443
+ if (isObject(uiSrefActive)) {
4444
+ forEach(uiSrefActive, function(stateOrName, activeClass) {
4445
+ if (isString(stateOrName)) {
4446
+ var ref = parseStateRef(stateOrName, $state.current.name);
4447
+ addState(ref.state, $scope.$eval(ref.paramExpr), activeClass);
4448
+ }
4449
+ });
4450
+ }
3011
4451
 
3012
- // Allow uiSref to communicate with uiSrefActive
3013
- this.$$setStateInfo = function(newState, newParams) {
3014
- state = $state.get(newState, stateContext($element));
3015
- params = newParams;
4452
+ // Allow uiSref to communicate with uiSrefActive[Equals]
4453
+ this.$$addStateInfo = function (newState, newParams) {
4454
+ // we already got an explicit state provided by ui-sref-active, so we
4455
+ // shadow the one that comes from ui-sref
4456
+ if (isObject(uiSrefActive) && states.length > 0) {
4457
+ return;
4458
+ }
4459
+ var deregister = addState(newState, newParams, uiSrefActive);
3016
4460
  update();
4461
+ return deregister;
3017
4462
  };
3018
4463
 
3019
4464
  $scope.$on('$stateChangeSuccess', update);
3020
4465
 
4466
+ function addState(stateName, stateParams, activeClass) {
4467
+ var state = $state.get(stateName, stateContext($element));
4468
+ var stateHash = createStateHash(stateName, stateParams);
4469
+
4470
+ var stateInfo = {
4471
+ state: state || { name: stateName },
4472
+ params: stateParams,
4473
+ hash: stateHash
4474
+ };
4475
+
4476
+ states.push(stateInfo);
4477
+ activeClasses[stateHash] = activeClass;
4478
+
4479
+ return function removeState() {
4480
+ var idx = states.indexOf(stateInfo);
4481
+ if (idx !== -1) states.splice(idx, 1);
4482
+ };
4483
+ }
4484
+
4485
+ /**
4486
+ * @param {string} state
4487
+ * @param {Object|string} [params]
4488
+ * @return {string}
4489
+ */
4490
+ function createStateHash(state, params) {
4491
+ if (!isString(state)) {
4492
+ throw new Error('state should be a string');
4493
+ }
4494
+ if (isObject(params)) {
4495
+ return state + toJson(params);
4496
+ }
4497
+ params = $scope.$eval(params);
4498
+ if (isObject(params)) {
4499
+ return state + toJson(params);
4500
+ }
4501
+ return state;
4502
+ }
4503
+
3021
4504
  // Update route state
3022
4505
  function update() {
3023
- if ($state.$current.self === state && matchesParams()) {
3024
- $element.addClass(activeClass);
3025
- } else {
3026
- $element.removeClass(activeClass);
4506
+ for (var i = 0; i < states.length; i++) {
4507
+ if (anyMatch(states[i].state, states[i].params)) {
4508
+ addClass($element, activeClasses[states[i].hash]);
4509
+ } else {
4510
+ removeClass($element, activeClasses[states[i].hash]);
4511
+ }
4512
+
4513
+ if (exactMatch(states[i].state, states[i].params)) {
4514
+ addClass($element, activeEqClass);
4515
+ } else {
4516
+ removeClass($element, activeEqClass);
4517
+ }
3027
4518
  }
3028
4519
  }
3029
4520
 
3030
- function matchesParams() {
3031
- return !params || equalForKeys(params, $stateParams);
3032
- }
4521
+ function addClass(el, className) { $timeout(function () { el.addClass(className); }); }
4522
+ function removeClass(el, className) { el.removeClass(className); }
4523
+ function anyMatch(state, params) { return $state.includes(state.name, params); }
4524
+ function exactMatch(state, params) { return $state.is(state.name, params); }
4525
+
4526
+ update();
3033
4527
  }]
3034
4528
  };
3035
4529
  }
3036
4530
 
3037
4531
  angular.module('ui.router.state')
3038
4532
  .directive('uiSref', $StateRefDirective)
3039
- .directive('uiSrefActive', $StateActiveDirective);
4533
+ .directive('uiSrefActive', $StateRefActiveDirective)
4534
+ .directive('uiSrefActiveEq', $StateRefActiveDirective)
4535
+ .directive('uiState', $StateRefDynamicDirective);
3040
4536
 
3041
4537
  /**
3042
4538
  * @ngdoc filter
@@ -3049,9 +4545,11 @@ angular.module('ui.router.state')
3049
4545
  */
3050
4546
  $IsStateFilter.$inject = ['$state'];
3051
4547
  function $IsStateFilter($state) {
3052
- return function(state) {
3053
- return $state.is(state);
4548
+ var isFilter = function (state, params) {
4549
+ return $state.is(state, params);
3054
4550
  };
4551
+ isFilter.$stateful = true;
4552
+ return isFilter;
3055
4553
  }
3056
4554
 
3057
4555
  /**
@@ -3065,159 +4563,14 @@ function $IsStateFilter($state) {
3065
4563
  */
3066
4564
  $IncludedByStateFilter.$inject = ['$state'];
3067
4565
  function $IncludedByStateFilter($state) {
3068
- return function(state) {
3069
- return $state.includes(state);
4566
+ var includesFilter = function (state, params, options) {
4567
+ return $state.includes(state, params, options);
3070
4568
  };
4569
+ includesFilter.$stateful = true;
4570
+ return includesFilter;
3071
4571
  }
3072
4572
 
3073
4573
  angular.module('ui.router.state')
3074
4574
  .filter('isState', $IsStateFilter)
3075
4575
  .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
4576
  })(window, window.angular);