11/**
22 * State-based routing for AngularJS
3- * @version v0.2.12
3+ * @version v0.2.13
44 * @link http://angular-ui.github.com/
55 * @license MIT License, http://www.opensource.org/licenses/MIT
66 */
@@ -187,7 +187,7 @@ function omit(obj) {
187187 var copy = { } ;
188188 var keys = Array . prototype . concat . apply ( Array . prototype , Array . prototype . slice . call ( arguments , 1 ) ) ;
189189 for ( var key in obj ) {
190- if ( keys . indexOf ( key ) == - 1 ) copy [ key ] = obj [ key ] ;
190+ if ( indexOf ( keys , key ) == - 1 ) copy [ key ] = obj [ key ] ;
191191 }
192192 return copy ;
193193}
@@ -202,10 +202,12 @@ function pluck(collection, key) {
202202}
203203
204204function filter ( collection , callback ) {
205- var result = isArray ( collection ) ? [ ] : { } ;
205+ var array = isArray ( collection ) ;
206+ var result = array ? [ ] : { } ;
206207 forEach ( collection , function ( val , i ) {
207- if ( callback ( val , i ) )
208- result [ i ] = val ;
208+ if ( callback ( val , i ) ) {
209+ result [ array ? result . length : i ] = val ;
210+ }
209211 } ) ;
210212 return result ;
211213}
@@ -754,9 +756,11 @@ function UrlMatcher(pattern, config, parentMatcher) {
754756 compiled = '^' , last = 0 , m ,
755757 segments = this . segments = [ ] ,
756758 parentParams = parentMatcher ? parentMatcher . params : { } ,
757- params = this . params = parentMatcher ? parentMatcher . params . $$new ( ) : new $$UMFP . ParamSet ( ) ;
759+ params = this . params = parentMatcher ? parentMatcher . params . $$new ( ) : new $$UMFP . ParamSet ( ) ,
760+ paramNames = [ ] ;
758761
759762 function addParameter ( id , type , config , location ) {
763+ paramNames . push ( id ) ;
760764 if ( parentParams [ id ] ) return parentParams [ id ] ;
761765 if ( ! / ^ \w + ( - + \w + ) * (?: \[ \] ) ? $ / . test ( id ) ) throw new Error ( "Invalid parameter name '" + id + "' in pattern '" + pattern + "'" ) ;
762766 if ( params [ id ] ) throw new Error ( "Duplicate parameter name '" + id + "' in pattern '" + pattern + "'" ) ;
@@ -830,6 +834,7 @@ function UrlMatcher(pattern, config, parentMatcher) {
830834
831835 this . regexp = new RegExp ( compiled , config . caseInsensitive ? 'i' : undefined ) ;
832836 this . prefix = segments [ 0 ] ;
837+ this . $$paramNames = paramNames ;
833838}
834839
835840/**
@@ -945,7 +950,7 @@ UrlMatcher.prototype.exec = function (path, searchParams) {
945950 * pattern has no parameters, an empty array is returned.
946951 */
947952UrlMatcher . prototype . parameters = function ( param ) {
948- if ( ! isDefined ( param ) ) return this . params . $$keys ( ) ;
953+ if ( ! isDefined ( param ) ) return this . $$paramNames ;
949954 return this . params [ param ] || null ;
950955} ;
951956
@@ -1161,28 +1166,51 @@ Type.prototype.$asArray = function(mode, isSearch) {
11611166 return new ArrayType ( this , mode ) ;
11621167
11631168 function ArrayType ( type , mode ) {
1164- function bindTo ( thisObj , callback ) {
1169+ function bindTo ( type , callbackName ) {
11651170 return function ( ) {
1166- return callback . apply ( thisObj , arguments ) ;
1171+ return type [ callbackName ] . apply ( type , arguments ) ;
11671172 } ;
11681173 }
11691174
1170- function arrayHandler ( callback , reducefn ) {
1171- // Wraps type functions to operate on each value of an array
1175+ // Wrap non-array value as array
1176+ function arrayWrap ( val ) { return isArray ( val ) ? val : ( isDefined ( val ) ? [ val ] : [ ] ) ; }
1177+ // Unwrap array value for "auto" mode. Return undefined for empty array.
1178+ function arrayUnwrap ( val ) {
1179+ switch ( val . length ) {
1180+ case 0 : return undefined ;
1181+ case 1 : return mode === "auto" ? val [ 0 ] : val ;
1182+ default : return val ;
1183+ }
1184+ }
1185+ function falsey ( val ) { return ! val ; }
1186+
1187+ // Wraps type (.is/.encode/.decode) functions to operate on each value of an array
1188+ function arrayHandler ( callback , allTruthyMode ) {
11721189 return function handleArray ( val ) {
1173- if ( ! isArray ( val ) ) val = [ val ] ;
1190+ val = arrayWrap ( val ) ;
11741191 var result = map ( val , callback ) ;
1175- if ( reducefn )
1176- return result . reduce ( reducefn , true ) ;
1177- return ( result && result . length == 1 && mode === "auto" ) ? result [ 0 ] : result ;
1192+ if ( allTruthyMode === true )
1193+ return filter ( result , falsey ) . length === 0 ;
1194+ return arrayUnwrap ( result ) ;
1195+ } ;
1196+ }
1197+
1198+ // Wraps type (.equals) functions to operate on each value of an array
1199+ function arrayEqualsHandler ( callback ) {
1200+ return function handleArray ( val1 , val2 ) {
1201+ var left = arrayWrap ( val1 ) , right = arrayWrap ( val2 ) ;
1202+ if ( left . length !== right . length ) return false ;
1203+ for ( var i = 0 ; i < left . length ; i ++ ) {
1204+ if ( ! callback ( left [ i ] , right [ i ] ) ) return false ;
1205+ }
1206+ return true ;
11781207 } ;
11791208 }
11801209
1181- function alltruthy ( val , memo ) { return val && memo ; }
1182- this . encode = arrayHandler ( bindTo ( this , type . encode ) ) ;
1183- this . decode = arrayHandler ( bindTo ( this , type . decode ) ) ;
1184- this . equals = arrayHandler ( bindTo ( this , type . equals ) , alltruthy ) ;
1185- this . is = arrayHandler ( bindTo ( this , type . is ) , alltruthy ) ;
1210+ this . encode = arrayHandler ( bindTo ( type , 'encode' ) ) ;
1211+ this . decode = arrayHandler ( bindTo ( type , 'decode' ) ) ;
1212+ this . is = arrayHandler ( bindTo ( type , 'is' ) , true ) ;
1213+ this . equals = arrayEqualsHandler ( bindTo ( type , 'equals' ) ) ;
11861214 this . pattern = type . pattern ;
11871215 this . $arrayMode = mode ;
11881216 }
@@ -1203,9 +1231,8 @@ function $UrlMatcherFactory() {
12031231
12041232 var isCaseInsensitive = false , isStrictMode = true , defaultSquashPolicy = false ;
12051233
1206- function valToString ( val ) { return val != null ? val . toString ( ) . replace ( "/" , "%2F" ) : val ; }
1207- function valFromString ( val ) { return val != null ? val . toString ( ) . replace ( "%2F" , "/" ) : val ; }
1208- function angularEquals ( left , right ) { return angular . equals ( left , right ) ; }
1234+ function valToString ( val ) { return val != null ? val . toString ( ) . replace ( / \/ / g, "%2F" ) : val ; }
1235+ function valFromString ( val ) { return val != null ? val . toString ( ) . replace ( / % 2 F / g, "/" ) : val ; }
12091236// TODO: in 1.0, make string .is() return false if value is undefined by default.
12101237// function regexpMatches(val) { /*jshint validthis:true */ return isDefined(val) && this.pattern.test(val); }
12111238 function regexpMatches ( val ) { /*jshint validthis:true */ return this . pattern . test ( val ) ; }
@@ -1230,16 +1257,37 @@ function $UrlMatcherFactory() {
12301257 pattern : / 0 | 1 /
12311258 } ,
12321259 date : {
1233- encode : function ( val ) { return [
1234- val . getFullYear ( ) ,
1260+ encode : function ( val ) {
1261+ if ( ! this . is ( val ) )
1262+ return undefined ;
1263+ return [ val . getFullYear ( ) ,
12351264 ( '0' + ( val . getMonth ( ) + 1 ) ) . slice ( - 2 ) ,
12361265 ( '0' + val . getDate ( ) ) . slice ( - 2 )
12371266 ] . join ( "-" ) ;
12381267 } ,
1239- decode : function ( val ) { return new Date ( val ) ; } ,
1268+ decode : function ( val ) {
1269+ if ( this . is ( val ) ) return val ;
1270+ var match = this . capture . exec ( val ) ;
1271+ return match ? new Date ( match [ 1 ] , match [ 2 ] - 1 , match [ 3 ] ) : undefined ;
1272+ } ,
12401273 is : function ( val ) { return val instanceof Date && ! isNaN ( val . valueOf ( ) ) ; } ,
1241- equals : function ( a , b ) { return a . toISOString ( ) === b . toISOString ( ) ; } ,
1242- pattern : / [ 0 - 9 ] { 4 } - (?: 0 [ 1 - 9 ] | 1 [ 0 - 2 ] ) - (?: 0 [ 1 - 9 ] | [ 1 - 2 ] [ 0 - 9 ] | 3 [ 0 - 1 ] ) /
1274+ equals : function ( a , b ) { return this . is ( a ) && this . is ( b ) && a . toISOString ( ) === b . toISOString ( ) ; } ,
1275+ pattern : / [ 0 - 9 ] { 4 } - (?: 0 [ 1 - 9 ] | 1 [ 0 - 2 ] ) - (?: 0 [ 1 - 9 ] | [ 1 - 2 ] [ 0 - 9 ] | 3 [ 0 - 1 ] ) / ,
1276+ capture : / ( [ 0 - 9 ] { 4 } ) - ( 0 [ 1 - 9 ] | 1 [ 0 - 2 ] ) - ( 0 [ 1 - 9 ] | [ 1 - 2 ] [ 0 - 9 ] | 3 [ 0 - 1 ] ) /
1277+ } ,
1278+ json : {
1279+ encode : angular . toJson ,
1280+ decode : angular . fromJson ,
1281+ is : angular . isObject ,
1282+ equals : angular . equals ,
1283+ pattern : / [ ^ / ] * /
1284+ } ,
1285+ any : { // does not encode/decode
1286+ encode : angular . identity ,
1287+ decode : angular . identity ,
1288+ is : angular . identity ,
1289+ equals : angular . equals ,
1290+ pattern : / .* /
12431291 }
12441292 } ;
12451293
@@ -1506,33 +1554,29 @@ function $UrlMatcherFactory() {
15061554
15071555 this . Param = function Param ( id , type , config , location ) {
15081556 var self = this ;
1509- var defaultValueConfig = getDefaultValueConfig ( config ) ;
1510- config = config || { } ;
1511- type = getType ( config , type ) ;
1557+ config = unwrapShorthand ( config ) ;
1558+ type = getType ( config , type , location ) ;
15121559 var arrayMode = getArrayMode ( ) ;
15131560 type = arrayMode ? type . $asArray ( arrayMode , location === "search" ) : type ;
1514- if ( type . name === "string" && ! arrayMode && location === "path" && defaultValueConfig . value === undefined )
1515- defaultValueConfig . value = "" ; // for 0.2.x; in 0.3.0+ do not automatically default to ""
1516- var isOptional = defaultValueConfig . value !== undefined ;
1561+ if ( type . name === "string" && ! arrayMode && location === "path" && config . value === undefined )
1562+ config . value = "" ; // for 0.2.x; in 0.3.0+ do not automatically default to ""
1563+ var isOptional = config . value !== undefined ;
15171564 var squash = getSquashPolicy ( config , isOptional ) ;
15181565 var replace = getReplace ( config , arrayMode , isOptional , squash ) ;
15191566
1520- function getDefaultValueConfig ( config ) {
1567+ function unwrapShorthand ( config ) {
15211568 var keys = isObject ( config ) ? objectKeys ( config ) : [ ] ;
15221569 var isShorthand = indexOf ( keys , "value" ) === - 1 && indexOf ( keys , "type" ) === - 1 &&
15231570 indexOf ( keys , "squash" ) === - 1 && indexOf ( keys , "array" ) === - 1 ;
1524- var configValue = isShorthand ? config : config . value ;
1525- var result = {
1526- fn : isInjectable ( configValue ) ? configValue : function ( ) { return result . value ; } ,
1527- value : configValue
1528- } ;
1529- return result ;
1571+ if ( isShorthand ) config = { value : config } ;
1572+ config . $$fn = isInjectable ( config . value ) ? config . value : function ( ) { return config . value ; } ;
1573+ return config ;
15301574 }
15311575
1532- function getType ( config , urlType ) {
1576+ function getType ( config , urlType , location ) {
15331577 if ( config . type && urlType ) throw new Error ( "Param '" + id + "' has two type configurations." ) ;
15341578 if ( urlType ) return urlType ;
1535- if ( ! config . type ) return $types . string ;
1579+ if ( ! config . type ) return ( location === "config" ? $types . any : $types . string ) ;
15361580 return config . type instanceof Type ? config . type : new Type ( config . type ) ;
15371581 }
15381582
@@ -1571,7 +1615,7 @@ function $UrlMatcherFactory() {
15711615 */
15721616 function $$getDefaultValue ( ) {
15731617 if ( ! injector ) throw new Error ( "Injectable functions cannot be called at configuration time" ) ;
1574- return injector . invoke ( defaultValueConfig . fn ) ;
1618+ return injector . invoke ( config . $$ fn) ;
15751619 }
15761620
15771621 /**
@@ -1593,13 +1637,14 @@ function $UrlMatcherFactory() {
15931637 extend ( this , {
15941638 id : id ,
15951639 type : type ,
1640+ location : location ,
15961641 array : arrayMode ,
1597- config : config ,
15981642 squash : squash ,
15991643 replace : replace ,
16001644 isOptional : isOptional ,
1601- dynamic : undefined ,
16021645 value : $value ,
1646+ dynamic : undefined ,
1647+ config : config ,
16031648 toString : toString
16041649 } ) ;
16051650 } ;
@@ -1646,7 +1691,7 @@ function $UrlMatcherFactory() {
16461691 param = self [ key ] ;
16471692 val = paramValues [ key ] ;
16481693 isOptional = ! val && param . isOptional ;
1649- result = result && ( isOptional || param . type . is ( val ) ) ;
1694+ result = result && ( isOptional || ! ! param . type . is ( val ) ) ;
16501695 } ) ;
16511696 return result ;
16521697 } ,
@@ -1927,7 +1972,7 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
19271972 $get . $inject = [ '$location' , '$rootScope' , '$injector' , '$browser' ] ;
19281973 function $get ( $location , $rootScope , $injector , $browser ) {
19291974
1930- var baseHref = $browser . baseHref ( ) , location = $location . url ( ) ;
1975+ var baseHref = $browser . baseHref ( ) , location = $location . url ( ) , lastPushedUrl ;
19311976
19321977 function appendBasePath ( url , isHtml5 , absolute ) {
19331978 if ( baseHref === '/' ) return url ;
@@ -1939,6 +1984,9 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
19391984 // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
19401985 function update ( evt ) {
19411986 if ( evt && evt . defaultPrevented ) return ;
1987+ var ignoreUpdate = lastPushedUrl && $location . url ( ) === lastPushedUrl ;
1988+ lastPushedUrl = undefined ;
1989+ if ( ignoreUpdate ) return true ;
19421990
19431991 function check ( rule ) {
19441992 var handled = rule ( $injector , $location ) ;
@@ -2011,6 +2059,7 @@ function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
20112059
20122060 push : function ( urlMatcher , params , options ) {
20132061 $location . url ( urlMatcher . format ( params || { } ) ) ;
2062+ lastPushedUrl = options && options . $$avoidResync ? $location . url ( ) : undefined ;
20142063 if ( options && options . replace ) $location . replace ( ) ;
20152064 } ,
20162065
@@ -2140,7 +2189,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
21402189 ownParams : function ( state ) {
21412190 var params = state . url && state . url . params || new $$UMFP . ParamSet ( ) ;
21422191 forEach ( state . params || { } , function ( config , id ) {
2143- if ( ! params [ id ] ) params [ id ] = new $$UMFP . Param ( id , null , config ) ;
2192+ if ( ! params [ id ] ) params [ id ] = new $$UMFP . Param ( id , null , config , "config" ) ;
21442193 } ) ;
21452194 return params ;
21462195 } ,
@@ -2266,7 +2315,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
22662315 if ( ! state [ abstractKey ] && state . url ) {
22672316 $urlRouterProvider . when ( state . url , [ '$match' , '$stateParams' , function ( $match , $stateParams ) {
22682317 if ( $state . $current . navigable != state || ! equalForKeys ( $match , $stateParams ) ) {
2269- $state . transitionTo ( state , $match , { location : false } ) ;
2318+ $state . transitionTo ( state , $match , { inherit : true , location : false } ) ;
22702319 }
22712320 } ] ) ;
22722321 }
@@ -3147,7 +3196,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
31473196
31483197 if ( options . location && to . navigable ) {
31493198 $urlRouter . push ( to . navigable . url , to . navigable . locals . globals . $stateParams , {
3150- replace : options . location === 'replace'
3199+ $$avoidResync : true , replace : options . location === 'replace'
31513200 } ) ;
31523201 }
31533202
@@ -3243,15 +3292,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
32433292 options = extend ( { relative : $state . $current } , options || { } ) ;
32443293 var state = findState ( stateOrName , options . relative ) ;
32453294
3246- if ( ! isDefined ( state ) ) {
3247- return undefined ;
3248- }
3249-
3250- if ( $state . $current !== state ) {
3251- return false ;
3252- }
3253-
3254- return isDefined ( params ) && params !== null ? angular . equals ( $stateParams , params ) : true ;
3295+ if ( ! isDefined ( state ) ) { return undefined ; }
3296+ if ( $state . $current !== state ) { return false ; }
3297+ return params ? equalForKeys ( state . params . $$values ( params ) , $stateParams ) : true ;
32553298 } ;
32563299
32573300 /**
@@ -3315,13 +3358,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
33153358 }
33163359
33173360 var state = findState ( stateOrName , options . relative ) ;
3318- if ( ! isDefined ( state ) ) {
3319- return undefined ;
3320- }
3321- if ( ! isDefined ( $state . $current . includes [ state . name ] ) ) {
3322- return false ;
3323- }
3324- return equalForKeys ( params , $stateParams ) ;
3361+ if ( ! isDefined ( state ) ) { return undefined ; }
3362+ if ( ! isDefined ( $state . $current . includes [ state . name ] ) ) { return false ; }
3363+ return params ? equalForKeys ( state . params . $$values ( params ) , $stateParams , objectKeys ( params ) ) : true ;
33253364 } ;
33263365
33273366
@@ -4137,15 +4176,11 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
41374176
41384177 function isMatch ( ) {
41394178 if ( typeof $attrs . uiSrefActiveEq !== 'undefined' ) {
4140- return $ state. $current . self === state && matchesParams ( ) ;
4179+ return state && $state . is ( state . name , params ) ;
41414180 } else {
4142- return state && $state . includes ( state . name ) && matchesParams ( ) ;
4181+ return state && $state . includes ( state . name , params ) ;
41434182 }
41444183 }
4145-
4146- function matchesParams ( ) {
4147- return ! params || equalForKeys ( params , $stateParams ) ;
4148- }
41494184 } ]
41504185 } ;
41514186}
0 commit comments