-
Notifications
You must be signed in to change notification settings - Fork 7
/
can-ajax.js
283 lines (258 loc) · 8.84 KB
/
can-ajax.js
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
'use strict';
var Global = require("can-globals/global/global");
var canReflect = require("can-reflect");
var namespace = require("can-namespace");
var parseURI = require('can-parse-uri');
var param = require("can-param");
/**
* @module {function} can-ajax can-ajax
* @parent can-dom-utilities
* @collection can-infrastructure
* @package ./package.json
*
* Make an asynchronous HTTP (AJAX) request.
*
* @signature `ajax( ajaxOptions )`
*
* Is used to make an asynchronous HTTP (AJAX) request similar to [jQuery.ajax()](http://api.jquery.com/jQuery.ajax/).
*
* ```js
* import { ajax } from "can";
*
* ajax({
* url: "http://query.yahooapis.com/v1/public/yql",
* data: {
* format: "json",
* q: 'select * from geo.places where text="sunnyvale, ca"'
* }
* }).then(function(response){
* console.log( response.query.count ); // => 2
* });
* ```
*
* @param {Object} ajaxOptions Configuration options for the AJAX request.
* - __url__ `{String}` The requested url.
* - __type__ `{String}` The method of the request. Ex: `GET`, `PUT`, `POST`, etc. Capitalization is ignored. _Default is `GET`_.
* - __data__ `{Object}` The data of the request. If data needs to be urlencoded (e.g. for GET requests or for CORS) it is serialized with [can-param].
* - __dataType__ `{String}` Type of data. _Default is `json`_.
* - __crossDomain__ `{Boolean}` If you wish to force a crossDomain request (such as JSONP) on the same domain, set the value of crossDomain to true. This allows, for example, server-side redirection to another domain. Default: `false` for same-domain requests, `true` for cross-domain requests.
* - __xhrFields__ `{Object}` Any fields to be set directly on the xhr request, [https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest] such as the withCredentials attribute that indicates whether or not cross-site Access-Control requests should be made using credentials such as cookies or authorization headers.
* - __beforeSend__ `{callback}` A pre-request callback function that can be used to modify the XHR object before it is sent. Use this to set custom headers, etc. The XHR and settings objects are passed as arguments.
* - __success__ `{callback}` A callback passed the response body when the request completes without error. Using the promise returned from ajax() should be preferred to passing a success callback
* - __error__ `{callback}` A callback passed the XHR object when the request fails to complete correctly. Using the promise returned from ajax() should be preferred to passing an error callback
* - __async__ `{Boolean}` Set `async` to `false` to create a synchronous XHR that blocks the thread until the request completes. success() or error() is called synchronously on completion, but promise callbacks are still resolved asychronously. Synchronous AJAX calls are **not recommended** and are only supported here for legacy reasons.
*
* @return {Promise} A Promise that resolves to the data. The Promise instance is abortable and exposes an `abort` method. Invoking abort on the Promise instance indirectly rejects it.
*
*
* @signature `ajaxSetup( ajaxOptions )`
*
* Is used to persist ajaxOptions across all ajax requests and they can be over-written in the ajaxOptions of the actual request.
* [https://api.jquery.com/jquery.ajaxsetup/]
*
* ```js
* import { ajax } from "can";
*
* ajax.ajaxSetup({xhrFields: {withCredentials: true}});
*
* ajax({
* url: "http://query.yahooapis.com/v1/public/yql",
* data: {
* format: "json",
* q: 'select * from geo.places where text="sunnyvale, ca"'
* }
* }).then(function(response){
* console.log( response.query.count ); // => 2
* });
* ```
*/
// from https://gist.github.com/mythz/1334560
var xhrs = [
function () { return new XMLHttpRequest(); },
function () { return new ActiveXObject("Microsoft.XMLHTTP"); },
function () { return new ActiveXObject("MSXML2.XMLHTTP.3.0"); },
function () { return new ActiveXObject("MSXML2.XMLHTTP"); }
],
_xhrf = null;
// used to check for Cross Domain requests
var originUrl = parseURI(Global().location.href);
var globalSettings = {};
var makeXhr = function () {
if (_xhrf != null) {
return _xhrf();
}
for (var i = 0, l = xhrs.length; i < l; i++) {
try {
var f = xhrs[i], req = f();
if (req != null) {
_xhrf = f;
return req;
}
} catch (e) {
continue;
}
}
return function () { };
};
var contentTypes = {
json: "application/json",
form: "application/x-www-form-urlencoded"
};
var _xhrResp = function (xhr, options) {
try{
var type = (options.dataType || xhr.getResponseHeader("Content-Type").split(";")[0]);
if(type && (xhr.responseText || xhr.responseXML)){
switch (type) {
case "text/xml":
case "xml":
return xhr.responseXML;
case "text/json":
case "application/json":
case "text/javascript":
case "application/javascript":
case "application/x-javascript":
case "json":
return xhr.responseText && JSON.parse(xhr.responseText);
default:
return xhr.responseText;
}
} else {
return xhr;
}
} catch(e){
return xhr;
}
};
function ajax(o) {
var xhr = makeXhr(), timer, n = 0;
var deferred = {}, isFormData;
var promise = new Promise(function(resolve,reject){
deferred.resolve = resolve;
deferred.reject = reject;
});
var requestUrl;
var isAborted = false;
promise.abort = function () {
isAborted = true;
xhr.abort();
};
o = [{
userAgent: "XMLHttpRequest",
lang: "en",
type: "GET",
data: null,
dataType: "json"
}, globalSettings, o].reduce(function(a,b,i) {
return canReflect.assignDeep(a,b);
});
var async = o.async !== false;
// Set the default contentType
if(!o.contentType) {
o.contentType = o.type.toUpperCase() === "GET" ?
contentTypes.form : contentTypes.json;
}
//how jquery handles check for cross domain
if(o.crossDomain == null){
try {
requestUrl = parseURI(o.url);
o.crossDomain = !!((requestUrl.protocol && requestUrl.protocol !== originUrl.protocol) ||
(requestUrl.host && requestUrl.host !== originUrl.host));
} catch (e){
o.crossDomain = true;
}
}
if (o.timeout) {
timer = setTimeout(function () {
xhr.abort();
if (o.timeoutFn) {
o.timeoutFn(o.url);
}
}, o.timeout);
}
xhr.onreadystatechange = function () {
try {
if (xhr.readyState === 4) {
if (timer) {
clearTimeout(timer);
}
if (xhr.status < 300) {
if (o.success) {
o.success( _xhrResp(xhr, o) );
}
}
else if (o.error) {
o.error(xhr, xhr.status, xhr.statusText);
}
if (o.complete) {
o.complete(xhr, xhr.statusText);
}
if (xhr.status >= 200 && xhr.status < 300) {
deferred.resolve( _xhrResp(xhr, o) );
} else {
deferred.reject( _xhrResp(xhr, o) );
}
}
else if (o.progress) {
o.progress(++n);
}
} catch(e) {
deferred.reject(e);
}
};
var url = o.url, data = null, type = o.type.toUpperCase();
var isJsonContentType = o.contentType === contentTypes.json;
var isPost = type === "POST" || type === "PUT" || type === "PATCH";
if (!isPost && o.data) {
url += "?" + (isJsonContentType ? JSON.stringify(o.data) : param(o.data));
}
xhr.open(type, url, async);
// For CORS to send a "simple" request (to avoid a preflight check), the following methods are allowed: GET/POST/HEAD,
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Simple_requests
var isSimpleCors = o.crossDomain && ['GET', 'POST', 'HEAD'].indexOf(type) !== -1;
isFormData = typeof FormData !== "undefined" && o.data instanceof FormData;
if (isPost) {
if (isFormData) {
// do not set "Content-Type" let the browser handle it
// do not stringify FormData XHR handles it natively
data = o.data;
} else {
if (isJsonContentType && !isSimpleCors) {
data = typeof o.data === "object" ? JSON.stringify(o.data) : o.data;
xhr.setRequestHeader("Content-Type", "application/json");
} else {
data = param(o.data);
// CORS simple: `Content-Type` has to be `application/x-www-form-urlencoded`:
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
}
}
} else {
xhr.setRequestHeader("Content-Type", o.contentType);
}
// CORS simple: no custom headers, so we don't add `X-Requested-With` header:
if (!isSimpleCors){
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
}
if (o.xhrFields) {
for (var f in o.xhrFields) {
xhr[f] = o.xhrFields[f];
}
}
function send () {
if(!isAborted) {
xhr.send(data);
}
}
if(o.beforeSend){
var result = o.beforeSend.call( o, xhr, o );
if(canReflect.isPromise(result)) {
result.then(send).catch(deferred.reject);
return promise;
}
}
send();
return promise;
}
module.exports = namespace.ajax = ajax;
module.exports.ajaxSetup = function (o) {
globalSettings = o || {};
};