blob: 2f3d4a5fd5d93b9d8beaec2473aeae83a084dea2 [file] [log] [blame]
(function(){
/**
* ajax is a encapsulated function that used to send data to server
* asynchronously. It uses XMLHttpRequest object to send textual or binary
* data through HTTP method GET, POST etc. It can custom request method,
* request header. Response can be parsed automatically by MIME type of
* response's Content-type, and it can handle success, error or progress event
* in course of sending request and retrieving response.
* @param {Object} option
*/
function ajax(option) {
if (arguments.length < 1 || option.constructor != Object)
throw new Error('Bad parameter.');
var url = option.url;
var success = option.success;
var complete = option.complete;
if (!url || !(success || complete))
throw new Error('Parameter url and success or complete are required.');
var parameters = option.parameters || {};
var method = option.method || 'GET';
var status = option.status;
var headers = option.headers || {};
var data = option.data || null;
var multipartData = option.multipartData;
var queryString = constructQueryString(parameters);
if (multipartData) {
var boundary = multipartData.boundary || 'XMLHttpRequest2';
method = 'POST';
var multipartDataString;
var contentType = headers['Content-Type'] || 'multipart/form-data';
if (contentType.indexOf('multipart/form-data') == 0) {
headers['Content-Type'] = 'multipart/form-data; boundary=' + boundary;
multipartDataString = constructMultipartFormData(multipartData, boundary,
parameters);
} else if (contentType.indexOf('multipart/related') == 0) {
headers['Content-Type'] = 'multipart/related; boundary=' + boundary;
multipartDataString = constructMultipartRelatedData(boundary,
multipartData.dataList);
}
data = constructBufferData(multipartDataString);
} else {
if (queryString)
url += '?' + queryString;
}
var xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
var statusCode = xhr.status;
var parsedResponse = parseResponse(xhr);
if (complete)
complete(statusCode, parsedResponse);
if (success && (statusCode == 200 || statusCode == 304)) {
success(parsedResponse);
} else if (status) {
if (status[statusCode]) {
// Call specified status code handler
status[statusCode](parsedResponse);
} else if (status['others']) {
// Call others status code handler
status['others'](parsedResponse, statusCode);
}
}
}
};
// Handle request progress
var progress = option.progress;
if (progress) {
xhr.upload.addEventListener('progress', function(e) {
// lengthComputable return true when the length of the progress is known
if (e.lengthComputable) {
progress(e.loaded, e.total);
}
}, false);
}
// Set request header
for (var headerKey in headers) {
xhr.setRequestHeader(headerKey, headers[headerKey]);
}
xhr.send(data);
}
function constructQueryString(parameters) {
var tmpParameter = [];
for(var name in parameters) {
var value = parameters[name];
if (value.constructor == Array) {
value.forEach(function(val) {
tmpParameter.push(name + '=' + val);
});
} else {
tmpParameter.push(name + '=' + value);
}
}
return tmpParameter.join('&');
}
// Parse response data according to content type of response
function parseResponse(xhr) {
var ct = xhr.getResponseHeader("content-type");
if (typeof ct == 'string') {
if (ct.indexOf('xml') >= 0)
return xhr.responseXML;
else if (ct.indexOf('json') >= 0)
return JSON.parse(xhr.responseText);
}
return xhr.responseText;
}
/**
* Construct multipart/form-data formatted data string.
* @param {Object} binaryData binary data
* @param {String} boundary boundary of parts
* @param {Object} otherParameters other text parameters
*/
function constructMultipartFormData(binaryData, boundary, otherParameters) {
var commonHeader = 'Content-Disposition: form-data; ';
var data = [];
for (var key in otherParameters) {
// Add boundary of one header part
data.push('--' + boundary + '\r\n');
// Add same Content-Disposition information
data.push(commonHeader);
data.push('name="' + key + '"\r\n\r\n' + otherParameters[key] + '\r\n');
}
// Construct file data header
data.push('--' + boundary + '\r\n');
data.push(commonHeader);
data.push('name="' + (binaryData.name || 'binaryfilename') + '"; ');
data.push('filename=\"' + binaryData.value + '\"\r\n');
data.push('Content-type: ' + binaryData.type + '\r\n\r\n');
data.push(binaryData.data + '\r\n');
data.push('--' + boundary + '--\r\n');
return data.join('');
}
function constructBufferData(dataString, contentType) {
var len = dataString.length;
// Create a 8-bit unsigned integer ArrayBuffer view
var data = new Uint8Array(len);
for (var i = 0; i < len; i++) {
data[i] = dataString.charCodeAt(i);
}
return data.buffer
}
function constructMultipartRelatedData(boundary, dataList) {
var result = [];
dataList.forEach(function(data) {
result.push('--' + boundary + '\r\n');
result.push('Content-Type: ' + data.contentType + '\r\n\r\n');
result.push(data.data + '\r\n');
});
result.push('--' + boundary + '--\r\n');
return result.join('');
}
ajax.encodeForBinary = function(string) {
string = encodeURI(string).replace(/%([A-Z0-9]{2})/g, '%u00$1');
return unescape(string);
};
ajax.convertEntityString = function(string) {
var entitychars = ['<', '>', '&', '"', '\''];
var entities = ['&lt;', '&gt;', '&amp;', '&quot;', '&apos;'];
entitychars.forEach(function(character, index) {
string = string.replace(character, entities[index]);
});
return string;
};
window.ajax = ajax;
})();