├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── demo.html ├── dist ├── url.js └── url.min.js ├── index.d.ts ├── package.json ├── prettier.config.js ├── rollup.config.js ├── src ├── _buildNesting.js ├── _buildParams.js ├── _buildParamsExtended.js ├── _buildQuery.js ├── _buildQueryDeep.js ├── _decodeUrlParameter.js ├── _mergeObjects.js ├── _mergeObjectsDeep.js ├── _simplifyObject.js ├── url.js └── url.test.js └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true 4 | }, 5 | "extends": "eslint:recommended", 6 | "globals": { 7 | "Atomics": "readonly", 8 | "SharedArrayBuffer": "readonly" 9 | }, 10 | "parserOptions": { 11 | "ecmaVersion": 2018, 12 | "sourceType": "module" 13 | }, 14 | "rules": { 15 | "indent": [ 16 | "error", 17 | 4 18 | ], 19 | "linebreak-style": [ 20 | "error", 21 | "windows" 22 | ], 23 | "quotes": [ 24 | "error", 25 | "single" 26 | ], 27 | "semi": [ 28 | "error", 29 | "always" 30 | ], 31 | "no-undef": 0, 32 | "camelcase": 2, 33 | "eol-last": 2, 34 | "object-shorthand": [ 35 | 1, 36 | "properties" 37 | ], 38 | "object-curly-spacing": [ 39 | "error", 40 | "always" 41 | ], 42 | "no-useless-escape": 0, 43 | "no-prototype-builtins": 0, 44 | "prefer-template": 2, 45 | "prefer-destructuring": 0, 46 | "no-var": 2, 47 | "no-console": 2 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | yarn-error.log 4 | dev.js 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 All contributors to worka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vanilla-js-url 2 | 3 | A lightweight module to construct and parse query parameters of URLs. 4 | 5 | A set of functions for working with url. Easy to add parameters to url, easy to extract parameters from url. You can also get `path` from url. 6 | 7 | 🖐 If you find my plugin helpful, please donate me 🤝 8 | 9 | ![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/worka/vanilla-js-url) 10 | [![GitHub stars](https://img.shields.io/github/stars/worka/vanilla-js-url)](https://github.com/worka/vanilla-js-url/stargazers) 11 | [![GitHub issues](https://img.shields.io/github/issues/worka/vanilla-js-url)](https://github.com/worka/vanilla-js-url/issues) 12 | [![GitHub forks](https://img.shields.io/github/forks/worka/vanilla-js-url)](https://github.com/worka/vanilla-js-url/network) 13 | 14 | **simple get**, **simple add** 15 | 16 | Demo 17 | 18 | ### Install 19 | 20 | ```cmd 21 | npm i vanilla-js-url 22 | ``` 23 | 24 | or 25 | 26 | ```cmd 27 | yarn add vanilla-js-url 28 | ``` 29 | 30 | ### Get started 31 | 32 | ```javascript 33 | wurl.getParams('example.com?bar=1&foo'); 34 | wurl.addParams('example.com', { bar: 1, foo: 2 }); 35 | 36 | wurl.getPath('example.com/path/to/page'); 37 | 38 | wurl.getParamsExtended('example.com?bar[roo][boo]=1&foo[puu]=test'); 39 | wurl.addParamsExtended('example.com', { bar: { foo: 'test', joo: 2 } }); 40 | ```` 41 | 42 | #### getParams(url, decode) 43 | `alias get()` 44 | 45 | | param | type | default | 46 | |--------|---------|----------------------| 47 | | url | String | window.location.href | 48 | | decode | Boolean | true | 49 | 50 | > If you have simple parameters like `bar=1` or `foo[]=3&foo[]=5`, then use `getParams()`.
51 | > In response, you will get a simple (single-level) object whose keys will contain either simple values or simple arrays. 52 | > This function is suitable in 99% of cases. 53 | 54 | ```javascript 55 | wurl.get('example.com'); 56 | // {} 57 | 58 | wurl.get('example.com?bar=1&foo'); 59 | // { bar: '1', foo: '' } 60 | 61 | wurl.get('example.com?bar=1&bar=2'); 62 | // { bar: '2' } 63 | 64 | wurl.get('example.com?bar[]=1&bar[]=2'); 65 | // { bar: ['1', '2'] } 66 | 67 | wurl.get('example.com?bar=1&bar[]=2'); 68 | // { bar: ['2'] } 69 | ``` 70 | 71 | #### addParams(url, params, encode) 72 | `alias add()` 73 | 74 | | param | type | default | 75 | |--------|---------|------------| 76 | | url | String | `required` | 77 | | params | Object | `required` | 78 | | encode | Boolean | false | 79 | 80 | ```javascript 81 | wurl.add('example.com', { bar: 1, foo: 2 }); 82 | // example.com?bar=1&foo=2 83 | 84 | wurl.add('example.com?bar=1&foo', { bar: 2, foo: 2 }); 85 | // example.com?bar=2&foo=2 86 | 87 | wurl.add('example.com?bar=1', { bar: [2, 3] }); 88 | // example.com?bar[]=2&bar[]=3 89 | 90 | wurl.add('example.com?bar=1&bar[]=2', { bar: [3, 4] }); 91 | // example.com?bar[]=2&bar[]=3&bar[]=4 92 | ``` 93 | 94 | #### getPath(url) 95 | `alias path()` 96 | 97 | | param | type | default | 98 | |--------|---------|----------------------| 99 | | url | String | window.location.href | 100 | 101 | ```javascript 102 | wurl.path('https://example.com/path/to/page?bar=1'); 103 | // /path/to/page 104 | ``` 105 | 106 | #### getParamsExtended(url) 107 | `alias getExt()` 108 | 109 | | param | type | default | 110 | |--------|---------|----------------------| 111 | | url | String | window.location.href | 112 | 113 | > If you have complex parameters like: `bar[foo][too][poo]=3&bar[foo][goo]=4`, then use `getParamsExtended()`.
114 | > In response, you will get a multi-level object. 115 | > Most likely you will not need this function. 116 | 117 | ```javascript 118 | wurl.getExt('example.com?bar[t]=1&bar[j]=2'); 119 | // { bar: { t: '1', j: '2' } } 120 | ``` 121 | 122 | #### addParamsExtended(url, params) 123 | `alias addExt()` 124 | 125 | | param | type | default | 126 | |--------|---------|------------| 127 | | url | String | `required` | 128 | | params | Object | `required` | 129 | 130 | ```javascript 131 | wurl.addExt('example.com', { bar: { foo: 'test', joo: 2 } }); 132 | // example.com?bar[foo]=test&bar[joo]=2 133 | ``` 134 | 135 | The `getParamsExtended` and `addParamsExtended` functions may not answer exactly. Please tell me which tests failed. 136 | 137 | ### MIT LICENSE 138 | 139 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 140 | 141 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 142 | 143 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 144 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/env'] 4 | ] 5 | }; 6 | -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | worka/vanilla-js-url 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 |

vanilla-js-url

13 | 14 |

15 | 16 | https://github.com/worka/vanilla-js-url 17 | 18 |

19 | 20 |
21 | wurl.get() 22 | 23 |
24 | 29 | 30 |
31 |
32 | 33 | 34 | 35 |
36 |
37 |
38 | 39 |
Result: nothing...
40 |
41 | 42 |
43 | wurl.add() 44 | 45 |
46 | 51 |
52 | 53 | 54 | 55 | Example: { "bar": ["test 2"], "too": 3 } 56 | 57 |
58 | 59 |
60 |
61 | 62 | 63 | 64 |
65 |
66 |
67 | 68 |
Result: nothing...
69 |
70 | 71 |
72 | wurl.path() 73 | 74 |
75 |
76 | 77 | 78 | Example: https://github.com/worka/vanilla-js-url 79 |
80 |
81 | 82 |
Result: nothing...
83 |
84 | 85 | 86 | 87 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /dist/url.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' 3 | ? factory(exports) 4 | : typeof define === 'function' && define.amd 5 | ? define(['exports'], factory) 6 | : ((global = global || self), factory((global.wurl = {}))); 7 | })(this, function (exports) { 8 | 'use strict'; 9 | 10 | /** 11 | * Thanks https://gist.github.com/bchapuis/5575512 12 | * 13 | * @param string 14 | * @returns {string} 15 | */ 16 | function decodeUrlParameter(string) { 17 | return decodeURIComponent(''.concat(string).replace(/\+/g, '%20')); 18 | } 19 | 20 | /** 21 | * @param query 22 | * @param decode 23 | * @returns {{}} 24 | */ 25 | 26 | function _buildParams(query) { 27 | var decode = 28 | arguments.length > 1 && arguments[1] !== undefined 29 | ? arguments[1] 30 | : true; 31 | var params = {}; 32 | 33 | if (query) { 34 | query.split('&').forEach(function (_query) { 35 | // %26 => & 36 | if (decode) { 37 | _query = decodeUrlParameter(_query); 38 | } 39 | 40 | var row = _query.split('=', 2); 41 | 42 | var key = row[0]; 43 | var value = row[1] || ''; 44 | 45 | if (key) { 46 | var match = key.match(/(.+?)\[(\d*)\]/i); 47 | 48 | if (match) { 49 | key = match[1]; 50 | var index = match[2]; 51 | 52 | if ( 53 | params[key] === undefined || 54 | !Array.isArray(params[key]) 55 | ) { 56 | params[key] = []; 57 | } 58 | 59 | if (index === '') { 60 | params[key].push(value); 61 | } else { 62 | params[key][index] = value; 63 | } 64 | } else { 65 | params[key] = value; 66 | } 67 | } 68 | }); 69 | } 70 | 71 | return params; 72 | } 73 | /** 74 | * Description: 75 | * 76 | * IN: bar=1&foo 77 | * OUT: { bar: '1', foo: '' } 78 | */ 79 | 80 | /** 81 | * @param array 82 | * @param value 83 | * @returns {{}} 84 | */ 85 | function _buildNesting(array, value) { 86 | var object = {}; 87 | object[array[array.length - 1]] = value; 88 | return array.length === 1 89 | ? object 90 | : _buildNesting(array.slice(0, array.length - 1), object); 91 | } 92 | /** 93 | * Description: 94 | * 95 | * IN: [ 'bar', 'foo', 'goo' ], 5 96 | * OUT: { bar: { foo: { goo: '5' } } } 97 | */ 98 | 99 | /** 100 | * Thanks @anneb for his inspiration (https://gist.github.com/ahtcx/0cd94e62691f539160b32ecda18af3d6#gistcomment-2930530) 101 | * 102 | * @param target 103 | * @param source 104 | * @returns {*} 105 | */ 106 | function _mergeObjectsDeep(target, source) { 107 | var isObject = function isObject(obj) { 108 | return obj && obj instanceof Object; 109 | }; 110 | 111 | if (!isObject(target) || !isObject(source)) { 112 | return source; 113 | } 114 | 115 | Object.keys(source).forEach(function (key) { 116 | var targetValue = target[key]; 117 | var sourceValue = source[key]; 118 | 119 | if (Array.isArray(targetValue) && Array.isArray(sourceValue)) { 120 | target[key] = targetValue.concat(sourceValue); 121 | } else if (isObject(targetValue) && isObject(sourceValue)) { 122 | target[key] = _mergeObjectsDeep( 123 | Object.assign({}, targetValue), 124 | sourceValue 125 | ); 126 | } else { 127 | target[key] = sourceValue; 128 | } 129 | }); 130 | return target; 131 | } 132 | /** 133 | * Description: 134 | * 135 | * IN: { bar: { '1': '0', tr: '1' } }, { bar: { j: '2' } } 136 | * OUT: { bar: { '1': '0', tr: '1', j: '2' } } 137 | */ 138 | 139 | /** 140 | * @param query 141 | * @param decode 142 | * @returns {{}} 143 | */ 144 | 145 | function _buildParamsExtended(query) { 146 | var decode = 147 | arguments.length > 1 && arguments[1] !== undefined 148 | ? arguments[1] 149 | : true; 150 | var params = {}; 151 | 152 | if (query) { 153 | query.split('&').forEach(function (_query, i) { 154 | // %26 => & 155 | if (decode) { 156 | _query = decodeUrlParameter(_query); 157 | } 158 | 159 | var row = _query.split('=', 2); 160 | 161 | var key = row[0]; 162 | var value = row[1] || ''; // @todo написать получение ключей по-нормальному 163 | 164 | var match = key.match(/(.+?)(\[(.*)\])/i); // example.com?s%5B%5D=4%264&s%5B%5D=3&r=s+s%2Bs 165 | //@todo не срабатывает, так как r без [] 166 | 167 | if (match) { 168 | var raw = match[3] || i.toString(); 169 | var array = raw.split(']['); 170 | array.unshift(match[1]); 171 | 172 | var nesting = _buildNesting( 173 | array, 174 | decodeUrlParameter(value) 175 | ); 176 | 177 | params = _mergeObjectsDeep(params, nesting); 178 | } 179 | }); 180 | } 181 | 182 | return params; 183 | } 184 | /** 185 | * Description: 186 | * 187 | * IN: bar[foo][too][poo]=3&bar[foo][goo]=4&bar[foo][too][hoo]=5&newbar[tee]=5 188 | * OUT: { bar: { foo: { too: { poo: 3, hoo: 5 }, goo: '4' } }, newbar: { tee: '5' } } 189 | */ 190 | 191 | /** 192 | * @param params 193 | * @param encode 194 | * @returns {string} 195 | */ 196 | function _buildQuery(params) { 197 | var encode = 198 | arguments.length > 1 && arguments[1] !== undefined 199 | ? arguments[1] 200 | : false; 201 | var queries = []; 202 | 203 | function e(string) { 204 | return encode ? encodeURIComponent(string) : string; 205 | } 206 | 207 | var _loop = function _loop(key) { 208 | if (params.hasOwnProperty(key)) { 209 | var value = params[key]; 210 | 211 | if (Array.isArray(value) && value.length) { 212 | value.forEach(function (_value) { 213 | queries.push( 214 | '' 215 | .concat(e(''.concat(key, '[]')), '=') 216 | .concat(e(_value)) 217 | ); 218 | }); 219 | } else { 220 | queries.push(''.concat(e(key), '=').concat(e(value))); 221 | } 222 | } 223 | }; 224 | 225 | for (var key in params) { 226 | _loop(key); 227 | } 228 | 229 | return queries.join('&'); 230 | } 231 | /** 232 | * Description: 233 | * 234 | * IN: { bar: 2, foo: 2 } 235 | * OUT: bar=2&foo=2 236 | */ 237 | 238 | /** 239 | * @param params 240 | * @param branch 241 | * @param tree 242 | */ 243 | function _simplifyObject(params, branch, tree) { 244 | Object.keys(params).forEach(function (key) { 245 | var branch2 = branch.concat([key]); 246 | var params2 = params[key]; 247 | 248 | if (params2 instanceof Object) { 249 | _simplifyObject(params2, branch2, tree); 250 | } else { 251 | branch2.push(params2); 252 | tree.push(branch2); 253 | } 254 | }); 255 | } 256 | /** 257 | * Description: 258 | * 259 | * IN: { 260 | * bar: { t: '1', j: '2', y: '2' }, 261 | * foo: 4, 262 | * roo: { y: { gh: 6, tr: { t: 9 } }, t: { e: 2 } }, 263 | * uoo: { y: 3, t: { e: 2 } }, 264 | * joo: [ 2, 4 ] 265 | * } 266 | * OUT: [ 267 | * [ 'bar', 't', '1' ], 268 | * [ 'bar', 'j', '2' ], 269 | * [ 'bar', 'y', '2' ], 270 | * [ 'foo', 4 ], 271 | * [ 'roo', 'y', 'gh', 6 ], 272 | * [ 'roo', 'y', 'tr', 't', 9 ], 273 | * [ 'roo', 't', 'e', 2 ], 274 | * [ 'uoo', 'y', 3 ], 275 | * [ 'uoo', 't', 'e', 2 ], 276 | * [ 'joo', '0', 2 ], 277 | * [ 'joo', '1', 4 ] 278 | * ] 279 | */ 280 | 281 | /** 282 | * @param params 283 | * @param encode 284 | * @returns {string} 285 | */ 286 | 287 | function _buildQueryDeep(params) { 288 | var encode = 289 | arguments.length > 1 && arguments[1] !== undefined 290 | ? arguments[1] 291 | : false; 292 | var tree = []; 293 | 294 | function e(string) { 295 | return encode ? encodeURIComponent(string) : string; 296 | } 297 | 298 | _simplifyObject(params, [], tree); 299 | 300 | var parts = tree.map(function (branch) { 301 | return branch.reduce(function (str, item, i) { 302 | if (!str) { 303 | return str + item; 304 | } else if (i < branch.length - 1) { 305 | return ''.concat(str, '[').concat(item, ']'); 306 | } else { 307 | return ''.concat(e(str), '=').concat(e(item)); 308 | } 309 | }, ''); 310 | }); 311 | return parts.join('&'); 312 | } 313 | /** 314 | * Description: 315 | * 316 | * IN: { 317 | * bar: { t: '1', j: '2', y: '2' }, 318 | * foo: 4, 319 | * roo: { y: { gh: 6, tr: { t: 9 } }, t: { e: 2 } }, 320 | * uoo: { y: 3, t: { e: 2 } }, 321 | * joo: [ 2, 4 ] 322 | * } 323 | * OUT: bar[t]=1&bar[j]=2&bar[y]=2&foo=4&roo[y][gh]=6&roo[y][tr][t]=9&roo[t][e]=2&uoo[y]=3&uoo[t][e]=2&joo[0]=2&joo[1]=4 324 | */ 325 | 326 | /** 327 | * @param currentObject 328 | * @param newObject 329 | * @returns {*} 330 | */ 331 | function _mergeObjects(currentObject, newObject) { 332 | var _loop = function _loop(key) { 333 | if (newObject.hasOwnProperty(key)) { 334 | var value = newObject[key]; 335 | 336 | if (Array.isArray(value) && value.length) { 337 | if ( 338 | currentObject[key] === undefined || 339 | !Array.isArray(currentObject[key]) 340 | ) { 341 | currentObject[key] = []; 342 | } 343 | 344 | value.forEach(function (_value) { 345 | currentObject[key].push(_value); 346 | }); 347 | } else { 348 | currentObject[key] = newObject[key]; 349 | } 350 | } 351 | }; 352 | 353 | for (var key in newObject) { 354 | _loop(key); 355 | } 356 | 357 | return currentObject; 358 | } 359 | /** 360 | * Description: 361 | * 362 | * IN: { bar: 2 }, { foo: 2 } 363 | * OUT: { bar: 2 , foo: 2 } 364 | */ 365 | 366 | function getParams() { 367 | var url = 368 | arguments.length > 0 && arguments[0] !== undefined 369 | ? arguments[0] 370 | : window.location.href; 371 | var decode = 372 | arguments.length > 1 && arguments[1] !== undefined 373 | ? arguments[1] 374 | : true; 375 | var splitUrl = url.split('?', 2); 376 | return _buildParams(splitUrl.length === 2 ? splitUrl[1] : '', decode); 377 | } 378 | 379 | function getParamsExtended() { 380 | var url = 381 | arguments.length > 0 && arguments[0] !== undefined 382 | ? arguments[0] 383 | : window.location.href; 384 | var decode = 385 | arguments.length > 1 && arguments[1] !== undefined 386 | ? arguments[1] 387 | : true; 388 | var splitUrl = url.split('?', 2); 389 | return _buildParamsExtended( 390 | splitUrl.length === 2 ? splitUrl[1] : '', 391 | decode 392 | ); 393 | } 394 | 395 | function addParams(url, newParams) { 396 | var encode = 397 | arguments.length > 2 && arguments[2] !== undefined 398 | ? arguments[2] 399 | : false; 400 | 401 | if (newParams instanceof Object) { 402 | var uri = url.split('?', 2)[0]; 403 | var currentParams = getParams(url); 404 | 405 | var params = _mergeObjects(currentParams, newParams); 406 | 407 | url = ''.concat(uri, '?').concat(_buildQuery(params, encode)); 408 | } 409 | 410 | return url; 411 | } 412 | 413 | function addParamsExtended(url, newParams) { 414 | var encode = 415 | arguments.length > 2 && arguments[2] !== undefined 416 | ? arguments[2] 417 | : false; 418 | 419 | if (newParams instanceof Object) { 420 | var uri = url.split('?', 2)[0]; 421 | var currentParams = getParams(url); 422 | 423 | var params = _mergeObjectsDeep(currentParams, newParams); 424 | 425 | url = ''.concat(uri, '?').concat(_buildQueryDeep(params, encode)); 426 | } 427 | 428 | return url; 429 | } 430 | 431 | function getPath() { 432 | var url = 433 | arguments.length > 0 && arguments[0] !== undefined 434 | ? arguments[0] 435 | : window.location.href; 436 | var path = '/'; 437 | var match = url 438 | .replace(/^((?:https?:)?\/\/)/i, '') // remove scheme 439 | .match(/\/(?![#?&\s])([^#?\s]+)/); 440 | 441 | if (match) { 442 | path += match[1]; 443 | 444 | if (path[path.length - 1] === '/') { 445 | path = path.substr(0, path.length - 1); 446 | } 447 | } 448 | 449 | return path; 450 | } 451 | 452 | exports.add = addParams; 453 | exports.addExt = addParamsExtended; 454 | exports.addParams = addParams; 455 | exports.addParamsExtended = addParamsExtended; 456 | exports.get = getParams; 457 | exports.getExt = getParamsExtended; 458 | exports.getParams = getParams; 459 | exports.getParamsExtended = getParamsExtended; 460 | exports.getPath = getPath; 461 | exports.path = getPath; 462 | 463 | Object.defineProperty(exports, '__esModule', { value: true }); 464 | }); 465 | -------------------------------------------------------------------------------- /dist/url.min.js: -------------------------------------------------------------------------------- 1 | (function(a,b){"object"==typeof exports&&"undefined"!=typeof module?b(exports):"function"==typeof define&&define.amd?define(["exports"],b):(a=a||self,b(a.wurl={}))})(this,function(a){'use strict';function b(a){return decodeURIComponent("".concat(a).replace(/\+/g,"%20"))}function c(a){var c=!(1 { 13 | // %26 => & 14 | if (decode) { 15 | _query = _decodeUrlParameter(_query); 16 | } 17 | 18 | const row = _query.split('=', 2); 19 | 20 | let key = row[0]; 21 | let value = row[1] || ''; 22 | 23 | if (key) { 24 | const match = key.match(/(.+?)\[(\d*)\]/i); 25 | 26 | if (match) { 27 | key = match[1]; 28 | let index = match[2]; 29 | 30 | if (params[key] === undefined || !Array.isArray(params[key])) { 31 | params[key] = []; 32 | } 33 | 34 | if (index === '') { 35 | params[key].push(value); 36 | } else { 37 | params[key][index] = value; 38 | } 39 | } else { 40 | params[key] = value; 41 | } 42 | } 43 | }); 44 | } 45 | 46 | return params; 47 | } 48 | 49 | /** 50 | * Description: 51 | * 52 | * IN: bar=1&foo 53 | * OUT: { bar: '1', foo: '' } 54 | */ 55 | -------------------------------------------------------------------------------- /src/_buildParamsExtended.js: -------------------------------------------------------------------------------- 1 | import _buildNesting from './_buildNesting'; 2 | import _mergeObjectsDeep from './_mergeObjectsDeep'; 3 | import _decodeUrlParameter from './_decodeUrlParameter'; 4 | 5 | /** 6 | * @param query 7 | * @param decode 8 | * @returns {{}} 9 | */ 10 | export default function _buildParamsExtended(query, decode = true) { 11 | let params = {}; 12 | 13 | if (query) { 14 | query.split('&').forEach((_query, i) => { 15 | // %26 => & 16 | if (decode) { 17 | _query = _decodeUrlParameter(_query); 18 | } 19 | 20 | const row = _query.split('=', 2); 21 | 22 | let key = row[0]; 23 | let value = row[1] || ''; 24 | 25 | // @todo написать получение ключей по-нормальному 26 | const match = key.match(/(.+?)(\[(.*)\])/i); 27 | 28 | // example.com?s%5B%5D=4%264&s%5B%5D=3&r=s+s%2Bs 29 | //@todo не срабатывает, так как r без [] 30 | 31 | if (match) { 32 | const raw = match[3] || i.toString(); 33 | const array = raw.split(']['); 34 | array.unshift(match[1]); 35 | 36 | const nesting = _buildNesting(array, _decodeUrlParameter(value)); 37 | 38 | params = _mergeObjectsDeep(params, nesting); 39 | } 40 | }); 41 | } 42 | 43 | return params; 44 | } 45 | 46 | /** 47 | * Description: 48 | * 49 | * IN: bar[foo][too][poo]=3&bar[foo][goo]=4&bar[foo][too][hoo]=5&newbar[tee]=5 50 | * OUT: { bar: { foo: { too: { poo: 3, hoo: 5 }, goo: '4' } }, newbar: { tee: '5' } } 51 | */ 52 | -------------------------------------------------------------------------------- /src/_buildQuery.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param params 3 | * @param encode 4 | * @returns {string} 5 | */ 6 | export default function _buildQuery(params, encode = false) { 7 | let queries = []; 8 | 9 | function e(string) { 10 | return encode ? encodeURIComponent(string) : string; 11 | } 12 | 13 | for (let key in params) { 14 | if (params.hasOwnProperty(key)) { 15 | const value = params[key]; 16 | 17 | if (Array.isArray(value) && value.length) { 18 | value.forEach(_value => { 19 | queries.push(`${ e(`${ key }[]`) }=${ e(_value) }`); 20 | }); 21 | } else { 22 | queries.push(`${ e(key) }=${ e(value) }`); 23 | } 24 | } 25 | } 26 | 27 | return queries.join('&'); 28 | } 29 | 30 | /** 31 | * Description: 32 | * 33 | * IN: { bar: 2, foo: 2 } 34 | * OUT: bar=2&foo=2 35 | */ 36 | -------------------------------------------------------------------------------- /src/_buildQueryDeep.js: -------------------------------------------------------------------------------- 1 | import _simplifyObject from './_simplifyObject'; 2 | 3 | /** 4 | * @param params 5 | * @param encode 6 | * @returns {string} 7 | */ 8 | export default function _buildQueryDeep(params, encode = false) { 9 | const tree = []; 10 | 11 | function e(string) { 12 | return encode ? encodeURIComponent(string) : string; 13 | } 14 | 15 | _simplifyObject(params, [], tree); 16 | 17 | let parts = tree.map(branch => { 18 | return branch.reduce((str, item, i) => { 19 | if (!str) { 20 | return str + item; 21 | } else if (i < branch.length - 1) { 22 | return `${ str }[${ item }]`; 23 | } else { 24 | return `${ e(str) }=${ e(item) }`; 25 | } 26 | }, ''); 27 | }); 28 | 29 | return parts.join('&'); 30 | } 31 | 32 | /** 33 | * Description: 34 | * 35 | * IN: { 36 | * bar: { t: '1', j: '2', y: '2' }, 37 | * foo: 4, 38 | * roo: { y: { gh: 6, tr: { t: 9 } }, t: { e: 2 } }, 39 | * uoo: { y: 3, t: { e: 2 } }, 40 | * joo: [ 2, 4 ] 41 | * } 42 | * OUT: bar[t]=1&bar[j]=2&bar[y]=2&foo=4&roo[y][gh]=6&roo[y][tr][t]=9&roo[t][e]=2&uoo[y]=3&uoo[t][e]=2&joo[0]=2&joo[1]=4 43 | */ 44 | -------------------------------------------------------------------------------- /src/_decodeUrlParameter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Thanks https://gist.github.com/bchapuis/5575512 3 | * 4 | * @param string 5 | * @returns {string} 6 | */ 7 | export default function decodeUrlParameter(string) { 8 | return decodeURIComponent(`${ string }`.replace(/\+/g, '%20')); 9 | } 10 | -------------------------------------------------------------------------------- /src/_mergeObjects.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param currentObject 3 | * @param newObject 4 | * @returns {*} 5 | */ 6 | export default function _mergeObjects(currentObject, newObject) { 7 | for (let key in newObject) { 8 | if (newObject.hasOwnProperty(key)) { 9 | const value = newObject[key]; 10 | 11 | if (Array.isArray(value) && value.length) { 12 | if ( 13 | currentObject[key] === undefined || 14 | !Array.isArray(currentObject[key]) 15 | ) { 16 | currentObject[key] = []; 17 | } 18 | 19 | value.forEach(_value => { 20 | currentObject[key].push(_value); 21 | }); 22 | } else { 23 | currentObject[key] = newObject[key]; 24 | } 25 | } 26 | } 27 | 28 | return currentObject; 29 | } 30 | 31 | /** 32 | * Description: 33 | * 34 | * IN: { bar: 2 }, { foo: 2 } 35 | * OUT: { bar: 2 , foo: 2 } 36 | */ 37 | -------------------------------------------------------------------------------- /src/_mergeObjectsDeep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Thanks @anneb for his inspiration (https://gist.github.com/ahtcx/0cd94e62691f539160b32ecda18af3d6#gistcomment-2930530) 3 | * 4 | * @param target 5 | * @param source 6 | * @returns {*} 7 | */ 8 | export default function _mergeObjectsDeep(target, source) { 9 | const isObject = obj => obj && obj instanceof Object; 10 | 11 | if (!isObject(target) || !isObject(source)) { 12 | return source; 13 | } 14 | 15 | Object.keys(source).forEach(key => { 16 | const targetValue = target[key]; 17 | const sourceValue = source[key]; 18 | 19 | if (Array.isArray(targetValue) && Array.isArray(sourceValue)) { 20 | target[key] = targetValue.concat(sourceValue); 21 | } else if (isObject(targetValue) && isObject(sourceValue)) { 22 | target[key] = _mergeObjectsDeep( 23 | Object.assign({}, targetValue), 24 | sourceValue 25 | ); 26 | } else { 27 | target[key] = sourceValue; 28 | } 29 | }); 30 | 31 | return target; 32 | } 33 | 34 | /** 35 | * Description: 36 | * 37 | * IN: { bar: { '1': '0', tr: '1' } }, { bar: { j: '2' } } 38 | * OUT: { bar: { '1': '0', tr: '1', j: '2' } } 39 | */ 40 | -------------------------------------------------------------------------------- /src/_simplifyObject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param params 3 | * @param branch 4 | * @param tree 5 | */ 6 | export default function _simplifyObject(params, branch, tree) { 7 | Object.keys(params).forEach(key => { 8 | const branch2 = branch.concat([key]); 9 | const params2 = params[key]; 10 | 11 | if (params2 instanceof Object) { 12 | _simplifyObject(params2, branch2, tree); 13 | } else { 14 | branch2.push(params2); 15 | tree.push(branch2); 16 | } 17 | }); 18 | } 19 | 20 | /** 21 | * Description: 22 | * 23 | * IN: { 24 | * bar: { t: '1', j: '2', y: '2' }, 25 | * foo: 4, 26 | * roo: { y: { gh: 6, tr: { t: 9 } }, t: { e: 2 } }, 27 | * uoo: { y: 3, t: { e: 2 } }, 28 | * joo: [ 2, 4 ] 29 | * } 30 | * OUT: [ 31 | * [ 'bar', 't', '1' ], 32 | * [ 'bar', 'j', '2' ], 33 | * [ 'bar', 'y', '2' ], 34 | * [ 'foo', 4 ], 35 | * [ 'roo', 'y', 'gh', 6 ], 36 | * [ 'roo', 'y', 'tr', 't', 9 ], 37 | * [ 'roo', 't', 'e', 2 ], 38 | * [ 'uoo', 'y', 3 ], 39 | * [ 'uoo', 't', 'e', 2 ], 40 | * [ 'joo', '0', 2 ], 41 | * [ 'joo', '1', 4 ] 42 | * ] 43 | */ 44 | -------------------------------------------------------------------------------- /src/url.js: -------------------------------------------------------------------------------- 1 | import _buildParams from './_buildParams'; 2 | import _buildParamsExtended from './_buildParamsExtended'; 3 | import _buildQuery from './_buildQuery'; 4 | import _buildQueryDeep from './_buildQueryDeep'; 5 | import _mergeObjects from './_mergeObjects'; 6 | import _mergeObjectsDeep from './_mergeObjectsDeep'; 7 | 8 | function getParams(url = window.location.href, decode = true) { 9 | const splitUrl = url.split('?', 2); 10 | 11 | return _buildParams(splitUrl.length === 2 ? splitUrl[1] : '', decode); 12 | } 13 | 14 | function getParamsExtended(url = window.location.href, decode = true) { 15 | const splitUrl = url.split('?', 2); 16 | 17 | return _buildParamsExtended(splitUrl.length === 2 ? splitUrl[1] : '', decode); 18 | } 19 | 20 | function addParams(url, newParams, encode = false) { 21 | if (newParams instanceof Object) { 22 | const uri = url.split('?', 2)[0]; 23 | const currentParams = getParams(url); 24 | const params = _mergeObjects(currentParams, newParams); 25 | 26 | url = `${ uri }?${ _buildQuery(params, encode) }`; 27 | } 28 | 29 | return url; 30 | } 31 | 32 | function addParamsExtended(url, newParams, encode = false) { 33 | if (newParams instanceof Object) { 34 | const uri = url.split('?', 2)[0]; 35 | const currentParams = getParams(url); 36 | const params = _mergeObjectsDeep(currentParams, newParams); 37 | 38 | url = `${ uri }?${ _buildQueryDeep(params, encode) }`; 39 | } 40 | 41 | return url; 42 | } 43 | 44 | function getPath(url = window.location.href) { 45 | let path = '/'; 46 | 47 | const match = url 48 | .replace(/^((?:https?:)?\/\/)/i, '') // remove scheme 49 | .match(/\/(?![#?&\s])([^#?\s]+)/); 50 | 51 | if (match) { 52 | path += match[1]; 53 | 54 | if (path[path.length - 1] === '/') { 55 | path = path.substr(0, path.length - 1); 56 | } 57 | } 58 | 59 | return path; 60 | } 61 | 62 | export { 63 | getParams, 64 | getParamsExtended, 65 | addParams, 66 | addParamsExtended, 67 | getPath, 68 | // short aliases 69 | getParams as get, 70 | getParamsExtended as getExt, 71 | addParams as add, 72 | addParamsExtended as addExt, 73 | getPath as path 74 | }; 75 | -------------------------------------------------------------------------------- /src/url.test.js: -------------------------------------------------------------------------------- 1 | import { getParams, getParamsExtended, addParams, addParamsExtended, getPath } from './url'; 2 | 3 | describe('GET', () => { 4 | test('getParams()', () => { 5 | expect(getParams('example.com')).toEqual({}); 6 | expect(getParams('example.com?bar=1&foo')).toEqual({ bar: '1', foo: '' }); 7 | expect(getParams('example.com?bar=1&bar=2&')).toEqual({ bar: '2' }); 8 | expect(getParams('example.com?bar[]=1&bar[]=2')).toEqual({ bar: ['1', '2'] }); 9 | expect(getParams('example.com?bar=1&bar[]=2')).toEqual({ bar: ['2'] }); 10 | expect(getParams('example.com?bar=test+test%2Ctest')).toEqual({ bar: 'test test,test' }); 11 | expect(getParams('example.com?s%5B%5D=4%264&s%5B%5D=3&r=s+s%2Bs')).toEqual({ s: ['4&4', '3'], r: 's s+s' }); 12 | expect(getParams('example.com?bar[]=0&bar[1]=1&bar[2]=2')).toEqual({ bar: ['0', '1', '2'] }); 13 | expect(getParams('example.com?too=q&bar[]=1&bar[0]=0&bar[2]=2&foo[]=1&foo[]=2')) 14 | .toEqual({ too: 'q', bar: ['0', undefined, '2'], foo: ['1', '2'] }); 15 | }); 16 | 17 | test('getParamsExtended()', () => { 18 | expect(getParamsExtended('example.com?bar[t]=test+test%2Ctest&')).toEqual({ bar: { t: 'test test,test' } }); 19 | expect(getParamsExtended('example.com?bar[t]=1&bar[j]=2')).toEqual({ bar: { t: '1', j: '2' } }); 20 | expect(getParamsExtended('example.com?bar[t]=1&bar[j]=2&bar[j]=3')).toEqual({ bar: { t: '1', j: '3' } }); 21 | expect(getParamsExtended('example.com?b[t]=1&b[j]=2&b[j][g]=3')) 22 | .toEqual({ b: { t: '1', j: { g: '3' } } }); 23 | expect(getParamsExtended('example.com?bar=-1&bar[]=0&bar[tr]=1&bar[j]=2&bar[foo][too][poo]=3&bar[]=4&bar[foo][too][hoo]=5')) 24 | .toEqual({ bar: { '1': '0', tr: '1', j: '2', foo: { too: { poo: '3', hoo: '5' } }, '5': '4' } }); 25 | }); 26 | }); 27 | 28 | describe('ADD', () => { 29 | test('addParams()', () => { 30 | expect(addParams('example.com', { bar: 1 })).toBe('example.com?bar=1'); 31 | expect(addParams('example.com', { bar: 1, foo: 2 })).toBe('example.com?bar=1&foo=2'); 32 | expect(addParams('example.com?bar=1&foo', { bar: 2 })).toBe('example.com?bar=2&foo='); 33 | expect(addParams('example.com?bar=1&foo', { bar: 2, foo: 2 })).toBe('example.com?bar=2&foo=2'); 34 | expect(addParams('example.com?bar=1', { bar: [2, 3] })).toBe('example.com?bar[]=2&bar[]=3'); 35 | expect(addParams('example.com?bar=1&bar[]=2', { bar: [3, 4] })).toBe('example.com?bar[]=2&bar[]=3&bar[]=4'); 36 | expect(addParams('example.com?bar=1&bar=2', { bar: [3, 4] })).toBe('example.com?bar[]=3&bar[]=4'); 37 | expect(addParams('example.com?bar[]=1&bar[]=2', { bar: [3, 4] })).toBe('example.com?bar[]=1&bar[]=2&bar[]=3&bar[]=4'); 38 | expect(addParams('example.com?bar[0]=1&bar[2]=2', { bar: [3, 4] })).toBe('example.com?bar[]=1&bar[]=2&bar[]=3&bar[]=4'); 39 | }); 40 | 41 | test('addParamsExtended()', () => { 42 | expect(addParamsExtended('example.com', { bar: 1, foo: 2 })).toBe('example.com?bar=1&foo=2'); 43 | /** 44 | * BUG 45 | */ 46 | // expect(addParamsExtended('example.com?bar[foo]=test&bar[joo]=2', { bar: { foo: 'new', too: 5 } })) 47 | // .toBe('example.com?bar[foo]=new&bar[joo]=2&bar[too]=5'); 48 | expect(addParamsExtended('example.com', { bar: { foo: 'test', joo: 2 } })) 49 | .toBe('example.com?bar[foo]=test&bar[joo]=2'); 50 | expect(addParamsExtended('example.com', { 51 | f1: { f11: 1, f12: 2, f13: 3 }, f2: 4, f3: { f31: { f311: 6, f312: { f3121: 9 } }, f32: { f321: 2 } }, 52 | f4: { f41: 3, f42: { f421: 2 } }, f5: [2, 4] 53 | })).toBe('example.com?f1[f11]=1&f1[f12]=2&f1[f13]=3&f2=4&f3[f31][f311]=6&f3[f31][f312][f3121]=9&f3[f32][f321]=2&f4[f41]=3&f4[f42][f421]=2&f5[0]=2&f5[1]=4'); 54 | }); 55 | }); 56 | 57 | describe('PATH', () => { 58 | test('getPath()', () => { 59 | expect(getPath('example.com')).toEqual('/'); 60 | expect(getPath('example.com/path/to/page')).toEqual('/path/to/page'); 61 | expect(getPath('http://example.com/path/to/page/')).toEqual('/path/to/page'); 62 | expect(getPath('example.com/path/to/page////')).toEqual('/path/to/page///'); 63 | expect(getPath('https://example.com/path/to/page?bar=1')).toEqual('/path/to/page'); 64 | expect(getPath('example.com/path/to/page#anchor')).toEqual('/path/to/page'); 65 | expect(getPath('example.com#anchor')).toEqual('/'); 66 | expect(getPath('//example.com?anchor')).toEqual('/'); 67 | expect(getPath('example.com/path other')).toEqual('/path'); 68 | expect(getPath('example.com/')).toEqual('/'); 69 | }); 70 | }); 71 | --------------------------------------------------------------------------------