Added an own Ajax implementation
authorAlexander Ebert <ebert@woltlab.com>
Tue, 19 May 2015 13:39:45 +0000 (15:39 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Tue, 19 May 2015 13:39:45 +0000 (15:39 +0200)
wcfsetup/install/files/js/WoltLab/WCF/Ajax.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLab/WCF/BootstrapFrontend.js
wcfsetup/install/files/js/WoltLab/WCF/Core.js
wcfsetup/install/files/js/require.config.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 (file)
index 0000000..903fe13
--- /dev/null
@@ -0,0 +1,254 @@
+/**
+ * Handles AJAX requests.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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<string, *>}     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 = '<br /><p>Stacktrace:</p><p>' + data.stacktrace + '</p>';
+                                       else if (data.exceptionID) details = '<br /><p>Exception ID: <code>' + data.exceptionID + '</code></p>';
+                                       
+                                       message = data.message;
+                               }
+                               else {
+                                       message = xhr.responseText;
+                               }
+                               
+                               if (!message || message === 'undefined') {
+                                       return;
+                               }
+                               
+                               var html = '<div class="ajaxDebugMessage"><p>' + message + '</p>' + details + '</div>';
+                               
+                               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<string, *>}     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;
+});
index 11762f26db62d13cd574d76c6762f3703ac36260..ad046fd6631e76170474c9a6cbeceaa72554c699 100644 (file)
@@ -6,7 +6,7 @@
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @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',
index 2fb807aa8306ceada0451bd4f5757f03347cf7da..7f78bb42551c53d30a9d55e286ef7e4b3c04665b 100644 (file)
@@ -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) {
index 8673fe10561a29854e78187cc20ca2ad47b3581f..5dcfd82a4b2adb640be864c82d26a11f68de7fb4 100644 (file)
@@ -5,6 +5,7 @@ requirejs.config({
        },
        map: {
                '*': {
+                       'Ajax': 'WoltLab/WCF/Ajax',
                        'CallbackList': 'WoltLab/WCF/CallbackList',
                        'Core': 'WoltLab/WCF/Core',
                        'Dictionary': 'WoltLab/WCF/Dictionary',