From 7eafbb98c64c0dc079d7d3ec589f1270b7f6fea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Ram=C3=B3n=20L=C3=B3pez?= Date: Mon, 12 Nov 2012 22:09:53 +0100 Subject: [PATCH] feat(routeProvider): Add support to catch-all parameters in routes This allows routeProvider to accept parameters that matches substrings even when they contain slashes if they are prefixed with an asterisk instead of a colon. For example, routes like edit/color/:color/largecode/*largecode will match with something like this http://appdomain.com/edit/color/brown/largecode/code/with/slashs. --- src/ng/route.js | 30 +++++++++++++++++------ test/ng/routeSpec.js | 57 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 7 deletions(-) diff --git a/src/ng/route.js b/src/ng/route.js index 971caa1ccbdb..afdac3fb3ce6 100644 --- a/src/ng/route.js +++ b/src/ng/route.js @@ -23,9 +23,18 @@ function $RouteProvider(){ * `$location.path` will be updated to add or drop the trailing slash to exactly match the * route definition. * - * `path` can contain named groups starting with a colon (`:name`). All characters up to the - * next slash are matched and stored in `$routeParams` under the given `name` when the route - * matches. + * * `path` can contain named groups starting with a colon (`:name`). All characters up + * to the next slash are matched and stored in `$routeParams` under the given `name` + * when the route matches. + * * `path` can contain named groups starting with a star (`*name`). All characters are + * eagerly stored in `$routeParams` under the given `name` when the route matches. + * + * For example, routes like `/color/:color/largecode/*largecode/edit` will match + * `/color/brown/largecode/code/with/slashs/edit` and extract: + * + * * `color: brown` + * * `largecode: code/with/slashs`. + * * * @param {Object} route Mapping information to be assigned to `$route.current` on route * match. @@ -341,12 +350,12 @@ function $RouteProvider(){ // regex only once and then reuse it // Escape regexp special characters. - when = '^' + when.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") + '$'; + when = '^' + when.replace(/[-\/\\^$:*+?.()|[\]{}]/g, "\\$&") + '$'; var regex = '', params = [], dst = {}; - var re = /:(\w+)/g, + var re = /\\([:*])(\w+)/g, paramMatch, lastMatchedIndex = 0; @@ -354,8 +363,15 @@ function $RouteProvider(){ // Find each :param in `when` and replace it with a capturing group. // Append all other sections of when unchanged. regex += when.slice(lastMatchedIndex, paramMatch.index); - regex += '([^\\/]*)'; - params.push(paramMatch[1]); + switch(paramMatch[1]) { + case ':': + regex += '([^\\/]*)'; + break; + case '*': + regex += '(.*)'; + break; + } + params.push(paramMatch[2]); lastMatchedIndex = re.lastIndex; } // Append trailing path part. diff --git a/test/ng/routeSpec.js b/test/ng/routeSpec.js index e8e63b77337c..d43dcfba7e1e 100644 --- a/test/ng/routeSpec.js +++ b/test/ng/routeSpec.js @@ -59,6 +59,63 @@ describe('$route', function() { }); }); + it('should route and fire change event when catch-all params are used', function() { + var log = '', + lastRoute, + nextRoute; + + module(function($routeProvider) { + $routeProvider.when('/Book1/:book/Chapter/:chapter/*highlight/edit', + {controller: noop, templateUrl: 'Chapter.html'}); + $routeProvider.when('/Book2/:book/*highlight/Chapter/:chapter', + {controller: noop, templateUrl: 'Chapter.html'}); + $routeProvider.when('/Blank', {}); + }); + inject(function($route, $location, $rootScope) { + $rootScope.$on('$routeChangeStart', function(event, next, current) { + log += 'before();'; + expect(current).toBe($route.current); + lastRoute = current; + nextRoute = next; + }); + $rootScope.$on('$routeChangeSuccess', function(event, current, last) { + log += 'after();'; + expect(current).toBe($route.current); + expect(lastRoute).toBe(last); + expect(nextRoute).toBe(current); + }); + + $location.path('/Book1/Moby/Chapter/Intro/one/edit').search('p=123'); + $rootScope.$digest(); + $httpBackend.flush(); + expect(log).toEqual('before();after();'); + expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one', p:'123'}); + + log = ''; + $location.path('/Blank').search('ignore'); + $rootScope.$digest(); + expect(log).toEqual('before();after();'); + expect($route.current.params).toEqual({ignore:true}); + + log = ''; + $location.path('/Book1/Moby/Chapter/Intro/one/two/edit').search('p=123'); + $rootScope.$digest(); + expect(log).toEqual('before();after();'); + expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one/two', p:'123'}); + + log = ''; + $location.path('/Book2/Moby/one/two/Chapter/Intro').search('p=123'); + $rootScope.$digest(); + expect(log).toEqual('before();after();'); + expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', highlight:'one/two', p:'123'}); + + log = ''; + $location.path('/NONE'); + $rootScope.$digest(); + expect(log).toEqual('before();after();'); + expect($route.current).toEqual(null); + }); + }); it('should not change route when location is canceled', function() { module(function($routeProvider) {