From 3bf0120c9bac0bcd3bb3b1f2d7f3086ba8579a89 Mon Sep 17 00:00:00 2001 From: joshkurz Date: Fri, 26 Apr 2013 17:31:56 -0400 Subject: [PATCH] feature(urlKeyValue): not rewriting duplicate params in url parseKeyValue and toKeyValue can work with duplicates: 1)parseKeyValue looks for presence of obj[key] 2)detects and replaces obj[key] with [obj[key],val] 3)then pushes more duplicates if neccessary 4)toKeyValue decodes array correctly 5)(not changed)$location.search({param: 'key'}) still replaces if neccessary 6)(not changed)$location.search({param: ['key','key2']}) sets the url with duplicates BREAKING CHANGE: if the server relied on the buggy behavior then either the backend should be fixed or a simple serialization of the array should be done on the client before calling the resource service. --- src/Angular.js | 17 +++++++++++++++-- src/ng/location.js | 3 ++- test/AngularSpec.js | 22 ++++++++++++++++++++-- test/ng/locationSpec.js | 23 +++++++++++++++++++++++ 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/Angular.js b/src/Angular.js index bf64c6c27de5..b76cc940370b 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -827,7 +827,14 @@ function parseKeyValue(/**string*/keyValue) { if (keyValue) { key_value = keyValue.split('='); key = decodeURIComponent(key_value[0]); - obj[key] = isDefined(key_value[1]) ? decodeURIComponent(key_value[1]) : true; + var val = isDefined(key_value[1]) ? decodeURIComponent(key_value[1]) : true; + if (!obj[key]) { + obj[key] = val; + } else if(isArray(obj[key])) { + obj[key].push(val); + } else { + obj[key] = [obj[key],val]; + } } }); return obj; @@ -836,7 +843,13 @@ function parseKeyValue(/**string*/keyValue) { function toKeyValue(obj) { var parts = []; forEach(obj, function(value, key) { - parts.push(encodeUriQuery(key, true) + (value === true ? '' : '=' + encodeUriQuery(value, true))); + if (isArray(value)) { + forEach(value, function(arrayValue) { + parts.push(encodeUriQuery(key, true) + (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); + }); + } else { + parts.push(encodeUriQuery(key, true) + (value === true ? '' : '=' + encodeUriQuery(value, true))); + } }); return parts.length ? parts.join('&') : ''; } diff --git a/src/ng/location.js b/src/ng/location.js index 9a5a68ba35d3..2fa84c0bc19d 100644 --- a/src/ng/location.js +++ b/src/ng/location.js @@ -343,7 +343,8 @@ LocationHashbangInHtml5Url.prototype = * * Change search part when called with parameter and return `$location`. * - * @param {string|object=} search New search params - string or hash object + * @param {string|Object.|Object.>} search New search params - string or hash object. Hash object + * may contain an array of values, which will be decoded as duplicates in the url. * @param {string=} paramValue If `search` is a string, then `paramValue` will override only a * single search parameter. If the value is `null`, the parameter will be deleted. * diff --git a/test/AngularSpec.js b/test/AngularSpec.js index 7f1ab8a32c26..8b9387c09c02 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -253,10 +253,22 @@ describe('angular', function() { expect(parseKeyValue('flag1&key=value&flag2')). toEqual({flag1: true, key: 'value', flag2: true}); }); + + it('should parse a string into key-value pairs with duplicates grouped in an array', function() { + expect(parseKeyValue('')).toEqual({}); + expect(parseKeyValue('duplicate=pair')).toEqual({duplicate: 'pair'}); + expect(parseKeyValue('first=1&first=2')).toEqual({first: ['1','2']}); + expect(parseKeyValue('escaped%20key=escaped%20value&&escaped%20key=escaped%20value2')). + toEqual({'escaped key': ['escaped value','escaped value2']}); + expect(parseKeyValue('flag1&key=value&flag1')). + toEqual({flag1: [true,true], key: 'value'}); + expect(parseKeyValue('flag1&flag1=value&flag1=value2&flag1')). + toEqual({flag1: [true,'value','value2',true]}); + }); }); describe('toKeyValue', function() { - it('should parse key-value pairs into string', function() { + it('should serialize key-value pairs into string', function() { expect(toKeyValue({})).toEqual(''); expect(toKeyValue({simple: 'pair'})).toEqual('simple=pair'); expect(toKeyValue({first: '1', second: '2'})).toEqual('first=1&second=2'); @@ -265,9 +277,15 @@ describe('angular', function() { expect(toKeyValue({emptyKey: ''})).toEqual('emptyKey='); }); - it('should parse true values into flags', function() { + it('should serialize true values into flags', function() { expect(toKeyValue({flag1: true, key: 'value', flag2: true})).toEqual('flag1&key=value&flag2'); }); + + it('should serialize duplicates into duplicate param strings', function() { + expect(toKeyValue({key: [323,'value',true]})).toEqual('key=323&key=value&key'); + expect(toKeyValue({key: [323,'value',true, 1234]})). + toEqual('key=323&key=value&key&key=1234'); + }); }); diff --git a/test/ng/locationSpec.js b/test/ng/locationSpec.js index bb0659a5d0bb..7a6e9dcaecc9 100644 --- a/test/ng/locationSpec.js +++ b/test/ng/locationSpec.js @@ -381,6 +381,29 @@ describe('$location', function() { locationUrl.search('q', '1/2 3'); expect(locationUrl.search()).toEqual({'q': '1/2 3'}); }); + + it('should return an array for duplicate params', function() { + var locationUrl = new LocationHtml5Url('http://host.com'); + locationUrl.$$parse('http://host.com') + locationUrl.search('q', ['1/2 3','4/5 6']); + expect(locationUrl.search()).toEqual({'q': ['1/2 3','4/5 6']}); + }); + + it('should encode an array correctly from search and add to url', function() { + var locationUrl = new LocationHtml5Url('http://host.com'); + locationUrl.$$parse('http://host.com') + locationUrl.search({'q': ['1/2 3','4/5 6']}); + expect(locationUrl.absUrl()).toEqual('http://host.com?q=1%2F2%203&q=4%2F5%206'); + }); + + it('should rewrite params when specifing a single param in search', function() { + var locationUrl = new LocationHtml5Url('http://host.com'); + locationUrl.$$parse('http://host.com') + locationUrl.search({'q': '1/2 3'}); + expect(locationUrl.absUrl()).toEqual('http://host.com?q=1%2F2%203'); + locationUrl.search({'q': '4/5 6'}); + expect(locationUrl.absUrl()).toEqual('http://host.com?q=4%2F5%206'); + }); }); });