1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.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: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * Singleton tooltip class. 26 */ 27 DwtToolTip = function(shell, className, dialog) { 28 29 if (arguments.length == 0) { return; } 30 31 this.shell = shell; 32 this._dialog = dialog; 33 this._poppedUp = false; 34 this._div = document.createElement("div"); 35 this._div.className = className || "DwtToolTip"; 36 this._div.style.position = DwtControl.ABSOLUTE_STYLE; 37 this.shell.getHtmlElement().appendChild(this._div); 38 Dwt.setZIndex(this._div, Dwt.Z_HIDDEN); 39 Dwt.setLocation(this._div, Dwt.LOC_NOWHERE, Dwt.LOC_NOWHERE); 40 41 this._eventMgr = new AjxEventMgr(); 42 43 // create html 44 // NOTE: This id is ok because there's only ever one instance of a tooltip 45 var templateId = "dwt.Widgets#DwtToolTip"; 46 this._div.innerHTML = AjxTemplate.expand(templateId); 47 48 var params = AjxTemplate.getParams(templateId); 49 this._offsetX = (params.width != null) ? Number(params.width) : DwtToolTip.POPUP_OFFSET_X; 50 this._offsetY = (params.height != null) ? Number(params.height) : DwtToolTip.POPUP_OFFSET_Y; 51 52 // save reference to content div 53 this._contentDiv = document.getElementById("tooltipContents"); 54 55 Dwt.setHandler(this._div, DwtEvent.ONMOUSEOVER, AjxCallback.simpleClosure(this._mouseOverListener, this)); 56 Dwt.setHandler(this._div, DwtEvent.ONMOUSEOUT, AjxCallback.simpleClosure(this._mouseOutListener, this)); 57 58 var events = [DwtEvent.ONCLICK,DwtEvent.ONDBLCLICK,DwtEvent.ONMOUSEDOWN,DwtEvent.ONMOUSEENTER,DwtEvent.ONMOUSELEAVE,DwtEvent.ONMOUSEMOVE,DwtEvent.ONMOUSEUP,DwtEvent.ONMOUSEWHEEL,DwtEvent.ONSCROLL]; 59 for (var i=0; i<events.length; i++) { 60 var event = events[i]; 61 Dwt.setHandler(this._div, event, AjxCallback.simpleClosure(this.notifyListeners, this, [event])); 62 } 63 }; 64 65 DwtToolTip.prototype.isDwtToolTip = true; 66 DwtToolTip.prototype.toString = function() { return "DwtToolTip"; }; 67 68 // 69 // Constants 70 // 71 72 DwtToolTip.TOOLTIP_DELAY = 750; 73 74 DwtToolTip.WINDOW_GUTTER = 5; // space to leave between tooltip and edge of shell 75 DwtToolTip.POPUP_OFFSET_X = 5; // default horizontal offset from control 76 DwtToolTip.POPUP_OFFSET_Y = 5; // default vertical offset from control 77 78 // 79 // Data 80 // 81 82 // 83 // Public methods 84 // 85 86 DwtToolTip.prototype.getContent = 87 function() { 88 return this._div.innerHTML; 89 }; 90 91 DwtToolTip.prototype.setContent = 92 function(content, setInnerHTML) { 93 this._content = content; 94 if(setInnerHTML) { 95 this._contentDiv.innerHTML = this._content; 96 } 97 }; 98 99 /** 100 * Shows the tooltip. By default, its position will be relative to the location of the 101 * cursor when the mouseover event happened. Alternatively, the control that generated 102 * the tooltip can be passed in, and the tooltip will be positioned relative to it. If 103 * the control is a large composite control (eg a DwtListView), the hover event can be 104 * passed so that the actual target of the event can be found. 105 * 106 * @param {number} x X-coordinate of cursor 107 * @param {number} y Y-coordinate of cursor 108 * @param {boolean} skipInnerHTML if true, do not copy content to DOM 109 * @param {boolean} popdownOnMouseOver if true, hide tooltip on mouseover 110 * @param {DwtControl} obj control that tooltip is for (optional) 111 * @param {DwtHoverEvent} hoverEv hover event (optional) 112 * @param {AjxCallback} popdownListener callback to run when tooltip pops down 113 */ 114 DwtToolTip.prototype.popup = 115 function(x, y, skipInnerHTML, popdownOnMouseOver, obj, hoverEv, popdownListener) { 116 this._hovered = false; 117 if (this._popupAction) { 118 AjxTimedAction.cancelAction(this._popupAction); 119 this._popupAction = null; 120 } 121 // popdownOnMouseOver may be true to pop down the tooltip if the mouse hovers over the tooltip. Optionally, 122 // it can be an AjxCallback that will be called after popping the tooltip down. 123 this._popdownOnMouseOver = popdownOnMouseOver; 124 // popdownListener is always called after popping the tooltip down, regardless of what called the popdown 125 this._popdownListener = popdownListener; 126 if (this._content != null) { 127 if(!skipInnerHTML) { 128 this._contentDiv.innerHTML = this._content; 129 } 130 131 this._popupAction = new AjxTimedAction(this, this._positionElement, [x, y, obj, hoverEv]); 132 AjxTimedAction.scheduleAction(this._popupAction, 5); 133 } 134 }; 135 136 /* 137 * setSticky allows making the tooltip not to popdown. 138 * IMPORTANT: Tooltip is singleton inside Zimbra i.e. only one instance of tooltip is reused by all objects. 139 * So, it is very important for the code setting tooltip to sticky to have some mechanism to close the tooltip by itself. 140 * Like have a close-button inside tooltip and when clicked, should set the setSticky(false) and then close the tooltip. 141 * 142 * If setSticky(true) is called, _poppedUp is set to false, which is essentially pretending the tooltip is not 143 * up. In that case, a call to popdown will not close the tooltip. And that means tooltip will stay up even if some other 144 * code path calls popdown on the singleton tooltip. 145 * 146 */ 147 DwtToolTip.prototype.setSticky = 148 function(bool) { 149 this._poppedUp = !bool; 150 }; 151 152 DwtToolTip.prototype.popdown = 153 function() { 154 this._popdownOnMouseOver = false; 155 this._hovered = false; 156 if (this._popupAction) { 157 AjxTimedAction.cancelAction(this._popupAction); 158 this._popupAction = null; 159 } 160 if (this._content != null && this._poppedUp) { 161 Dwt.setLocation(this._div, Dwt.LOC_NOWHERE, Dwt.LOC_NOWHERE); 162 this._poppedUp = false; 163 if (this._popdownListener instanceof AjxCallback) { 164 this._popdownListener.run(); 165 } 166 this._popdownListener = null; 167 } 168 }; 169 170 // 171 // Protected methods 172 // 173 174 // Positions the tooltip relative to the base element based on vertical and horizontal offsets. 175 DwtToolTip.prototype._positionElement = 176 function(startX, startY, obj, hoverEv) { 177 178 this._popupAction = null; 179 180 var wdSize = DwtShell.getShell(window).getSize(); 181 var wdWidth = wdSize.x, wdHeight = wdSize.y; 182 183 var tooltipX, tooltipY, baseLoc; 184 var baseEl = obj && obj.getTooltipBase(hoverEv); 185 if (baseEl) { 186 baseLoc = Dwt.toWindow(baseEl); 187 var baseSz = Dwt.getSize(baseEl); 188 tooltipX = baseLoc.x + this._offsetX; 189 tooltipY = baseLoc.y + baseSz.y + this._offsetY; 190 } 191 else { 192 tooltipX = startX + this._offsetX; 193 tooltipY = startY + this._offsetY; 194 } 195 196 var popupSize = Dwt.getSize(this._div); 197 var popupWidth = popupSize.x, popupHeight = popupSize.y; 198 199 // check for sufficient room to the right 200 if (tooltipX + popupWidth > wdWidth - DwtToolTip.WINDOW_GUTTER) { 201 tooltipX = wdWidth - DwtToolTip.WINDOW_GUTTER - popupWidth; 202 } 203 // check for sufficient room below 204 if (tooltipY + popupHeight > wdHeight - DwtToolTip.WINDOW_GUTTER) { 205 tooltipY = (baseLoc ? baseLoc.y : tooltipY) - this._offsetY - popupHeight; 206 } 207 208 Dwt.setLocation(this._div, tooltipX, tooltipY); 209 var zIndex = this._dialog ? this._dialog.getZIndex() + Dwt._Z_INC : Dwt.Z_TOOLTIP; 210 Dwt.setZIndex(this._div, zIndex); 211 this._poppedUp = true; 212 }; 213 214 DwtToolTip.prototype._mouseOverListener = 215 function(ev) { 216 this._hovered = true; 217 if (this._popdownOnMouseOver && this._poppedUp) { 218 var callback = (this._popdownOnMouseOver.isAjxCallback || AjxUtil.isFunction(this._popdownOnMouseOver)) ? this._popdownOnMouseOver : null; 219 this.popdown(); 220 if (callback) { 221 callback.run(); 222 } 223 } 224 this.notifyListeners(DwtEvent.ONMOUSEOVER); 225 }; 226 227 DwtToolTip.prototype._mouseOutListener = 228 function(ev) { 229 ev = DwtUiEvent.getEvent(ev, this._div) 230 var location = Dwt.toWindow(this._div); 231 var size = Dwt.getSize(this._div); 232 // We sometimes get mouseover events even though the cursor is inside the tooltip, so double-check before popping down 233 if (ev.clientX <= location.x || ev.clientX >= (location.x + size.x) || ev.clientY <= location.y || ev.clientY >= (location.y + size.y)) { 234 this.popdown(); 235 this.notifyListeners(DwtEvent.ONMOUSEOUT); 236 } 237 }; 238 239 DwtToolTip.prototype.getHovered = 240 function() { 241 return this._hovered; 242 }; 243 244 245 // The com_zimbra_email zimlet wants to put a listener on our mouseout event, but overwriting the existing handler is a no-no 246 // and we actually only want that event when the double-check above succeeds. Let API users add event listeners in a more clean way. 247 DwtToolTip.prototype.addListener = 248 function(eventType, listener, index) { 249 return this._eventMgr.addListener(eventType, listener, index); 250 }; 251 252 DwtToolTip.prototype.setListener = 253 function(eventType, listener, index) { 254 this.removeAllListeners(eventType); 255 return this._eventMgr.addListener(eventType, listener, index); 256 }; 257 258 DwtToolTip.prototype.removeListener = 259 function(eventType, listener) { 260 return this._eventMgr.removeListener(eventType, listener); 261 }; 262 263 DwtToolTip.prototype.removeAllListeners = 264 function(eventType) { 265 return this._eventMgr.removeAll(eventType); 266 }; 267 268 DwtToolTip.prototype.isListenerRegistered = 269 function(eventType) { 270 return this._eventMgr.isListenerRegistered(eventType); 271 }; 272 273 DwtToolTip.prototype.notifyListeners = 274 function(eventType, event) { 275 return this._eventMgr.notifyListeners(eventType, event); 276 }; 277 278