Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/fx/dragscrollsupport.js
2868 views
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
/**
16
* @fileoverview Class to support scrollable containers for drag and drop.
17
*
18
* @author [email protected] (Damian Gajda)
19
*/
20
21
goog.provide('goog.fx.DragScrollSupport');
22
23
goog.require('goog.Disposable');
24
goog.require('goog.Timer');
25
goog.require('goog.dom');
26
goog.require('goog.events.EventHandler');
27
goog.require('goog.events.EventType');
28
goog.require('goog.math.Coordinate');
29
goog.require('goog.style');
30
31
32
33
/**
34
* A scroll support class. Currently this class will automatically scroll
35
* a scrollable container node and scroll it by a fixed amount at a timed
36
* interval when the mouse is moved above or below the container or in vertical
37
* margin areas. Intended for use in drag and drop. This could potentially be
38
* made more general and could support horizontal scrolling.
39
*
40
* @param {Element} containerNode A container that can be scrolled.
41
* @param {number=} opt_margin Optional margin to use while scrolling.
42
* @param {boolean=} opt_externalMouseMoveTracking Whether mouse move events
43
* are tracked externally by the client object which calls the mouse move
44
* event handler, useful when events are generated for more than one source
45
* element and/or are not real mousemove events.
46
* @constructor
47
* @struct
48
* @extends {goog.Disposable}
49
* @see ../demos/dragscrollsupport.html
50
*/
51
goog.fx.DragScrollSupport = function(
52
containerNode, opt_margin, opt_externalMouseMoveTracking) {
53
goog.fx.DragScrollSupport.base(this, 'constructor');
54
55
/**
56
* Whether scrolling should be constrained to happen only when the cursor is
57
* inside the container node.
58
* @private {boolean}
59
*/
60
this.constrainScroll_ = false;
61
62
/**
63
* Whether horizontal scrolling is allowed.
64
* @private {boolean}
65
*/
66
this.horizontalScrolling_ = true;
67
68
/**
69
* The container to be scrolled.
70
* @type {Element}
71
* @private
72
*/
73
this.containerNode_ = containerNode;
74
75
/**
76
* Scroll timer that will scroll the container until it is stopped.
77
* It will scroll when the mouse is outside the scrolling area of the
78
* container.
79
*
80
* @type {goog.Timer}
81
* @private
82
*/
83
this.scrollTimer_ = new goog.Timer(goog.fx.DragScrollSupport.TIMER_STEP_);
84
85
/**
86
* EventHandler used to set up and tear down listeners.
87
* @type {goog.events.EventHandler<!goog.fx.DragScrollSupport>}
88
* @private
89
*/
90
this.eventHandler_ = new goog.events.EventHandler(this);
91
92
/**
93
* The current scroll delta.
94
* @type {goog.math.Coordinate}
95
* @private
96
*/
97
this.scrollDelta_ = new goog.math.Coordinate();
98
99
/**
100
* The container bounds.
101
* @type {goog.math.Rect}
102
* @private
103
*/
104
this.containerBounds_ = goog.style.getBounds(containerNode);
105
106
/**
107
* The margin for triggering a scroll.
108
* @type {number}
109
* @private
110
*/
111
this.margin_ = opt_margin || 0;
112
113
/**
114
* The bounding rectangle which if left triggers scrolling.
115
* @type {goog.math.Rect}
116
* @private
117
*/
118
this.scrollBounds_ = opt_margin ?
119
this.constrainBounds_(this.containerBounds_.clone()) :
120
this.containerBounds_;
121
122
this.setupListeners_(!!opt_externalMouseMoveTracking);
123
};
124
goog.inherits(goog.fx.DragScrollSupport, goog.Disposable);
125
126
127
/**
128
* The scroll timer step in ms.
129
* @type {number}
130
* @private
131
*/
132
goog.fx.DragScrollSupport.TIMER_STEP_ = 50;
133
134
135
/**
136
* The scroll step in pixels.
137
* @type {number}
138
* @private
139
*/
140
goog.fx.DragScrollSupport.SCROLL_STEP_ = 8;
141
142
143
/**
144
* The suggested scrolling margin.
145
* @type {number}
146
*/
147
goog.fx.DragScrollSupport.MARGIN = 32;
148
149
150
/**
151
* Sets whether scrolling should be constrained to happen only when the cursor
152
* is inside the container node.
153
* NOTE: If a margin is not set, then it does not make sense to
154
* contain the scroll, because in that case scroll will never be triggered.
155
* @param {boolean} constrain Whether scrolling should be constrained to happen
156
* only when the cursor is inside the container node.
157
*/
158
goog.fx.DragScrollSupport.prototype.setConstrainScroll = function(constrain) {
159
this.constrainScroll_ = !!this.margin_ && constrain;
160
};
161
162
163
/**
164
* Sets whether horizontal scrolling is allowed.
165
* @param {boolean} scrolling Whether horizontal scrolling is allowed.
166
*/
167
goog.fx.DragScrollSupport.prototype.setHorizontalScrolling = function(
168
scrolling) {
169
this.horizontalScrolling_ = scrolling;
170
};
171
172
173
/**
174
* Constrains the container bounds with respect to the margin.
175
*
176
* @param {goog.math.Rect} bounds The container element.
177
* @return {goog.math.Rect} The bounding rectangle used to calculate scrolling
178
* direction.
179
* @private
180
*/
181
goog.fx.DragScrollSupport.prototype.constrainBounds_ = function(bounds) {
182
var margin = this.margin_;
183
if (margin) {
184
var quarterHeight = bounds.height * 0.25;
185
var yMargin = Math.min(margin, quarterHeight);
186
bounds.top += yMargin;
187
bounds.height -= 2 * yMargin;
188
189
var quarterWidth = bounds.width * 0.25;
190
var xMargin = Math.min(margin, quarterWidth);
191
bounds.left += xMargin;
192
bounds.width -= 2 * xMargin;
193
}
194
return bounds;
195
};
196
197
198
/**
199
* Attaches listeners and activates automatic scrolling.
200
* @param {boolean} externalMouseMoveTracking Whether to enable internal
201
* mouse move event handling.
202
* @private
203
*/
204
goog.fx.DragScrollSupport.prototype.setupListeners_ = function(
205
externalMouseMoveTracking) {
206
if (!externalMouseMoveTracking) {
207
// Track mouse pointer position to determine scroll direction.
208
this.eventHandler_.listen(
209
goog.dom.getOwnerDocument(this.containerNode_),
210
goog.events.EventType.MOUSEMOVE, this.onMouseMove);
211
}
212
213
// Scroll with a constant speed.
214
this.eventHandler_.listen(this.scrollTimer_, goog.Timer.TICK, this.onTick_);
215
};
216
217
218
/**
219
* Handler for timer tick event, scrolls the container by one scroll step if
220
* needed.
221
* @param {goog.events.Event} event Timer tick event.
222
* @private
223
*/
224
goog.fx.DragScrollSupport.prototype.onTick_ = function(event) {
225
this.containerNode_.scrollTop += this.scrollDelta_.y;
226
this.containerNode_.scrollLeft += this.scrollDelta_.x;
227
};
228
229
230
/**
231
* Handler for mouse moves events.
232
* @param {goog.events.Event} event Mouse move event.
233
*/
234
goog.fx.DragScrollSupport.prototype.onMouseMove = function(event) {
235
var deltaX = this.horizontalScrolling_ ?
236
this.calculateScrollDelta(
237
event.clientX, this.scrollBounds_.left, this.scrollBounds_.width) :
238
0;
239
var deltaY = this.calculateScrollDelta(
240
event.clientY, this.scrollBounds_.top, this.scrollBounds_.height);
241
this.scrollDelta_.x = deltaX;
242
this.scrollDelta_.y = deltaY;
243
244
// If the scroll data is 0 or the event fired outside of the
245
// bounds of the container node.
246
if ((!deltaX && !deltaY) ||
247
(this.constrainScroll_ &&
248
!this.isInContainerBounds_(event.clientX, event.clientY))) {
249
this.scrollTimer_.stop();
250
} else if (!this.scrollTimer_.enabled) {
251
this.scrollTimer_.start();
252
}
253
};
254
255
256
/**
257
* Gets whether the input coordinate is in the container bounds.
258
* @param {number} x The x coordinate.
259
* @param {number} y The y coordinate.
260
* @return {boolean} Whether the input coordinate is in the container bounds.
261
* @private
262
*/
263
goog.fx.DragScrollSupport.prototype.isInContainerBounds_ = function(x, y) {
264
var containerBounds = this.containerBounds_;
265
return containerBounds.left <= x &&
266
containerBounds.left + containerBounds.width >= x &&
267
containerBounds.top <= y &&
268
containerBounds.top + containerBounds.height >= y;
269
};
270
271
272
/**
273
* Calculates scroll delta.
274
*
275
* @param {number} coordinate Current mouse pointer coordinate.
276
* @param {number} min The coordinate value below which scrolling up should be
277
* started.
278
* @param {number} rangeLength The length of the range in which scrolling should
279
* be disabled and above which scrolling down should be started.
280
* @return {number} The calculated scroll delta.
281
* @protected
282
*/
283
goog.fx.DragScrollSupport.prototype.calculateScrollDelta = function(
284
coordinate, min, rangeLength) {
285
var delta = 0;
286
if (coordinate < min) {
287
delta = -goog.fx.DragScrollSupport.SCROLL_STEP_;
288
} else if (coordinate > min + rangeLength) {
289
delta = goog.fx.DragScrollSupport.SCROLL_STEP_;
290
}
291
return delta;
292
};
293
294
295
/** @override */
296
goog.fx.DragScrollSupport.prototype.disposeInternal = function() {
297
goog.fx.DragScrollSupport.superClass_.disposeInternal.call(this);
298
this.eventHandler_.dispose();
299
this.scrollTimer_.dispose();
300
};
301
302