/** * A bound panel is a panel that is shown at a certain place * @constructor * @param {Object} options The options * @param {Node} options.element The root element * @param {string} options.name The component name */ hui.ui.BoundPanel = function(options) { this.options = options; this.element = hui.get(options.element); this.name = options.name; this.visible = false; this.content = hui.get.firstByClass(this.element, 'hui_boundpanel_content'); this.arrow = hui.get.firstByClass(this.element, 'hui_boundpanel_arrow'); this.arrowWide = 37; this.arrowNarrow = 18; if (options.variant == 'light') { this.arrowWide = 23; this.arrowNarrow = 12; } hui.ui.extend(this); }; /** * Creates a new bound panel * * @param {Object} options The options * @param {String} options.name The component name * @param {String} options.variant A visual variation * @param {Number} options.left Pixels from left * @param {Number} options.top Pixels from top * @param {Number} options.width Width in pixels * @param {Number} options.padding Padding in pixels */ hui.ui.BoundPanel.create = function(options) { options = hui.override({ name: null, top: 0, left: 0, width: null, padding: null, modal: false, hideOnClick: false }, options); var html = '<div class="hui_boundpanel_arrow"></div>' + '<div class="hui_boundpanel_content" style="'; if (options.width) { html += 'width:' + options.width + 'px;'; } if (options.padding) { html += 'padding:' + options.padding + 'px;'; } html += '"></div>'; options.element = hui.build( 'div', { 'class': options.variant ? 'hui_boundpanel hui_boundpanel-' + options.variant : 'hui_boundpanel', style: 'display:none;zIndex:' + hui.ui.nextPanelIndex() + ';top:' + options.top + 'px;left:' + options.left + 'px', html: html, parent: document.body } ); return new hui.ui.BoundPanel(options); }; hui.ui.BoundPanel.prototype = { /** * Show or hide the panel */ toggle: function() { if (!this.visible) { this.show(); } else { this.hide(); } }, /** Shows the panel */ show: function(options) { options = options || {}; var target = options.target || this.options.target; if (target) { if (target.nodeName) { this.position(target); } else { this.position(hui.ui.get(target)); } } if (this.visible) { this.element.style.zIndex = hui.ui.nextPanelIndex(); return; } if (hui.browser.opacity) { hui.style.setOpacity(this.element, 0); } var vert; if (this.relativePosition == 'left') { vert = false; this.element.style.marginLeft = '20px'; } else if (this.relativePosition == 'right') { vert = false; this.element.style.marginLeft = '-20px'; } else if (this.relativePosition == 'top') { vert = true; this.element.style.marginTop = '20px'; } else if (this.relativePosition == 'bottom') { vert = true; this.element.style.marginTop = '-20px'; } this.element.style.visibility = 'visible'; this.element.style.display = 'block'; var index = hui.ui.nextPanelIndex(); this.element.style.zIndex = index; hui.ui.callVisible(this); if (hui.browser.opacity) { hui.animate(this.element, 'opacity', 1, 300, { ease: hui.ease.fastSlow }); } hui.animate(this.element, vert ? 'margin-top' : 'margin-left', '0px', 300, { ease: hui.ease.fastSlow }); this.visible = true; if (this.options.modal) { hui.ui.showCurtain({ widget: this, zIndex: index - 1, transparent: this.options.modal == 'transparent', color: 'auto' }); } if (this.options.hideOnClick) { this.hideListener = hui.listen(document.body, 'click', function(e) { if (!hui.ui.isWithin(e, this.element)) { this.hide(); } }.bind(this)); } }, /** @private */ $curtainWasClicked: function() { hui.ui.hideCurtain(this); this.hide(); }, /** Hides the panel */ hide: function() { if (!this.visible) { return; } if (!hui.browser.opacity) { this.element.style.display = 'none'; hui.ui.callVisible(this); } else { hui.animate(this.element, 'opacity', 0, 100, { ease: hui.ease.slowFast, $complete: function() { this.element.style.display = 'none'; hui.ui.callVisible(this); }.bind(this) }); } if (this.options.modal) { hui.ui.hideCurtain(this); } this.visible = false; hui.unListen(document.body, 'click', this.hideListener); }, /** * If the panel is currently visible */ isVisible: function() { return this.visible; }, /** * Adds a widget or element to the panel * @param {Node | Widget} child The object to add */ add: function(child) { if (child.getElement) { this.content.appendChild(child.getElement()); } else { this.content.appendChild(child); } }, clear: function() { hui.ui.destroyDescendants(this.content); this.content.innerHTML = ''; }, /** * Adds som vertical space to the panel * @param {pixels} height The height of the space in pixels */ addSpace: function(height) { this.add(hui.build('div', { style: 'font-size:0px;height:' + height + 'px' })); }, _getDimensions: function() { var width, height; if (this.element.style.display == 'none') { this.element.style.visibility = 'hidden'; this.element.style.display = 'block'; width = this.element.clientWidth; height = this.element.clientHeight; this.element.style.display = 'none'; this.element.style.visibility = ''; } else { width = this.element.clientWidth; height = this.element.clientHeight; } return { width: width, height: height }; }, $$childSizeChanged: function() { this._rePosition(); }, $$layout: function() { this._rePosition(); }, _rePosition: function() { if (this._latest) { this.position(this._latest); } }, /** Position the panel at a node * @param {Node} node The node the panel should be positioned at */ position: function(options) { this._latest = options; var node, position, nodeOffset, nodeScrollOffset; if (options.getElement) { node = options.getElement(); } else if (options.element) { node = options.element; position = options.position; } else if (options.rect) { position = options.position; node = { offsetWidth: options.rect.width, offsetHeight: options.rect.height }; nodeOffset = { left: options.rect.left, top: options.rect.top }; nodeScrollOffset = { left: 0, top: 0 }; } else { node = hui.get(options); } if (!nodeOffset) { nodeOffset = { left: hui.position.getLeft(node), top: hui.position.getTop(node) }; } if (!nodeScrollOffset) { nodeScrollOffset = hui.position.getScrollOffset(node); } var windowScrollOffset = { left: hui.window.getScrollLeft(), top: hui.window.getScrollTop() }; var nodeLeft = nodeOffset.left - windowScrollOffset.left + hui.window.getScrollLeft(); var nodeWidth = node.clientWidth || node.offsetWidth; var nodeHeight = node.clientHeight || node.offsetHeight; var panelDimensions = this._getDimensions(); var viewportWidth = document.body.clientWidth; var viewportHeight = hui.window.getViewHeight(); var arrowLeft, arrowTop, left, top; var positionOnScreen = { top: nodeOffset.top - windowScrollOffset.top - (nodeScrollOffset.top - windowScrollOffset.top) }; var vertical = positionOnScreen.top / viewportHeight; if (position == 'vertical') { vertical = vertical > 0.5 ? 0.9 : 0.1; } var min, max; if (vertical <= 0.1) { this.relativePosition = 'top'; this.arrow.className = 'hui_boundpanel_arrow hui_boundpanel_arrow_top'; if (this.options.variant == 'light') { arrowTop = this.arrowNarrow * -1; } else { arrowTop = this.arrowNarrow * -1; } left = Math.min(viewportWidth - panelDimensions.width - 2, Math.max(3, nodeLeft + (nodeWidth / 2) - ((panelDimensions.width) / 2))); arrowLeft = (nodeLeft + nodeWidth / 2) - left - this.arrowNarrow; top = nodeOffset.top + nodeHeight + 8 - (nodeScrollOffset.top - windowScrollOffset.top); } else if (vertical >= 0.9) { this.relativePosition = 'bottom'; this.arrow.className = 'hui_boundpanel_arrow hui_boundpanel_arrow_bottom'; if (this.options.variant == 'light') { arrowTop = panelDimensions.height - 1; } else { arrowTop = panelDimensions.height; } left = Math.min(viewportWidth - panelDimensions.width - 3, Math.max(3, nodeLeft + (nodeWidth / 2) - ((panelDimensions.width) / 2))); arrowLeft = (nodeLeft + nodeWidth / 2) - left - this.arrowNarrow; top = nodeOffset.top - panelDimensions.height - 5 - (nodeScrollOffset.top - windowScrollOffset.top); } else if ((nodeLeft + nodeWidth / 2) / viewportWidth < 0.5) { this.relativePosition = 'left'; left = nodeLeft + nodeWidth + 10; this.arrow.className = 'hui_boundpanel_arrow hui_boundpanel_arrow_left'; top = nodeOffset.top + (nodeHeight - panelDimensions.height) / 2; //top = Math.min(top,viewportHeight-panelDimensions.height+(windowScrollOffset.top+nodeScrollOffset.top)); top -= (nodeScrollOffset.top - windowScrollOffset.top); min = windowScrollOffset.top + 3; max = windowScrollOffset.top + (viewportHeight - panelDimensions.height) - 3; top = Math.min(Math.max(top, min), max); arrowTop = nodeOffset.top - top; arrowTop -= (nodeScrollOffset.top - windowScrollOffset.top); arrowTop -= this.arrowWide / 2; arrowTop += nodeHeight / 2; if (this.options.variant == 'light') { arrowLeft = -12; arrowTop += 2; } else { arrowLeft = -18; } } else { this.relativePosition = 'right'; left = nodeLeft - panelDimensions.width - 10; this.arrow.className = 'hui_boundpanel_arrow hui_boundpanel_arrow_right'; top = nodeOffset.top + (nodeHeight - panelDimensions.height) / 2; //top = Math.min(top,viewportHeight-panelDimensions.height+(windowScrollOffset.top+nodeScrollOffset.top)); top -= (nodeScrollOffset.top - windowScrollOffset.top); min = windowScrollOffset.top + 3; max = windowScrollOffset.top + (viewportHeight - panelDimensions.height) - 3; top = Math.min(Math.max(top, min), max); arrowTop = nodeOffset.top - top; arrowTop -= (nodeScrollOffset.top - windowScrollOffset.top); arrowTop -= this.arrowWide / 2; arrowTop += nodeHeight / 2; if (this.options.variant == 'light') { arrowLeft = panelDimensions.width; arrowTop += 2; } else { arrowLeft = panelDimensions.width; } } this.arrow.style.marginTop = arrowTop + 'px'; this.arrow.style.marginLeft = arrowLeft + 'px'; if (this.visible) { hui.animate(this.element, 'top', top + 'px', 500, { ease: hui.ease.fastSlow }); hui.animate(this.element, 'left', left + 'px', 500, { ease: hui.ease.fastSlow }); } else { this.element.style.top = top + 'px'; this.element.style.left = left + 'px'; } }, detach: function() { hui.ui.hideCurtain(this); } };