Updating minified JavaScript files
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / WoltLabSuite / Core / Ajax / Request.js
CommitLineData
8737e1cc
AE
1/**
2 * Versatile AJAX request handling.
3 *
4 * In case you want to issue JSONP requests, please use `AjaxJsonp` instead.
5 *
6 * @author Alexander Ebert
50d96bd8 7 * @copyright 2001-2017 WoltLab GmbH
8737e1cc 8 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
58d7e8f8 9 * @module WoltLabSuite/Core/Ajax/Request
8737e1cc 10 */
58d7e8f8 11define(['Core', 'Language', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Dialog', 'WoltLabSuite/Core/Ajax/Status'], function(Core, Language, DomChangeListener, DomUtil, UiDialog, AjaxStatus) {
28dfae01
AE
12 "use strict";
13
14 var _didInit = false;
15 var _ignoreAllErrors = false;
16
8737e1cc
AE
17 /**
18 * @constructor
19 */
28dfae01
AE
20 function AjaxRequest(options) {
21 this._data = null;
22 this._options = {};
23 this._previousXhr = null;
24 this._xhr = null;
25
26 this._init(options);
f27e5de2 27 }
28dfae01
AE
28 AjaxRequest.prototype = {
29 /**
30 * Initializes the request options.
31 *
f27e5de2 32 * @param {Object} options request options
28dfae01
AE
33 */
34 _init: function(options) {
35 this._options = Core.extend({
36 // request data
37 data: {},
2fb800af 38 contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
4a179dfa 39 responseType: 'application/json',
28dfae01
AE
40 type: 'POST',
41 url: '',
bb282d8b 42 withCredentials: false,
28dfae01
AE
43
44 // behavior
45 autoAbort: false,
46 ignoreError: false,
47 pinData: false,
48 silent: false,
49
50 // callbacks
51 failure: null,
52 finalize: null,
53 success: null,
500102c2
MS
54 progress: null,
55 uploadProgress: null,
28dfae01
AE
56
57 callbackObject: null
58 }, options);
59
15a48bb3
AE
60 if (typeof options.callbackObject === 'object') {
61 this._options.callbackObject = options.callbackObject;
62 }
63
28dfae01 64 this._options.url = Core.convertLegacyUrl(this._options.url);
ca5140b5 65 if (this._options.url.indexOf('index.php') === 0) {
8186015c 66 this._options.url = WSC_API_URL + this._options.url;
ca5140b5 67 }
28dfae01 68
5a4c5344
AE
69 if (this._options.url.indexOf(WSC_API_URL) === 0) {
70 // allows allow credentials when querying the very own server
71 this._options.withCredentials = true;
72 }
73
28dfae01
AE
74 if (this._options.pinData) {
75 this._data = Core.extend({}, this._options.data);
76 }
77
78 if (this._options.callbackObject !== null) {
b5a32d79
AE
79 if (typeof this._options.callbackObject._ajaxFailure === 'function') this._options.failure = this._options.callbackObject._ajaxFailure.bind(this._options.callbackObject);
80 if (typeof this._options.callbackObject._ajaxFinalize === 'function') this._options.finalize = this._options.callbackObject._ajaxFinalize.bind(this._options.callbackObject);
81 if (typeof this._options.callbackObject._ajaxSuccess === 'function') this._options.success = this._options.callbackObject._ajaxSuccess.bind(this._options.callbackObject);
500102c2
MS
82 if (typeof this._options.callbackObject._ajaxProgress === 'function') this._options.progress = this._options.callbackObject._ajaxProgress.bind(this._options.callbackObject);
83 if (typeof this._options.callbackObject._ajaxUploadProgress === 'function') this._options.uploadProgress = this._options.callbackObject._ajaxUploadProgress.bind(this._options.callbackObject);
28dfae01
AE
84 }
85
86 if (_didInit === false) {
87 _didInit = true;
88
89 window.addEventListener('beforeunload', function() { _ignoreAllErrors = true; });
90 }
91 },
92
93 /**
94 * Dispatches a request, optionally aborting a currently active request.
95 *
96 * @param {boolean} abortPrevious abort currently active request
97 */
98 sendRequest: function(abortPrevious) {
99 if (abortPrevious === true || this._options.autoAbort) {
100 this.abortPrevious();
101 }
102
103 if (!this._options.silent) {
104 AjaxStatus.show();
105 }
106
107 if (this._xhr instanceof XMLHttpRequest) {
108 this._previousXhr = this._xhr;
109 }
110
111 this._xhr = new XMLHttpRequest();
112 this._xhr.open(this._options.type, this._options.url, true);
2fb800af
MS
113 if (this._options.contentType) {
114 this._xhr.setRequestHeader('Content-Type', this._options.contentType);
115 }
28dfae01 116 this._xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
bb282d8b
TD
117 if (this._options.withCredentials) {
118 this._xhr.withCredentials = true;
119 }
28dfae01
AE
120
121 var self = this;
b5a32d79 122 var options = Core.clone(this._options);
28dfae01
AE
123 this._xhr.onload = function() {
124 if (this.readyState === XMLHttpRequest.DONE) {
125 if (this.status >= 200 && this.status < 300 || this.status === 304) {
0e5544d3 126 if (options.responseType && this.getResponseHeader('Content-Type').indexOf(options.responseType) !== 0) {
4a179dfa
AE
127 // request succeeded but invalid response type
128 self._failure(this, options);
129 }
130 else {
131 self._success(this, options);
132 }
28dfae01
AE
133 }
134 else {
b5a32d79 135 self._failure(this, options);
28dfae01
AE
136 }
137 }
138 };
139 this._xhr.onerror = function() {
b5a32d79 140 self._failure(this, options);
28dfae01
AE
141 };
142
500102c2
MS
143 if (this._options.progress) {
144 this._xhr.onprogress = this._options.progress;
145 }
146 if (this._options.uploadProgress) {
147 this._xhr.upload.onprogress = this._options.uploadProgress;
148 }
149
28dfae01
AE
150 if (this._options.type === 'POST') {
151 var data = this._options.data;
f8270d7c 152 if (typeof data === 'object' && Core.getType(data) !== 'FormData') {
28dfae01
AE
153 data = Core.serialize(data);
154 }
155
156 this._xhr.send(data);
157 }
158 else {
159 this._xhr.send();
160 }
161 },
162
163 /**
164 * Aborts a previous request.
165 */
166 abortPrevious: function() {
167 if (this._previousXhr === null) {
168 return;
169 }
170
171 this._previousXhr.abort();
172 this._previousXhr = null;
173
174 if (!this._options.silent) {
175 AjaxStatus.hide();
176 }
177 },
178
179 /**
180 * Sets a specific option.
181 *
28dfae01 182 * @param {string} key option name
f27e5de2 183 * @param {?} value option value
28dfae01
AE
184 */
185 setOption: function(key, value) {
186 this._options[key] = value;
187 },
188
b99935c8
AE
189 /**
190 * Returns an option by key or undefined.
191 *
192 * @param {string} key option name
193 * @return {(*|null)} option value or null
194 */
195 getOption: function(key) {
f5336f4f 196 if (objOwns(this._options, key)) {
b99935c8
AE
197 return this._options[key];
198 }
199
200 return null;
201 },
202
28dfae01
AE
203 /**
204 * Sets request data while honoring pinned data from setup callback.
205 *
f27e5de2 206 * @param {Object} data request data
28dfae01
AE
207 */
208 setData: function(data) {
f8270d7c 209 if (this._data !== null && Core.getType(data) !== 'FormData') {
28dfae01
AE
210 data = Core.extend(this._data, data);
211 }
212
213 this._options.data = data;
214 },
215
216 /**
217 * Handles a successful request.
218 *
b5a32d79 219 * @param {XMLHttpRequest} xhr request object
f27e5de2 220 * @param {Object} options request options
28dfae01 221 */
b5a32d79
AE
222 _success: function(xhr, options) {
223 if (!options.silent) {
28dfae01
AE
224 AjaxStatus.hide();
225 }
226
b5a32d79 227 if (typeof options.success === 'function') {
28dfae01
AE
228 var data = null;
229 if (xhr.getResponseHeader('Content-Type') === 'application/json') {
230 try {
231 data = JSON.parse(xhr.responseText);
232 }
233 catch (e) {
234 // invalid JSON
b99935c8 235 this._failure(xhr, options);
28dfae01
AE
236
237 return;
238 }
239
240 // trim HTML before processing, see http://jquery.com/upgrade-guide/1.9/#jquery-htmlstring-versus-jquery-selectorstring
2e97a69c 241 if (data && data.returnValues && data.returnValues.template !== undefined) {
28dfae01
AE
242 data.returnValues.template = data.returnValues.template.trim();
243 }
244 }
245
b99935c8 246 options.success(data, xhr.responseText, xhr, options.data);
28dfae01
AE
247 }
248
b5a32d79 249 this._finalize(options);
28dfae01
AE
250 },
251
252 /**
253 * Handles failed requests, this can be both a successful request with
254 * a non-success status code or an entirely failed request.
255 *
b5a32d79 256 * @param {XMLHttpRequest} xhr request object
f27e5de2 257 * @param {Object} options request options
28dfae01 258 */
b5a32d79 259 _failure: function (xhr, options) {
28dfae01
AE
260 if (_ignoreAllErrors) {
261 return;
262 }
263
b5a32d79 264 if (!options.silent) {
28dfae01
AE
265 AjaxStatus.hide();
266 }
267
268 var data = null;
269 try {
270 data = JSON.parse(xhr.responseText);
271 }
272 catch (e) {}
273
274 var showError = true;
f27e5de2 275 if (data !== null && typeof options.failure === 'function') {
b99935c8 276 showError = options.failure(data, xhr.responseText, xhr, options.data);
28dfae01
AE
277 }
278
b5a32d79 279 if (options.ignoreError !== true && showError !== false) {
28dfae01
AE
280 var details = '';
281 var message = '';
282
283 if (data !== null) {
227cda57
AE
284 if (data.stacktrace) details = '<br><p>Stacktrace:</p><p>' + data.stacktrace + '</p>';
285 else if (data.exceptionID) details = '<br><p>Exception ID: <code>' + data.exceptionID + '</code></p>';
28dfae01
AE
286
287 message = data.message;
227cda57
AE
288
289 data.previous.forEach(function(previous) {
290 details += '<hr><p>' + previous.message + '</p>';
291 details += '<br><p>Stacktrace</p><p>' + previous.stacktrace + '</p>';
292 });
28dfae01
AE
293 }
294 else {
295 message = xhr.responseText;
296 }
297
298 if (!message || message === 'undefined') {
299 return;
300 }
301
302 var html = '<div class="ajaxDebugMessage"><p>' + message + '</p>' + details + '</div>';
303
eae3eb79 304 if (UiDialog === undefined) UiDialog = require('Ui/Dialog');
9a421cc7 305 UiDialog.openStatic(DomUtil.getUniqueId(), html, {
28dfae01
AE
306 title: Language.get('wcf.global.error.title')
307 });
308 }
309
b5a32d79 310 this._finalize(options);
28dfae01
AE
311 },
312
313 /**
314 * Finalizes a request.
b5a32d79 315 *
f27e5de2 316 * @param {Object} options request options
28dfae01 317 */
b5a32d79
AE
318 _finalize: function(options) {
319 if (typeof options.finalize === 'function') {
320 options.finalize(this._xhr);
28dfae01
AE
321 }
322
323 this._previousXhr = null;
324
9a421cc7 325 DomChangeListener.trigger();
28dfae01
AE
326
327 // fix anchor tags generated through WCF::getAnchor()
d0023381 328 var links = elBySelAll('a[href*="#"]');
28dfae01
AE
329 for (var i = 0, length = links.length; i < length; i++) {
330 var link = links[i];
d0023381 331 var href = elAttr(link, 'href');
28dfae01
AE
332 if (href.indexOf('AJAXProxy') !== -1 || href.indexOf('ajax-proxy') !== -1) {
333 href = href.substr(href.indexOf('#'));
d0023381 334 elAttr(link, 'href', document.location.toString().replace(/#.*/, '') + href);
28dfae01
AE
335 }
336 }
337 }
338 };
339
340 return AjaxRequest;
341});