-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathURLSearchParams.mjs
More file actions
147 lines (147 loc) · 6.16 KB
/
URLSearchParams.mjs
File metadata and controls
147 lines (147 loc) · 6.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
export class URLSearchParams {
constructor(params) {
switch (typeof params) {
case "string": {
if (params.length === 0)
break;
if (params.startsWith("?"))
params = params.slice(1);
const pairs = params.split("&").map(pair => pair.split("="));
pairs.forEach(([key, value]) => {
this.#params.push(key ? decodeURIComponent(key) : key);
this.#values.push(value ? decodeURIComponent(value) : value);
});
break;
}
case "object":
if (Array.isArray(params)) {
Object.entries(params).forEach(([key, value]) => {
this.#params.push(key);
this.#values.push(value);
});
}
else if (Symbol.iterator in Object(params)) {
for (const [key, value] of params) {
this.#params.push(key);
this.#values.push(value);
}
}
break;
}
this.#updateSearchString(this.#params, this.#values);
}
// Create 2 seperate arrays for the params and values to make management and lookup easier.
#param = "";
#params = [];
#values = [];
// Custom encode function that doesn't encode commas and other safe characters
// Only encodes characters that are not allowed in query strings according to RFC 3986
#encodeQueryComponent(str) {
// encodeURIComponent encodes too many characters, so we need to unencode safe ones
return encodeURIComponent(str)
.replace(/%2C/g, ",") // Comma is safe
.replace(/%21/g, "!") // Exclamation mark
.replace(/%27/g, "'") // Single quote
.replace(/%28/g, "(") // Left parenthesis
.replace(/%29/g, ")") // Right parenthesis
.replace(/%2A/g, "*"); // Asterisk
}
// Update the search property of the URL instance with the new params and values.
#updateSearchString(params, values) {
if (params.length === 0)
this.#param = "";
else
this.#param = params
.map((param, index) => {
switch (typeof values[index]) {
case "object":
return `${this.#encodeQueryComponent(param)}=${this.#encodeQueryComponent(JSON.stringify(values[index]))}`;
case "boolean":
case "number":
case "string":
return `${this.#encodeQueryComponent(param)}=${this.#encodeQueryComponent(values[index])}`;
case "undefined":
default:
return this.#encodeQueryComponent(param);
}
})
.join("&");
}
// Add a given param with a given value to the end.
append(name, value) {
this.#params.push(name);
this.#values.push(value);
this.#updateSearchString(this.#params, this.#values);
}
// Remove all occurances of a given param
delete(name, value) {
while (this.#params.indexOf(name) > -1) {
this.#values.splice(this.#params.indexOf(name), 1);
this.#params.splice(this.#params.indexOf(name), 1);
}
this.#updateSearchString(this.#params, this.#values);
}
// Return an array to be structured in this way: [[param1, value1], [param2, value2]] to mimic the native method's ES6 iterator.
entries() {
return this.#params.map((param, index) => [param, this.#values[index]]);
}
// Return the value matched to the first occurance of a given param.
get(name) {
return this.#values[this.#params.indexOf(name)];
}
// Return all values matched to all occurances of a given param.
getAll(name) {
return this.#values.filter((value, index) => this.#params[index] === name);
}
// Return a boolean to indicate whether a given param exists.
has(name, value) {
return this.#params.indexOf(name) > -1;
}
// Return an array of the param names to mimic the native method's ES6 iterator.
keys() {
return this.#params;
}
// Set a given param to a given value.
set(name, value) {
if (this.#params.indexOf(name) === -1) {
this.append(name, value); // If the given param doesn't already exist, append it.
}
else {
let first = true;
const newValues = [];
// If the param already exists, change the value of the first occurance and remove any remaining occurances.
this.#params = this.#params.filter((currentParam, index) => {
if (currentParam !== name) {
newValues.push(this.#values[index]);
return true;
// If the currentParam matches the one being changed and it's the first one, keep the param and change its value to the given one.
}
else if (first) {
first = false;
newValues.push(value);
return true;
}
// If the currentParam matches the one being changed, but it's not the first, remove it.
return false;
});
this.#values = newValues;
this.#updateSearchString(this.#params, this.#values);
}
}
// Sort all key/value pairs, if any, by their keys then by their values.
sort() {
// Call entries to make sorting easier, then rewrite the params and values in the new order.
const sortedPairs = this.entries().sort();
this.#params = [];
this.#values = [];
sortedPairs.forEach(pair => {
this.#params.push(pair[0]);
this.#values.push(pair[1]);
});
this.#updateSearchString(this.#params, this.#values);
}
// Return the search string without the '?'.
toString = () => this.#param;
// Return and array of the param values to mimic the native method's ES6 iterator..
values = () => this.#values.values();
}