@@ -70,12 +70,14 @@ function UrlMatcher(pattern, config) {
7070 // The regular expression is somewhat complicated due to the need to allow curly braces
7171 // inside the regular expression. The placeholder regexp breaks down as follows:
7272 // ([:*])(\w+) classic placeholder ($1 / $2)
73- // \{(\w+)(?:\:( ... ))?\} curly brace placeholder ($3) with optional regexp ... ($4)
73+ // ([:]?)([\w-]+) classic search placeholder (supports snake-case-params) ($1 / $2)
74+ // \{(\w+)(?:\:( ... ))?\} curly brace placeholder ($3) with optional regexp/type ... ($4)
7475 // (?: ... | ... | ... )+ the regexp consists of any number of atoms, an atom being either
7576 // [^{}\\]+ - anything other than curly braces or backslash
7677 // \\. - a backslash escape
7778 // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms
7879 var placeholder = / ( [: * ] ) ( \w + ) | \{ ( \w + ) (?: \: ( (?: [ ^ { } \\ ] + | \\ .| \{ (?: [ ^ { } \\ ] + | \\ .) * \} ) + ) ) ? \} / g,
80+ searchPlaceholder = / ( [: ] ? ) ( [ \w - ] + ) | \{ ( \w + ) (?: \: ( (?: [ ^ { } \\ ] + | \\ .| \{ (?: [ ^ { } \\ ] + | \\ .) * \} ) + ) ) ? \} / g,
7981 compiled = '^' , last = 0 , m ,
8082 segments = this . segments = [ ] ,
8183 params = this . params = new $$UrlMatcherFactoryProvider . ParamSet ( ) ;
@@ -94,32 +96,33 @@ function UrlMatcher(pattern, config) {
9496 return result + flag + '(' + pattern + ')' + flag ;
9597 }
9698
97- function regexpType ( regexp ) {
98- var type = new Type ( {
99- pattern : new RegExp ( regexp ) ,
100- is : function ( value ) { return type . pattern . exec ( type . encode ( value ) ) ; }
101- } ) ;
102- return type ;
103- }
104-
10599 this . source = pattern ;
106100
107101 // Split into static segments separated by path parameter placeholders.
108102 // The number of segments is always 1 more than the number of parameters.
109- var id , regexp , segment , type , cfg ;
110-
111- while ( ( m = placeholder . exec ( pattern ) ) ) {
103+ function matchDetails ( m , isSearch ) {
104+ var id , regexp , segment , type , typeId , cfg ;
105+ var $types = UrlMatcher . prototype . $types ;
106+ var defaultTypeId = ( isSearch ? "searchParam" : "pathParam" ) ;
112107 id = m [ 2 ] || m [ 3 ] ; // IE[78] returns '' for unmatched groups instead of null
113- regexp = m [ 4 ] || ( m [ 1 ] == '*' ? '.*' : '[^/]*' ) ;
114108 segment = pattern . substring ( last , m . index ) ;
115- type = this . $types [ regexp ] || regexpType ( regexp ) ;
109+ regexp = isSearch ? m [ 4 ] : m [ 4 ] || ( m [ 1 ] == '*' ? '.*' : null ) ;
110+ typeId = regexp || defaultTypeId ;
111+ type = $types [ typeId ] || extend ( { } , $types [ defaultTypeId ] , { pattern : new RegExp ( regexp ) } ) ;
116112 cfg = config . params [ id ] ;
113+ return {
114+ id : id , regexp : regexp , segment : segment , type : type , cfg : cfg
115+ } ;
116+ }
117117
118- if ( segment . indexOf ( '?' ) >= 0 ) break ; // we're into the search part
118+ var p , param , segment ;
119+ while ( ( m = placeholder . exec ( pattern ) ) ) {
120+ p = matchDetails ( m , false ) ;
121+ if ( p . segment . indexOf ( '?' ) >= 0 ) break ; // we're into the search part
119122
120- var param = addParameter ( id , type , cfg ) ;
121- compiled += quoteRegExp ( segment , type . $subPattern ( ) , param . isOptional ) ;
122- segments . push ( segment ) ;
123+ param = addParameter ( p . id , p . type , p . cfg ) ;
124+ compiled += quoteRegExp ( p . segment , param . type . pattern . source , param . isOptional ) ;
125+ segments . push ( p . segment ) ;
123126 last = placeholder . lastIndex ;
124127 }
125128 segment = pattern . substring ( last ) ;
@@ -132,10 +135,15 @@ function UrlMatcher(pattern, config) {
132135 segment = segment . substring ( 0 , i ) ;
133136 this . sourcePath = pattern . substring ( 0 , last + i ) ;
134137
135- // Allow parameters to be separated by '?' as well as '&' to make concat() easier
136- forEach ( search . substring ( 1 ) . split ( / [ & ? ] / ) , function ( key ) {
137- addParameter ( key , null , config . params [ key ] ) ;
138- } ) ;
138+ if ( search . length > 0 ) {
139+ last = 0 ;
140+ while ( ( m = searchPlaceholder . exec ( search ) ) ) {
141+ p = matchDetails ( m , true ) ;
142+ param = addParameter ( p . id , p . type , p . cfg ) ;
143+ last = placeholder . lastIndex ;
144+ // check if ?&
145+ }
146+ }
139147 } else {
140148 this . sourcePath = pattern ;
141149 this . sourceSearch = '' ;
@@ -385,7 +393,7 @@ Type.prototype.encode = function(val, key) {
385393 * @methodOf ui.router.util.type:Type
386394 *
387395 * @description
388- * Converts a string URL parameter value to a custom/native value.
396+ * Converts a parameter value (from URL string or transition param) to a custom/native value.
389397 *
390398 * @param {string } val The URL parameter value to decode.
391399 * @param {string } key The name of the parameter in which `val` is stored. Can be used for
@@ -433,7 +441,36 @@ function $UrlMatcherFactory() {
433441
434442 var isCaseInsensitive = false , isStrictMode = true ;
435443
444+ function safeString ( val ) { return isDefined ( val ) ? val . toString ( ) : val ; }
445+ function coerceEquals ( left , right ) { return left == right ; }
446+ function angularEquals ( left , right ) { return angular . equals ( left , right ) ; }
447+ // TODO: function regexpMatches(val) { return isDefined(val) && this.pattern.test(val); }
448+ function regexpMatches ( val ) { /*jshint validthis:true */ return this . pattern . test ( val ) ; }
449+ function normalizeStringOrArray ( val ) {
450+ if ( isArray ( val ) ) {
451+ var encoded = [ ] ;
452+ forEach ( val , function ( item ) { encoded . push ( safeString ( item ) ) ; } ) ;
453+ return encoded ;
454+ } else {
455+ return safeString ( val ) ;
456+ }
457+ }
458+
436459 var enqueue = true , typeQueue = [ ] , injector , defaultTypes = {
460+ "searchParam" : {
461+ encode : normalizeStringOrArray ,
462+ decode : normalizeStringOrArray ,
463+ equals : angularEquals ,
464+ is : regexpMatches ,
465+ pattern : / [ ^ & ? ] * /
466+ } ,
467+ "pathParam" : {
468+ encode : safeString ,
469+ decode : safeString ,
470+ equals : coerceEquals ,
471+ is : regexpMatches ,
472+ pattern : / [ ^ / ] * /
473+ } ,
437474 int : {
438475 decode : function ( val ) {
439476 return parseInt ( val , 10 ) ;
@@ -449,7 +486,7 @@ function $UrlMatcherFactory() {
449486 return val ? 1 : 0 ;
450487 } ,
451488 decode : function ( val ) {
452- return parseInt ( val , 10 ) === 0 ? false : true ;
489+ return parseInt ( val , 10 ) !== 0 ;
453490 } ,
454491 is : function ( val ) {
455492 return val === true || val === false ;
@@ -720,8 +757,9 @@ function $UrlMatcherFactory() {
720757
721758 function getType ( config , urlType ) {
722759 if ( config . type && urlType ) throw new Error ( "Param '" + id + "' has two type configurations." ) ;
723- if ( urlType && ! config . type ) return urlType ;
724- return config . type instanceof Type ? config . type : new Type ( config . type || { } ) ;
760+ if ( urlType ) return urlType ;
761+ if ( ! config . type ) return UrlMatcher . prototype . $types . pathParam ;
762+ return config . type instanceof Type ? config . type : new Type ( config . type ) ;
725763 }
726764
727765 /**
0 commit comments