*
* @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);
},
/**
*
* @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);
},
/**
}
});
+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 = $('<div id="popover" class="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('<div class="popoverLoading icon48"><img src="' + WCF.Icon.get('wcf.icon.loading') + '" alt="" class="icon48" /></div>');
+ 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.
*/