123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- // Copyright 2008 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- /**
- * @fileoverview Class to support scrollable containers for drag and drop.
- *
- * @author dgajda@google.com (Damian Gajda)
- */
- goog.provide('goog.fx.DragScrollSupport');
- goog.require('goog.Disposable');
- goog.require('goog.Timer');
- goog.require('goog.dom');
- goog.require('goog.events.EventHandler');
- goog.require('goog.events.EventType');
- goog.require('goog.math.Coordinate');
- goog.require('goog.style');
- /**
- * A scroll support class. Currently this class will automatically scroll
- * a scrollable container node and scroll it by a fixed amount at a timed
- * interval when the mouse is moved above or below the container or in vertical
- * margin areas. Intended for use in drag and drop. This could potentially be
- * made more general and could support horizontal scrolling.
- *
- * @param {Element} containerNode A container that can be scrolled.
- * @param {number=} opt_margin Optional margin to use while scrolling.
- * @param {boolean=} opt_externalMouseMoveTracking Whether mouse move events
- * are tracked externally by the client object which calls the mouse move
- * event handler, useful when events are generated for more than one source
- * element and/or are not real mousemove events.
- * @constructor
- * @struct
- * @extends {goog.Disposable}
- * @see ../demos/dragscrollsupport.html
- */
- goog.fx.DragScrollSupport = function(
- containerNode, opt_margin, opt_externalMouseMoveTracking) {
- goog.fx.DragScrollSupport.base(this, 'constructor');
- /**
- * Whether scrolling should be constrained to happen only when the cursor is
- * inside the container node.
- * @private {boolean}
- */
- this.constrainScroll_ = false;
- /**
- * Whether horizontal scrolling is allowed.
- * @private {boolean}
- */
- this.horizontalScrolling_ = true;
- /**
- * The container to be scrolled.
- * @type {Element}
- * @private
- */
- this.containerNode_ = containerNode;
- /**
- * Scroll timer that will scroll the container until it is stopped.
- * It will scroll when the mouse is outside the scrolling area of the
- * container.
- *
- * @type {goog.Timer}
- * @private
- */
- this.scrollTimer_ = new goog.Timer(goog.fx.DragScrollSupport.TIMER_STEP_);
- /**
- * EventHandler used to set up and tear down listeners.
- * @type {goog.events.EventHandler<!goog.fx.DragScrollSupport>}
- * @private
- */
- this.eventHandler_ = new goog.events.EventHandler(this);
- /**
- * The current scroll delta.
- * @type {goog.math.Coordinate}
- * @private
- */
- this.scrollDelta_ = new goog.math.Coordinate();
- /**
- * The container bounds.
- * @type {goog.math.Rect}
- * @private
- */
- this.containerBounds_ = goog.style.getBounds(containerNode);
- if (containerNode.tagName === 'BODY') {
- var size = goog.dom.getViewportSize();
- this.containerBounds_.height = size.height;
- this.containerBounds_.width = size.width;
- }
- /**
- * The margin for triggering a scroll.
- * @type {number}
- * @private
- */
- this.margin_ = opt_margin || 0;
- /**
- * The bounding rectangle which if left triggers scrolling.
- * @type {goog.math.Rect}
- * @private
- */
- this.scrollBounds_ = opt_margin ?
- this.constrainBounds_(this.containerBounds_.clone()) :
- this.containerBounds_;
- this.setupListeners_(!!opt_externalMouseMoveTracking);
- };
- goog.inherits(goog.fx.DragScrollSupport, goog.Disposable);
- /**
- * The scroll timer step in ms.
- * @type {number}
- * @private
- */
- goog.fx.DragScrollSupport.TIMER_STEP_ = 50;
- /**
- * The scroll step in pixels.
- * @type {number}
- * @private
- */
- goog.fx.DragScrollSupport.SCROLL_STEP_ = 8;
- /**
- * The suggested scrolling margin.
- * @type {number}
- */
- goog.fx.DragScrollSupport.MARGIN = 32;
- /**
- * Sets whether scrolling should be constrained to happen only when the cursor
- * is inside the container node.
- * NOTE: If a margin is not set, then it does not make sense to
- * contain the scroll, because in that case scroll will never be triggered.
- * @param {boolean} constrain Whether scrolling should be constrained to happen
- * only when the cursor is inside the container node.
- */
- goog.fx.DragScrollSupport.prototype.setConstrainScroll = function(constrain) {
- this.constrainScroll_ = !!this.margin_ && constrain;
- };
- /**
- * Sets whether horizontal scrolling is allowed.
- * @param {boolean} scrolling Whether horizontal scrolling is allowed.
- */
- goog.fx.DragScrollSupport.prototype.setHorizontalScrolling = function(
- scrolling) {
- this.horizontalScrolling_ = scrolling;
- };
- /**
- * Constrains the container bounds with respect to the margin.
- *
- * @param {goog.math.Rect} bounds The container element.
- * @return {goog.math.Rect} The bounding rectangle used to calculate scrolling
- * direction.
- * @private
- */
- goog.fx.DragScrollSupport.prototype.constrainBounds_ = function(bounds) {
- var margin = this.margin_;
- if (margin) {
- var quarterHeight = bounds.height * 0.25;
- var yMargin = Math.min(margin, quarterHeight);
- bounds.top += yMargin;
- bounds.height -= 2 * yMargin;
- var quarterWidth = bounds.width * 0.25;
- var xMargin = Math.min(margin, quarterWidth);
- bounds.left += xMargin;
- bounds.width -= 2 * xMargin;
- }
- return bounds;
- };
- /**
- * Attaches listeners and activates automatic scrolling.
- * @param {boolean} externalMouseMoveTracking Whether to enable internal
- * mouse move event handling.
- * @private
- */
- goog.fx.DragScrollSupport.prototype.setupListeners_ = function(
- externalMouseMoveTracking) {
- if (!externalMouseMoveTracking) {
- // Track mouse pointer position to determine scroll direction.
- this.eventHandler_.listen(
- goog.dom.getOwnerDocument(this.containerNode_),
- goog.events.EventType.MOUSEMOVE, this.onMouseMove);
- }
- // Scroll with a constant speed.
- this.eventHandler_.listen(this.scrollTimer_, goog.Timer.TICK, this.onTick_);
- };
- /**
- * Handler for timer tick event, scrolls the container by one scroll step if
- * needed.
- * @param {goog.events.Event} event Timer tick event.
- * @private
- */
- goog.fx.DragScrollSupport.prototype.onTick_ = function(event) {
- this.containerNode_.scrollTop += this.scrollDelta_.y;
- this.containerNode_.scrollLeft += this.scrollDelta_.x;
- };
- /**
- * Handler for mouse moves events.
- * @param {goog.events.Event} event Mouse move event.
- */
- goog.fx.DragScrollSupport.prototype.onMouseMove = function(event) {
- var deltaX = this.horizontalScrolling_ ?
- this.calculateScrollDelta(
- event.clientX, this.scrollBounds_.left, this.scrollBounds_.width) :
- 0;
- var deltaY = this.calculateScrollDelta(
- event.clientY, this.scrollBounds_.top, this.scrollBounds_.height);
- this.scrollDelta_.x = deltaX;
- this.scrollDelta_.y = deltaY;
- // If the scroll data is 0 or the event fired outside of the
- // bounds of the container node.
- if ((!deltaX && !deltaY) ||
- (this.constrainScroll_ &&
- !this.isInContainerBounds_(event.clientX, event.clientY))) {
- this.scrollTimer_.stop();
- } else if (!this.scrollTimer_.enabled) {
- this.scrollTimer_.start();
- }
- };
- /**
- * Gets whether the input coordinate is in the container bounds.
- * @param {number} x The x coordinate.
- * @param {number} y The y coordinate.
- * @return {boolean} Whether the input coordinate is in the container bounds.
- * @private
- */
- goog.fx.DragScrollSupport.prototype.isInContainerBounds_ = function(x, y) {
- var containerBounds = this.containerBounds_;
- return containerBounds.left <= x &&
- containerBounds.left + containerBounds.width >= x &&
- containerBounds.top <= y &&
- containerBounds.top + containerBounds.height >= y;
- };
- /**
- * Calculates scroll delta.
- *
- * @param {number} coordinate Current mouse pointer coordinate.
- * @param {number} min The coordinate value below which scrolling up should be
- * started.
- * @param {number} rangeLength The length of the range in which scrolling should
- * be disabled and above which scrolling down should be started.
- * @return {number} The calculated scroll delta.
- * @protected
- */
- goog.fx.DragScrollSupport.prototype.calculateScrollDelta = function(
- coordinate, min, rangeLength) {
- var delta = 0;
- if (coordinate < min) {
- delta = -goog.fx.DragScrollSupport.SCROLL_STEP_;
- } else if (coordinate > min + rangeLength) {
- delta = goog.fx.DragScrollSupport.SCROLL_STEP_;
- }
- return delta;
- };
- /** @override */
- goog.fx.DragScrollSupport.prototype.disposeInternal = function() {
- goog.fx.DragScrollSupport.superClass_.disposeInternal.call(this);
- this.eventHandler_.dispose();
- this.scrollTimer_.dispose();
- };
|