Path: blob/trunk/third_party/closure/goog/fx/dragscrollsupport.js
2868 views
// Copyright 2008 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Class to support scrollable containers for drag and drop.16*17* @author [email protected] (Damian Gajda)18*/1920goog.provide('goog.fx.DragScrollSupport');2122goog.require('goog.Disposable');23goog.require('goog.Timer');24goog.require('goog.dom');25goog.require('goog.events.EventHandler');26goog.require('goog.events.EventType');27goog.require('goog.math.Coordinate');28goog.require('goog.style');29303132/**33* A scroll support class. Currently this class will automatically scroll34* a scrollable container node and scroll it by a fixed amount at a timed35* interval when the mouse is moved above or below the container or in vertical36* margin areas. Intended for use in drag and drop. This could potentially be37* made more general and could support horizontal scrolling.38*39* @param {Element} containerNode A container that can be scrolled.40* @param {number=} opt_margin Optional margin to use while scrolling.41* @param {boolean=} opt_externalMouseMoveTracking Whether mouse move events42* are tracked externally by the client object which calls the mouse move43* event handler, useful when events are generated for more than one source44* element and/or are not real mousemove events.45* @constructor46* @struct47* @extends {goog.Disposable}48* @see ../demos/dragscrollsupport.html49*/50goog.fx.DragScrollSupport = function(51containerNode, opt_margin, opt_externalMouseMoveTracking) {52goog.fx.DragScrollSupport.base(this, 'constructor');5354/**55* Whether scrolling should be constrained to happen only when the cursor is56* inside the container node.57* @private {boolean}58*/59this.constrainScroll_ = false;6061/**62* Whether horizontal scrolling is allowed.63* @private {boolean}64*/65this.horizontalScrolling_ = true;6667/**68* The container to be scrolled.69* @type {Element}70* @private71*/72this.containerNode_ = containerNode;7374/**75* Scroll timer that will scroll the container until it is stopped.76* It will scroll when the mouse is outside the scrolling area of the77* container.78*79* @type {goog.Timer}80* @private81*/82this.scrollTimer_ = new goog.Timer(goog.fx.DragScrollSupport.TIMER_STEP_);8384/**85* EventHandler used to set up and tear down listeners.86* @type {goog.events.EventHandler<!goog.fx.DragScrollSupport>}87* @private88*/89this.eventHandler_ = new goog.events.EventHandler(this);9091/**92* The current scroll delta.93* @type {goog.math.Coordinate}94* @private95*/96this.scrollDelta_ = new goog.math.Coordinate();9798/**99* The container bounds.100* @type {goog.math.Rect}101* @private102*/103this.containerBounds_ = goog.style.getBounds(containerNode);104105/**106* The margin for triggering a scroll.107* @type {number}108* @private109*/110this.margin_ = opt_margin || 0;111112/**113* The bounding rectangle which if left triggers scrolling.114* @type {goog.math.Rect}115* @private116*/117this.scrollBounds_ = opt_margin ?118this.constrainBounds_(this.containerBounds_.clone()) :119this.containerBounds_;120121this.setupListeners_(!!opt_externalMouseMoveTracking);122};123goog.inherits(goog.fx.DragScrollSupport, goog.Disposable);124125126/**127* The scroll timer step in ms.128* @type {number}129* @private130*/131goog.fx.DragScrollSupport.TIMER_STEP_ = 50;132133134/**135* The scroll step in pixels.136* @type {number}137* @private138*/139goog.fx.DragScrollSupport.SCROLL_STEP_ = 8;140141142/**143* The suggested scrolling margin.144* @type {number}145*/146goog.fx.DragScrollSupport.MARGIN = 32;147148149/**150* Sets whether scrolling should be constrained to happen only when the cursor151* is inside the container node.152* NOTE: If a margin is not set, then it does not make sense to153* contain the scroll, because in that case scroll will never be triggered.154* @param {boolean} constrain Whether scrolling should be constrained to happen155* only when the cursor is inside the container node.156*/157goog.fx.DragScrollSupport.prototype.setConstrainScroll = function(constrain) {158this.constrainScroll_ = !!this.margin_ && constrain;159};160161162/**163* Sets whether horizontal scrolling is allowed.164* @param {boolean} scrolling Whether horizontal scrolling is allowed.165*/166goog.fx.DragScrollSupport.prototype.setHorizontalScrolling = function(167scrolling) {168this.horizontalScrolling_ = scrolling;169};170171172/**173* Constrains the container bounds with respect to the margin.174*175* @param {goog.math.Rect} bounds The container element.176* @return {goog.math.Rect} The bounding rectangle used to calculate scrolling177* direction.178* @private179*/180goog.fx.DragScrollSupport.prototype.constrainBounds_ = function(bounds) {181var margin = this.margin_;182if (margin) {183var quarterHeight = bounds.height * 0.25;184var yMargin = Math.min(margin, quarterHeight);185bounds.top += yMargin;186bounds.height -= 2 * yMargin;187188var quarterWidth = bounds.width * 0.25;189var xMargin = Math.min(margin, quarterWidth);190bounds.left += xMargin;191bounds.width -= 2 * xMargin;192}193return bounds;194};195196197/**198* Attaches listeners and activates automatic scrolling.199* @param {boolean} externalMouseMoveTracking Whether to enable internal200* mouse move event handling.201* @private202*/203goog.fx.DragScrollSupport.prototype.setupListeners_ = function(204externalMouseMoveTracking) {205if (!externalMouseMoveTracking) {206// Track mouse pointer position to determine scroll direction.207this.eventHandler_.listen(208goog.dom.getOwnerDocument(this.containerNode_),209goog.events.EventType.MOUSEMOVE, this.onMouseMove);210}211212// Scroll with a constant speed.213this.eventHandler_.listen(this.scrollTimer_, goog.Timer.TICK, this.onTick_);214};215216217/**218* Handler for timer tick event, scrolls the container by one scroll step if219* needed.220* @param {goog.events.Event} event Timer tick event.221* @private222*/223goog.fx.DragScrollSupport.prototype.onTick_ = function(event) {224this.containerNode_.scrollTop += this.scrollDelta_.y;225this.containerNode_.scrollLeft += this.scrollDelta_.x;226};227228229/**230* Handler for mouse moves events.231* @param {goog.events.Event} event Mouse move event.232*/233goog.fx.DragScrollSupport.prototype.onMouseMove = function(event) {234var deltaX = this.horizontalScrolling_ ?235this.calculateScrollDelta(236event.clientX, this.scrollBounds_.left, this.scrollBounds_.width) :2370;238var deltaY = this.calculateScrollDelta(239event.clientY, this.scrollBounds_.top, this.scrollBounds_.height);240this.scrollDelta_.x = deltaX;241this.scrollDelta_.y = deltaY;242243// If the scroll data is 0 or the event fired outside of the244// bounds of the container node.245if ((!deltaX && !deltaY) ||246(this.constrainScroll_ &&247!this.isInContainerBounds_(event.clientX, event.clientY))) {248this.scrollTimer_.stop();249} else if (!this.scrollTimer_.enabled) {250this.scrollTimer_.start();251}252};253254255/**256* Gets whether the input coordinate is in the container bounds.257* @param {number} x The x coordinate.258* @param {number} y The y coordinate.259* @return {boolean} Whether the input coordinate is in the container bounds.260* @private261*/262goog.fx.DragScrollSupport.prototype.isInContainerBounds_ = function(x, y) {263var containerBounds = this.containerBounds_;264return containerBounds.left <= x &&265containerBounds.left + containerBounds.width >= x &&266containerBounds.top <= y &&267containerBounds.top + containerBounds.height >= y;268};269270271/**272* Calculates scroll delta.273*274* @param {number} coordinate Current mouse pointer coordinate.275* @param {number} min The coordinate value below which scrolling up should be276* started.277* @param {number} rangeLength The length of the range in which scrolling should278* be disabled and above which scrolling down should be started.279* @return {number} The calculated scroll delta.280* @protected281*/282goog.fx.DragScrollSupport.prototype.calculateScrollDelta = function(283coordinate, min, rangeLength) {284var delta = 0;285if (coordinate < min) {286delta = -goog.fx.DragScrollSupport.SCROLL_STEP_;287} else if (coordinate > min + rangeLength) {288delta = goog.fx.DragScrollSupport.SCROLL_STEP_;289}290return delta;291};292293294/** @override */295goog.fx.DragScrollSupport.prototype.disposeInternal = function() {296goog.fx.DragScrollSupport.superClass_.disposeInternal.call(this);297this.eventHandler_.dispose();298this.scrollTimer_.dispose();299};300301302