@pi0/framework7
Version:
Full featured mobile HTML framework for building iOS & Android apps
290 lines (255 loc) • 8.76 kB
JavaScript
import Utils from './utils';
const globals = {};
let jsonpRequests = 0;
function Request(requestOptions) {
const globalsNoCallbacks = Utils.extend({}, globals);
('beforeCreate beforeOpen beforeSend error complete success statusCode').split(' ').forEach((callbackName) => {
delete globalsNoCallbacks[callbackName];
});
const defaults = Utils.extend({
url: window.location.toString(),
method: 'GET',
data: false,
async: true,
cache: true,
user: '',
password: '',
headers: {},
xhrFields: {},
statusCode: {},
processData: true,
dataType: 'text',
contentType: 'application/x-www-form-urlencoded',
timeout: 0,
}, globalsNoCallbacks);
const options = Utils.extend({}, defaults, requestOptions);
// Function to run XHR callbacks and events
function fireCallback(callbackName, ...data) {
/*
Callbacks:
beforeCreate (xhr, options),
beforeOpen (xhr, options),
beforeSend (xhr, options),
error (xhr, status),
complete (xhr, stautus),
success (response, status, xhr),
statusCode ()
*/
if (globals[callbackName]) globals[callbackName](...data);
if (options[callbackName]) options[callbackName](...data);
}
// Before create callback
fireCallback('beforeCreate', options);
// For jQuery guys
if (options.type) options.method = options.type;
// Parameters Prefix
let paramsPrefix = options.url.indexOf('?') >= 0 ? '&' : '?';
// UC method
const method = options.method.toUpperCase();
// Data to modify GET URL
if ((method === 'GET' || method === 'HEAD' || method === 'OPTIONS' || method === 'DELETE') && options.data) {
let stringData;
if (typeof options.data === 'string') {
// Should be key=value string
if (options.data.indexOf('?') >= 0) stringData = options.data.split('?')[1];
else stringData = options.data;
} else {
// Should be key=value object
stringData = Utils.serializeObject(options.data);
}
if (stringData.length) {
options.url += paramsPrefix + stringData;
if (paramsPrefix === '?') paramsPrefix = '&';
}
}
// JSONP
if (options.dataType === 'json' && options.url.indexOf('callback=') >= 0) {
const callbackName = `f7jsonp_${Date.now() + ((jsonpRequests += 1))}`;
let abortTimeout;
const callbackSplit = options.url.split('callback=');
let requestUrl = `${callbackSplit[0]}callback=${callbackName}`;
if (callbackSplit[1].indexOf('&') >= 0) {
const addVars = callbackSplit[1].split('&').filter(el => el.indexOf('=') > 0).join('&');
if (addVars.length > 0) requestUrl += `&${addVars}`;
}
// Create script
let script = document.createElement('script');
script.type = 'text/javascript';
script.onerror = function onerror() {
clearTimeout(abortTimeout);
fireCallback('error', null, 'scripterror');
fireCallback('complete', null, 'scripterror');
};
script.src = requestUrl;
// Handler
window[callbackName] = function jsonpCallback(data) {
clearTimeout(abortTimeout);
fireCallback('success', data);
script.parentNode.removeChild(script);
script = null;
delete window[callbackName];
};
document.querySelector('head').appendChild(script);
if (options.timeout > 0) {
abortTimeout = setTimeout(() => {
script.parentNode.removeChild(script);
script = null;
fireCallback('error', null, 'timeout');
}, options.timeout);
}
return undefined;
}
// Cache for GET/HEAD requests
if (method === 'GET' || method === 'HEAD' || method === 'OPTIONS' || method === 'DELETE') {
if (options.cache === false) {
options.url += `${paramsPrefix}_nocache${Date.now()}`;
}
}
// Create XHR
const xhr = new XMLHttpRequest();
// Save Request URL
xhr.requestUrl = options.url;
xhr.requestParameters = options;
// Before open callback
fireCallback('beforeOpen', xhr, options);
// Open XHR
xhr.open(method, options.url, options.async, options.user, options.password);
// Create POST Data
let postData = null;
if ((method === 'POST' || method === 'PUT' || method === 'PATCH') && options.data) {
if (options.processData) {
const postDataInstances = [ArrayBuffer, Blob, Document, FormData];
// Post Data
if (postDataInstances.indexOf(options.data.constructor) >= 0) {
postData = options.data;
} else {
// POST Headers
const boundary = `---------------------------${Date.now().toString(16)}`;
if (options.contentType === 'multipart/form-data') {
xhr.setRequestHeader('Content-Type', `multipart/form-data; boundary=${boundary}`);
} else {
xhr.setRequestHeader('Content-Type', options.contentType);
}
postData = '';
let data = Utils.serializeObject(options.data);
if (options.contentType === 'multipart/form-data') {
data = data.split('&');
const newData = [];
for (let i = 0; i < data.length; i += 1) {
newData.push(`Content-Disposition: form-data; name="${data[i].split('=')[0]}"\r\n\r\n${data[i].split('=')[1]}\r\n`);
}
postData = `--${boundary}\r\n${newData.join(`--${boundary}\r\n`)}--${boundary}--\r\n`;
} else {
postData = data;
}
}
} else {
postData = options.data;
}
}
// Additional headers
if (options.headers) {
Object.keys(options.headers).forEach((headerName) => {
xhr.setRequestHeader(headerName, options[headerName]);
});
}
// Check for crossDomain
if (typeof options.crossDomain === 'undefined') {
// eslint-disable-next-line
options.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(options.url) && RegExp.$2 !== window.location.host;
}
if (!options.crossDomain) {
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
}
if (options.xhrFields) {
Utils.extend(xhr, options.xhrFields);
}
let xhrTimeout;
// Handle XHR
xhr.onload = function onload() {
if (xhrTimeout) clearTimeout(xhrTimeout);
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 0) {
let responseData;
if (options.dataType === 'json') {
try {
responseData = JSON.parse(xhr.responseText);
fireCallback('success', responseData, xhr.status, xhr);
} catch (err) {
fireCallback('error', xhr, 'parseerror');
}
} else {
responseData = xhr.responseType === 'text' || xhr.responseType === '' ? xhr.responseText : xhr.response;
fireCallback('success', responseData, xhr.status, xhr);
}
} else {
fireCallback('error', xhr, xhr.status);
}
if (options.statusCode) {
if (globals.statusCode && globals.statusCode[xhr.status]) globals.statusCode[xhr.status](xhr);
if (options.statusCode[xhr.status]) options.statusCode[xhr.status](xhr);
}
fireCallback('complete', xhr, xhr.status);
};
xhr.onerror = function onerror() {
if (xhrTimeout) clearTimeout(xhrTimeout);
fireCallback('error', xhr, xhr.status);
fireCallback('complete', xhr, 'error');
};
// Timeout
if (options.timeout > 0) {
xhr.onabort = function onabort() {
if (xhrTimeout) clearTimeout(xhrTimeout);
};
xhrTimeout = setTimeout(() => {
xhr.abort();
fireCallback('error', xhr, 'timeout');
fireCallback('complete', xhr, 'timeout');
}, options.timeout);
}
// Ajax start callback
fireCallback('beforeSend', xhr, options);
// Send XHR
xhr.send(postData);
// Return XHR object
return xhr;
}
function RequestShortcut(method, ...args) {
let [url, data, success, error, dataType] = [];
if (typeof args[1] === 'function') {
[url, success, error, dataType] = args;
} else {
[url, data, success, error, dataType] = args;
}
[success, error].forEach((callback) => {
if (typeof callback === 'string') {
dataType = callback;
if (callback === success) success = undefined;
else error = undefined;
}
});
dataType = dataType || (method === 'json' ? 'json' : undefined);
return Request({
url,
method: method === 'post' ? 'POST' : 'GET',
data,
success,
error,
dataType,
});
}
Request.get = function get(...args) {
return RequestShortcut('get', ...args);
};
Request.post = function post(...args) {
return RequestShortcut('post', ...args);
};
Request.json = function json(...args) {
return RequestShortcut('json', ...args);
};
Request.setup = function setup(options) {
if (options.type && !options.method) {
Utils.extend(options, { method: options.type });
}
Utils.extend(globals, options);
};
export default Request;