300 lines
8.7 KiB
JavaScript
300 lines
8.7 KiB
JavaScript
// 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.
|
|
*
|
|
*/
|
|
|
|
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
|
|
* @extends {goog.Disposable}
|
|
* @see ../demos/dragscrollsupport.html
|
|
*/
|
|
goog.fx.DragScrollSupport = function(containerNode, opt_margin,
|
|
opt_externalMouseMoveTracking) {
|
|
goog.Disposable.call(this);
|
|
|
|
/**
|
|
* 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}
|
|
* @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);
|
|
|
|
/**
|
|
* 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;
|
|
|
|
|
|
/**
|
|
* Whether scrolling should be constrained to happen only when the cursor is
|
|
* inside the container node.
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.fx.DragScrollSupport.prototype.constrainScroll_ = false;
|
|
|
|
|
|
/**
|
|
* Whether horizontal scrolling is allowed.
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.fx.DragScrollSupport.prototype.horizontalScrolling_ = true;
|
|
|
|
|
|
/**
|
|
* 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.top += xMargin;
|
|
bounds.height -= 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();
|
|
};
|