@@ -72,15 +72,14 @@ function UrlMatcher(pattern, caseInsensitiveMatch) {
72
72
// \\. - a backslash escape
73
73
// \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms
74
74
var placeholder = / ( [: * ] ) ( \w + ) | \{ ( \w + ) (?: \: ( (?: [ ^ { } \\ ] + | \\ .| \{ (?: [ ^ { } \\ ] + | \\ .) * \} ) + ) ) ? \} / g,
75
- names = { } , compiled = '^' , last = 0 , m ,
75
+ compiled = '^' , last = 0 , m ,
76
76
segments = this . segments = [ ] ,
77
- params = this . params = [ ] ;
77
+ params = this . params = { } ;
78
78
79
- function addParameter ( id ) {
79
+ function addParameter ( id , type ) {
80
80
if ( ! / ^ \w + ( - + \w + ) * $ / . test ( id ) ) throw new Error ( "Invalid parameter name '" + id + "' in pattern '" + pattern + "'" ) ;
81
- if ( names [ id ] ) throw new Error ( "Duplicate parameter name '" + id + "' in pattern '" + pattern + "'" ) ;
82
- names [ id ] = true ;
83
- params . push ( id ) ;
81
+ if ( params [ id ] ) throw new Error ( "Duplicate parameter name '" + id + "' in pattern '" + pattern + "'" ) ;
82
+ params [ id ] = type ;
84
83
}
85
84
86
85
function quoteRegExp ( string ) {
@@ -91,25 +90,30 @@ function UrlMatcher(pattern, caseInsensitiveMatch) {
91
90
92
91
// Split into static segments separated by path parameter placeholders.
93
92
// The number of segments is always 1 more than the number of parameters.
94
- var id , regexp , segment ;
93
+ var id , regexp , segment , type ;
94
+
95
95
while ( ( m = placeholder . exec ( pattern ) ) ) {
96
- id = m [ 2 ] || m [ 3 ] ; // IE[78] returns '' for unmatched groups instead of null
97
- regexp = m [ 4 ] || ( m [ 1 ] == '*' ? '.*' : '[^/]*' ) ;
96
+ id = m [ 2 ] || m [ 3 ] ; // IE[78] returns '' for unmatched groups instead of null
97
+ regexp = m [ 4 ] || ( m [ 1 ] == '*' ? '.*' : '[^/]*' ) ;
98
98
segment = pattern . substring ( last , m . index ) ;
99
+ type = this . $types [ regexp ] || new Type ( { pattern : new RegExp ( regexp ) } ) ;
100
+
99
101
if ( segment . indexOf ( '?' ) >= 0 ) break ; // we're into the search part
100
- compiled += quoteRegExp ( segment ) + '(' + regexp + ')' ;
101
- addParameter ( id ) ;
102
+
103
+ compiled += quoteRegExp ( segment ) + '(' + type . $subPattern ( ) + ')' ;
104
+ addParameter ( id , type ) ;
102
105
segments . push ( segment ) ;
103
106
last = placeholder . lastIndex ;
104
107
}
105
108
segment = pattern . substring ( last ) ;
106
109
107
110
// Find any search parameter names and remove them from the last segment
108
111
var i = segment . indexOf ( '?' ) ;
112
+
109
113
if ( i >= 0 ) {
110
114
var search = this . sourceSearch = segment . substring ( i ) ;
111
115
segment = segment . substring ( 0 , i ) ;
112
- this . sourcePath = pattern . substring ( 0 , last + i ) ;
116
+ this . sourcePath = pattern . substring ( 0 , last + i ) ;
113
117
114
118
// Allow parameters to be separated by '?' as well as '&' to make concat() easier
115
119
forEach ( search . substring ( 1 ) . split ( / [ & ? ] / ) , addParameter ) ;
@@ -120,12 +124,8 @@ function UrlMatcher(pattern, caseInsensitiveMatch) {
120
124
121
125
compiled += quoteRegExp ( segment ) + '$' ;
122
126
segments . push ( segment ) ;
123
- if ( caseInsensitiveMatch ) {
124
- this . regexp = new RegExp ( compiled , 'i' ) ;
125
- } else {
126
- this . regexp = new RegExp ( compiled ) ;
127
- }
128
-
127
+
128
+ this . regexp = ( caseInsensitiveMatch ) ? new RegExp ( compiled , 'i' ) : new RegExp ( compiled ) ;
129
129
this . prefix = segments [ 0 ] ;
130
130
}
131
131
@@ -187,14 +187,18 @@ UrlMatcher.prototype.exec = function (path, searchParams) {
187
187
var m = this . regexp . exec ( path ) ;
188
188
if ( ! m ) return null ;
189
189
190
- var params = this . params , nTotal = params . length ,
191
- nPath = this . segments . length - 1 ,
192
- values = { } , i ;
190
+ var params = this . parameters ( ) , nTotal = params . length ,
191
+ nPath = this . segments . length - 1 ,
192
+ values = { } , i , type , param ;
193
193
194
194
if ( nPath !== m . length - 1 ) throw new Error ( "Unbalanced capture group in route '" + this . source + "'" ) ;
195
195
196
- for ( i = 0 ; i < nPath ; i ++ ) values [ params [ i ] ] = m [ i + 1 ] ;
197
- for ( /**/ ; i < nTotal ; i ++ ) values [ params [ i ] ] = searchParams [ params [ i ] ] ;
196
+ for ( i = 0 ; i < nPath ; i ++ ) {
197
+ param = params [ i ] ;
198
+ type = this . params [ param ] ;
199
+ values [ param ] = type . decode ( m [ i + 1 ] ) ;
200
+ }
201
+ for ( /**/ ; i < nTotal ; i ++ ) values [ params [ i ] ] = searchParams [ params [ i ] ] ;
198
202
199
203
return values ;
200
204
} ;
@@ -211,7 +215,7 @@ UrlMatcher.prototype.exec = function (path, searchParams) {
211
215
* pattern has no parameters, an empty array is returned.
212
216
*/
213
217
UrlMatcher . prototype . parameters = function ( ) {
214
- return this . params ;
218
+ return keys ( this . params ) ;
215
219
} ;
216
220
217
221
/**
@@ -234,30 +238,59 @@ UrlMatcher.prototype.parameters = function () {
234
238
* @returns {string } the formatted URL (path and optionally search part).
235
239
*/
236
240
UrlMatcher . prototype . format = function ( values ) {
237
- var segments = this . segments , params = this . params ;
241
+ var segments = this . segments , params = this . parameters ( ) ;
238
242
if ( ! values ) return segments . join ( '' ) ;
239
243
240
- var nPath = segments . length - 1 , nTotal = params . length ,
241
- result = segments [ 0 ] , i , search , value ;
244
+ var nPath = segments . length - 1 , nTotal = params . length ,
245
+ result = segments [ 0 ] , i , search , value , param , type ;
242
246
243
- for ( i = 0 ; i < nPath ; i ++ ) {
244
- value = values [ params [ i ] ] ;
245
- // TODO: Maybe we should throw on null here? It's not really good style to use '' and null interchangeabley
246
- if ( value != null ) result += encodeURIComponent ( value ) ;
247
- result += segments [ i + 1 ] ;
248
- }
249
- for ( /**/ ; i < nTotal ; i ++ ) {
250
- value = values [ params [ i ] ] ;
251
- if ( value != null ) {
252
- result += ( search ? '&' : '?' ) + params [ i ] + '=' + encodeURIComponent ( value ) ;
253
- search = true ;
254
- }
247
+ for ( i = 0 ; i < nPath ; i ++ ) {
248
+ param = params [ i ] ;
249
+ value = values [ param ] ;
250
+ type = this . params [ param ] ;
251
+ // TODO: Maybe we should throw on null here? It's not really good style
252
+ // to use '' and null interchangeabley
253
+ if ( value != null ) result += encodeURIComponent ( type . encode ( value ) ) ;
254
+ result += segments [ i + 1 ] ;
255
255
}
256
256
257
+ for ( /**/ ; i < nTotal ; i ++ ) {
258
+ param = params [ i ]
259
+ if ( values [ param ] == null ) continue ;
260
+ result += ( search ? '&' : '?' ) + param + '=' + encodeURIComponent ( values [ param ] ) ;
261
+ search = true ;
262
+ }
257
263
return result ;
258
264
} ;
259
265
266
+ UrlMatcher . prototype . $types = { } ;
267
+
268
+ function Type ( options ) {
269
+ extend ( this , options ) ;
270
+ }
271
+
272
+ Type . prototype . is = function ( val , key ) {
273
+ return angular . toJson ( this . decode ( this . encode ( val ) ) ) === angular . toJson ( val ) ;
274
+ } ;
275
+
276
+ Type . prototype . encode = function ( val , key ) {
277
+ return String ( val ) ;
278
+ } ;
260
279
280
+ Type . prototype . decode = function ( val , key ) {
281
+ return val ;
282
+ }
283
+
284
+ Type . prototype . equals = function ( a , b ) {
285
+ return a == b ;
286
+ } ;
287
+
288
+ Type . prototype . $subPattern = function ( ) {
289
+ var sub = this . pattern . toString ( ) ;
290
+ return sub . substr ( 1 , sub . length - 2 ) ;
291
+ } ;
292
+
293
+ Type . prototype . pattern = / .* / ;
261
294
262
295
/**
263
296
* @ngdoc object
@@ -271,6 +304,33 @@ function $UrlMatcherFactory() {
271
304
272
305
var useCaseInsensitiveMatch = false ;
273
306
307
+ var enqueue = true , typeQueue = [ ] , injector , defaultTypes = {
308
+ int : {
309
+ decode : function ( val ) {
310
+ return parseInt ( val , 10 ) ;
311
+ } ,
312
+ is : function ( val ) {
313
+ return this . decode ( val . toString ( ) ) === val ;
314
+ } ,
315
+ pattern : / \d + /
316
+ } ,
317
+ bool : {
318
+ encode : function ( val ) {
319
+ return val ? 1 : 0 ;
320
+ } ,
321
+ decode : function ( val ) {
322
+ return parseInt ( val , 10 ) === 0 ? false : true ;
323
+ } ,
324
+ is : function ( val ) {
325
+ return val === true || val === false ;
326
+ } ,
327
+ pattern : / 0 | 1 /
328
+ } ,
329
+ string : {
330
+ pattern : / .* /
331
+ }
332
+ } ;
333
+
274
334
/**
275
335
* @ngdoc function
276
336
* @name ui.router.util.$urlMatcherFactory#caseInsensitiveMatch
@@ -281,7 +341,7 @@ function $UrlMatcherFactory() {
281
341
*
282
342
* @param {bool } value false to match URL in a case sensitive manner; otherwise true;
283
343
*/
284
- this . caseInsensitiveMatch = function ( value ) {
344
+ this . caseInsensitiveMatch = function ( value ) {
285
345
useCaseInsensitiveMatch = value ;
286
346
} ;
287
347
@@ -314,11 +374,36 @@ function $UrlMatcherFactory() {
314
374
this . isMatcher = function ( o ) {
315
375
return isObject ( o ) && isFunction ( o . exec ) && isFunction ( o . format ) && isFunction ( o . concat ) ;
316
376
} ;
317
-
318
- /* No need to document $get, since it returns this */
319
- this . $get = function ( ) {
377
+
378
+ this . type = function ( name , def ) {
379
+ if ( ! isDefined ( def ) ) return UrlMatcher . prototype . $types [ name ] ;
380
+ typeQueue . push ( { name : name , def : def } ) ;
381
+ if ( ! enqueue ) flushTypeQueue ( ) ;
320
382
return this ;
321
383
} ;
384
+
385
+ /* No need to document $get, since it returns this */
386
+ this . $get = [ '$injector' , function ( $injector ) {
387
+ injector = $injector ;
388
+ enqueue = false ;
389
+ UrlMatcher . prototype . $types = { } ;
390
+ flushTypeQueue ( ) ;
391
+
392
+ forEach ( defaultTypes , function ( type , name ) {
393
+ if ( ! UrlMatcher . prototype . $types [ name ] ) UrlMatcher . prototype . $types [ name ] = new Type ( type ) ;
394
+ } ) ;
395
+ return this ;
396
+ } ] ;
397
+
398
+ function flushTypeQueue ( ) {
399
+ forEach ( typeQueue , function ( type ) {
400
+ if ( UrlMatcher . prototype . $types [ type . name ] ) {
401
+ throw new Error ( "A type named '" + type . name + "' has already been defined." ) ;
402
+ }
403
+ var def = new Type ( isFunction ( type . def ) ? injector . invoke ( type . def ) : type . def ) ;
404
+ UrlMatcher . prototype . $types [ type . name ] = def ;
405
+ } ) ;
406
+ }
322
407
}
323
408
324
409
// Register as a provider so it's available to other providers
0 commit comments