diff --git a/README.md b/README.md index 2176e4c..f542699 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ This library has **NO** external dependencies. * [aurelia-templating](https://github.com/aurelia/templating) * [aurelia-templating-router](https://github.com/aurelia/templating-router) * [aurelia-router](https://github.com/aurelia/router) +* [aurelia-route-recognizer](https://github.com/aurelia/route-recognizer) * [aurelia-loader](https://github.com/aurelia/loader) * [aurelia-framework](https://github.com/aurelia/framework) diff --git a/package.json b/package.json index f08d33e..29503d4 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "aurelia-http-client", "aurelia-templating", "aurelia-router", + "aurelia-route-recognizer", "aurelia-templating-router", "aurelia-loader", "aurelia-framework" diff --git a/src/index.js b/src/index.js index 42d613c..0450213 100644 --- a/src/index.js +++ b/src/index.js @@ -86,70 +86,91 @@ export function join(path1: string, path2: string): string { return urlPrefix + url3.join('/').replace(/\:\//g, '://') + trailingSlash; } -export function buildQueryString(a: Object, traditional?: boolean): string { - let s = []; - - function add(key: string, value: any) { - // If value is a function, invoke it and return its value - let v = value; - if (typeof value === 'function') { - v = value(); - } else if (value === null || value === undefined) { - v = ''; + +/** +* Generate a query string from an object. +* +* @param params Object containing the keys and values to be used. +* @returns The generated query string, excluding leading '?'. +*/ +export function buildQueryString(params: Object): string { + let pairs = []; + let keys = Object.keys(params || {}).sort(); + let encode = encodeURIComponent; + let encodeKey = k => encode(k).replace('%24', '$'); + + for (let i = 0, len = keys.length; i < len; i++) { + let key = keys[i]; + let value = params[key]; + if (value === null || value === undefined) { + continue; } - s.push(encodeURIComponent(key) + '=' + encodeURIComponent(v)); + if (Array.isArray(value)) { + let arrayKey = `${encodeKey(key)}[]`; + for (let j = 0, l = value.length; j < l; j++) { + pairs.push(`${arrayKey}=${encode(value[j])}`); + } + } else { + pairs.push(`${encodeKey(key)}=${encode(value)}`); + } } - for (let prefix in a) { - _buildQueryString(prefix, a[prefix], traditional, add); + if (pairs.length === 0) { + return ''; } - // Return the resulting serialization - return s.join('&').replace(r20, '+'); + return pairs.join('&'); } -function _buildQueryString(prefix: string, obj: any, traditional: boolean, add: (p: string, v: any) => void): void { - if (Array.isArray(obj)) { - // Serialize array item. - obj.forEach((v, i) => { - if (traditional || rbracket.test(prefix)) { - // Treat each array item as a scalar. - add(prefix, v); - } else { - // Item is non-scalar (array or object), encode its numeric index. - let innerPrefix = prefix + '[' + (typeof v === 'object' ? i : '') + ']'; - _buildQueryString(innerPrefix, v, traditional, add); - } - }); - } else if (!traditional && type(obj) === 'object') { - // Serialize object item. - for (let name in obj) { - _buildQueryString(prefix + '[' + name + ']', obj[name], traditional, add); - } - } else { - // Serialize scalar item. - add(prefix, obj); +/** +* Parse a query string. +* +* @param The query string to parse. +* @returns Object with keys and values mapped from the query string. +*/ +export function parseQueryString(queryString: string): Object { + let queryParams = {}; + if (!queryString || typeof queryString !== 'string') { + return queryParams; } -} -const r20 = /%20/g; -const rbracket = /\[\]$/; -const class2type = {}; + let query = queryString; + if (query.charAt(0) === '?') { + query = query.substr(1); + } + + let pairs = query.split('&'); + for (let i = 0; i < pairs.length; i++) { + let pair = pairs[i].split('='); + let key = decodeURIComponent(pair[0]); + let keyLength = key.length; + let isArray = false; + let value; -'Boolean Number String Function Array Date RegExp Object Error' - .split(' ') - .forEach((name) => { - class2type['[object ' + name + ']'] = name.toLowerCase(); - }); + if (!key) { + continue; + } else if (pair.length === 1) { + value = true; + } else { + //Handle arrays + if (keyLength > 2 && key.slice(keyLength - 2) === '[]') { + isArray = true; + key = key.slice(0, keyLength - 2); + if (!queryParams[key]) { + queryParams[key] = []; + } + } -function type(obj: any) { - if (obj === null || obj === undefined) { - return obj + ''; + value = pair[1] ? decodeURIComponent(pair[1]) : ''; + } + + if (isArray) { + queryParams[key].push(value); + } else { + queryParams[key] = value; + } } - // Support: Android<4.0 (functionish RegExp) - return typeof obj === 'object' || typeof obj === 'function' - ? class2type[toString.call(obj)] || 'object' - : typeof obj; + return queryParams; } diff --git a/test/path.spec.js b/test/path.spec.js index c63e04d..014cc9d 100644 --- a/test/path.spec.js +++ b/test/path.spec.js @@ -1,4 +1,4 @@ -import { relativeToFile, join } from '../src/index'; +import { relativeToFile, join, parseQueryString, buildQueryString } from '../src/index'; describe('relativeToFile', () => { it('can make a dot path relative to a simple file', () => { @@ -178,3 +178,49 @@ describe('join', () => { }); }); +describe('query strings', () => { + it('should build query strings', () => { + let gen = buildQueryString; + + expect(gen()).toBe(''); + expect(gen(null)).toBe(''); + expect(gen({})).toBe(''); + expect(gen({ a: null })).toBe(''); + + expect(gen({ '': 'a' })).toBe('=a'); + expect(gen({ a: 'b' })).toBe('a=b'); + expect(gen({ a: 'b', c: 'd' })).toBe('a=b&c=d'); + expect(gen({ a: 'b', c: null })).toBe('a=b'); + + expect(gen({ a: [ 'b', 'c' ]})).toBe('a[]=b&a[]=c'); + expect(gen({ '&': [ 'b', 'c' ]})).toBe('%26[]=b&%26[]=c'); + + expect(gen({ a: '&' })).toBe('a=%26'); + expect(gen({ '&': 'a' })).toBe('%26=a'); + expect(gen({ a: true })).toBe('a=true'); + expect(gen({ '$test': true })).toBe('$test=true'); + }); + + it('should parse query strings', () => { + let parse = parseQueryString; + + expect(parse('')).toEqual({}); + expect(parse('=')).toEqual({}); + expect(parse('&')).toEqual({}); + expect(parse('?')).toEqual({}); + + expect(parse('a')).toEqual({ a: true }); + expect(parse('a&b')).toEqual({ a: true, b: true }); + expect(parse('a=')).toEqual({ a: '' }); + expect(parse('a=&b=')).toEqual({ a: '', b: '' }); + + expect(parse('a=b')).toEqual({ a: 'b' }); + expect(parse('a=b&c=d')).toEqual({ a: 'b', c: 'd' }); + expect(parse('a=b&&c=d')).toEqual({ a: 'b', c: 'd' }); + expect(parse('a=b&a=c')).toEqual({ a: 'c' }); + + expect(parse('a=%26')).toEqual({ a: '&' }); + expect(parse('%26=a')).toEqual({ '&': 'a' }); + expect(parse('%26[]=b&%26[]=c')).toEqual({ '&': [ 'b', 'c' ]}); + }); +}); \ No newline at end of file