(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 = ['<', '>', '&', '"', ''']; | |
entitychars.forEach(function(character, index) { | |
string = string.replace(character, entities[index]); | |
}); | |
return string; | |
}; | |
window.ajax = ajax; | |
})(); |