From 827a69f3bc825a36ab44eed80cb5ed204ae2c733 Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Tue, 19 May 2015 15:39:45 +0200 Subject: [PATCH] Added an own Ajax implementation --- wcfsetup/install/files/js/WoltLab/WCF/Ajax.js | 254 ++++++++++++++++++ .../files/js/WoltLab/WCF/BootstrapFrontend.js | 5 +- wcfsetup/install/files/js/WoltLab/WCF/Core.js | 71 ++++- wcfsetup/install/files/js/require.config.js | 1 + 4 files changed, 327 insertions(+), 4 deletions(-) create mode 100644 wcfsetup/install/files/js/WoltLab/WCF/Ajax.js diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ajax.js b/wcfsetup/install/files/js/WoltLab/WCF/Ajax.js new file mode 100644 index 0000000000..903fe13778 --- /dev/null +++ b/wcfsetup/install/files/js/WoltLab/WCF/Ajax.js @@ -0,0 +1,254 @@ +/** + * Handles AJAX requests. + * + * @author Alexander Ebert + * @copyright 2001-2015 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLab/WCF/Ajax + */ +define(['Core', 'Language', 'DOM/Util', 'UI/Dialog'], function(Core, Language, DOMUtil, UIDialog) { + "use strict"; + + var _didInit = false; + var _ignoreAllErrors = false; + + /** + * @constructor + */ + function Ajax(options) { + this._options = {}; + this._previousXhr = null; + this._xhr = null; + + this._init(options); + }; + Ajax.prototype = { + /** + * Initializes the request options. + * + * @param {object} options request options + */ + _init: function(options) { + this._options = Core.extend({ + // request data + data: {}, + type: 'POST', + url: '', + + // behavior + autoAbort: false, + ignoreError: false, + silent: false, + + // callbacks + failure: null, + finalize: null, + success: null + }, options); + + this._options.url = Core.convertLegacyUrl(this._options.url); + + if (_didInit === false) { + _didInit = true; + + window.addEventListener('beforeunload', function() { _ignoreAllErrors = true; }); + } + }, + + /** + * Dispatches a request, optionally aborting a currently active request. + * + * @param {boolean} abortPrevious abort currently active request + */ + sendRequest: function(abortPrevious) { + if (abortPrevious === true || this._options.autoAbort) { + this.abortPrevious(); + } + + if (!this._options.silent) { + WCF.LoadingOverlayHandler.show(); + } + + this._xhr = new XMLHttpRequest(); + this._xhr.open(this._options.type, this._options.url, true); + this._xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); + this._xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + + var self = this; + this._xhr.onload = function() { + if (this.readyState === XMLHttpRequest.DONE) { + if (this.stauts >= 200 && this.status < 300 || this.status === 304) { + self._success(this); + } + else { + self._failure(this); + } + } + }; + this._xhr.onerror = function() { + self._failure(this); + } + + if (this._options.type === 'POST') { + var data = this._options.data; + if (typeof data === 'object') { + data = Core.serialize(data); + } + + this._xhr.send(data); + } + else { + this._xhr.send(); + } + }, + + /** + * Aborts a previous request. + */ + abortPrevious: function() { + if (this._previousXhr === null) { + return; + } + + this._previousXhr.abort(); + this._previousXhr = null; + + if (!this._options.silent) { + WCF.LoadingOverlayHandler.hide(); + } + }, + + /** + * Sets a specific option. + * + * Do not call this method, it exists for compatibility with WCF.Action.Proxy + * and will be removed at some point without further notice. + * + * @param {string} key option name + * @param {*} value option value + */ + setOption: function(key, value) { + this._options[key] = value; + }, + + /** + * Handles a successful request. + * + * @param {XMLHttpRequest} xhr request object + */ + _success: function(xhr) { + if (!this._options.silent) { + WCF.LoadingOverlayHandler.hide(); + } + + if (typeof this._options.success === 'function') { + var data = xhr.response; + if (xhr.responseType === 'json') { + // trim HTML before processing, see http://jquery.com/upgrade-guide/1.9/#jquery-htmlstring-versus-jquery-selectorstring + if (data.returnValues !== undefined && data.returnValues.template !== undefined) { + data.returnValues.template = data.returnValues.template.trim(); + } + } + + this._options.success(data, xhr.responseText, xhr); + } + + this._finalize(); + }, + + /** + * Handles failed requests, this can be both a successful request with + * a non-success status code or an entirely failed request. + * + * @param {XMLHttpRequest} xhr request object + */ + _failure: function (xhr) { + if (_ignoreAllErrors) { + return; + } + + if (!this._options.silent) { + WCF.LoadingOverlayHandler.hide(); + } + + var data = null; + try { + data = JSON.parse(xhr.responseText); + } + catch (e) {} + + var showError = true; + if (typeof this._options.failure === 'function') { + showError = this._options.failure(data, xhr); + } + + if (this._options.ignoreError !== true && showError !== false) { + var details = ''; + var message = ''; + + if (data !== null) { + if (data.stacktrace) details = '

Stacktrace:

' + data.stacktrace + '

'; + else if (data.exceptionID) details = '

Exception ID: ' + data.exceptionID + '

'; + + message = data.message; + } + else { + message = xhr.responseText; + } + + if (!message || message === 'undefined') { + return; + } + + var html = '

' + message + '

' + details + '
'; + + UIDialog.open(DOMUtil.getUniqueId(), message, { + title: Language.get('wcf.global.error.title') + }); + } + + this._finalize(); + }, + + /** + * Finalizes a request. + */ + _finalize: function() { + if (typeof this._options.finalize === 'function') { + this._options.finalize(this._xhr); + } + + this._previousXhr = null; + + WCF.DOMNodeInsertedHandler.execute(); + + // fix anchor tags generated through WCF::getAnchor() + var links = document.querySelectorAll('a[href*="#"]'); + for (var i = 0, length = links.length; i < length; i++) { + var link = links[i]; + var href = link.getAttribute('href'); + if (href.indexOf('AJAXProxy') !== -1 || href.indexOf('ajax-proxy') !== -1) { + href = href.substr(href.indexOf('#')); + link.setAttribute('href', document.location.toString().replace(/#.*/, '') + href); + } + } + } + }; + + /** + * Shorthand function to perform a request agains the WCF-API. + * + * @param {object} options request options + * @return {Ajax} + */ + Ajax.api = function(options) { + if (!options.url) options.url = 'index.php/AJAXProxy/?t=' + SECURITY_TOKEN + SID_ARG_2ND; + + var obj = new Ajax(options); + obj.sendRequest(); + + return obj; + }; + + return Ajax; +}); diff --git a/wcfsetup/install/files/js/WoltLab/WCF/BootstrapFrontend.js b/wcfsetup/install/files/js/WoltLab/WCF/BootstrapFrontend.js index 11762f26db..ad046fd663 100644 --- a/wcfsetup/install/files/js/WoltLab/WCF/BootstrapFrontend.js +++ b/wcfsetup/install/files/js/WoltLab/WCF/BootstrapFrontend.js @@ -6,7 +6,7 @@ * @license GNU Lesser General Public License * @module WoltLab/WCF/BootstrapFrontend */ -define(['WoltLab/WCF/Bootstrap', 'WoltLab/WCF/Controller/Sitemap', 'WoltLab/WCF/Controller/Style/Changer', 'WoltLab/WCF/Controller/Popover'], function(Bootstrap, ControllerSitemap, ControllerStyleChanger, ControllerPopover) { +define(['Ajax', 'WoltLab/WCF/Bootstrap', 'WoltLab/WCF/Controller/Sitemap', 'WoltLab/WCF/Controller/Style/Changer', 'WoltLab/WCF/Controller/Popover'], function(Ajax, Bootstrap, ControllerSitemap, ControllerStyleChanger, ControllerPopover) { "use strict"; /** @@ -40,8 +40,7 @@ define(['WoltLab/WCF/Bootstrap', 'WoltLab/WCF/Controller/Sitemap', 'WoltLab/WCF/ className: 'userLink', identifier: 'com.woltlab.wcf.user', loadCallback: function(objectId, popover) { - new WCF.Action.Proxy({ - autoSend: true, + Ajax.api({ data: { actionName: 'getUserProfile', className: 'wcf\\data\\user\\UserProfileAction', diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Core.js b/wcfsetup/install/files/js/WoltLab/WCF/Core.js index 2fb807aa83..7f78bb4255 100644 --- a/wcfsetup/install/files/js/WoltLab/WCF/Core.js +++ b/wcfsetup/install/files/js/WoltLab/WCF/Core.js @@ -14,6 +14,32 @@ define([], function() { */ function Core() {}; Core.prototype = { + /** + * Converts WCF 2.0-style URLs into the default URL layout. + * + * @param string url target url + * @return rewritten url + */ + convertLegacyUrl: function(url) { + if (URL_LEGACY_MODE) { + return url; + } + + return url.replace(/^index\.php\/(.*?)\/\?/, function(match, controller) { + var $parts = controller.split(/([A-Z][a-z0-9]+)/); + var $controller = ''; + for (var $i = 0, $length = $parts.length; $i < $length; $i++) { + var $part = $parts[$i].trim(); + if ($part.length) { + if ($controller.length) $controller += '-'; + $controller += $part.toLowerCase(); + } + } + + return 'index.php?' + $controller + '/&'; + }); + }, + /** * Merges objects with the first argument. * @@ -31,7 +57,7 @@ define([], function() { for (var key in obj) { if (obj.hasOwnProperty(key)) { - if (typeof obj[key] === 'object') { + if (!Array.isArray(obj[key]) && typeof obj[key] === 'object') { this.extend(out[key], obj[key]); } else { @@ -44,6 +70,49 @@ define([], function() { return out; }, + /** + * Returns the object's class name. + * + * @param {object} obj target object + * @return {string} object class name + */ + getType: function(obj) { + return Object.prototype.toString.call(obj).replace(/^\[object (.+)\]$/, '$1'); + }, + + /** + * Recursively serializes an object into an encoded URI parameter string. + * + * @param {object} obj target object + * @return encoded parameter string + */ + serialize: function(obj) { + var parameters = []; + + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + var value = obj[key]; + + if (Array.isArray(value)) { + for (var i = 0, length = value.length; i < length; i++) { + parameters.push(key + '[]=' + encodeURIComponent(value[i])); + } + + continue; + } + else if (this.getType(value) === 'Object') { + parameters.push(this.serialize(value)); + + continue; + } + + parameters.push(key + '=' + encodeURIComponent(value)); + } + } + + return parameters.join('&'); + }, + triggerEvent: function(el, eventName) { var ev; if (document.createEvent) { diff --git a/wcfsetup/install/files/js/require.config.js b/wcfsetup/install/files/js/require.config.js index 8673fe1056..5dcfd82a4b 100644 --- a/wcfsetup/install/files/js/require.config.js +++ b/wcfsetup/install/files/js/require.config.js @@ -5,6 +5,7 @@ requirejs.config({ }, map: { '*': { + 'Ajax': 'WoltLab/WCF/Ajax', 'CallbackList': 'WoltLab/WCF/CallbackList', 'Core': 'WoltLab/WCF/Core', 'Dictionary': 'WoltLab/WCF/Dictionary', -- 2.20.1