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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/bastion/bastion.module.js +1 -0
- data/app/assets/javascripts/bastion/components/bst-alert.directive.js +23 -3
- data/app/assets/javascripts/bastion/components/nutupane.factory.js +5 -0
- data/app/assets/javascripts/bastion/components/views/bst-alert.html +1 -1
- data/app/assets/javascripts/bastion/layouts/404.html +14 -0
- data/app/assets/javascripts/bastion/layouts/details-nutupane.html +27 -18
- data/app/assets/javascripts/bastion/layouts/nutupane.html +29 -23
- data/app/assets/javascripts/bastion/routing.module.js +8 -4
- data/app/assets/stylesheets/bastion/nutupane.scss +0 -4
- data/bower.json +1 -1
- data/grunt/karma.js +3 -1
- data/lib/bastion/version.rb +1 -1
- data/package.json +1 -1
- data/test/components/bst-alert.directive.test.js +96 -17
- data/test/components/nutupane.factory.test.js +9 -0
- data/test/routing.module.test.js +5 -6
- data/vendor/assets/javascripts/bastion/angular-ui-router/angular-ui-router.js +2250 -897
- metadata +4 -3
@@ -1,6 +1,6 @@
|
|
1
1
|
/**
|
2
2
|
* State-based routing for AngularJS
|
3
|
-
* @version v0.
|
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
|
66
|
+
function objectKeys(object) {
|
66
67
|
if (Object.keys) {
|
67
68
|
return Object.keys(object);
|
68
69
|
}
|
69
70
|
var result = [];
|
70
71
|
|
71
|
-
|
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
|
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]
|
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 (
|
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,
|
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
|
-
|
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
|
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#
|
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
|
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
|
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
|
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+)
|
682
|
-
// \{(\w+)(
|
683
|
-
// (?: ... | ... | ... )+
|
684
|
-
// [^{}\\]+
|
685
|
-
// \\.
|
686
|
-
// \{(?:[^{}\\]+|\\.)*\}
|
687
|
-
var placeholder
|
688
|
-
|
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
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
compiled += quoteRegExp(segment
|
714
|
-
|
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
|
-
|
728
|
-
|
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
|
-
|
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
|
-
* @
|
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
|
-
|
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', {
|
787
|
-
*
|
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
|
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
|
-
|
805
|
-
|
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
|
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
|
-
|
846
|
-
|
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
|
-
|
852
|
-
|
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
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
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
|
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
|
-
* @
|
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
|
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
|
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
|
-
|
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
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
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.
|
955
|
-
* @methodOf ui.router.
|
1446
|
+
* @name ui.router.util.$urlMatcherFactory#type
|
1447
|
+
* @methodOf ui.router.util.$urlMatcherFactory
|
956
1448
|
*
|
957
1449
|
* @description
|
958
|
-
*
|
959
|
-
*
|
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
|
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 {
|
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}
|
1816
|
+
* @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
|
982
1817
|
*/
|
983
|
-
this.rule =
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
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
|
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|
|
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}
|
1853
|
+
* @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
|
1020
1854
|
*/
|
1021
|
-
this.otherwise =
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
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.
|
1046
|
-
*
|
1047
|
-
*
|
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|
|
1909
|
+
* @param {string|function} handler The path you want to redirect your user to.
|
1075
1910
|
*/
|
1076
|
-
this.when =
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
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
|
-
|
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
|
-
|
1114
|
-
|
1115
|
-
return
|
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
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
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
|
-
|
1148
|
-
|
1149
|
-
|
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
|
-
|
2117
|
+
$location.url(url);
|
2118
|
+
lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;
|
2119
|
+
if (options && options.replace) $location.replace();
|
2120
|
+
},
|
1156
2121
|
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
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'
|
1216
|
-
function $StateProvider( $urlRouterProvider, $urlMatcherFactory
|
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 =
|
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
|
-
|
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)
|
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
|
-
|
1267
|
-
|
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 + "'
|
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
|
-
|
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(
|
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(
|
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}
|
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
|
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 (
|
2497
|
+
* $stateProvider.decorator('views', function (state, parent) {
|
1515
2498
|
* var result = {},
|
1516
2499
|
* views = parent(state);
|
1517
2500
|
*
|
1518
|
-
* angular.forEach(
|
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.<object>} - 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
|
-
*
|
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.<object>} - state parameters extracted from the current $location.path() by
|
1593
2587
|
* applying the current state
|
1594
2588
|
*
|
1595
|
-
* <
|
2589
|
+
* <pre>templateUrl: "home.html"</pre>
|
2590
|
+
* <pre>templateUrl: function(params) {
|
2591
|
+
* return myTemplates[params.pageId]; }</pre>
|
1596
2592
|
*
|
1597
|
-
*
|
1598
|
-
*
|
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
|
-
*
|
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
|
-
*
|
1608
|
-
*
|
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
|
-
*
|
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
|
-
*
|
2645
|
+
* An optional map<string, function> 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
|
1620
|
-
*
|
1621
|
-
* of the resolved promises are injected
|
1622
|
-
* of the promises are rejected the $stateChangeError event is fired.
|
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
|
-
*
|
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
|
-
*
|
2673
|
+
* (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for
|
2674
|
+
* more details on acceptable patterns )
|
1636
2675
|
*
|
1637
|
-
*
|
1638
|
-
*
|
1639
|
-
*
|
1640
|
-
*
|
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<string, object> which defined multiple views, or targets views
|
2690
|
+
* manually/explicitly.
|
1643
2691
|
*
|
1644
|
-
*
|
1645
|
-
* manually/explicitly.
|
2692
|
+
* Examples:
|
1646
2693
|
*
|
1647
|
-
*
|
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
|
-
*
|
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
|
-
*
|
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
|
-
*
|
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
|
-
*
|
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
|
-
*
|
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', '$
|
1732
|
-
function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $
|
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
|
-
|
1742
|
-
|
1743
|
-
|
1744
|
-
|
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,
|
1763
|
-
*
|
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:
|
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:
|
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.
|
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
|
1834
|
-
* have
|
1835
|
-
*
|
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
|
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
|
-
|
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("
|
1972
|
-
throw new Error("
|
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
|
-
|
1984
|
-
|
1985
|
-
|
1986
|
-
|
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
|
1992
|
-
// because we might accidentally abort a legitimate
|
1993
|
-
|
1994
|
-
|
1995
|
-
|
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
|
-
//
|
2001
|
-
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
|
-
|
2033
|
-
|
2034
|
-
|
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
|
-
|
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
|
-
|
2090
|
-
|
2091
|
-
|
2092
|
-
|
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
|
-
|
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
|
-
|
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
|
-
* //
|
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}
|
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
|
-
|
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
|
-
|
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
|
-
*
|
2210
|
-
*
|
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
|
-
*
|
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
|
2226
|
-
*
|
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
|
-
|
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
|
-
|
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=
|
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({
|
2288
|
-
|
2289
|
-
|
3530
|
+
options = extend({
|
3531
|
+
lossy: true,
|
3532
|
+
inherit: true,
|
3533
|
+
absolute: false,
|
3534
|
+
relative: $state.$current
|
3535
|
+
}, options || {});
|
2290
3536
|
|
2291
|
-
|
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 (
|
2299
|
-
|
2300
|
-
|
2301
|
-
|
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 (
|
2307
|
-
|
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
|
-
* @
|
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 (
|
2330
|
-
|
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 = [
|
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
|
-
|
2357
|
-
|
2358
|
-
|
2359
|
-
|
2360
|
-
|
2361
|
-
|
2362
|
-
|
2363
|
-
|
2364
|
-
|
2365
|
-
|
2366
|
-
|
2367
|
-
|
2368
|
-
|
2369
|
-
|
2370
|
-
|
2371
|
-
|
2372
|
-
|
2373
|
-
|
2374
|
-
|
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
|
2388
|
-
|
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
|
-
.
|
2396
|
-
.
|
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=}
|
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#
|
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) {
|
2668
|
-
|
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
|
2728
|
-
name =
|
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,
|
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
|
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#
|
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#
|
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
|
2899
|
-
var
|
2900
|
-
var
|
2901
|
-
var
|
2902
|
-
|
2903
|
-
var
|
2904
|
-
|
2905
|
-
};
|
2906
|
-
|
2907
|
-
|
2908
|
-
if (
|
2909
|
-
|
2910
|
-
|
2911
|
-
|
2912
|
-
|
2913
|
-
|
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(
|
2931
|
-
|
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 (
|
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
|
-
|
2940
|
-
|
2941
|
-
|
2942
|
-
|
2943
|
-
|
2944
|
-
|
2945
|
-
|
2946
|
-
|
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
|
-
*
|
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
|
-
|
3003
|
-
|
3004
|
-
|
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
|
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
|
-
|
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.$$
|
3014
|
-
state
|
3015
|
-
|
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
|
-
|
3024
|
-
|
3025
|
-
|
3026
|
-
|
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
|
3031
|
-
|
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', $
|
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
|
-
|
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
|
-
|
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);
|