Overhauled popover functionality
authorAlexander Ebert <ebert@woltlab.com>
Tue, 26 Jan 2016 21:56:58 +0000 (22:56 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Tue, 26 Jan 2016 21:56:58 +0000 (22:56 +0100)
wcfsetup/install/files/js/WoltLab/WCF/Controller/Popover.js
wcfsetup/install/files/style/ui/popover.scss

index 06ab96ff0d335806c64e185359b4ce12af19e302..7d7949ea5636dc15a4ecde9b8b57626734dcd4a0 100644 (file)
@@ -10,7 +10,6 @@ define(['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', '
        "use strict";
        
        var _activeId = null;
-       var _baseHeight = 0;
        var _cache = new Dictionary();
        var _elements = new Dictionary();
        var _handlers = new Dictionary();
@@ -21,7 +20,6 @@ define(['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', '
        
        var _popover = null;
        var _popoverContent = null;
-       var _popoverLoading = null;
        
        var _callbackClick = null;
        var _callbackHide = null;
@@ -32,13 +30,13 @@ define(['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', '
        /** @const */ var STATE_LOADING = 1;
        /** @const */ var STATE_READY = 2;
        
-       /** @const */ var DELAY_SHOW = 800;
        /** @const */ var DELAY_HIDE = 500;
+       /** @const */ var DELAY_SHOW = 300;
        
        /**
         * @exports     WoltLab/WCF/Controller/Popover
         */
-       var ControllerPopover = {
+       return {
                /**
                 * Builds popover DOM elements and binds event listeners.
                 */
@@ -59,10 +57,6 @@ define(['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', '
                        pointer.appendChild(elCreate('span'));
                        _popover.appendChild(pointer);
                        
-                       _popoverLoading = elCreate('span');
-                       _popoverLoading.className = 'icon icon32 fa-spinner';
-                       _popover.appendChild(_popoverLoading);
-                       
                        document.body.appendChild(_popover);
                        
                        // static binding for callbacks (they don't change anyway and binding each time is expensive)
@@ -74,13 +68,7 @@ define(['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', '
                        _popover.addEventListener('mouseenter', this._popoverMouseEnter.bind(this));
                        _popover.addEventListener('mouseleave', _callbackMouseLeave);
                        
-                       _popoverContent.addEventListener('transitionend', function(event) {
-                               if (event.propertyName === 'height') {
-                                       _popoverContent.classList.remove('loading');
-                               }
-                       });
-                       
-                       _popover.addEventListener('transitionend', this._clearContent.bind(this));
+                       _popover.addEventListener('animationend', this._clearContent.bind(this));
                        
                        window.addEventListener('beforeunload', (function() {
                                _suspended = true;
@@ -201,7 +189,7 @@ define(['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', '
                 * Sets the content for given identifier and object id.
                 * 
                 * @param       {string}        identifier      handler identifier
-                * @param       {integer}       objectId        object id
+                * @param       {int}           objectId        object id
                 * @param       {string}        content         HTML string
                 */
                setContent: function(identifier, objectId, content) {
@@ -299,9 +287,25 @@ define(['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', '
                                _timeoutLeave = null;
                        }
                        
-                       var disableAnimation = (_activeId !== null && _activeId !== _hoverId);
+                       var forceHide = false;
                        if (_popover.classList.contains('active')) {
-                               this._hide(disableAnimation);
+                               this._hide();
+                               
+                               forceHide = true;
+                       }
+                       else if (_popoverContent.childElementCount) {
+                               forceHide = true;
+                       }
+                       
+                       if (forceHide) {
+                               _popover.classList.add('forceHide');
+                               
+                               // force layout
+                               _popover.offsetTop;
+                               
+                               this._clearContent();
+                               
+                               _popover.classList.remove('forceHide');
                        }
                        
                        _activeId = _hoverId;
@@ -311,14 +315,10 @@ define(['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', '
                        
                        if (data.state === STATE_READY) {
                                _popoverContent.appendChild(data.content);
+                               
+                               this._rebuild(_activeId);
                        }
                        else if (data.state === STATE_NONE) {
-                               _popoverContent.classList.add('loading');
-                       }
-                       
-                       this._rebuild(_activeId);
-                       
-                       if (data.state === STATE_NONE) {
                                data.state = STATE_LOADING;
                                
                                _handlers.get(elData.identifier).loadCallback(elData.objectId, this);
@@ -327,25 +327,14 @@ define(['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', '
                
                /**
                 * Hides the popover element.
-                * 
-                * @param       {(object|boolean)}      event   event object or boolean if popover should be forced hidden
                 */
-               _hide: function(event) {
+               _hide: function() {
                        if (_timeoutLeave !== null) {
                                window.clearTimeout(_timeoutLeave);
                                _timeoutLeave = null;
                        }
                        
                        _popover.classList.remove('active');
-                       
-                       if (typeof event === 'boolean' && event === true) {
-                               _popover.classList.add('disableAnimation');
-                               
-                               // force reflow
-                               _popover.offsetHeight;
-                               
-                               this._clearContent();
-                       }
                },
                
                /**
@@ -357,8 +346,6 @@ define(['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', '
                                while (_popoverContent.childNodes.length) {
                                        activeElData.content.appendChild(_popoverContent.childNodes[0]);
                                }
-                               
-                               _popoverContent.style.removeProperty('height');
                        }
                },
                
@@ -371,27 +358,6 @@ define(['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', '
                        }
                        
                        _popover.classList.add('active');
-                       _popover.classList.remove('disableAnimation');
-                       if (_popoverContent.classList.contains('loading')) {
-                               if (_popoverContent.childElementCount === 0) {
-                                       if (_baseHeight === 0) {
-                                               _baseHeight = _popoverContent.offsetHeight;
-                                       }
-                                       
-                                       _popoverContent.style.setProperty('height', _baseHeight + 'px');
-                               }
-                               else {
-                                       _popoverContent.style.removeProperty('height');
-                                       
-                                       var height = _popoverContent.offsetHeight;
-                                       _popoverContent.style.setProperty('height', _baseHeight + 'px');
-                                       
-                                       // force reflow
-                                       _popoverContent.offsetHeight;
-                                       
-                                       _popoverContent.style.setProperty('height', height + 'px');
-                               }
-                       }
                        
                        UiAlignment.set(_popover, _elements.get(_activeId).element, {
                                pointer: true,
@@ -420,6 +386,4 @@ define(['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', '
                        Ajax.api(this, data, success, failure);
                }
        };
-       
-       return ControllerPopover;
 });
index abe8880a58eebb8869e005bd42c70fef7198a3ee..a77b4726282af116768dc33a970584fe113bfaba 100644 (file)
+@keyframes wcfPopover {
+       0%   { visibility: visible; transform: translateY(-20px); opacity: 0; }
+       100% { visibility: visible; transform: translateY(0);     opacity: 1; }
+}
+
+@keyframes wcfPopoverOut {
+       0%   { visibility: visible; transform: translateY(0);     opacity: 1; }
+       100% { visibility: hidden;  transform: translateY(-20px); opacity: 0; }
+}
+
+/* outer element containg both the pointer and content element */
 .popover {
+       animation: wcfPopoverOut .3s;
+       animation-fill-mode: forwards;
        background-color: $wcfContentBackground;
-       border: 1px solid $wcfContentBorder;
-       opacity: 0;
+       border: 1px solid $wcfDropdownBorder;
+       box-shadow: 2px 2px 10px 0 rgba(0, 0, 0, .2);
        position: absolute;
        top: 0;
        vertical-align: middle;
-       visibility: hidden;
        width: 400px !important;
        z-index: 500;
        
-       @include boxShadow(2px, 2px, rgba(0, 0, 0, .2), 10px);
-       
-       transition: visibility 0s linear .3s, opacity .3s linear;
-       
        &.active {
-               opacity: 1;
-               visibility: visible;
-               
-               transition-delay: 0s;
+               animation: wcfPopover .3s;
+               animation-fill-mode: forwards;
        }
        
-       &.disableAnimation {
-               transition: none !important;
-               
-               > .popoverContent {
-                       transition: none !important;
-               }
-               
-               > .elementPointer > span {
-                       transition: none !important;
-               }
-       }
-       
-       > .popoverContent {
-               background-color: $wcfContentBackground;
-               border-radius: 3px;
-               color: $wcfContentText;
-               max-height: 320px;
-               min-height: 36px;
-               opacity: 1;
-               overflow: hidden;
-               padding: 10px;
-               
-               transition: opacity .3s linear;
-               
-               a {
-                       color: $wcfContentLink;
-                       
-                       &:hover {
-                               color: $wcfContentLinkActive;
-                       }
-               }
-               
-               &:not(.loading) {
-                       ~ .fa-spinner {
-                               display: none;
-                       }
-                       
-                       ~ .elementPointer {
-                               > span {
-                                       border-color: $wcfContentBackground $wcfContentBorder;
-                                       border-style: solid;
-                                       border-width: 0 5px 5px;
-                                       left: -5px;
-                                       opacity: 1;
-                                       position: absolute;
-                                       top: 3px;
-                                       
-                                       transition: opacity .3s linear;
-                               }
-                               
-                               &.flipVertical > span {
-                                       border-width: 5px 5px 0;
-                                       bottom: 3px;
-                                       top: auto;
-                               }
-                       }
-               }
-               
-               &.loading {
-                       opacity: 0;
-                       transition: height .3s linear, opacity 0s;
-                       
-                       ~ .elementPointer > span {
-                               opacity: 0;
-                               
-                               transition: opacity 0s;
-                       }
-               }
+       &.forceHide {
+               animation: 0;
+               visibility: hidden;
        }
        
        > .elementPointer {
-               border-color: rgba(0, 0, 0, .4) transparent;
+               border-width: 0 7px 7px;
                border-style: solid;
-               border-width: 0 6px 6px;
-               top: -2px;
+               border-color: $wcfDropdownBorder transparent;
+               top: 0;
                
                &.flipVertical {
-                       border-width: 6px 6px 0;
-                       bottom: -2px;
+                       border-width: 7px 7px 0;
+                       bottom: 0;
                        top: auto;
+                       
+                       > span {
+                               border-width: 5px 5px 0;
+                               bottom: 2px;
+                               top: auto;
+                       }
                }
+               
+               > span {
+                       border-color: $wcfContentBackground transparent;
+                       border-style: solid;
+                       border-width: 0 5px 5px;
+                       left: -5px;
+                       position: absolute;
+                       top: 2px;
+               } 
        }
+}
+
+/* actual popover content */
+.popoverContent {
+       background-color: $wcfContentBackground;
+       border-radius: 3px;
+       color: $wcfContentText;
+       max-height: 320px;
+       min-height: 36px;
+       overflow: hidden;
+       padding: 10px;
        
-       > .fa-spinner {
-               color: rgba(255, 255, 255, 1);
-               left: 50%;
-               margin-left: -14px;
-               margin-top: -14px;
-               position: absolute;
-               top: 50%;
+       a {
+               color: $wcfContentLink;
                
-               @include textShadow(black);
+               &:hover {
+                       color: $wcfContentLinkActive;
+               }
        }
 }