@brandup/ui-ajax 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # brandup-ui-ajax
2
+
3
+ [![Build Status](https://dev.azure.com/brandup/BrandUp%20Core/_apis/build/status%2FBrandUp%2Fbrandup-ui?branchName=master)]()
4
+
5
+ ## Installation
6
+
7
+ Install NPM package [@brandup/ui-ajax](https://www.npmjs.com/package/@brandup/ui-ajax).
8
+
9
+ ```
10
+ npm i @brandup/ui-ajax@latest
11
+ ```
12
+
13
+ ## AJAX request
14
+
15
+ Simplify async ajax request method.
16
+
17
+ ```
18
+ import { request } from "@brandup/ui-ajax";
19
+
20
+ await request({
21
+ url?: string | null;
22
+ query?: QueryData | null;
23
+ method?: AJAXMethod | null;
24
+ timeout?: number | null;
25
+ headers?: { [key: string]: string } | null;
26
+ type?: AJAXReqestType | null;
27
+ data?: string | object | Blob | FormData | HTMLFormElement | null;
28
+ success?: ResponseDelegate | null;
29
+ error?: ErrorDelegate | null;
30
+ disableCache?: boolean | null;
31
+ state?: TState | null;
32
+ })
33
+ .then(response => {
34
+ // response.status: number;
35
+ // response.redirected: boolean;
36
+ // response.url: string | null;
37
+ // response.type: "none" | "json" | "blob" | "text" | "html";
38
+ // response.contentType: string | null;
39
+ // response.data: TData | null;
40
+ // response.state?: TState | null;
41
+ })
42
+ .catch(reason => console.error(reason));
43
+ ```
44
+
45
+ ### Request cancellation
46
+
47
+ ```
48
+ import { request } from "@brandup/ui-ajax";
49
+
50
+ const cancellation = new AbortController();
51
+
52
+ await request({ }, cancellation.signal)
53
+ .catch(reason => console.error(reason));
54
+ ```
55
+
56
+ ## Queue requests
57
+
58
+ Sequential execution of AJAX requests.
59
+
60
+ ```
61
+ import { AjaxQueue } from "@brandup/ui-ajax";
62
+
63
+ const queue = new AjaxQueue({
64
+ canRequest?: (request: AjaxRequest) => void | boolean;
65
+ successRequest?: (request: AjaxRequest, response: AjaxResponse) => void;
66
+ errorRequest?: (response: AjaxRequest, reason?: any) => void;
67
+ });
68
+
69
+ queue.push({ request options });
70
+
71
+ queue.reset(); // clear queue without abort current request
72
+
73
+ queue.reset(true); // abort current request and clear queue
74
+
75
+ queue.destroy();
76
+ ```
@@ -0,0 +1,392 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_TIMEOUT = 30000;
4
+ const FORM_URL = "application/x-www-form-urlencoded";
5
+ const FORM_DATA = "multipart/form-data";
6
+ const urlEncode = (data, rfc3986 = true) => {
7
+ data = encodeURIComponent(data);
8
+ data = data.replace(/%20/g, '+');
9
+ if (rfc3986) {
10
+ data = data.replace(/[!'()*]/g, function (c) {
11
+ return '%' + c.charCodeAt(0).toString(16);
12
+ });
13
+ }
14
+ return data;
15
+ };
16
+ const ajaxRequest = (options) => {
17
+ let url = options.url || location.href;
18
+ let { query } = options;
19
+ if (options.disableCache) {
20
+ if (!query)
21
+ query = {};
22
+ query["_"] = new Date().getTime().toString();
23
+ }
24
+ url = extendUrl(url, query);
25
+ const method = options.method ? options.method : "GET";
26
+ if (options.data && method === "GET")
27
+ throw new Error("GET method is not support request with data.");
28
+ detectRequestType(options);
29
+ const prepared = prepareRequest(options, options.data);
30
+ const xhr = new XMLHttpRequest();
31
+ xhr.withCredentials = true;
32
+ if (options.timeout === 0 || options.timeout)
33
+ xhr.timeout = options.timeout;
34
+ xhr.onreadystatechange = (e) => {
35
+ switch (xhr.readyState) {
36
+ case XMLHttpRequest.DONE: {
37
+ if (options.success) {
38
+ let responseData = null;
39
+ let responseType = "none";
40
+ const contentType = xhr.getResponseHeader("Content-Type");
41
+ if (xhr.response) {
42
+ if (contentType) {
43
+ if (contentType.includes("json")) {
44
+ responseType = "json";
45
+ responseData = JSON.parse(xhr.responseText);
46
+ }
47
+ else if (contentType.includes("text/plain")) {
48
+ responseType = "text";
49
+ responseData = xhr.responseText;
50
+ }
51
+ else if (contentType.includes("text/html")) {
52
+ responseType = "html";
53
+ responseData = xhr.responseText;
54
+ }
55
+ }
56
+ }
57
+ const headers = {
58
+ get(name) {
59
+ return xhr.getResponseHeader(name);
60
+ },
61
+ has(name) {
62
+ return !!xhr.getResponseHeader(name);
63
+ },
64
+ forEach(callbackfn, thisArg) {
65
+ const headers = xhr.getAllResponseHeaders();
66
+ // Convert the header string into an array
67
+ // of individual headers
68
+ const arr = headers.trim().split(/[\r\n]+/);
69
+ // Create a map of header names to values
70
+ arr.forEach((line) => {
71
+ const parts = line.split(": ");
72
+ const header = parts.shift() || "";
73
+ const value = parts.join(": ");
74
+ callbackfn.call(thisArg, value, header.toLowerCase(), {});
75
+ });
76
+ }
77
+ };
78
+ options.success({
79
+ status: xhr.status,
80
+ url: xhr.responseURL,
81
+ redirected: false,
82
+ type: responseType,
83
+ contentType,
84
+ headers,
85
+ data: responseData,
86
+ state: options.state
87
+ });
88
+ }
89
+ break;
90
+ }
91
+ }
92
+ };
93
+ xhr.onabort = (e) => {
94
+ if (options.error)
95
+ options.error(options, "Request aborted");
96
+ };
97
+ xhr.open(method, url, true);
98
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
99
+ for (const key in prepared.headers) {
100
+ const value = prepared.headers[key];
101
+ if (!value)
102
+ continue;
103
+ xhr.setRequestHeader(key, value);
104
+ }
105
+ if (method === "GET")
106
+ xhr.send();
107
+ else
108
+ xhr.send(prepared.body);
109
+ return xhr;
110
+ };
111
+ const request = async (options, abortSignal) => {
112
+ let url = options.url || location.href;
113
+ url = extendUrl(url, options.query);
114
+ const method = options.method ? options.method.toUpperCase() : "GET";
115
+ let body = options.data;
116
+ if (body && method === "GET")
117
+ throw new Error("GET method is not support request with data.");
118
+ detectRequestType(options);
119
+ const prepared = prepareRequest(options, body);
120
+ const abortSignals = [AbortSignal.timeout(options.timeout ?? DEFAULT_TIMEOUT)];
121
+ if (abortSignal)
122
+ abortSignals.push(abortSignal);
123
+ try {
124
+ const response = await fetch(url, {
125
+ method,
126
+ headers: new Headers(prepared.headers),
127
+ cache: options.disableCache ? "no-cache" : "default",
128
+ redirect: "follow",
129
+ signal: AbortSignal.any(abortSignals),
130
+ body: prepared.body
131
+ });
132
+ let result;
133
+ switch (response.type) {
134
+ case "basic":
135
+ case "default": {
136
+ let responseData = null;
137
+ let responseType = "none";
138
+ let contentType = response.headers.get("content-type");
139
+ if (!response.redirected && response.body) {
140
+ if (contentType) {
141
+ const ctSplitIndex = contentType.indexOf(";");
142
+ if (ctSplitIndex > 0)
143
+ contentType = contentType.substring(0, ctSplitIndex);
144
+ if (contentType.includes("json")) {
145
+ responseType = "json";
146
+ responseData = await response.json();
147
+ }
148
+ else if (contentType.includes("text/html")) {
149
+ responseType = "html";
150
+ responseData = await response.text();
151
+ }
152
+ else if (contentType.includes("text/plain")) {
153
+ responseType = "text";
154
+ responseData = await response.text();
155
+ }
156
+ else {
157
+ responseType = "blob";
158
+ responseData = await response.blob();
159
+ }
160
+ }
161
+ }
162
+ result = {
163
+ status: response.status,
164
+ url: response.url,
165
+ redirected: response.redirected,
166
+ type: responseType,
167
+ contentType,
168
+ headers: response.headers,
169
+ data: responseData,
170
+ state: options.state
171
+ };
172
+ break;
173
+ }
174
+ case "opaqueredirect":
175
+ throw new Error("Not supported opaqueredirect.");
176
+ case "error":
177
+ throw new Error("Response error.");
178
+ case "cors":
179
+ throw new Error("Response type cors is not supported.");
180
+ case "opaque":
181
+ throw new Error("Response type opaque is not supported.");
182
+ default:
183
+ throw new Error(`Unknown response type: ${response.type}`);
184
+ }
185
+ if (options.success)
186
+ options.success(result);
187
+ return result;
188
+ }
189
+ catch (error) {
190
+ if (options.error)
191
+ options.error(options, error);
192
+ throw new Error(`Error ajax request: ${(error && error.message) ? error.message : error}`);
193
+ }
194
+ };
195
+ const detectRequestType = (options) => {
196
+ if (!options.type && options.data) {
197
+ const body = options.data;
198
+ if (body instanceof Blob)
199
+ options.type = "BLOB";
200
+ else if (body instanceof FormData)
201
+ options.type = null;
202
+ else if (body instanceof HTMLFormElement)
203
+ options.type = "FORM";
204
+ else if (body instanceof Object)
205
+ options.type = "JSON";
206
+ else if (typeof body === "string")
207
+ options.type = "TEXT";
208
+ }
209
+ };
210
+ const prepareRequest = (options, body) => {
211
+ const headers = {};
212
+ if (options.headers) {
213
+ for (const key in options.headers) {
214
+ const value = options.headers[key];
215
+ if (!value)
216
+ continue;
217
+ headers[key] = value;
218
+ }
219
+ }
220
+ if (options.type) {
221
+ let contentType = null;
222
+ let accept = null;
223
+ switch (options.type) {
224
+ case "XML":
225
+ contentType = "application/xml; charset=utf-8";
226
+ accept = "application/xml, text/xml, */*; q=0.01";
227
+ break;
228
+ case "JSON":
229
+ contentType = "application/json; charset=utf-8";
230
+ accept = "application/json, text/json, */*; q=0.01";
231
+ body = JSON.stringify(body);
232
+ break;
233
+ case "FORM":
234
+ if (body instanceof HTMLFormElement) {
235
+ //const form = <HTMLFormElement>body;
236
+ //contentType = form.enctype ?? FORM_URL;
237
+ body = new FormData(body);
238
+ }
239
+ else if (body instanceof FormData)
240
+ contentType = FORM_URL;
241
+ if (contentType == FORM_URL)
242
+ body = encodeForm(body);
243
+ else if (contentType == FORM_DATA)
244
+ contentType = null;
245
+ break;
246
+ case "FORMDATA":
247
+ break;
248
+ case "TEXT":
249
+ contentType = "text/plain";
250
+ break;
251
+ }
252
+ if (accept)
253
+ headers["Accept"] = accept;
254
+ if (contentType)
255
+ headers['Content-Type'] = contentType;
256
+ }
257
+ return {
258
+ headers,
259
+ body
260
+ };
261
+ };
262
+ const createSearchParams = (query) => {
263
+ const urlParams = new URLSearchParams();
264
+ if (!query)
265
+ return urlParams;
266
+ for (const key in query) {
267
+ const val = query[key];
268
+ if (val === null)
269
+ continue;
270
+ if (Array.isArray(val))
271
+ val.forEach(v => urlParams.append(key, v));
272
+ else
273
+ urlParams.append(key, val);
274
+ }
275
+ return urlParams;
276
+ };
277
+ const extendUrl = (url, query) => {
278
+ if (query) {
279
+ const urlParams = createSearchParams(query);
280
+ if (urlParams.size) {
281
+ if (url.indexOf("?") === -1)
282
+ url += "?";
283
+ else
284
+ url += "&";
285
+ url += urlParams.toString();
286
+ }
287
+ }
288
+ return url;
289
+ };
290
+ const encodeForm = (data) => {
291
+ const query = new URLSearchParams();
292
+ data.forEach((value, key) => {
293
+ if (!key)
294
+ return;
295
+ query.append(key, value.toString());
296
+ });
297
+ return query.toString();
298
+ };
299
+
300
+ class AjaxQueue {
301
+ _options;
302
+ _requests = [];
303
+ _curent = null;
304
+ _destroyed = false;
305
+ constructor(options) {
306
+ this._options = options ? options : {};
307
+ }
308
+ get length() { return this._requests.length; }
309
+ get isFree() { return !this._requests.length && !this._curent; }
310
+ get isEmpty() { return !this._requests.length; }
311
+ push(request) {
312
+ if (this._destroyed)
313
+ throw new Error("AjaxQueue is destroyed.");
314
+ this._requests.push({ request, abort: new AbortController() });
315
+ if (!this._curent)
316
+ this.__execute();
317
+ }
318
+ enque(request) {
319
+ const { success, error } = request;
320
+ return new Promise((resolve, reject) => {
321
+ request.success = (response) => {
322
+ if (success)
323
+ success(response);
324
+ resolve(response);
325
+ };
326
+ request.error = (response, reason) => {
327
+ if (error)
328
+ error(request, reason);
329
+ reject(reason);
330
+ };
331
+ this.push(request);
332
+ });
333
+ }
334
+ reset(cancelCurrentRequest = false) {
335
+ this._requests = [];
336
+ const current = this._curent;
337
+ this._curent = null;
338
+ if (cancelCurrentRequest && current)
339
+ current.abort?.abort("ResetAjaxQueue");
340
+ }
341
+ destroy() {
342
+ if (this._destroyed)
343
+ return;
344
+ this._destroyed = true;
345
+ this._requests = [];
346
+ if (this._curent) {
347
+ this._curent.abort?.abort("DestroyAjaxQueue");
348
+ this._curent = null;
349
+ }
350
+ }
351
+ __execute() {
352
+ if (this._destroyed)
353
+ return;
354
+ if (this._curent)
355
+ throw new Error("AjaxQueue currently is executing.");
356
+ const task = this._curent = this._requests.shift() ?? null;
357
+ if (task) {
358
+ if (this._options.canRequest && this._options.canRequest(task.request) === false) {
359
+ this.__next();
360
+ return;
361
+ }
362
+ task.abort = new AbortController();
363
+ task.xhr = request(task.request, task.abort.signal);
364
+ task.xhr
365
+ .then(response => {
366
+ if (this._destroyed)
367
+ return;
368
+ if (this._options.successRequest)
369
+ this._options.successRequest(task.request, response);
370
+ })
371
+ .catch(reason => {
372
+ if (this._destroyed)
373
+ return;
374
+ if (this._options.errorRequest)
375
+ this._options.errorRequest(task.request, reason);
376
+ })
377
+ .finally(() => this.__next());
378
+ }
379
+ }
380
+ __next() {
381
+ if (this._destroyed)
382
+ return;
383
+ this._curent = null;
384
+ this.__execute();
385
+ }
386
+ }
387
+
388
+ exports.AjaxQueue = AjaxQueue;
389
+ exports.ajaxRequest = ajaxRequest;
390
+ exports.request = request;
391
+ exports.urlEncode = urlEncode;
392
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,387 @@
1
+ const DEFAULT_TIMEOUT = 30000;
2
+ const FORM_URL = "application/x-www-form-urlencoded";
3
+ const FORM_DATA = "multipart/form-data";
4
+ const urlEncode = (data, rfc3986 = true) => {
5
+ data = encodeURIComponent(data);
6
+ data = data.replace(/%20/g, '+');
7
+ if (rfc3986) {
8
+ data = data.replace(/[!'()*]/g, function (c) {
9
+ return '%' + c.charCodeAt(0).toString(16);
10
+ });
11
+ }
12
+ return data;
13
+ };
14
+ const ajaxRequest = (options) => {
15
+ let url = options.url || location.href;
16
+ let { query } = options;
17
+ if (options.disableCache) {
18
+ if (!query)
19
+ query = {};
20
+ query["_"] = new Date().getTime().toString();
21
+ }
22
+ url = extendUrl(url, query);
23
+ const method = options.method ? options.method : "GET";
24
+ if (options.data && method === "GET")
25
+ throw new Error("GET method is not support request with data.");
26
+ detectRequestType(options);
27
+ const prepared = prepareRequest(options, options.data);
28
+ const xhr = new XMLHttpRequest();
29
+ xhr.withCredentials = true;
30
+ if (options.timeout === 0 || options.timeout)
31
+ xhr.timeout = options.timeout;
32
+ xhr.onreadystatechange = (e) => {
33
+ switch (xhr.readyState) {
34
+ case XMLHttpRequest.DONE: {
35
+ if (options.success) {
36
+ let responseData = null;
37
+ let responseType = "none";
38
+ const contentType = xhr.getResponseHeader("Content-Type");
39
+ if (xhr.response) {
40
+ if (contentType) {
41
+ if (contentType.includes("json")) {
42
+ responseType = "json";
43
+ responseData = JSON.parse(xhr.responseText);
44
+ }
45
+ else if (contentType.includes("text/plain")) {
46
+ responseType = "text";
47
+ responseData = xhr.responseText;
48
+ }
49
+ else if (contentType.includes("text/html")) {
50
+ responseType = "html";
51
+ responseData = xhr.responseText;
52
+ }
53
+ }
54
+ }
55
+ const headers = {
56
+ get(name) {
57
+ return xhr.getResponseHeader(name);
58
+ },
59
+ has(name) {
60
+ return !!xhr.getResponseHeader(name);
61
+ },
62
+ forEach(callbackfn, thisArg) {
63
+ const headers = xhr.getAllResponseHeaders();
64
+ // Convert the header string into an array
65
+ // of individual headers
66
+ const arr = headers.trim().split(/[\r\n]+/);
67
+ // Create a map of header names to values
68
+ arr.forEach((line) => {
69
+ const parts = line.split(": ");
70
+ const header = parts.shift() || "";
71
+ const value = parts.join(": ");
72
+ callbackfn.call(thisArg, value, header.toLowerCase(), {});
73
+ });
74
+ }
75
+ };
76
+ options.success({
77
+ status: xhr.status,
78
+ url: xhr.responseURL,
79
+ redirected: false,
80
+ type: responseType,
81
+ contentType,
82
+ headers,
83
+ data: responseData,
84
+ state: options.state
85
+ });
86
+ }
87
+ break;
88
+ }
89
+ }
90
+ };
91
+ xhr.onabort = (e) => {
92
+ if (options.error)
93
+ options.error(options, "Request aborted");
94
+ };
95
+ xhr.open(method, url, true);
96
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
97
+ for (const key in prepared.headers) {
98
+ const value = prepared.headers[key];
99
+ if (!value)
100
+ continue;
101
+ xhr.setRequestHeader(key, value);
102
+ }
103
+ if (method === "GET")
104
+ xhr.send();
105
+ else
106
+ xhr.send(prepared.body);
107
+ return xhr;
108
+ };
109
+ const request = async (options, abortSignal) => {
110
+ let url = options.url || location.href;
111
+ url = extendUrl(url, options.query);
112
+ const method = options.method ? options.method.toUpperCase() : "GET";
113
+ let body = options.data;
114
+ if (body && method === "GET")
115
+ throw new Error("GET method is not support request with data.");
116
+ detectRequestType(options);
117
+ const prepared = prepareRequest(options, body);
118
+ const abortSignals = [AbortSignal.timeout(options.timeout ?? DEFAULT_TIMEOUT)];
119
+ if (abortSignal)
120
+ abortSignals.push(abortSignal);
121
+ try {
122
+ const response = await fetch(url, {
123
+ method,
124
+ headers: new Headers(prepared.headers),
125
+ cache: options.disableCache ? "no-cache" : "default",
126
+ redirect: "follow",
127
+ signal: AbortSignal.any(abortSignals),
128
+ body: prepared.body
129
+ });
130
+ let result;
131
+ switch (response.type) {
132
+ case "basic":
133
+ case "default": {
134
+ let responseData = null;
135
+ let responseType = "none";
136
+ let contentType = response.headers.get("content-type");
137
+ if (!response.redirected && response.body) {
138
+ if (contentType) {
139
+ const ctSplitIndex = contentType.indexOf(";");
140
+ if (ctSplitIndex > 0)
141
+ contentType = contentType.substring(0, ctSplitIndex);
142
+ if (contentType.includes("json")) {
143
+ responseType = "json";
144
+ responseData = await response.json();
145
+ }
146
+ else if (contentType.includes("text/html")) {
147
+ responseType = "html";
148
+ responseData = await response.text();
149
+ }
150
+ else if (contentType.includes("text/plain")) {
151
+ responseType = "text";
152
+ responseData = await response.text();
153
+ }
154
+ else {
155
+ responseType = "blob";
156
+ responseData = await response.blob();
157
+ }
158
+ }
159
+ }
160
+ result = {
161
+ status: response.status,
162
+ url: response.url,
163
+ redirected: response.redirected,
164
+ type: responseType,
165
+ contentType,
166
+ headers: response.headers,
167
+ data: responseData,
168
+ state: options.state
169
+ };
170
+ break;
171
+ }
172
+ case "opaqueredirect":
173
+ throw new Error("Not supported opaqueredirect.");
174
+ case "error":
175
+ throw new Error("Response error.");
176
+ case "cors":
177
+ throw new Error("Response type cors is not supported.");
178
+ case "opaque":
179
+ throw new Error("Response type opaque is not supported.");
180
+ default:
181
+ throw new Error(`Unknown response type: ${response.type}`);
182
+ }
183
+ if (options.success)
184
+ options.success(result);
185
+ return result;
186
+ }
187
+ catch (error) {
188
+ if (options.error)
189
+ options.error(options, error);
190
+ throw new Error(`Error ajax request: ${(error && error.message) ? error.message : error}`);
191
+ }
192
+ };
193
+ const detectRequestType = (options) => {
194
+ if (!options.type && options.data) {
195
+ const body = options.data;
196
+ if (body instanceof Blob)
197
+ options.type = "BLOB";
198
+ else if (body instanceof FormData)
199
+ options.type = null;
200
+ else if (body instanceof HTMLFormElement)
201
+ options.type = "FORM";
202
+ else if (body instanceof Object)
203
+ options.type = "JSON";
204
+ else if (typeof body === "string")
205
+ options.type = "TEXT";
206
+ }
207
+ };
208
+ const prepareRequest = (options, body) => {
209
+ const headers = {};
210
+ if (options.headers) {
211
+ for (const key in options.headers) {
212
+ const value = options.headers[key];
213
+ if (!value)
214
+ continue;
215
+ headers[key] = value;
216
+ }
217
+ }
218
+ if (options.type) {
219
+ let contentType = null;
220
+ let accept = null;
221
+ switch (options.type) {
222
+ case "XML":
223
+ contentType = "application/xml; charset=utf-8";
224
+ accept = "application/xml, text/xml, */*; q=0.01";
225
+ break;
226
+ case "JSON":
227
+ contentType = "application/json; charset=utf-8";
228
+ accept = "application/json, text/json, */*; q=0.01";
229
+ body = JSON.stringify(body);
230
+ break;
231
+ case "FORM":
232
+ if (body instanceof HTMLFormElement) {
233
+ //const form = <HTMLFormElement>body;
234
+ //contentType = form.enctype ?? FORM_URL;
235
+ body = new FormData(body);
236
+ }
237
+ else if (body instanceof FormData)
238
+ contentType = FORM_URL;
239
+ if (contentType == FORM_URL)
240
+ body = encodeForm(body);
241
+ else if (contentType == FORM_DATA)
242
+ contentType = null;
243
+ break;
244
+ case "FORMDATA":
245
+ break;
246
+ case "TEXT":
247
+ contentType = "text/plain";
248
+ break;
249
+ }
250
+ if (accept)
251
+ headers["Accept"] = accept;
252
+ if (contentType)
253
+ headers['Content-Type'] = contentType;
254
+ }
255
+ return {
256
+ headers,
257
+ body
258
+ };
259
+ };
260
+ const createSearchParams = (query) => {
261
+ const urlParams = new URLSearchParams();
262
+ if (!query)
263
+ return urlParams;
264
+ for (const key in query) {
265
+ const val = query[key];
266
+ if (val === null)
267
+ continue;
268
+ if (Array.isArray(val))
269
+ val.forEach(v => urlParams.append(key, v));
270
+ else
271
+ urlParams.append(key, val);
272
+ }
273
+ return urlParams;
274
+ };
275
+ const extendUrl = (url, query) => {
276
+ if (query) {
277
+ const urlParams = createSearchParams(query);
278
+ if (urlParams.size) {
279
+ if (url.indexOf("?") === -1)
280
+ url += "?";
281
+ else
282
+ url += "&";
283
+ url += urlParams.toString();
284
+ }
285
+ }
286
+ return url;
287
+ };
288
+ const encodeForm = (data) => {
289
+ const query = new URLSearchParams();
290
+ data.forEach((value, key) => {
291
+ if (!key)
292
+ return;
293
+ query.append(key, value.toString());
294
+ });
295
+ return query.toString();
296
+ };
297
+
298
+ class AjaxQueue {
299
+ _options;
300
+ _requests = [];
301
+ _curent = null;
302
+ _destroyed = false;
303
+ constructor(options) {
304
+ this._options = options ? options : {};
305
+ }
306
+ get length() { return this._requests.length; }
307
+ get isFree() { return !this._requests.length && !this._curent; }
308
+ get isEmpty() { return !this._requests.length; }
309
+ push(request) {
310
+ if (this._destroyed)
311
+ throw new Error("AjaxQueue is destroyed.");
312
+ this._requests.push({ request, abort: new AbortController() });
313
+ if (!this._curent)
314
+ this.__execute();
315
+ }
316
+ enque(request) {
317
+ const { success, error } = request;
318
+ return new Promise((resolve, reject) => {
319
+ request.success = (response) => {
320
+ if (success)
321
+ success(response);
322
+ resolve(response);
323
+ };
324
+ request.error = (response, reason) => {
325
+ if (error)
326
+ error(request, reason);
327
+ reject(reason);
328
+ };
329
+ this.push(request);
330
+ });
331
+ }
332
+ reset(cancelCurrentRequest = false) {
333
+ this._requests = [];
334
+ const current = this._curent;
335
+ this._curent = null;
336
+ if (cancelCurrentRequest && current)
337
+ current.abort?.abort("ResetAjaxQueue");
338
+ }
339
+ destroy() {
340
+ if (this._destroyed)
341
+ return;
342
+ this._destroyed = true;
343
+ this._requests = [];
344
+ if (this._curent) {
345
+ this._curent.abort?.abort("DestroyAjaxQueue");
346
+ this._curent = null;
347
+ }
348
+ }
349
+ __execute() {
350
+ if (this._destroyed)
351
+ return;
352
+ if (this._curent)
353
+ throw new Error("AjaxQueue currently is executing.");
354
+ const task = this._curent = this._requests.shift() ?? null;
355
+ if (task) {
356
+ if (this._options.canRequest && this._options.canRequest(task.request) === false) {
357
+ this.__next();
358
+ return;
359
+ }
360
+ task.abort = new AbortController();
361
+ task.xhr = request(task.request, task.abort.signal);
362
+ task.xhr
363
+ .then(response => {
364
+ if (this._destroyed)
365
+ return;
366
+ if (this._options.successRequest)
367
+ this._options.successRequest(task.request, response);
368
+ })
369
+ .catch(reason => {
370
+ if (this._destroyed)
371
+ return;
372
+ if (this._options.errorRequest)
373
+ this._options.errorRequest(task.request, reason);
374
+ })
375
+ .finally(() => this.__next());
376
+ }
377
+ }
378
+ __next() {
379
+ if (this._destroyed)
380
+ return;
381
+ this._curent = null;
382
+ this.__execute();
383
+ }
384
+ }
385
+
386
+ export { AjaxQueue, ajaxRequest, request, urlEncode };
387
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,65 @@
1
+ type AJAXMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | string;
2
+ type AJAXReqestType = "NONE" | "JSON" | "XML" | "FORM" | "FORMDATA" | "TEXT" | "BLOB";
3
+ type ResponseTye = "none" | "json" | "blob" | "text" | "html";
4
+ type ResponseDelegate = (response: AjaxResponse) => void;
5
+ type ErrorDelegate = (request: AjaxRequest, reason?: any) => void;
6
+ type QueryData = {
7
+ [key: string]: string | string[];
8
+ };
9
+ interface AjaxRequest<TState = any> {
10
+ url?: string | null;
11
+ query?: QueryData | null;
12
+ method?: AJAXMethod | null;
13
+ timeout?: number | null;
14
+ headers?: {
15
+ [key: string]: string;
16
+ } | null;
17
+ type?: AJAXReqestType | null;
18
+ data?: string | object | Blob | FormData | HTMLFormElement | null;
19
+ success?: ResponseDelegate | null;
20
+ error?: ErrorDelegate | null;
21
+ disableCache?: boolean | null;
22
+ state?: TState | null;
23
+ }
24
+ interface AjaxResponse<TData = any, TState = any> {
25
+ status: number;
26
+ redirected: boolean;
27
+ url: string | null;
28
+ type: ResponseTye;
29
+ contentType: string | null;
30
+ headers: ResponseHeaders;
31
+ data: TData | null;
32
+ state?: TState | null;
33
+ }
34
+ interface ResponseHeaders {
35
+ get(name: string): string | null;
36
+ has(name: string): boolean;
37
+ forEach(callbackfn: (value: string, key: string, parent: ResponseHeaders) => void, thisArg?: any): void;
38
+ }
39
+ declare const urlEncode: (data: string, rfc3986?: boolean) => string;
40
+ declare const ajaxRequest: (options: AjaxRequest) => XMLHttpRequest;
41
+ declare const request: (options: AjaxRequest, abortSignal?: AbortSignal) => Promise<AjaxResponse>;
42
+
43
+ interface AjaxQueueOptions {
44
+ canRequest?: (request: AjaxRequest) => void | boolean;
45
+ successRequest?: (request: AjaxRequest, response: AjaxResponse) => void;
46
+ errorRequest?: (response: AjaxRequest, reason?: any) => void;
47
+ }
48
+ declare class AjaxQueue {
49
+ private _options;
50
+ private _requests;
51
+ private _curent;
52
+ private _destroyed;
53
+ constructor(options?: AjaxQueueOptions);
54
+ get length(): number;
55
+ get isFree(): boolean;
56
+ get isEmpty(): boolean;
57
+ push(request: AjaxRequest): void;
58
+ enque(request: AjaxRequest): Promise<AjaxResponse<any, any>>;
59
+ reset(cancelCurrentRequest?: boolean): void;
60
+ destroy(): void;
61
+ private __execute;
62
+ private __next;
63
+ }
64
+
65
+ export { type AJAXMethod, type AJAXReqestType, AjaxQueue, type AjaxQueueOptions, type AjaxRequest, type AjaxResponse, type ErrorDelegate, type QueryData, type ResponseDelegate, type ResponseHeaders, type ResponseTye, ajaxRequest, request, urlEncode };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@brandup/ui-ajax",
3
+ "description": "Basic AJAX framework.",
4
+ "keywords": [
5
+ "brandup",
6
+ "typescript",
7
+ "ui",
8
+ "ajax"
9
+ ],
10
+ "author": {
11
+ "name": "Dmitry Kovyazin",
12
+ "email": "it@brandup.online"
13
+ },
14
+ "homepage": "https://github.com/brandup-online/brandup-ui/npm/brandup-ui-ajax",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/brandup-online/brandup-ui.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/brandup-online/brandup-ui/issues",
21
+ "email": "it@brandup.online"
22
+ },
23
+ "license": "Apache-2.0",
24
+ "version": "1.0.1",
25
+ "main": "dist/cjs/index.js",
26
+ "module": "dist/mjs/index.js",
27
+ "types": "dist/types.d.ts",
28
+ "devDependencies": {
29
+ "@rollup/plugin-commonjs": "^25.0.8",
30
+ "@rollup/plugin-node-resolve": "^15.2.3",
31
+ "@rollup/plugin-terser": "^0.4.4",
32
+ "rollup": "^4.19.0",
33
+ "rollup-plugin-dts": "^6.1.1",
34
+ "rollup-plugin-peer-deps-external": "^2.2.4",
35
+ "rollup-plugin-typescript2": "^0.36.0",
36
+ "tslib": "^2.6.3",
37
+ "typescript": "^5.5.3"
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "README.md"
42
+ ],
43
+ "scripts": {
44
+ "build": "rollup -c --bundleConfigAsCjs",
45
+ "watch": "rollup -c -w --bundleConfigAsCjs"
46
+ }
47
+ }