1
1
/**
2
2
* State-based routing for AngularJS
3
- * @version v0.2.13
3
+ * @version v0.2.13-dev-2015-01-29
4
4
* @link http://angular-ui.github.com/
5
5
* @license MIT License, http://www.opensource.org/licenses/MIT
6
6
*/
@@ -39,6 +39,27 @@ function merge(dst) {
39
39
return dst ;
40
40
}
41
41
42
+ function copyParams ( src , dest ) {
43
+ var params = { } ;
44
+ forEach ( src , function ( param , key ) {
45
+ var segments = key . split ( '.' ) ;
46
+ var tmp = params ;
47
+ forEach ( segments , function ( segment , index ) {
48
+ if ( index < segments . length - 1 ) {
49
+ if ( ! tmp [ segment ] ) {
50
+ tmp [ segment ] = { } ;
51
+ }
52
+ if ( ! isObject ( tmp [ segment ] ) ) throw new Error ( "State parameter '" + key + "' is invalid: '" + segments . slice ( 0 , index ) . join ( '.' ) + "' is already defined." ) ;
53
+ tmp = tmp [ segment ] ;
54
+ } else {
55
+ if ( isObject ( tmp [ segment ] ) ) throw new Error ( "Unexpected parameter '" + key + "': it already has sub-parameters." ) ;
56
+ tmp [ segment ] = param ;
57
+ }
58
+ } ) ;
59
+ } ) ;
60
+ copy ( params , dest ) ;
61
+ }
62
+
42
63
/**
43
64
* Finds the common ancestor path between two states.
44
65
*
@@ -68,7 +89,7 @@ function objectKeys(object) {
68
89
}
69
90
var result = [ ] ;
70
91
71
- angular . forEach ( object , function ( val , key ) {
92
+ forEach ( object , function ( val , key ) {
72
93
result . push ( key ) ;
73
94
} ) ;
74
95
return result ;
@@ -752,7 +773,7 @@ function UrlMatcher(pattern, config, parentMatcher) {
752
773
// \\. - a backslash escape
753
774
// \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms
754
775
var placeholder = / ( [: * ] ) ( [ \w \[ \] ] + ) | \{ ( [ \w \[ \] ] + ) (?: \: ( (?: [ ^ { } \\ ] + | \\ .| \{ (?: [ ^ { } \\ ] + | \\ .) * \} ) + ) ) ? \} / g,
755
- searchPlaceholder = / ( [: ] ? ) ( [ \w \[ \] - ] + ) | \{ ( [ \w \[ \] - ] + ) (?: \: ( (?: [ ^ { } \\ ] + | \\ .| \{ (?: [ ^ { } \\ ] + | \\ .) * \} ) + ) ) ? \} / g,
776
+ searchPlaceholder = / ( [: ] ? ) ( [ \w \[ \] \. - ] + ) | \{ ( [ \w \[ \] \. - ] + ) (?: \: ( (?: [ ^ { } \\ ] + | \\ .| \{ (?: [ ^ { } \\ ] + | \\ .) * \} ) + ) ) ? \} / g,
756
777
compiled = '^' , last = 0 , m ,
757
778
segments = this . segments = [ ] ,
758
779
parentParams = parentMatcher ? parentMatcher . params : { } ,
@@ -762,19 +783,19 @@ function UrlMatcher(pattern, config, parentMatcher) {
762
783
function addParameter ( id , type , config , location ) {
763
784
paramNames . push ( id ) ;
764
785
if ( parentParams [ id ] ) return parentParams [ id ] ;
765
- if ( ! / ^ \w + ( - + \w + ) * (?: \[ \] ) ? $ / . test ( id ) ) throw new Error ( "Invalid parameter name '" + id + "' in pattern '" + pattern + "'" ) ;
786
+ if ( ! / ^ \w + ( ( - + \w + ) * | ( \. \w + ) * ) ? (?: \[ \] ) ? $ / . test ( id ) ) throw new Error ( "Invalid parameter name '" + id + "' in pattern '" + pattern + "'" ) ;
766
787
if ( params [ id ] ) throw new Error ( "Duplicate parameter name '" + id + "' in pattern '" + pattern + "'" ) ;
767
788
params [ id ] = new $$UMFP . Param ( id , type , config , location ) ;
768
789
return params [ id ] ;
769
790
}
770
791
771
- function quoteRegExp ( string , pattern , squash ) {
792
+ function quoteRegExp ( string , pattern , squash , optional ) {
772
793
var surroundPattern = [ '' , '' ] , result = string . replace ( / [ \\ \[ \] \^ $ * + ? . ( ) | { } ] / g, "\\$&" ) ;
773
794
if ( ! pattern ) return result ;
774
795
switch ( squash ) {
775
- case false : surroundPattern = [ '(' , ')' ] ; break ;
796
+ case false : surroundPattern = [ '(' , ')' + ( optional ? "?" : "" ) ] ; break ;
776
797
case true : surroundPattern = [ '?(' , ')?' ] ; break ;
777
- default : surroundPattern = [ '(' + squash + "|" , ')?' ] ; break ;
798
+ default : surroundPattern = [ '(' + squash + "|" , ')?' ] ; break ;
778
799
}
779
800
return result + surroundPattern [ 0 ] + pattern + surroundPattern [ 1 ] ;
780
801
}
@@ -801,7 +822,7 @@ function UrlMatcher(pattern, config, parentMatcher) {
801
822
if ( p . segment . indexOf ( '?' ) >= 0 ) break ; // we're into the search part
802
823
803
824
param = addParameter ( p . id , p . type , p . cfg , "path" ) ;
804
- compiled += quoteRegExp ( p . segment , param . type . pattern . source , param . squash ) ;
825
+ compiled += quoteRegExp ( p . segment , param . type . pattern . source , param . squash , param . isOptional ) ;
805
826
segments . push ( p . segment ) ;
806
827
last = placeholder . lastIndex ;
807
828
}
@@ -819,6 +840,7 @@ function UrlMatcher(pattern, config, parentMatcher) {
819
840
last = 0 ;
820
841
while ( ( m = searchPlaceholder . exec ( search ) ) ) {
821
842
p = matchDetails ( m , true ) ;
843
+
822
844
param = addParameter ( p . id , p . type , p . cfg , "search" ) ;
823
845
last = placeholder . lastIndex ;
824
846
// check if ?&
@@ -912,7 +934,7 @@ UrlMatcher.prototype.exec = function (path, searchParams) {
912
934
913
935
function decodePathArray ( string ) {
914
936
function reverseString ( str ) { return str . split ( "" ) . reverse ( ) . join ( "" ) ; }
915
- function unquoteDashes ( str ) { return str . replace ( / \\ - / , "-" ) ; }
937
+ function unquoteDashes ( str ) { return str . replace ( / \\ - / g , "-" ) ; }
916
938
917
939
var split = reverseString ( string ) . split ( / - (? ! \\ ) / ) ;
918
940
var allReversed = map ( split , reverseString ) ;
@@ -1150,6 +1172,11 @@ Type.prototype.pattern = /.*/;
1150
1172
1151
1173
Type . prototype . toString = function ( ) { return "{Type:" + this . name + "}" ; } ;
1152
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
+
1153
1180
/*
1154
1181
* Wraps an existing custom Type as an array of Type, depending on 'mode'.
1155
1182
* e.g.:
@@ -1163,7 +1190,6 @@ Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };
1163
1190
Type . prototype . $asArray = function ( mode , isSearch ) {
1164
1191
if ( ! mode ) return this ;
1165
1192
if ( mode === "auto" && ! isSearch ) throw new Error ( "'auto' array mode is for query parameters only" ) ;
1166
- return new ArrayType ( this , mode ) ;
1167
1193
1168
1194
function ArrayType ( type , mode ) {
1169
1195
function bindTo ( type , callbackName ) {
@@ -1212,8 +1238,12 @@ Type.prototype.$asArray = function(mode, isSearch) {
1212
1238
this . is = arrayHandler ( bindTo ( type , 'is' ) , true ) ;
1213
1239
this . equals = arrayEqualsHandler ( bindTo ( type , 'equals' ) ) ;
1214
1240
this . pattern = type . pattern ;
1241
+ this . $normalize = arrayHandler ( bindTo ( type , '$normalize' ) ) ;
1242
+ this . name = type . name ;
1215
1243
this . $arrayMode = mode ;
1216
1244
}
1245
+
1246
+ return new ArrayType ( this , mode ) ;
1217
1247
} ;
1218
1248
1219
1249
@@ -1241,7 +1271,7 @@ function $UrlMatcherFactory() {
1241
1271
string : {
1242
1272
encode : valToString ,
1243
1273
decode : valFromString ,
1244
- is : regexpMatches ,
1274
+ is : function ( val ) { return typeof val === "string" ; } ,
1245
1275
pattern : / [ ^ / ] * /
1246
1276
} ,
1247
1277
int : {
@@ -1615,7 +1645,10 @@ function $UrlMatcherFactory() {
1615
1645
*/
1616
1646
function $$getDefaultValue ( ) {
1617
1647
if ( ! injector ) throw new Error ( "Injectable functions cannot be called at configuration time" ) ;
1618
- return injector . invoke ( config . $$fn ) ;
1648
+ var defaultValue = injector . invoke ( config . $$fn ) ;
1649
+ if ( defaultValue !== null && defaultValue !== undefined && ! self . type . is ( defaultValue ) )
1650
+ throw new Error ( "Default value (" + defaultValue + ") for parameter '" + self . id + "' is not an instance of Type (" + self . type . name + ")" ) ;
1651
+ return defaultValue ;
1619
1652
}
1620
1653
1621
1654
/**
@@ -1629,7 +1662,7 @@ function $UrlMatcherFactory() {
1629
1662
return replacement . length ? replacement [ 0 ] : value ;
1630
1663
}
1631
1664
value = $replace ( value ) ;
1632
- return isDefined ( value ) ? self . type . decode ( value ) : $$getDefaultValue ( ) ;
1665
+ return ! isDefined ( value ) ? $$getDefaultValue ( ) : self . type . $normalize ( value ) ;
1633
1666
}
1634
1667
1635
1668
function toString ( ) { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}" ; }
@@ -1685,15 +1718,20 @@ function $UrlMatcherFactory() {
1685
1718
return equal ;
1686
1719
} ,
1687
1720
$$validates : function $$validate ( paramValues ) {
1688
- var result = true , isOptional , val , param , self = this ;
1689
-
1690
- forEach ( this . $$keys ( ) , function ( key ) {
1691
- param = self [ key ] ;
1692
- val = paramValues [ key ] ;
1693
- isOptional = ! val && param . isOptional ;
1694
- result = result && ( isOptional || ! ! param . type . is ( val ) ) ;
1695
- } ) ;
1696
- return result ;
1721
+ var keys = this . $$keys ( ) , i , param , rawVal , normalized , encoded ;
1722
+ for ( i = 0 ; i < keys . length ; i ++ ) {
1723
+ param = this [ keys [ i ] ] ;
1724
+ rawVal = paramValues [ keys [ i ] ] ;
1725
+ if ( ( rawVal === undefined || rawVal === null ) && param . isOptional )
1726
+ break ; // There was no parameter value, but the param is optional
1727
+ normalized = param . type . $normalize ( rawVal ) ;
1728
+ if ( ! param . type . is ( normalized ) )
1729
+ return false ; // The value was not of the correct Type, and could not be decoded to the correct Type
1730
+ encoded = param . type . encode ( normalized ) ;
1731
+ if ( angular . isString ( encoded ) && ! param . type . pattern . exec ( encoded ) )
1732
+ return false ; // The value was of the correct type, but when encoded, did not match the Type's regexp
1733
+ }
1734
+ return true ;
1697
1735
} ,
1698
1736
$$parent : undefined
1699
1737
} ;
@@ -2336,6 +2374,13 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
2336
2374
var globSegments = glob . split ( '.' ) ,
2337
2375
segments = $state . $current . name . split ( '.' ) ;
2338
2376
2377
+ //match single stars
2378
+ for ( var i = 0 , l = globSegments . length ; i < l ; i ++ ) {
2379
+ if ( globSegments [ i ] === '*' ) {
2380
+ segments [ i ] = '*' ;
2381
+ }
2382
+ }
2383
+
2339
2384
//match greedy starts
2340
2385
if ( globSegments [ 0 ] === '**' ) {
2341
2386
segments = segments . slice ( indexOf ( segments , globSegments [ 1 ] ) ) ;
@@ -2351,13 +2396,6 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
2351
2396
return false ;
2352
2397
}
2353
2398
2354
- //match single stars
2355
- for ( var i = 0 , l = globSegments . length ; i < l ; i ++ ) {
2356
- if ( globSegments [ i ] === '*' ) {
2357
- segments [ i ] = '*' ;
2358
- }
2359
- }
2360
-
2361
2399
return segments . join ( '' ) === globSegments . join ( '' ) ;
2362
2400
}
2363
2401
@@ -2566,6 +2604,13 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
2566
2604
* published to scope under the controllerAs name.
2567
2605
* <pre>controllerAs: "myCtrl"</pre>
2568
2606
*
2607
+ * @param {string|object= } stateConfig.parent
2608
+ * <a id='parent'></a>
2609
+ * Optionally specifies the parent state of this state.
2610
+ *
2611
+ * <pre>parent: 'parentState'</pre>
2612
+ * <pre>parent: parentState // JS variable</pre>
2613
+ *
2569
2614
* @param {object= } stateConfig.resolve
2570
2615
* <a id='resolve'></a>
2571
2616
*
@@ -2597,15 +2642,19 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
2597
2642
* transitioned to, the `$stateParams` service will be populated with any
2598
2643
* parameters that were passed.
2599
2644
*
2645
+ * (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for
2646
+ * more details on acceptable patterns )
2647
+ *
2600
2648
* examples:
2601
2649
* <pre>url: "/home"
2602
2650
* url: "/users/:userid"
2603
2651
* url: "/books/{bookid:[a-zA-Z_-]}"
2604
2652
* url: "/books/{categoryid:int}"
2605
2653
* url: "/books/{publishername:string}/{categoryid:int}"
2606
2654
* url: "/messages?before&after"
2607
- * url: "/messages?{before:date}&{after:date}"</pre>
2655
+ * url: "/messages?{before:date}&{after:date}"
2608
2656
* url: "/messages/:mailboxid?{before:date}&{after:date}"
2657
+ * </pre>
2609
2658
*
2610
2659
* @param {object= } stateConfig.views
2611
2660
* <a id='views'></a>
@@ -3191,7 +3240,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
3191
3240
$state . $current = to ;
3192
3241
$state . current = to . self ;
3193
3242
$state . params = toParams ;
3194
- copy ( $state . params , $stateParams ) ;
3243
+ copyParams ( $state . params , $stateParams ) ;
3195
3244
$state . transition = null ;
3196
3245
3197
3246
if ( options . location && to . navigable ) {
@@ -3609,7 +3658,7 @@ function $ViewScrollProvider() {
3609
3658
}
3610
3659
3611
3660
return function ( $element ) {
3612
- $timeout ( function ( ) {
3661
+ return $timeout ( function ( ) {
3613
3662
$element [ 0 ] . scrollIntoView ( ) ;
3614
3663
} , 0 , false ) ;
3615
3664
} ;
@@ -3894,6 +3943,7 @@ function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate
3894
3943
3895
3944
if ( locals . $$controller ) {
3896
3945
locals . $scope = scope ;
3946
+ locals . $element = $element ;
3897
3947
var controller = $controller ( locals . $$controller , locals ) ;
3898
3948
if ( locals . $$controllerAs ) {
3899
3949
scope [ locals . $$controllerAs ] = controller ;
@@ -4009,9 +4059,12 @@ function $StateRefDirective($state, $timeout) {
4009
4059
link : function ( scope , element , attrs , uiSrefActive ) {
4010
4060
var ref = parseStateRef ( attrs . uiSref , $state . current . name ) ;
4011
4061
var params = null , url = null , base = stateContext ( element ) || $state . $current ;
4012
- var newHref = null , isAnchor = element . prop ( "tagName" ) === "A" ;
4062
+ // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
4063
+ var hrefKind = toString . call ( element . prop ( 'href' ) ) === '[object SVGAnimatedString]' ?
4064
+ 'xlink:href' : 'href' ;
4065
+ var newHref = null , isAnchor = element . prop ( "tagName" ) . toUpperCase ( ) === "A" ;
4013
4066
var isForm = element [ 0 ] . nodeName === "FORM" ;
4014
- var attr = isForm ? "action" : "href" , nav = true ;
4067
+ var attr = isForm ? "action" : hrefKind , nav = true ;
4015
4068
4016
4069
var options = { relative : base , inherit : true } ;
4017
4070
var optionsOverride = scope . $eval ( attrs . uiSrefOpts ) || { } ;
0 commit comments