From: Alexander Ebert Date: Fri, 20 Apr 2012 20:24:46 +0000 (+0200) Subject: Implemented flexible popovers X-Git-Tag: 2.0.0_Beta_1~1132^2~6 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=453aced632875ee31393f916c9393aa126c909a2;p=GitHub%2FWoltLab%2FWCF.git Implemented flexible popovers --- diff --git a/wcfsetup/install/files/js/WCF.js b/wcfsetup/install/files/js/WCF.js index d1bf12e34e..aa3ccddb9a 100644 --- a/wcfsetup/install/files/js/WCF.js +++ b/wcfsetup/install/files/js/WCF.js @@ -288,12 +288,14 @@ $.fn.extend({ * * @param string direction * @param object callback + * @param integer duration * @returns jQuery */ - wcfDropIn: function(direction, callback) { + wcfDropIn: function(direction, callback, duration) { if (!direction) direction = 'up'; + if (!duration || !parseInt(duration)) duration = 200; - return this.show(WCF.getEffect(this.getTagName(), 'drop'), { direction: direction }, 600, callback); + return this.show(WCF.getEffect(this.getTagName(), 'drop'), { direction: direction }, duration, callback); }, /** @@ -301,12 +303,14 @@ $.fn.extend({ * * @param string direction * @param object callback + * @param integer duration * @returns jQuery */ - wcfDropOut: function(direction, callback) { + wcfDropOut: function(direction, callback, duration) { if (!direction) direction = 'down'; + if (!duration || !parseInt(duration)) duration = 200; - return this.hide(WCF.getEffect(this.getTagName(), 'drop'), { direction: direction }, 600, callback); + return this.hide(WCF.getEffect(this.getTagName(), 'drop'), { direction: direction }, duration, callback); }, /** @@ -4906,6 +4910,406 @@ WCF.Sortable.List = Class.extend({ } }); +WCF.Popover = Class.extend({ + /** + * currently active element id + * @var string + */ + _activeElementID: '', + + /** + * element data + * @var object + */ + _data: { }, + + /** + * default dimensions, should reflect the estimated size + * @var object + */ + _defaultDimensions: { + height: 30, + width: 150, + }, + + /** + * default orientation, may be a combintion of left/right and bottom/top + * @var object + */ + _defaultOrientation: { + x: 'right', + y: 'top' + }, + + /** + * delay to show or hide popover, values in miliseconds + * @var object + */ + _delay: { + show: 250, + hide: 500 + }, + + /** + * true, if an element is being hovered + * @var boolean + */ + _hoverElement: false, + + /** + * element id of element being hovered + * @var string + */ + _hoverElementID: '', + + /** + * true, if popover is being hovered + * @var boolean + */ + _hoverPopover: false, + + /** + * minimum margin (all directions) for popover + * @var integer + */ + _margin: 20, + + /** + * periodical executer once an element is being hovered + * @var WCF.PeriodicalExecuter + */ + _peOverElement: null, + + /** + * popover object + * @var jQuery + */ + _popover: null, + + /** + * popover horizontal offset + * @var integer + */ + _popoverOffset: 10, + + /** + * element selector + * @var string + */ + _selector: '', + + /** + * Initializes a new WCF.Popover object. + * + * @param string selector + */ + init: function(selector) { + // assign default values + this._activeElementID = ''; + this._data = { }; + this._defaultDimensions = { + height: 30, + width: 150 + }; + this._defaultOrientation = { + x: 'right', + y: 'top' + }; + this._delay = { + show: 250, + hide: 500 + }; + this._hoverElement = false; + this._hoverElementID = ''; + this._hoverPopover = false; + this._margin = 20; + this._popoverOffset = 10; + this._selector = selector; + + // reuse existing instance or create a new popover + this._popover = $('#popover'); + if (!this._popover.length) { + this._popover = $('
').hide().appendTo(document.body); + this._popover.hover($.proxy(this._overPopover, this), $.proxy(this._out, this)); + } + + this._initContainers(); + }, + + /** + * Initializes all element triggers. + */ + _initContainers: function() { + var $elements = $(this._selector); + if (!$elements.length) { + return; + } + + $elements.each($.proxy(function(index, element) { + var $element = $(element); + var $elementID = $element.wcfIdentify(); + + if (!this._data[$elementID]) { + this._data[$elementID] = { + 'content': null, + 'isLoading': false + }; + + $element.hover($.proxy(this._overElement, this), $.proxy(this._out, this)); + } + }, this)); + }, + + /** + * Triggered once an element is being hovered. + * + * @param object event + */ + _overElement: function(event) { + if (this._peOverElement !== null) { + this._peOverElement.stop(); + } + + var $elementID = $(event.currentTarget).wcfIdentify(); + this._hoverElementID = $elementID; + this._peOverElement = new WCF.PeriodicalExecuter($.proxy(function(pe) { + pe.stop(); + + // still above the same element + if (this._hoverElementID === $elementID) { + this._activeElementID = $elementID; + this._prepare(); + } + }, this), this._delay.show); + + this._hoverElement = true; + this._hoverPopover = false; + }, + + /** + * Prepares popover to be displayed. + */ + _prepare: function() { + // hide and reset + if (this._popover.is(':visible')) { + this._popover.empty().hide(); + } + + // insert html + if (!this._data[this._activeElementID].loading && this._data[this._activeElementID].content) { + this._popover.html(this._data[this._activeElementID].content); + } + else { + this._popover.html('
'); + this._data[this._activeElementID].loading = true; + } + + // get dimensions + var $dimensions = this._popover.show().getDimensions(); + if (this._data[this._activeElementID].loading) { + $dimensions = { + height: Math.max($dimensions.height, this._defaultDimensions.height), + width: Math.max($dimensions.width, this._defaultDimensions.width) + }; + } + this._popover.hide(); + + // get orientation + var $orientation = this._getOrientation($dimensions.height, $dimensions.width); + this._popover.css(this._getCSS($orientation.x, $orientation.y)); + + this._show(); + }, + + /** + * Displays the popover. + */ + _show: function() { + this._popover.show(); + + this._loadContent(); + }, + + /** + * Loads content, should be overwritten by child classes. + */ + _loadContent: function() { }, + + /** + * Hides the popover. + */ + _hide: function() { + this._popover.hide(); + }, + + /** + * Triggered once popover is being hovered. + */ + _overPopover: function() { + this._hoverElement = false; + this._hoverPopover = true; + }, + + /** + * Triggered once element *or* popover is now longer hovered. + */ + _out: function(event) { + this._hoverElement = false; + this._hoverPopover = false; + + new WCF.PeriodicalExecuter($.proxy(function(pe) { + pe.stop(); + + // hide popover is neither element nor popover was hovered given time + if (!this._hoverElement && !this._hoverPopover) { + this._activeElementID = ''; + this._hide(); + } + }, this), this._delay.hide); + }, + + /** + * Resolves popover orientation, tries to use default orientation first. + * + * @param integer height + * @param integer width + * @return object + */ + _getOrientation: function(height, width) { + // get offsets and dimensions + var $element = $('#' + this._activeElementID); + var $offsets = $element.getOffsets(); + var $elementDimensions = $element.getDimensions(); + var $documentDimensions = $(document).getDimensions(); + + // try default orientation first + var $orientationX = (this._defaultOrientation.x === 'left') ? 'left' : 'right'; + var $orientationY = (this._defaultOrientation.y === 'bottom') ? 'bottom' : 'top'; + $result = this._evaluateOrientation($orientationX, $orientationY, $offsets, $elementDimensions, $documentDimensions, height, width); + + if ($result.flawed) { + // try flipping orientationX + $orientationX = ($orientationX === 'left') ? 'right' : 'left'; + $result = this._evaluateOrientation($orientationX, $orientationY, $offsets, $elementDimensions, $documentDimensions, height, width); + + if ($result.flawed) { + // try flipping orientationY while maintaing original orientationX + $orientationX = ($orientationX === 'right') ? 'left' : 'right'; + $orientationY = ($orientationY === 'bottom') ? 'top' : 'bottom'; + $result = this._evaluateOrientation($orientationX, $orientationY, $offsets, $elementDimensions, $documentDimensions, height, width); + + if ($result.flawed) { + // try flipping both orientationX and orientationY compared to default values + $orientationX = ($orientationX === 'left') ? 'right' : 'left'; + $result = this._evaluateOrientation($orientationX, $orientationY, $offsets, $elementDimensions, $documentDimensions, height, width); + + if ($result.flawed) { + // fuck this shit, we will use the default orientation + $orientationX = (this._defaultOrientationX === 'left') ? 'left' : 'right'; + $orientationY = (this._defaultOrientationY === 'bottom') ? 'bottom' : 'top'; + } + } + } + } + + return { + x: $orientationX, + y: $orientationY + } + }, + + /** + * Evaluates if popover fits into given orientation. + * + * @param string orientationX + * @param string orientationY + * @param object offsets + * @param object elementDimensions + * @param object documentDimensions + * @param integer height + * @param integer width + * @return object + */ + _evaluateOrientation: function(orientationX, orientationY, offsets, elementDimensions, documentDimensions, height, width) { + var $heightDifference = 0, $widthDifference = 0; + switch (orientationX) { + case 'left': + $widthDifference = offsets.left - width; + break; + + case 'right': + $widthDifference = documentDimensions.width - (offsets.left + elementDimensions.width); + break; + } + + switch (orientationY) { + case 'bottom': + $heightDifference = documentDimensions.height - (offsets.top + elementDimensions.height + this._popoverOffset); + break; + + case 'top': + $heightDifference = offsets.top - (height - this._popoverOffset); + break; + } + + // check if both difference are above margin + var $flawed = false; + if ($heightDifference < this._margin || $widthDifference < this._margin) { + $flawed = true; + } + + return { + flawed: $flawed, + x: $widthDifference, + y: $heightDifference + }; + }, + + /** + * Computes CSS for popover. + * + * @param string orientationX + * @param string orientationY + * @return object + */ + _getCSS: function(orientationX, orientationY) { + var $css = { + bottom: 'auto', + left: 'auto', + position: 'absolute', + right: 'auto', + top: 'auto' + }; + + var $element = $('#' + this._activeElementID); + var $offsets = $element.getOffsets(); + var $elementDimensions = $element.getDimensions(); + var $documentDimensions = $(document).getDimensions(); + + switch (orientationX) { + case 'left': + $css.right = $documentDimensions.width - ($offsets.left + $elementDimensions.width); + break; + + case 'right': + $css.left = $offsets.left; + break; + } + + switch (orientationY) { + case 'bottom': + $css.top = $offsets.top + ($elementDimensions.height + this._popoverOffset); + break; + + case 'top': + $css.bottom = $documentDimensions.height - ($offsets.top - this._popoverOffset); + break; + } + + return $css; + } +}); + /** * Provides a toggleable sidebar. */