@@ -97,7 +97,7 @@ function UrlMatcher(pattern, config) {
97
97
switch ( squashPolicy ) {
98
98
case "nosquash" : flags = [ '' , '' ] ; break ;
99
99
case "value" : flags = [ '' , '?' ] ; break ;
100
- case "slash" : flags = [ '?' , '?' ] ; break ;
100
+ case "slash" : flags = [ '?' , '?' ] ; break ;
101
101
}
102
102
return result + flags [ 0 ] + '(' + pattern + ')' + flags [ 1 ] ;
103
103
}
@@ -107,13 +107,12 @@ function UrlMatcher(pattern, config) {
107
107
// Split into static segments separated by path parameter placeholders.
108
108
// The number of segments is always 1 more than the number of parameters.
109
109
function matchDetails ( m , isSearch ) {
110
- var id , regexp , segment , type , typeId , cfg ;
111
- var defaultTypeId = ( isSearch ? "searchParam" : "pathParam" ) ;
110
+ var id , regexp , segment , type , cfg ;
112
111
id = m [ 2 ] || m [ 3 ] ; // IE[78] returns '' for unmatched groups instead of null
113
112
segment = pattern . substring ( last , m . index ) ;
114
113
regexp = isSearch ? m [ 4 ] : m [ 4 ] || ( m [ 1 ] == '*' ? '.*' : null ) ;
115
- typeId = regexp || defaultTypeId ;
116
- type = $$UMFP . type ( typeId ) || extend ( { } , $$UMFP . type ( defaultTypeId ) , { pattern : new RegExp ( regexp ) } ) ;
114
+ type = $$UMFP . type ( regexp || "string" ) || inherit ( $$UMFP . type ( "string" ) , { pattern : new RegExp ( regexp ) } ) ;
115
+ type = isSearch ? type . $asSearchType ( ) : type ;
117
116
cfg = config . params [ id ] ;
118
117
return {
119
118
id : id , regexp : regexp , segment : segment , type : type , cfg : cfg
@@ -293,47 +292,39 @@ UrlMatcher.prototype.validates = function (params) {
293
292
* @returns {string } the formatted URL (path and optionally search part).
294
293
*/
295
294
UrlMatcher . prototype . format = function ( values ) {
296
- var segments = this . segments , params = this . parameters ( ) ;
297
- var paramset = this . params ;
298
295
values = values || { } ;
299
-
300
- var nPath = segments . length - 1 , nTotal = params . length ,
301
- result = segments [ 0 ] , i , search , value , name , param , array , isDefaultValue ;
302
-
296
+ var segments = this . segments , params = this . parameters ( ) , paramset = this . params ;
303
297
if ( ! this . validates ( values ) ) return null ;
304
298
305
- for ( i = 0 ; i < nPath ; i ++ ) {
306
- name = params [ i ] ;
307
- param = paramset [ name ] ;
308
- value = param . value ( values [ name ] ) ;
309
- isDefaultValue = param . isOptional && param . type . equals ( param . value ( ) , value ) ;
299
+ var i , search = false , nPath = segments . length - 1 , nTotal = params . length , result = segments [ 0 ] ;
300
+
301
+ for ( i = 0 ; i < nTotal ; i ++ ) {
302
+ var isPathParam = i < nPath ;
303
+ var name = params [ i ] , param = paramset [ name ] , value = param . value ( values [ name ] ) ;
304
+ var isDefaultValue = param . isOptional && param . type . equals ( param . value ( ) , value ) ;
310
305
var squash = isDefaultValue ? param . squash : "nosquash" ;
311
306
var encoded = param . type . encode ( value ) ;
312
307
313
- var nextSegment = segments [ i + 1 ] ;
314
- if ( squash === "nosquash" ) {
315
- if ( encoded != null ) result += encodeURIComponent ( encoded ) ;
316
- result += nextSegment ;
317
- } else if ( squash === "value" ) {
318
- result += nextSegment ;
319
- } else if ( squash === "slash" ) {
320
- var capture = result . match ( / \/ $ / ) ? / \/ ? ( .* ) / : / ( .* ) / ;
321
- result += nextSegment . match ( capture ) [ 1 ] ;
308
+ if ( isPathParam ) {
309
+ var nextSegment = segments [ i + 1 ] ;
310
+ if ( squash === "nosquash" ) {
311
+ if ( encoded != null ) result += encodeURIComponent ( encoded ) ;
312
+ result += nextSegment ;
313
+ } else if ( squash === "value" ) {
314
+ result += nextSegment ;
315
+ } else if ( squash === "slash" ) {
316
+ var capture = result . match ( / \/ $ / ) ? / \/ ? ( .* ) / : / ( .* ) / ;
317
+ result += nextSegment . match ( capture ) [ 1 ] ;
318
+ }
319
+ } else {
320
+ if ( encoded == null || ( isDefaultValue && squash !== "nosquash" ) ) continue ;
321
+ if ( ! isArray ( encoded ) ) encoded = [ encoded ] ;
322
+ encoded = encoded . map ( encodeURIComponent ) . join ( '&' + name + '=' ) ;
323
+ result += ( search ? '&' : '?' ) + ( name + '=' + encoded ) ;
324
+ search = true ;
322
325
}
323
326
}
324
327
325
- for ( /**/ ; i < nTotal ; i ++ ) {
326
- name = params [ i ] ;
327
- value = values [ name ] ;
328
- if ( value == null ) continue ;
329
- array = isArray ( value ) ;
330
-
331
- if ( array ) {
332
- value = value . map ( encodeURIComponent ) . join ( '&' + name + '=' ) ;
333
- }
334
- result += ( search ? '&' : '?' ) + name + '=' + ( array ? value : encodeURIComponent ( value ) ) ;
335
- search = true ;
336
- }
337
328
return result ;
338
329
} ;
339
330
@@ -449,6 +440,57 @@ Type.prototype.$subPattern = function() {
449
440
450
441
Type . prototype . pattern = / .* / ;
451
442
443
+ Type . prototype . toString = function ( ) { return "{Type:" + this . name + "}" ; } ;
444
+
445
+ /*
446
+ * Wraps an existing custom Type as a search-query aware type which adds multi-value support.
447
+ * e.g.:
448
+ * - urlmatcher pattern "/path?{queryParam:int}"
449
+ * - url: "/path?queryParam=1&queryParam=2
450
+ * - $stateParams.queryParam will be [1, 2]
451
+ */
452
+ Type . prototype . $asSearchType = function ( ) {
453
+ return new SearchType ( this ) ;
454
+
455
+ function SearchType ( type ) {
456
+ var self = this ;
457
+ if ( type . $$autoSearchArray === false ) return type ;
458
+
459
+ function allTruthy ( array ) { // TODO: use reduce fn
460
+ var result = true ;
461
+ forEach ( array , function ( val ) { result = result && ! ! val ; } ) ;
462
+ return result ;
463
+ }
464
+
465
+ function map ( array , callback ) { // TODO: move to common.js in 1.0
466
+ var result = [ ] ;
467
+ forEach ( array , function ( val ) { result . push ( callback ( val ) ) ; } ) ;
468
+ return result ;
469
+ }
470
+
471
+ function autoHandleArray ( callback , reducefn ) {
472
+ return function ( val ) {
473
+ if ( isArray ( val ) ) {
474
+ var result = map ( val , callback ) ;
475
+ return reducefn ? reducefn ( result ) : result ;
476
+ } else {
477
+ return callback ( val ) ;
478
+ }
479
+ } ;
480
+ }
481
+
482
+ function bindTo ( thisObj , callback ) { return function ( ) { return callback . apply ( thisObj , arguments ) ; } ; }
483
+
484
+ this . encode = autoHandleArray ( bindTo ( this , type . encode ) ) ;
485
+ this . decode = autoHandleArray ( bindTo ( this , type . decode ) ) ;
486
+ this . equals = autoHandleArray ( bindTo ( this , type . equals ) , allTruthy ) ;
487
+ this . is = autoHandleArray ( bindTo ( this , type . is ) , allTruthy ) ;
488
+ this . pattern = type . pattern ;
489
+ }
490
+ } ;
491
+
492
+
493
+
452
494
/**
453
495
* @ngdoc object
454
496
* @name ui.router.util.$urlMatcherFactory
@@ -462,75 +504,41 @@ function $UrlMatcherFactory() {
462
504
463
505
var isCaseInsensitive = false , isStrictMode = true , defaultSquashPolicy = "nosquash" ;
464
506
465
- function safeString ( val ) { return val != null ? val . toString ( ) : val ; }
466
- function coerceEquals ( left , right ) { return left == right ; }
507
+ function valToString ( val ) { return val != null ? val . toString ( ) : val ; }
467
508
function angularEquals ( left , right ) { return angular . equals ( left , right ) ; }
468
- // TODO: function regexpMatches(val) { return isDefined(val) && this.pattern.test(val); }
509
+ // TODO: in 1.0, make string .is() return false if value is undefined by default.
510
+ // function regexpMatches(val) { /*jshint validthis:true */ return isDefined(val) && this.pattern.test(val); }
469
511
function regexpMatches ( val ) { /*jshint validthis:true */ return this . pattern . test ( val ) ; }
470
- function normalizeStringOrArray ( val ) {
471
- if ( isArray ( val ) ) {
472
- var encoded = [ ] ;
473
- forEach ( val , function ( item ) { encoded . push ( safeString ( item ) ) ; } ) ;
474
- return encoded ;
475
- } else {
476
- return safeString ( val ) ;
477
- }
478
- }
479
512
480
513
var $types = { } , enqueue = true , typeQueue = [ ] , injector , defaultTypes = {
481
- "searchParam" : {
482
- encode : normalizeStringOrArray ,
483
- decode : normalizeStringOrArray ,
484
- equals : angularEquals ,
485
- is : regexpMatches ,
486
- pattern : / [ ^ & ? ] * /
487
- } ,
488
- "pathParam" : {
489
- encode : safeString ,
490
- decode : safeString ,
491
- equals : coerceEquals ,
514
+ string : {
515
+ encode : valToString ,
516
+ decode : valToString ,
492
517
is : regexpMatches ,
493
518
pattern : / [ ^ / ] * /
494
519
} ,
495
520
int : {
496
- decode : function ( val ) {
497
- return parseInt ( val , 10 ) ;
498
- } ,
499
- is : function ( val ) {
500
- if ( ! isDefined ( val ) ) return false ;
501
- return this . decode ( val . toString ( ) ) === val ;
502
- } ,
521
+ encode : valToString ,
522
+ decode : function ( val ) { return parseInt ( val , 10 ) ; } ,
523
+ is : function ( val ) { return isDefined ( val ) && this . decode ( val . toString ( ) ) === val ; } ,
503
524
pattern : / \d + /
504
525
} ,
505
526
bool : {
506
- encode : function ( val ) {
507
- return val ? 1 : 0 ;
508
- } ,
509
- decode : function ( val ) {
510
- return parseInt ( val , 10 ) !== 0 ;
511
- } ,
512
- is : function ( val ) {
513
- return val === true || val === false ;
514
- } ,
527
+ encode : function ( val ) { return val ? 1 : 0 ; } ,
528
+ decode : function ( val ) { return parseInt ( val , 10 ) !== 0 ; } ,
529
+ is : function ( val ) { return val === true || val === false ; } ,
515
530
pattern : / 0 | 1 /
516
531
} ,
517
- string : {
518
- pattern : / [ ^ \/ ] * /
519
- } ,
520
532
date : {
521
- equals : function ( a , b ) {
522
- return a . toISOString ( ) === b . toISOString ( ) ;
523
- } ,
524
- decode : function ( val ) {
525
- return new Date ( val ) ;
526
- } ,
527
- encode : function ( val ) {
528
- return [
533
+ encode : function ( val ) { return [
529
534
val . getFullYear ( ) ,
530
535
( '0' + ( val . getMonth ( ) + 1 ) ) . slice ( - 2 ) ,
531
536
( '0' + val . getDate ( ) ) . slice ( - 2 )
532
537
] . join ( "-" ) ;
533
538
} ,
539
+ decode : function ( val ) { return new Date ( val ) ; } ,
540
+ is : function ( val ) { return val instanceof Date && ! isNaN ( val . valueOf ( ) ) ; } ,
541
+ equals : function ( a , b ) { return a . toISOString ( ) === b . toISOString ( ) ; } ,
534
542
pattern : / [ 0 - 9 ] { 4 } - (?: 0 [ 1 - 9 ] | 1 [ 0 - 2 ] ) - (?: 0 [ 1 - 9 ] | [ 1 - 2 ] [ 0 - 9 ] | 3 [ 0 - 1 ] ) /
535
543
}
536
544
} ;
@@ -756,7 +764,7 @@ function $UrlMatcherFactory() {
756
764
if ( ! isDefined ( definition ) ) return $types [ name ] ;
757
765
if ( $types . hasOwnProperty ( name ) ) throw new Error ( "A type named '" + name + "' has already been defined." ) ;
758
766
759
- $types [ name ] = new Type ( definition ) ;
767
+ $types [ name ] = new Type ( extend ( { } , { name : name } , definition ) ) ;
760
768
if ( definitionFn ) {
761
769
typeQueue . push ( { name : name , def : definitionFn } ) ;
762
770
if ( ! enqueue ) flushTypeQueue ( ) ;
@@ -810,7 +818,7 @@ function $UrlMatcherFactory() {
810
818
function getType ( config , urlType ) {
811
819
if ( config . type && urlType ) throw new Error ( "Param '" + id + "' has two type configurations." ) ;
812
820
if ( urlType ) return urlType ;
813
- if ( ! config . type ) return $types . pathParam ;
821
+ if ( ! config . type ) return $types . string ;
814
822
return config . type instanceof Type ? config . type : new Type ( config . type ) ;
815
823
}
816
824
@@ -843,14 +851,17 @@ function $UrlMatcherFactory() {
843
851
return isDefined ( value ) ? self . type . decode ( value ) : $$getDefaultValue ( ) ;
844
852
}
845
853
854
+ function toString ( ) { return "{Param:" + id + " " + type + " squash: " + squash + " optional: " + isOptional + "}" ; }
855
+
846
856
extend ( this , {
847
857
id : id ,
848
858
type : type ,
849
859
config : config ,
850
860
squash : squash ,
851
861
dynamic : undefined ,
852
862
isOptional : isOptional ,
853
- value : $value
863
+ value : $value ,
864
+ toString : toString
854
865
} ) ;
855
866
} ;
856
867
@@ -870,7 +881,7 @@ function $UrlMatcherFactory() {
870
881
return values ;
871
882
} ,
872
883
$$equals : function ( paramValues1 , paramValues2 ) {
873
- var equal = true ; self = this ;
884
+ var equal = true , self = this ;
874
885
forEach ( self . $$keys ( ) , function ( key ) {
875
886
var left = paramValues1 && paramValues1 [ key ] , right = paramValues2 && paramValues2 [ key ] ;
876
887
if ( ! self [ key ] . type . equals ( left , right ) ) equal = false ;
0 commit comments