advancedtooltip.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. // Copyright 2007 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Advanced tooltip widget implementation.
  16. *
  17. * @author eae@google.com (Emil A Eklund)
  18. * @see ../demos/advancedtooltip.html
  19. */
  20. goog.provide('goog.ui.AdvancedTooltip');
  21. goog.require('goog.events');
  22. goog.require('goog.events.EventType');
  23. goog.require('goog.math.Box');
  24. goog.require('goog.math.Coordinate');
  25. goog.require('goog.style');
  26. goog.require('goog.ui.Tooltip');
  27. goog.require('goog.userAgent');
  28. /**
  29. * Advanced tooltip widget with cursor tracking abilities. Works like a regular
  30. * tooltip but can track the cursor position and direction to determine if the
  31. * tooltip should be dismissed or remain open.
  32. *
  33. * @param {Element|string=} opt_el Element to display tooltip for, either
  34. * element reference or string id.
  35. * @param {?string=} opt_str Text message to display in tooltip.
  36. * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
  37. * @constructor
  38. * @extends {goog.ui.Tooltip}
  39. */
  40. goog.ui.AdvancedTooltip = function(opt_el, opt_str, opt_domHelper) {
  41. goog.ui.Tooltip.call(this, opt_el, opt_str, opt_domHelper);
  42. };
  43. goog.inherits(goog.ui.AdvancedTooltip, goog.ui.Tooltip);
  44. goog.tagUnsealableClass(goog.ui.AdvancedTooltip);
  45. /**
  46. * Whether to track the cursor and thereby close the tooltip if it moves away
  47. * from the tooltip and keep it open if it moves towards it.
  48. *
  49. * @type {boolean}
  50. * @private
  51. */
  52. goog.ui.AdvancedTooltip.prototype.cursorTracking_ = false;
  53. /**
  54. * Delay in milliseconds before tooltips are hidden if cursor tracking is
  55. * enabled and the cursor is moving away from the tooltip.
  56. *
  57. * @type {number}
  58. * @private
  59. */
  60. goog.ui.AdvancedTooltip.prototype.cursorTrackingHideDelayMs_ = 100;
  61. /**
  62. * Box object representing a margin around the tooltip where the cursor is
  63. * allowed without dismissing the tooltip.
  64. *
  65. * @type {goog.math.Box}
  66. * @private
  67. */
  68. goog.ui.AdvancedTooltip.prototype.hotSpotPadding_;
  69. /**
  70. * Bounding box.
  71. *
  72. * @type {goog.math.Box}
  73. * @private
  74. */
  75. goog.ui.AdvancedTooltip.prototype.boundingBox_;
  76. /**
  77. * Anchor bounding box.
  78. *
  79. * @type {goog.math.Box}
  80. * @private
  81. */
  82. goog.ui.AdvancedTooltip.prototype.anchorBox_;
  83. /**
  84. * Whether the cursor tracking is active.
  85. *
  86. * @type {boolean}
  87. * @private
  88. */
  89. goog.ui.AdvancedTooltip.prototype.tracking_ = false;
  90. /**
  91. * Sets margin around the tooltip where the cursor is allowed without dismissing
  92. * the tooltip.
  93. *
  94. * @param {goog.math.Box=} opt_box The margin around the tooltip.
  95. */
  96. goog.ui.AdvancedTooltip.prototype.setHotSpotPadding = function(opt_box) {
  97. this.hotSpotPadding_ = opt_box || null;
  98. };
  99. /**
  100. * @return {goog.math.Box} box The margin around the tooltip where the cursor is
  101. * allowed without dismissing the tooltip.
  102. */
  103. goog.ui.AdvancedTooltip.prototype.getHotSpotPadding = function() {
  104. return this.hotSpotPadding_;
  105. };
  106. /**
  107. * Sets whether to track the cursor and thereby close the tooltip if it moves
  108. * away from the tooltip and keep it open if it moves towards it.
  109. *
  110. * @param {boolean} b Whether to track the cursor.
  111. */
  112. goog.ui.AdvancedTooltip.prototype.setCursorTracking = function(b) {
  113. this.cursorTracking_ = b;
  114. };
  115. /**
  116. * @return {boolean} Whether to track the cursor and thereby close the tooltip
  117. * if it moves away from the tooltip and keep it open if it moves towards
  118. * it.
  119. */
  120. goog.ui.AdvancedTooltip.prototype.getCursorTracking = function() {
  121. return this.cursorTracking_;
  122. };
  123. /**
  124. * Sets delay in milliseconds before tooltips are hidden if cursor tracking is
  125. * enabled and the cursor is moving away from the tooltip.
  126. *
  127. * @param {number} delay The delay in milliseconds.
  128. */
  129. goog.ui.AdvancedTooltip.prototype.setCursorTrackingHideDelayMs = function(
  130. delay) {
  131. this.cursorTrackingHideDelayMs_ = delay;
  132. };
  133. /**
  134. * @return {number} The delay in milliseconds before tooltips are hidden if
  135. * cursor tracking is enabled and the cursor is moving away from the
  136. * tooltip.
  137. */
  138. goog.ui.AdvancedTooltip.prototype.getCursorTrackingHideDelayMs = function() {
  139. return this.cursorTrackingHideDelayMs_;
  140. };
  141. /**
  142. * Called after the popup is shown.
  143. * @protected
  144. * @override
  145. */
  146. goog.ui.AdvancedTooltip.prototype.onShow = function() {
  147. goog.ui.AdvancedTooltip.superClass_.onShow.call(this);
  148. this.boundingBox_ = goog.style.getBounds(this.getElement()).toBox();
  149. if (this.anchor) {
  150. this.anchorBox_ = goog.style.getBounds(this.anchor).toBox();
  151. }
  152. this.tracking_ = this.cursorTracking_;
  153. goog.events.listen(
  154. this.getDomHelper().getDocument(), goog.events.EventType.MOUSEMOVE,
  155. this.handleMouseMove, false, this);
  156. };
  157. /**
  158. * Called after the popup is hidden.
  159. * @protected
  160. * @override
  161. */
  162. goog.ui.AdvancedTooltip.prototype.onHide = function() {
  163. goog.events.unlisten(
  164. this.getDomHelper().getDocument(), goog.events.EventType.MOUSEMOVE,
  165. this.handleMouseMove, false, this);
  166. this.boundingBox_ = null;
  167. this.anchorBox_ = null;
  168. this.tracking_ = false;
  169. goog.ui.AdvancedTooltip.superClass_.onHide.call(this);
  170. };
  171. /**
  172. * Returns true if the mouse is in the tooltip.
  173. * @return {boolean} True if the mouse is in the tooltip.
  174. */
  175. goog.ui.AdvancedTooltip.prototype.isMouseInTooltip = function() {
  176. return this.isCoordinateInTooltip(this.cursorPosition);
  177. };
  178. /**
  179. * Checks whether the supplied coordinate is inside the tooltip, including
  180. * padding if any.
  181. * @param {goog.math.Coordinate} coord Coordinate being tested.
  182. * @return {boolean} Whether the coord is in the tooltip.
  183. * @override
  184. */
  185. goog.ui.AdvancedTooltip.prototype.isCoordinateInTooltip = function(coord) {
  186. // Check if coord is inside the bounding box of the tooltip
  187. if (this.hotSpotPadding_) {
  188. var offset = goog.style.getPageOffset(this.getElement());
  189. var size = goog.style.getSize(this.getElement());
  190. return offset.x - this.hotSpotPadding_.left <= coord.x &&
  191. coord.x <= offset.x + size.width + this.hotSpotPadding_.right &&
  192. offset.y - this.hotSpotPadding_.top <= coord.y &&
  193. coord.y <= offset.y + size.height + this.hotSpotPadding_.bottom;
  194. }
  195. return goog.ui.AdvancedTooltip.superClass_.isCoordinateInTooltip.call(
  196. this, coord);
  197. };
  198. /**
  199. * Checks if supplied coordinate is in the tooltip, its triggering anchor, or
  200. * a tooltip that has been triggered by a child of this tooltip.
  201. * Called from handleMouseMove to determine if hide timer should be started,
  202. * and from maybeHide to determine if tooltip should be hidden.
  203. * @param {goog.math.Coordinate} coord Coordinate being tested.
  204. * @return {boolean} Whether coordinate is in the anchor, the tooltip, or any
  205. * tooltip whose anchor is a child of this tooltip.
  206. * @private
  207. */
  208. goog.ui.AdvancedTooltip.prototype.isCoordinateActive_ = function(coord) {
  209. if ((this.anchorBox_ && this.anchorBox_.contains(coord)) ||
  210. this.isCoordinateInTooltip(coord)) {
  211. return true;
  212. }
  213. // Check if mouse might be in active child element.
  214. var childTooltip = this.getChildTooltip();
  215. return !!childTooltip && childTooltip.isCoordinateInTooltip(coord);
  216. };
  217. /**
  218. * Called by timer from mouse out handler. Hides tooltip if cursor is still
  219. * outside element and tooltip.
  220. * @param {?Element|undefined} el Anchor when hide timer was started.
  221. * @override
  222. */
  223. goog.ui.AdvancedTooltip.prototype.maybeHide = function(el) {
  224. this.hideTimer = undefined;
  225. if (el == this.anchor) {
  226. // Check if cursor is inside the bounding box of the tooltip or the element
  227. // that triggered it, or if tooltip is active (possibly due to receiving
  228. // the focus), or if there is a nested tooltip being shown.
  229. if (!this.isCoordinateActive_(this.cursorPosition) &&
  230. !this.getActiveElement() && !this.hasActiveChild()) {
  231. // Under certain circumstances gecko fires ghost mouse events with the
  232. // coordinates 0, 0 regardless of the cursors position.
  233. if (goog.userAgent.GECKO && this.cursorPosition.x == 0 &&
  234. this.cursorPosition.y == 0) {
  235. return;
  236. }
  237. this.setVisible(false);
  238. }
  239. }
  240. };
  241. /**
  242. * Handler for mouse move events.
  243. *
  244. * @param {goog.events.BrowserEvent} event Event object.
  245. * @protected
  246. * @override
  247. */
  248. goog.ui.AdvancedTooltip.prototype.handleMouseMove = function(event) {
  249. var startTimer = this.isVisible();
  250. if (this.boundingBox_) {
  251. var scroll = this.getDomHelper().getDocumentScroll();
  252. var c = new goog.math.Coordinate(
  253. event.clientX + scroll.x, event.clientY + scroll.y);
  254. if (this.isCoordinateActive_(c)) {
  255. startTimer = false;
  256. } else if (this.tracking_) {
  257. var prevDist =
  258. goog.math.Box.distance(this.boundingBox_, this.cursorPosition);
  259. var currDist = goog.math.Box.distance(this.boundingBox_, c);
  260. startTimer = currDist >= prevDist;
  261. }
  262. }
  263. if (startTimer) {
  264. this.startHideTimer();
  265. // Even though the mouse coordinate is not on the tooltip (or nested child),
  266. // they may have an active element because of a focus event. Don't let
  267. // that prevent us from taking down the tooltip(s) on this mouse move.
  268. this.setActiveElement(null);
  269. var childTooltip = this.getChildTooltip();
  270. if (childTooltip) {
  271. childTooltip.setActiveElement(null);
  272. }
  273. } else if (this.getState() == goog.ui.Tooltip.State.WAITING_TO_HIDE) {
  274. this.clearHideTimer();
  275. }
  276. goog.ui.AdvancedTooltip.superClass_.handleMouseMove.call(this, event);
  277. };
  278. /**
  279. * Handler for mouse over events for the tooltip element.
  280. *
  281. * @param {goog.events.BrowserEvent} event Event object.
  282. * @protected
  283. * @override
  284. */
  285. goog.ui.AdvancedTooltip.prototype.handleTooltipMouseOver = function(event) {
  286. if (this.getActiveElement() != this.getElement()) {
  287. this.tracking_ = false;
  288. this.setActiveElement(this.getElement());
  289. }
  290. };
  291. /**
  292. * Override hide delay with cursor tracking hide delay while tracking.
  293. * @return {number} Hide delay to use.
  294. * @override
  295. */
  296. goog.ui.AdvancedTooltip.prototype.getHideDelayMs = function() {
  297. return this.tracking_ ? this.cursorTrackingHideDelayMs_ :
  298. goog.ui.AdvancedTooltip.base(this, 'getHideDelayMs');
  299. };
  300. /**
  301. * Forces the recalculation of the hotspot on the next mouse over event.
  302. * @deprecated Not ever necessary to call this function. Hot spot is calculated
  303. * as necessary.
  304. */
  305. goog.ui.AdvancedTooltip.prototype.resetHotSpot = goog.nullFunction;