1 /**
  2  * @fileOverview
  3  * @author <a href="https://www.labkey.org">LabKey</a> (<a href="mailto:info@labkey.com">info@labkey.com</a>)
  4  * @license Copyright (c) 2012-2019 LabKey Corporation
  5  * <p/>
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  * <p/>
 10  * http://www.apache.org/licenses/LICENSE-2.0
 11  * <p/>
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing permissions and
 16  * limitations under the License.
 17  * <p/>
 18  */
 19 
 20 /**
 21  * @private
 22  * @namespace LabKey Popup Menu Web Class.
 23  * This class allows an element to display a popup menu based on a LabKey webpart
 24  * The element will highlight on hover over.
 25  *
 26  * @constructor
 27  * @param {Object} config Describes the HoverPopup's properties.
 28  * @param {String} config.hoverElem Element or element id that should trigger the popup
 29  * @param {String} config.webPartName Name of webPart to load on hover
 30  * @param {Object} config.partConfig PartConfig for the webpart same as used in LABKEY.WebPart
 31  *
 32  */
 33 LABKEY.HoverPopup = function(config)
 34 {
 35     Ext.apply(this, config);
 36     LABKEY.HoverPopup.superclass.constructor.call(this);
 37 
 38     this.extElem = Ext.get(config.hoverElem);
 39     if (!this.extElem) {
 40         return;             // Custom Menu Bar may be hidden in which case element is null
 41     }
 42 
 43     this.showDelay = 150;
 44 
 45     var popup = Ext.DomHelper.insertAfter("menubar",
 46             {id:config.hoverElem + "_menu", tag:"div", cls:"labkey-webpart-menu",
 47                 children:[{tag:"div", cls:"loading-indicator", style:"width:100px;height:100px"}]});
 48     this.extPopup = new Ext.Layer({shadow:true,shadowOffset:8,zindex:1000},popup);
 49     this.webPartName = config.webPartName;
 50     this.partConfig = config.partConfig || {};
 51 
 52     this.extElem.hover(function(e) {
 53         this.cancelHide();
 54 
 55         // show immediately if we already have a menu up
 56         // Otherwise, make sure that someone hovers for a while
 57         LABKEY.HoverPopup._visiblePopup ? this.showFn() : this.delayShow();
 58 
 59     }, this.delayCheck, this);
 60 
 61     this.extPopup.hover(this.cancelHide, this.delayCheck, this);
 62 
 63     //Update the shadow on click, since we sometimes cause the change of the inner div
 64     this.extPopup.on("click", function(e) {this.extPopup.enableShadow(true)}, this);
 65 };
 66 
 67 Ext.extend(LABKEY.HoverPopup,  Ext.util.Observable, {
 68     //private
 69     cancelHide: function() {
 70         if (this.hideTimeout) {
 71             clearTimeout(this.hideTimeout);
 72             delete this.hideTimeout;
 73         }
 74     },
 75 
 76     //private
 77     delayCheck : function(e) {
 78         if (!this.extElem.getRegion().contains(e.getPoint()) && !this.extPopup.getRegion().contains(e.getPoint())) {
 79             this.delayHide();
 80         }
 81     },
 82 
 83     //private
 84     delayHide: function() {
 85         if (this.hideTimeout) {
 86             clearTimeout(this.hideTimeout);
 87         }
 88         if (this.showTimeout) {
 89             clearTimeout(this.showTimeout);
 90             delete this.showTimeout;
 91         }
 92         this.hideTimeout = this.hideFn.defer(200, this);
 93     },
 94 
 95     //private
 96     delayShow: function() {
 97         if (!this.showTimeout) {
 98             this.showTimeout = this.showFn.defer(this.showDelay, this);
 99         }
100     },
101 
102     //private
103     showFn: function() {
104         if (LABKEY.HoverPopup._visiblePopup) {
105             if (LABKEY.HoverPopup._visiblePopup == this) {
106                 return;
107             }
108             else {
109                 LABKEY.HoverPopup._visiblePopup.hideFn();
110             }
111         }
112         this.extElem.addClass("selected");
113         //console.log(this.extElem.id  + " extPopup.y: " + this.extPopup.getY());
114         if (!this.rendered) {
115             var p = new LABKEY.WebPart({
116                 partName   : this.webPartName,
117                 renderTo   :  this.extPopup.id,
118                 frame      : 'none',
119                 partConfig : this.partConfig,
120                 failure : function(err) {if (window.console && window.console.log) { window.console.log(err);}},
121                 success : function() {
122                     var x = function() {this.extPopup.enableShadow(true);};
123                     x.defer(100, this);
124                 },
125                 scope:this
126             });
127             p.render();
128             this.rendered = true;
129             this.showDelay = 100; // show more quickly
130         }
131         this.extPopup.show();
132         this.extPopup.constrain = false;
133         this.extPopup.alignTo(this.extElem, "tl-bl");
134         this.extPopup.setXY([Math.max(0 - this.extPopup.getBorderWidth('t'), this.extPopup.getX() - 1), this.extPopup.getY()- this.extPopup.getBorderWidth('t')]);
135         this.extPopup.enableShadow(true);
136         LABKEY.HoverPopup._visiblePopup = this;        
137     },
138 
139     //private
140     hideFn: function () {
141         this.extElem.removeClass("selected");
142         this.extPopup.hide();
143         if (LABKEY.HoverPopup._visiblePopup == this) {
144             LABKEY.HoverPopup._visiblePopup = null;
145         }
146     }
147 });
148 
149