containerscroller.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. // Copyright 2008 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 Scroll behavior that can be added onto a container.
  16. * @author gboyer@google.com (Garry Boyer)
  17. */
  18. goog.provide('goog.ui.ContainerScroller');
  19. goog.require('goog.Disposable');
  20. goog.require('goog.Timer');
  21. goog.require('goog.events.EventHandler');
  22. goog.require('goog.style');
  23. goog.require('goog.ui.Component');
  24. goog.require('goog.ui.Container');
  25. /**
  26. * Plug-on scrolling behavior for a container.
  27. *
  28. * Use this to style containers, such as pop-up menus, to be scrolling, and
  29. * automatically keep the highlighted element visible.
  30. *
  31. * To use this, first style your container with the desired overflow
  32. * properties and height to achieve vertical scrolling. Also, the scrolling
  33. * div should have no vertical padding, for two reasons: it is difficult to
  34. * compensate for, and is generally not what you want due to the strange way
  35. * CSS handles padding on the scrolling dimension.
  36. *
  37. * The container must already be rendered before this may be constructed.
  38. *
  39. * @param {!goog.ui.Container} container The container to attach behavior to.
  40. * @constructor
  41. * @extends {goog.Disposable}
  42. * @final
  43. */
  44. goog.ui.ContainerScroller = function(container) {
  45. goog.Disposable.call(this);
  46. /**
  47. * The container that we are bestowing scroll behavior on.
  48. * @type {!goog.ui.Container}
  49. * @private
  50. */
  51. this.container_ = container;
  52. /**
  53. * Event handler for this object.
  54. * @type {!goog.events.EventHandler<!goog.ui.ContainerScroller>}
  55. * @private
  56. */
  57. this.eventHandler_ = new goog.events.EventHandler(this);
  58. this.eventHandler_.listen(
  59. container, goog.ui.Component.EventType.HIGHLIGHT, this.onHighlight_);
  60. this.eventHandler_.listen(
  61. container, goog.ui.Component.EventType.ENTER, this.onEnter_);
  62. this.eventHandler_.listen(
  63. container, goog.ui.Container.EventType.AFTER_SHOW, this.onAfterShow_);
  64. this.eventHandler_.listen(
  65. container, goog.ui.Component.EventType.HIDE, this.onHide_);
  66. // TODO(gboyer): Allow a ContainerScroller to be attached with a Container
  67. // before the container is rendered.
  68. this.doScrolling_(true);
  69. };
  70. goog.inherits(goog.ui.ContainerScroller, goog.Disposable);
  71. /**
  72. * The last target the user hovered over.
  73. *
  74. * @see #onEnter_
  75. * @type {goog.ui.Component}
  76. * @private
  77. */
  78. goog.ui.ContainerScroller.prototype.lastEnterTarget_ = null;
  79. /**
  80. * The scrollTop of the container before it was hidden.
  81. * Used to restore the scroll position when the container is shown again.
  82. * @type {?number}
  83. * @private
  84. */
  85. goog.ui.ContainerScroller.prototype.scrollTopBeforeHide_ = null;
  86. /**
  87. * Whether we are disabling the default handler for hovering.
  88. *
  89. * @see #onEnter_
  90. * @see #temporarilyDisableHover_
  91. * @type {boolean}
  92. * @private
  93. */
  94. goog.ui.ContainerScroller.prototype.disableHover_ = false;
  95. /**
  96. * Handles hover events on the container's children.
  97. *
  98. * Helps enforce two constraints: scrolling should not cause mouse highlights,
  99. * and mouse highlights should not cause scrolling.
  100. *
  101. * @param {goog.events.Event} e The container's ENTER event.
  102. * @private
  103. */
  104. goog.ui.ContainerScroller.prototype.onEnter_ = function(e) {
  105. if (this.disableHover_) {
  106. // The container was scrolled recently. Since the mouse may be over the
  107. // container, stop the default action of the ENTER event from causing
  108. // highlights.
  109. e.preventDefault();
  110. } else {
  111. // The mouse is moving and causing hover events. Stop the resulting
  112. // highlight (if it happens) from causing a scroll.
  113. this.lastEnterTarget_ = /** @type {goog.ui.Component} */ (e.target);
  114. }
  115. };
  116. /**
  117. * Handles highlight events on the container's children.
  118. * @param {goog.events.Event} e The container's highlight event.
  119. * @private
  120. */
  121. goog.ui.ContainerScroller.prototype.onHighlight_ = function(e) {
  122. this.doScrolling_();
  123. };
  124. /**
  125. * Handles AFTER_SHOW events on the container. Makes the container
  126. * scroll to the previously scrolled position (if there was one),
  127. * then adjust it to make the highlighted element be in view (if there is one).
  128. * If there was no previous scroll position, then center the highlighted
  129. * element (if there is one).
  130. * @param {goog.events.Event} e The container's AFTER_SHOW event.
  131. * @private
  132. */
  133. goog.ui.ContainerScroller.prototype.onAfterShow_ = function(e) {
  134. if (this.scrollTopBeforeHide_ != null) {
  135. this.container_.getElement().scrollTop = this.scrollTopBeforeHide_;
  136. // Make sure the highlighted item is still visible, in case the list
  137. // or its hilighted item has changed.
  138. this.doScrolling_(false);
  139. } else {
  140. this.doScrolling_(true);
  141. }
  142. };
  143. /**
  144. * Handles hide events on the container. Clears out the last enter target,
  145. * since it is no longer applicable, and remembers the scroll position of
  146. * the menu so that it can be restored when the menu is reopened.
  147. * @param {goog.events.Event} e The container's hide event.
  148. * @private
  149. */
  150. goog.ui.ContainerScroller.prototype.onHide_ = function(e) {
  151. if (e.target == this.container_) {
  152. this.lastEnterTarget_ = null;
  153. this.scrollTopBeforeHide_ = this.container_.getElement().scrollTop;
  154. }
  155. };
  156. /**
  157. * Centers the currently highlighted item, if this is scrollable.
  158. * @param {boolean=} opt_center Whether to center the highlighted element
  159. * rather than simply ensure it is in view. Useful for the first
  160. * render.
  161. * @private
  162. */
  163. goog.ui.ContainerScroller.prototype.doScrolling_ = function(opt_center) {
  164. var highlighted = this.container_.getHighlighted();
  165. // Only scroll if we're visible and there is a highlighted item.
  166. if (this.container_.isVisible() && highlighted &&
  167. highlighted != this.lastEnterTarget_) {
  168. var element = this.container_.getElement();
  169. goog.style.scrollIntoContainerView(
  170. highlighted.getElement(), element, opt_center);
  171. this.temporarilyDisableHover_();
  172. this.lastEnterTarget_ = null;
  173. }
  174. };
  175. /**
  176. * Temporarily disables hover events from changing highlight.
  177. * @see #onEnter_
  178. * @private
  179. */
  180. goog.ui.ContainerScroller.prototype.temporarilyDisableHover_ = function() {
  181. this.disableHover_ = true;
  182. goog.Timer.callOnce(function() { this.disableHover_ = false; }, 0, this);
  183. };
  184. /** @override */
  185. goog.ui.ContainerScroller.prototype.disposeInternal = function() {
  186. goog.ui.ContainerScroller.superClass_.disposeInternal.call(this);
  187. this.eventHandler_.dispose();
  188. this.lastEnterTarget_ = null;
  189. };