Modified overlays
authorAlexander Ebert <ebert@woltlab.com>
Wed, 21 Sep 2011 16:50:13 +0000 (18:50 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Wed, 21 Sep 2011 16:50:13 +0000 (18:50 +0200)
The 'loading …'-overlay now stays for at least one second to provide a smoother behavior. Additionally implemented a global event handler 'WCF.CloseOverlayHandler' to provide a consistent handler to close overlays once they've lost focus (click event bubbled to body-tag).

wcfsetup/install/files/acp/style/testing.css
wcfsetup/install/files/js/WCF.js

index 903f5c2d5d2f9f9e955ffd80314e20aef6241283..749dbe7b241f5087988374530a16a55b77137678 100644 (file)
@@ -2249,7 +2249,13 @@ div#ajaxExceptionStacktrace {
        padding: 7px 21px 7px;
        position: fixed;
        top: 0;
-       left: 43%;
+       right: 20px;
+       z-index: 1000;
+}
+
+#actionProxyLoading img {
+       height: 24px;
+       width: 24px;
 }
 
 div.wcfDimensions {
index 69baf8245f2163c7e98916737e63367fdb9602ba..cdeab028c4c6f32c2546472568515b8a4b5517cb 100644 (file)
@@ -198,6 +198,20 @@ $.fn.extend({
        enable: function() {
                return this.removeAttr('disabled');
        },
+
+       /**
+        * Returns the element's id. If none is set, a random unique
+        * ID will be assigned.
+        * 
+        * @return      string
+        */
+       wcfIdentify: function() {
+               if (!this.attr('id')) {
+                       this.attr('id', WCF.getRandomID());
+               }
+
+               return this.attr('id');
+       },
        
        /**
         * Applies a grow-effect by resizing element while moving the
@@ -684,6 +698,9 @@ WCF.Clipboard = {
                                $containers[$typeName] = $container;
                        }
                        
+                       var $containerID = $container.wcfIdentify();
+                       WCF.CloseOverlayHandler.removeCallback($containerID);
+
                        $container.empty();
                });
                
@@ -719,8 +736,23 @@ WCF.Clipboard = {
                                // bind event
                                $listItem.click($.proxy(this._executeAction, this));
                        }
+
+                       // block click event
+                       $container.click(function(event) {
+                               event.stopPropagation();
+                       });
+
+                       // register event handler
+                       var $containerID = $container.wcfIdentify();
+                       WCF.CloseOverlayHandler.addCallback($containerID, $.proxy(this._closeLists, this));
                }
        },
+
+       _closeLists: function() {
+               $('.clipboardEditor ul ol').each(function(index, list) {
+                       $(this).hide();
+               });
+       },
        
        /**
         * Executes a clipboard editor item action.
@@ -809,6 +841,30 @@ WCF.Action = {};
  */
 WCF.Action.Proxy = function(options) { this.init(options); };
 WCF.Action.Proxy.prototype = {
+       /**
+        * count of active requests
+        * @var integer
+        */
+       _activeRequests: 0,
+
+       /**
+        * loading overlay
+        * @var jQuery
+        */
+       _loadingOverlay: null,
+
+       /**
+        * loading overlay state
+        * @var boolean
+        */
+       _loadingOverlayVisible: false,
+
+       /**
+        * timer for overlay activity
+        * @var integer
+        */
+       _loadingOverlayVisibleTimer: 0,
+
        /**
         * Initializes AJAXProxy.
         * 
@@ -860,9 +916,43 @@ WCF.Action.Proxy.prototype = {
                        this.options.init(this);
                }
                
-               $('<div id="actionProxyLoading" class="actionProxyLoading" style="display: none;">'+WCF.Language.get('wcf.global.loading')+'</div>').appendTo($('body'));
-               this.loading = $('#actionProxyLoading');
-               this.loading.wcfDropIn();
+               this._activeRequests++;
+               this._showLoadingOverlay();
+       },
+
+       /**
+        * Displays the loading overlay if not already visible due to an active request.
+        */
+       _showLoadingOverlay: function() {
+               // create loading overlay on first run
+               if (this._loadingOverlay === null) {
+                       this._loadingOverlay = $('<div id="actionProxyLoading" class="actionProxyLoading"><img src="' + RELATIVE_WCF_DIR + 'icon/spinner.svg" alt="" />' + WCF.Language.get('wcf.global.loading') + '</div>').hide().appendTo($('body'));
+               }
+
+               // fade in overlay
+               if (!this._loadingOverlayVisible) {
+                       this._loadingOverlayVisible = true;
+                       this._loadingOverlay.stop(true, true).fadeIn(200, $.proxy(function() {
+                               new WCF.PeriodicalExecuter($.proxy(this._hideLoadingOverlay, this), 100);
+                       }, this));
+               }
+       },
+
+       /**
+        * Hides loading overlay if no requests are active and the timer reached at least 1 second.
+        * 
+        * @param       object          pe
+        */
+       _hideLoadingOverlay: function(pe) {
+               this._loadingOverlayVisibleTimer += 100;
+               
+               if (this._activeRequests == 0 && this._loadingOverlayVisibleTimer >= 1000) {
+                       this._loadingOverlayVisible = false;
+                       this._loadingOverlayVisibleTimer = 0;
+                       pe.stop();
+
+                       this._loadingOverlay.fadeOut(200);
+               }
        },
        
        /**
@@ -916,8 +1006,8 @@ WCF.Action.Proxy.prototype = {
                if ($.isFunction(this.options.after)) {
                        this.options.after();
                }
-               
-               this.loading.wcfDropOut('up');
+
+               this._activeRequests--;
        },
        
        /**
@@ -2091,6 +2181,77 @@ WCF.Effect.BalloonTooltip.prototype = {
        }
 };
 
+/**
+ * Handles clicks outside an overlay, hitting body-tag through bubbling.
+ * 
+ * You should always remove callbacks before disposing the attached element,
+ * preventing errors from blocking the iteration. Furthermore you should
+ * always handle clicks on your overlay's container and return 'false' to
+ * prevent bubbling.
+ */
+WCF.CloseOverlayHandler = {
+       /**
+        * list of callbacks
+        * @var WCF.Dictionary
+        */
+       _callbacks: new WCF.Dictionary(),
+
+       /**
+        * indicates that overlay handler is listening to click events on body-tag
+        * @var boolean
+        */
+       _isListening: false,
+
+       /**
+        * Adds a new callback.
+        * 
+        * @param       string          identifier
+        * @param       object          callback
+        */
+       addCallback: function(identifier, callback) {
+               this._bindListener();
+
+               if (this._callbacks.isset(identifier)) {
+                       cosole.debug("[WCF.CloseOverlayHandler] identifier '" + identifier + "' is already bound to a callback");
+                       return false;
+               }
+
+               this._callbacks.add(identifier, callback);
+       },
+
+       /**
+        * Removes a callback from list.
+        * 
+        * @param       string          identifier
+        */
+       removeCallback: function(identifier) {
+               if (this._callbacks.isset(identifier)) {
+                       this._callbacks.remove(identifier);
+               }
+       },
+
+       /**
+        * Binds click event handler.
+        */
+       _bindListener: function() {
+               if (this._isListening) return;
+
+               $('body').click($.proxy(this._executeCallbacks, this));
+
+               this._isListening = true;
+       },
+
+       /**
+        * Executes callbacks on click.
+        */
+       _executeCallbacks: function() {
+               this._callbacks.each(function(pair) {
+                       // execute callback
+                       pair.value();
+               });
+       }
+};
+
 /**
  * Basic implementation for WCF dialogs.
  */