blob: 2f3d4a5fd5d93b9d8beaec2473aeae83a084dea2 [file] [log] [blame]
avm9996304def3e2016-11-27 22:53:05 +01001(function(){
2 /**
3 * ajax is a encapsulated function that used to send data to server
4 * asynchronously. It uses XMLHttpRequest object to send textual or binary
5 * data through HTTP method GET, POST etc. It can custom request method,
6 * request header. Response can be parsed automatically by MIME type of
7 * response's Content-type, and it can handle success, error or progress event
8 * in course of sending request and retrieving response.
9 * @param {Object} option
10 */
11 function ajax(option) {
12 if (arguments.length < 1 || option.constructor != Object)
13 throw new Error('Bad parameter.');
14 var url = option.url;
15 var success = option.success;
16 var complete = option.complete;
17 if (!url || !(success || complete))
18 throw new Error('Parameter url and success or complete are required.');
19
20 var parameters = option.parameters || {};
21 var method = option.method || 'GET';
22 var status = option.status;
23 var headers = option.headers || {};
24 var data = option.data || null;
25 var multipartData = option.multipartData;
26 var queryString = constructQueryString(parameters);
27
28 if (multipartData) {
29 var boundary = multipartData.boundary || 'XMLHttpRequest2';
30 method = 'POST';
31 var multipartDataString;
32 var contentType = headers['Content-Type'] || 'multipart/form-data';
33 if (contentType.indexOf('multipart/form-data') == 0) {
34 headers['Content-Type'] = 'multipart/form-data; boundary=' + boundary;
35 multipartDataString = constructMultipartFormData(multipartData, boundary,
36 parameters);
37 } else if (contentType.indexOf('multipart/related') == 0) {
38 headers['Content-Type'] = 'multipart/related; boundary=' + boundary;
39 multipartDataString = constructMultipartRelatedData(boundary,
40 multipartData.dataList);
41 }
42
43 data = constructBufferData(multipartDataString);
44 } else {
45 if (queryString)
46 url += '?' + queryString;
47 }
48
49 var xhr = new XMLHttpRequest();
50 xhr.open(method, url, true);
51 xhr.onreadystatechange = function() {
52 if (xhr.readyState == 4) {
53 var statusCode = xhr.status;
54 var parsedResponse = parseResponse(xhr);
55 if (complete)
56 complete(statusCode, parsedResponse);
57 if (success && (statusCode == 200 || statusCode == 304)) {
58 success(parsedResponse);
59 } else if (status) {
60 if (status[statusCode]) {
61 // Call specified status code handler
62 status[statusCode](parsedResponse);
63 } else if (status['others']) {
64 // Call others status code handler
65 status['others'](parsedResponse, statusCode);
66 }
67 }
68 }
69 };
70
71 // Handle request progress
72 var progress = option.progress;
73 if (progress) {
74 xhr.upload.addEventListener('progress', function(e) {
75 // lengthComputable return true when the length of the progress is known
76 if (e.lengthComputable) {
77 progress(e.loaded, e.total);
78 }
79 }, false);
80 }
81 // Set request header
82 for (var headerKey in headers) {
83 xhr.setRequestHeader(headerKey, headers[headerKey]);
84 }
85
86 xhr.send(data);
87 }
88
89 function constructQueryString(parameters) {
90 var tmpParameter = [];
91 for(var name in parameters) {
92 var value = parameters[name];
93 if (value.constructor == Array) {
94 value.forEach(function(val) {
95 tmpParameter.push(name + '=' + val);
96 });
97 } else {
98 tmpParameter.push(name + '=' + value);
99 }
100 }
101 return tmpParameter.join('&');
102 }
103
104 // Parse response data according to content type of response
105 function parseResponse(xhr) {
106 var ct = xhr.getResponseHeader("content-type");
107 if (typeof ct == 'string') {
108 if (ct.indexOf('xml') >= 0)
109 return xhr.responseXML;
110 else if (ct.indexOf('json') >= 0)
111 return JSON.parse(xhr.responseText);
112 }
113 return xhr.responseText;
114 }
115
116 /**
117 * Construct multipart/form-data formatted data string.
118 * @param {Object} binaryData binary data
119 * @param {String} boundary boundary of parts
120 * @param {Object} otherParameters other text parameters
121 */
122 function constructMultipartFormData(binaryData, boundary, otherParameters) {
123 var commonHeader = 'Content-Disposition: form-data; ';
124 var data = [];
125 for (var key in otherParameters) {
126
127 // Add boundary of one header part
128 data.push('--' + boundary + '\r\n');
129
130 // Add same Content-Disposition information
131 data.push(commonHeader);
132 data.push('name="' + key + '"\r\n\r\n' + otherParameters[key] + '\r\n');
133 }
134
135 // Construct file data header
136 data.push('--' + boundary + '\r\n');
137 data.push(commonHeader);
138
139 data.push('name="' + (binaryData.name || 'binaryfilename') + '"; ');
140 data.push('filename=\"' + binaryData.value + '\"\r\n');
141 data.push('Content-type: ' + binaryData.type + '\r\n\r\n');
142 data.push(binaryData.data + '\r\n');
143
144 data.push('--' + boundary + '--\r\n');
145 return data.join('');
146 }
147
148 function constructBufferData(dataString, contentType) {
149 var len = dataString.length;
150
151 // Create a 8-bit unsigned integer ArrayBuffer view
152 var data = new Uint8Array(len);
153 for (var i = 0; i < len; i++) {
154 data[i] = dataString.charCodeAt(i);
155 }
156
157 return data.buffer
158 }
159
160 function constructMultipartRelatedData(boundary, dataList) {
161 var result = [];
162 dataList.forEach(function(data) {
163 result.push('--' + boundary + '\r\n');
164 result.push('Content-Type: ' + data.contentType + '\r\n\r\n');
165 result.push(data.data + '\r\n');
166 });
167 result.push('--' + boundary + '--\r\n');
168 return result.join('');
169 }
170
171 ajax.encodeForBinary = function(string) {
172 string = encodeURI(string).replace(/%([A-Z0-9]{2})/g, '%u00$1');
173 return unescape(string);
174 };
175
176 ajax.convertEntityString = function(string) {
177 var entitychars = ['<', '>', '&', '"', '\''];
178 var entities = ['&lt;', '&gt;', '&amp;', '&quot;', '&apos;'];
179 entitychars.forEach(function(character, index) {
180 string = string.replace(character, entities[index]);
181 });
182 return string;
183 };
184 window.ajax = ajax;
185})();