Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/fx/abstractdragdrop.js
2868 views
1
// Copyright 2006 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 Abstract Base Class for Drag and Drop.
17
*
18
* Provides functionality for implementing drag and drop classes. Also provides
19
* support classes and events.
20
*
21
* @author [email protected] (Emil A Eklund)
22
*/
23
24
goog.provide('goog.fx.AbstractDragDrop');
25
goog.provide('goog.fx.AbstractDragDrop.EventType');
26
goog.provide('goog.fx.DragDropEvent');
27
goog.provide('goog.fx.DragDropItem');
28
29
goog.require('goog.array');
30
goog.require('goog.asserts');
31
goog.require('goog.dom');
32
goog.require('goog.dom.classlist');
33
goog.require('goog.events');
34
goog.require('goog.events.Event');
35
goog.require('goog.events.EventHandler');
36
goog.require('goog.events.EventTarget');
37
goog.require('goog.events.EventType');
38
goog.require('goog.fx.Dragger');
39
goog.require('goog.math.Box');
40
goog.require('goog.math.Coordinate');
41
goog.require('goog.style');
42
43
44
45
/**
46
* Abstract class that provides reusable functionality for implementing drag
47
* and drop functionality.
48
*
49
* This class also allows clients to define their own subtargeting function
50
* so that drop areas can have finer granularity then a singe element. This is
51
* accomplished by using a client provided function to map from element and
52
* coordinates to a subregion id.
53
*
54
* This class can also be made aware of scrollable containers that contain
55
* drop targets by calling addScrollableContainer. This will cause dnd to
56
* take changing scroll positions into account while a drag is occurring.
57
*
58
* @extends {goog.events.EventTarget}
59
* @constructor
60
* @struct
61
*/
62
goog.fx.AbstractDragDrop = function() {
63
goog.fx.AbstractDragDrop.base(this, 'constructor');
64
65
/**
66
* List of items that makes up the drag source or drop target.
67
* @protected {Array<goog.fx.DragDropItem>}
68
* @suppress {underscore|visibility}
69
*/
70
this.items_ = [];
71
72
/**
73
* List of associated drop targets.
74
* @private {Array<goog.fx.AbstractDragDrop>}
75
*/
76
this.targets_ = [];
77
78
/**
79
* Scrollable containers to account for during drag
80
* @private {Array<goog.fx.ScrollableContainer_>}
81
*/
82
this.scrollableContainers_ = [];
83
84
/**
85
* Flag indicating if it's a drag source, set by addTarget.
86
* @private {boolean}
87
*/
88
this.isSource_ = false;
89
90
/**
91
* Flag indicating if it's a drop target, set when added as target to another
92
* DragDrop object.
93
* @private {boolean}
94
*/
95
this.isTarget_ = false;
96
97
/**
98
* Subtargeting function accepting args:
99
* (goog.fx.DragDropItem, goog.math.Box, number, number)
100
* @private {?Function}
101
*/
102
this.subtargetFunction_;
103
104
/**
105
* Last active subtarget.
106
* @private {?Object}
107
*/
108
this.activeSubtarget_;
109
110
/**
111
* Class name to add to source elements being dragged. Set by setDragClass.
112
* @private {?string}
113
*/
114
this.dragClass_;
115
116
/**
117
* Class name to add to source elements. Set by setSourceClass.
118
* @private {?string}
119
*/
120
this.sourceClass_;
121
122
/**
123
* Class name to add to target elements. Set by setTargetClass.
124
* @private {?string}
125
*/
126
this.targetClass_;
127
128
/**
129
* The SCROLL event target used to make drag element follow scrolling.
130
* @private {?EventTarget}
131
*/
132
this.scrollTarget_;
133
134
/**
135
* Dummy target, {@see maybeCreateDummyTargetForPosition_}.
136
* @private {?goog.fx.ActiveDropTarget_}
137
*/
138
this.dummyTarget_;
139
140
/**
141
* Whether the object has been initialized.
142
* @private {boolean}
143
*/
144
this.initialized_ = false;
145
146
/** @private {?Element} */
147
this.dragEl_;
148
149
/** @private {?Array<!goog.fx.ActiveDropTarget_>} */
150
this.targetList_;
151
152
/** @private {?goog.math.Box} */
153
this.targetBox_;
154
155
/** @private {?goog.fx.ActiveDropTarget_} */
156
this.activeTarget_;
157
158
/** @private {?goog.fx.DragDropItem} */
159
this.dragItem_;
160
161
/** @private {?goog.fx.Dragger} */
162
this.dragger_;
163
};
164
goog.inherits(goog.fx.AbstractDragDrop, goog.events.EventTarget);
165
166
167
/**
168
* Minimum size (in pixels) for a dummy target. If the box for the target is
169
* less than the specified size it's not created.
170
* @type {number}
171
* @private
172
*/
173
goog.fx.AbstractDragDrop.DUMMY_TARGET_MIN_SIZE_ = 10;
174
175
176
/**
177
* Constants for event names
178
* @const
179
*/
180
goog.fx.AbstractDragDrop.EventType = {
181
DRAGOVER: 'dragover',
182
DRAGOUT: 'dragout',
183
DRAG: 'drag',
184
DROP: 'drop',
185
DRAGSTART: 'dragstart',
186
DRAGEND: 'dragend'
187
};
188
189
190
/**
191
* Constant for distance threshold, in pixels, an element has to be moved to
192
* initiate a drag operation.
193
* @type {number}
194
*/
195
goog.fx.AbstractDragDrop.initDragDistanceThreshold = 5;
196
197
198
/**
199
* Set class to add to source elements being dragged.
200
*
201
* @param {string} className Class to be added. Must be a single, valid
202
* classname.
203
*/
204
goog.fx.AbstractDragDrop.prototype.setDragClass = function(className) {
205
this.dragClass_ = className;
206
};
207
208
209
/**
210
* Set class to add to source elements.
211
*
212
* @param {string} className Class to be added. Must be a single, valid
213
* classname.
214
*/
215
goog.fx.AbstractDragDrop.prototype.setSourceClass = function(className) {
216
this.sourceClass_ = className;
217
};
218
219
220
/**
221
* Set class to add to target elements.
222
*
223
* @param {string} className Class to be added. Must be a single, valid
224
* classname.
225
*/
226
goog.fx.AbstractDragDrop.prototype.setTargetClass = function(className) {
227
this.targetClass_ = className;
228
};
229
230
231
/**
232
* Whether the control has been initialized.
233
*
234
* @return {boolean} True if it's been initialized.
235
*/
236
goog.fx.AbstractDragDrop.prototype.isInitialized = function() {
237
return this.initialized_;
238
};
239
240
241
/**
242
* Add item to drag object.
243
*
244
* @param {Element|string} element Dom Node, or string representation of node
245
* id, to be used as drag source/drop target.
246
* @throws Error Thrown if called on instance of abstract class
247
*/
248
goog.fx.AbstractDragDrop.prototype.addItem = goog.abstractMethod;
249
250
251
/**
252
* Associate drop target with drag element.
253
*
254
* @param {goog.fx.AbstractDragDrop} target Target to add.
255
*/
256
goog.fx.AbstractDragDrop.prototype.addTarget = function(target) {
257
this.targets_.push(target);
258
target.isTarget_ = true;
259
this.isSource_ = true;
260
};
261
262
263
/**
264
* Removes the specified target from the list of drop targets.
265
*
266
* @param {!goog.fx.AbstractDragDrop} target Target to remove.
267
*/
268
goog.fx.AbstractDragDrop.prototype.removeTarget = function(target) {
269
goog.array.remove(this.targets_, target);
270
if (this.activeTarget_ && this.activeTarget_.target_ == target) {
271
this.activeTarget_ = null;
272
}
273
this.recalculateDragTargets();
274
};
275
276
277
/**
278
* Sets the SCROLL event target to make drag element follow scrolling.
279
*
280
* @param {EventTarget} scrollTarget The element that dispatches SCROLL events.
281
*/
282
goog.fx.AbstractDragDrop.prototype.setScrollTarget = function(scrollTarget) {
283
this.scrollTarget_ = scrollTarget;
284
};
285
286
287
/**
288
* Initialize drag and drop functionality for sources/targets already added.
289
* Sources/targets added after init has been called will initialize themselves
290
* one by one.
291
*/
292
goog.fx.AbstractDragDrop.prototype.init = function() {
293
if (this.initialized_) {
294
return;
295
}
296
for (var item, i = 0; item = this.items_[i]; i++) {
297
this.initItem(item);
298
}
299
300
this.initialized_ = true;
301
};
302
303
304
/**
305
* Initializes a single item.
306
*
307
* @param {goog.fx.DragDropItem} item Item to initialize.
308
* @protected
309
*/
310
goog.fx.AbstractDragDrop.prototype.initItem = function(item) {
311
if (this.isSource_) {
312
goog.events.listen(
313
item.element, goog.events.EventType.MOUSEDOWN, item.mouseDown_, false,
314
item);
315
if (this.sourceClass_) {
316
goog.dom.classlist.add(
317
goog.asserts.assert(item.element), this.sourceClass_);
318
}
319
}
320
321
if (this.isTarget_ && this.targetClass_) {
322
goog.dom.classlist.add(
323
goog.asserts.assert(item.element), this.targetClass_);
324
}
325
};
326
327
328
/**
329
* Called when removing an item. Removes event listeners and classes.
330
*
331
* @param {goog.fx.DragDropItem} item Item to dispose.
332
* @protected
333
*/
334
goog.fx.AbstractDragDrop.prototype.disposeItem = function(item) {
335
if (this.isSource_) {
336
goog.events.unlisten(
337
item.element, goog.events.EventType.MOUSEDOWN, item.mouseDown_, false,
338
item);
339
if (this.sourceClass_) {
340
goog.dom.classlist.remove(
341
goog.asserts.assert(item.element), this.sourceClass_);
342
}
343
}
344
if (this.isTarget_ && this.targetClass_) {
345
goog.dom.classlist.remove(
346
goog.asserts.assert(item.element), this.targetClass_);
347
}
348
item.dispose();
349
};
350
351
352
/**
353
* Removes all items.
354
*/
355
goog.fx.AbstractDragDrop.prototype.removeItems = function() {
356
for (var item, i = 0; item = this.items_[i]; i++) {
357
this.disposeItem(item);
358
}
359
this.items_.length = 0;
360
};
361
362
363
/**
364
* Starts a drag event for an item if the mouse button stays pressed and the
365
* cursor moves a few pixels. Allows dragging of items without first having to
366
* register them with addItem.
367
*
368
* @param {goog.events.BrowserEvent} event Mouse down event.
369
* @param {goog.fx.DragDropItem} item Item that's being dragged.
370
*/
371
goog.fx.AbstractDragDrop.prototype.maybeStartDrag = function(event, item) {
372
item.maybeStartDrag_(event, item.element);
373
};
374
375
376
/**
377
* Event handler that's used to start drag.
378
*
379
* @param {goog.events.BrowserEvent} event Mouse move event.
380
* @param {goog.fx.DragDropItem} item Item that's being dragged.
381
*/
382
goog.fx.AbstractDragDrop.prototype.startDrag = function(event, item) {
383
384
// Prevent a new drag operation from being started if another one is already
385
// in progress (could happen if the mouse was released outside of the
386
// document).
387
if (this.dragItem_) {
388
return;
389
}
390
391
this.dragItem_ = item;
392
393
// Dispatch DRAGSTART event
394
var dragStartEvent = new goog.fx.DragDropEvent(
395
goog.fx.AbstractDragDrop.EventType.DRAGSTART, this, this.dragItem_,
396
undefined, // opt_target
397
undefined, // opt_targetItem
398
undefined, // opt_targetElement
399
undefined, // opt_clientX
400
undefined, // opt_clientY
401
undefined, // opt_x
402
undefined, // opt_y
403
undefined, // opt_subtarget
404
event);
405
if (this.dispatchEvent(dragStartEvent) == false) {
406
this.dragItem_ = null;
407
return;
408
}
409
410
// Get the source element and create a drag element for it.
411
var el = item.getCurrentDragElement();
412
this.dragEl_ = this.createDragElement(el);
413
var doc = goog.dom.getOwnerDocument(el);
414
doc.body.appendChild(this.dragEl_);
415
416
this.dragger_ = this.createDraggerFor(el, this.dragEl_, event);
417
this.dragger_.setScrollTarget(this.scrollTarget_);
418
419
goog.events.listen(
420
this.dragger_, goog.fx.Dragger.EventType.DRAG, this.moveDrag_, false,
421
this);
422
423
goog.events.listen(
424
this.dragger_, goog.fx.Dragger.EventType.END, this.endDrag, false, this);
425
426
// IE may issue a 'selectstart' event when dragging over an iframe even when
427
// default mousemove behavior is suppressed. If the default selectstart
428
// behavior is not suppressed, elements dragged over will show as selected.
429
goog.events.listen(
430
doc.body, goog.events.EventType.SELECTSTART, this.suppressSelect_);
431
432
this.recalculateDragTargets();
433
this.recalculateScrollableContainers();
434
this.activeTarget_ = null;
435
this.initScrollableContainerListeners_();
436
this.dragger_.startDrag(event);
437
438
event.preventDefault();
439
};
440
441
442
/**
443
* Recalculates the geometry of this source's drag targets. Call this
444
* if the position or visibility of a drag target has changed during
445
* a drag, or if targets are added or removed.
446
*
447
* TODO(user): this is an expensive operation; more efficient APIs
448
* may be necessary.
449
*/
450
goog.fx.AbstractDragDrop.prototype.recalculateDragTargets = function() {
451
this.targetList_ = [];
452
for (var target, i = 0; target = this.targets_[i]; i++) {
453
for (var itm, j = 0; itm = target.items_[j]; j++) {
454
this.addDragTarget_(target, itm);
455
}
456
}
457
if (!this.targetBox_) {
458
this.targetBox_ = new goog.math.Box(0, 0, 0, 0);
459
}
460
};
461
462
463
/**
464
* Recalculates the current scroll positions of scrollable containers and
465
* allocates targets. Call this if the position of a container changed or if
466
* targets are added or removed.
467
*/
468
goog.fx.AbstractDragDrop.prototype.recalculateScrollableContainers =
469
function() {
470
var container, i, j, target;
471
for (i = 0; container = this.scrollableContainers_[i]; i++) {
472
container.containedTargets_ = [];
473
container.savedScrollLeft_ = container.element_.scrollLeft;
474
container.savedScrollTop_ = container.element_.scrollTop;
475
var pos = goog.style.getPageOffset(container.element_);
476
var size = goog.style.getSize(container.element_);
477
container.box_ = new goog.math.Box(
478
pos.y, pos.x + size.width, pos.y + size.height, pos.x);
479
}
480
481
for (i = 0; target = this.targetList_[i]; i++) {
482
for (j = 0; container = this.scrollableContainers_[j]; j++) {
483
if (goog.dom.contains(container.element_, target.element_)) {
484
container.containedTargets_.push(target);
485
target.scrollableContainer_ = container;
486
}
487
}
488
}
489
};
490
491
492
/**
493
* Creates the Dragger for the drag element.
494
* @param {Element} sourceEl Drag source element.
495
* @param {Element} el the element created by createDragElement().
496
* @param {goog.events.BrowserEvent} event Mouse down event for start of drag.
497
* @return {!goog.fx.Dragger} The new Dragger.
498
* @protected
499
*/
500
goog.fx.AbstractDragDrop.prototype.createDraggerFor = function(
501
sourceEl, el, event) {
502
// Position the drag element.
503
var pos = this.getDragElementPosition(sourceEl, el, event);
504
el.style.position = 'absolute';
505
el.style.left = pos.x + 'px';
506
el.style.top = pos.y + 'px';
507
return new goog.fx.Dragger(el);
508
};
509
510
511
/**
512
* Event handler that's used to stop drag. Fires a drop event if over a valid
513
* target.
514
*
515
* @param {goog.fx.DragEvent} event Drag event.
516
*/
517
goog.fx.AbstractDragDrop.prototype.endDrag = function(event) {
518
var activeTarget = event.dragCanceled ? null : this.activeTarget_;
519
if (activeTarget && activeTarget.target_) {
520
var clientX = event.clientX;
521
var clientY = event.clientY;
522
var scroll = this.getScrollPos();
523
var x = clientX + scroll.x;
524
var y = clientY + scroll.y;
525
526
var subtarget;
527
// If a subtargeting function is enabled get the current subtarget
528
if (this.subtargetFunction_) {
529
subtarget =
530
this.subtargetFunction_(activeTarget.item_, activeTarget.box_, x, y);
531
}
532
533
var dragEvent = new goog.fx.DragDropEvent(
534
goog.fx.AbstractDragDrop.EventType.DRAG, this, this.dragItem_,
535
activeTarget.target_, activeTarget.item_, activeTarget.element_,
536
clientX, clientY, x, y);
537
this.dispatchEvent(dragEvent);
538
539
var dropEvent = new goog.fx.DragDropEvent(
540
goog.fx.AbstractDragDrop.EventType.DROP, this, this.dragItem_,
541
activeTarget.target_, activeTarget.item_, activeTarget.element_,
542
clientX, clientY, x, y, subtarget, event.browserEvent);
543
activeTarget.target_.dispatchEvent(dropEvent);
544
}
545
546
var dragEndEvent = new goog.fx.DragDropEvent(
547
goog.fx.AbstractDragDrop.EventType.DRAGEND, this, this.dragItem_,
548
activeTarget ? activeTarget.target_ : undefined,
549
activeTarget ? activeTarget.item_ : undefined,
550
activeTarget ? activeTarget.element_ : undefined);
551
this.dispatchEvent(dragEndEvent);
552
553
goog.events.unlisten(
554
this.dragger_, goog.fx.Dragger.EventType.DRAG, this.moveDrag_, false,
555
this);
556
goog.events.unlisten(
557
this.dragger_, goog.fx.Dragger.EventType.END, this.endDrag, false, this);
558
var doc = goog.dom.getOwnerDocument(this.dragItem_.getCurrentDragElement());
559
goog.events.unlisten(
560
doc.body, goog.events.EventType.SELECTSTART, this.suppressSelect_);
561
562
563
this.afterEndDrag(this.activeTarget_ ? this.activeTarget_.item_ : null);
564
};
565
566
567
/**
568
* Called after a drag operation has finished.
569
*
570
* @param {goog.fx.DragDropItem=} opt_dropTarget Target for successful drop.
571
* @protected
572
*/
573
goog.fx.AbstractDragDrop.prototype.afterEndDrag = function(opt_dropTarget) {
574
this.disposeDrag();
575
};
576
577
578
/**
579
* Called once a drag operation has finished. Removes event listeners and
580
* elements.
581
*
582
* @protected
583
*/
584
goog.fx.AbstractDragDrop.prototype.disposeDrag = function() {
585
this.disposeScrollableContainerListeners_();
586
this.dragger_.dispose();
587
588
goog.dom.removeNode(this.dragEl_);
589
delete this.dragItem_;
590
delete this.dragEl_;
591
delete this.dragger_;
592
delete this.targetList_;
593
delete this.activeTarget_;
594
};
595
596
597
/**
598
* Event handler for drag events. Determines the active drop target, if any, and
599
* fires dragover and dragout events appropriately.
600
*
601
* @param {goog.fx.DragEvent} event Drag event.
602
* @private
603
*/
604
goog.fx.AbstractDragDrop.prototype.moveDrag_ = function(event) {
605
var position = this.getEventPosition(event);
606
var x = position.x;
607
var y = position.y;
608
609
var activeTarget = this.activeTarget_;
610
611
this.dispatchEvent(
612
new goog.fx.DragDropEvent(
613
goog.fx.AbstractDragDrop.EventType.DRAG, this, this.dragItem_,
614
activeTarget ? activeTarget.target_ : undefined,
615
activeTarget ? activeTarget.item_ : undefined,
616
activeTarget ? activeTarget.element_ : undefined, event.clientX,
617
event.clientY, x, y));
618
619
// Check if we're still inside the bounds of the active target, if not fire
620
// a dragout event and proceed to find a new target.
621
var subtarget;
622
if (activeTarget) {
623
// If a subtargeting function is enabled get the current subtarget
624
if (this.subtargetFunction_ && activeTarget.target_) {
625
subtarget =
626
this.subtargetFunction_(activeTarget.item_, activeTarget.box_, x, y);
627
}
628
629
if (activeTarget.box_.contains(position) &&
630
subtarget == this.activeSubtarget_) {
631
return;
632
}
633
634
if (activeTarget.target_) {
635
var sourceDragOutEvent = new goog.fx.DragDropEvent(
636
goog.fx.AbstractDragDrop.EventType.DRAGOUT, this, this.dragItem_,
637
activeTarget.target_, activeTarget.item_, activeTarget.element_);
638
this.dispatchEvent(sourceDragOutEvent);
639
640
// The event should be dispatched the by target DragDrop so that the
641
// target DragDrop can manage these events without having to know what
642
// sources this is a target for.
643
var targetDragOutEvent = new goog.fx.DragDropEvent(
644
goog.fx.AbstractDragDrop.EventType.DRAGOUT, this, this.dragItem_,
645
activeTarget.target_, activeTarget.item_, activeTarget.element_,
646
undefined, undefined, undefined, undefined, this.activeSubtarget_);
647
activeTarget.target_.dispatchEvent(targetDragOutEvent);
648
}
649
this.activeSubtarget_ = subtarget;
650
this.activeTarget_ = null;
651
}
652
653
// Check if inside target box
654
if (this.targetBox_.contains(position)) {
655
// Search for target and fire a dragover event if found
656
activeTarget = this.activeTarget_ = this.getTargetFromPosition_(position);
657
if (activeTarget && activeTarget.target_) {
658
// If a subtargeting function is enabled get the current subtarget
659
if (this.subtargetFunction_) {
660
subtarget = this.subtargetFunction_(
661
activeTarget.item_, activeTarget.box_, x, y);
662
}
663
var sourceDragOverEvent = new goog.fx.DragDropEvent(
664
goog.fx.AbstractDragDrop.EventType.DRAGOVER, this, this.dragItem_,
665
activeTarget.target_, activeTarget.item_, activeTarget.element_);
666
sourceDragOverEvent.subtarget = subtarget;
667
this.dispatchEvent(sourceDragOverEvent);
668
669
// The event should be dispatched by the target DragDrop so that the
670
// target DragDrop can manage these events without having to know what
671
// sources this is a target for.
672
var targetDragOverEvent = new goog.fx.DragDropEvent(
673
goog.fx.AbstractDragDrop.EventType.DRAGOVER, this, this.dragItem_,
674
activeTarget.target_, activeTarget.item_, activeTarget.element_,
675
event.clientX, event.clientY, undefined, undefined, subtarget);
676
activeTarget.target_.dispatchEvent(targetDragOverEvent);
677
678
} else if (!activeTarget) {
679
// If no target was found create a dummy one so we won't have to iterate
680
// over all possible targets for every move event.
681
this.activeTarget_ = this.maybeCreateDummyTargetForPosition_(x, y);
682
}
683
}
684
};
685
686
687
/**
688
* Event handler for suppressing selectstart events. Selecting should be
689
* disabled while dragging.
690
*
691
* @param {goog.events.Event} event The selectstart event to suppress.
692
* @return {boolean} Whether to perform default behavior.
693
* @private
694
*/
695
goog.fx.AbstractDragDrop.prototype.suppressSelect_ = function(event) {
696
return false;
697
};
698
699
700
/**
701
* Sets up listeners for the scrollable containers that keep track of their
702
* scroll positions.
703
* @private
704
*/
705
goog.fx.AbstractDragDrop.prototype.initScrollableContainerListeners_ =
706
function() {
707
var container, i;
708
for (i = 0; container = this.scrollableContainers_[i]; i++) {
709
goog.events.listen(
710
container.element_, goog.events.EventType.SCROLL,
711
this.containerScrollHandler_, false, this);
712
}
713
};
714
715
716
/**
717
* Cleans up the scrollable container listeners.
718
* @private
719
*/
720
goog.fx.AbstractDragDrop.prototype.disposeScrollableContainerListeners_ =
721
function() {
722
for (var i = 0, container; container = this.scrollableContainers_[i]; i++) {
723
goog.events.unlisten(
724
container.element_, 'scroll', this.containerScrollHandler_, false,
725
this);
726
container.containedTargets_ = [];
727
}
728
};
729
730
731
/**
732
* Makes drag and drop aware of a target container that could scroll mid drag.
733
* @param {Element} element The scroll container.
734
*/
735
goog.fx.AbstractDragDrop.prototype.addScrollableContainer = function(element) {
736
this.scrollableContainers_.push(new goog.fx.ScrollableContainer_(element));
737
};
738
739
740
/**
741
* Removes all scrollable containers.
742
*/
743
goog.fx.AbstractDragDrop.prototype.removeAllScrollableContainers = function() {
744
this.disposeScrollableContainerListeners_();
745
this.scrollableContainers_ = [];
746
};
747
748
749
/**
750
* Event handler for containers scrolling.
751
* @param {goog.events.BrowserEvent} e The event.
752
* @suppress {visibility} TODO(martone): update dependent projects.
753
* @private
754
*/
755
goog.fx.AbstractDragDrop.prototype.containerScrollHandler_ = function(e) {
756
for (var i = 0, container; container = this.scrollableContainers_[i]; i++) {
757
if (e.target == container.element_) {
758
var deltaTop = container.savedScrollTop_ - container.element_.scrollTop;
759
var deltaLeft =
760
container.savedScrollLeft_ - container.element_.scrollLeft;
761
container.savedScrollTop_ = container.element_.scrollTop;
762
container.savedScrollLeft_ = container.element_.scrollLeft;
763
764
// When the container scrolls, it's possible that one of the targets will
765
// move to the region contained by the dummy target. Since we don't know
766
// which sides (if any) of the dummy target are defined by targets
767
// contained by this container, we are conservative and just shrink it.
768
if (this.dummyTarget_ && this.activeTarget_ == this.dummyTarget_) {
769
if (deltaTop > 0) {
770
this.dummyTarget_.box_.top += deltaTop;
771
} else {
772
this.dummyTarget_.box_.bottom += deltaTop;
773
}
774
if (deltaLeft > 0) {
775
this.dummyTarget_.box_.left += deltaLeft;
776
} else {
777
this.dummyTarget_.box_.right += deltaLeft;
778
}
779
}
780
for (var j = 0, target; target = container.containedTargets_[j]; j++) {
781
var box = target.box_;
782
box.top += deltaTop;
783
box.left += deltaLeft;
784
box.bottom += deltaTop;
785
box.right += deltaLeft;
786
787
this.calculateTargetBox_(box);
788
}
789
}
790
}
791
this.dragger_.onScroll_(e);
792
};
793
794
795
/**
796
* Set a function that provides subtargets. A subtargeting function
797
* returns an arbitrary identifier for each subtarget of an element.
798
* DnD code will generate additional drag over / out events when
799
* switching from subtarget to subtarget. This is useful for instance
800
* if you are interested if you are on the top half or the bottom half
801
* of the element.
802
* The provided function will be given the DragDropItem, box, x, y
803
* box is the current window coordinates occupied by element
804
* x, y is the mouse position in window coordinates
805
*
806
* @param {Function} f The new subtarget function.
807
*/
808
goog.fx.AbstractDragDrop.prototype.setSubtargetFunction = function(f) {
809
this.subtargetFunction_ = f;
810
};
811
812
813
/**
814
* Creates an element for the item being dragged.
815
*
816
* @param {Element} sourceEl Drag source element.
817
* @return {Element} The new drag element.
818
*/
819
goog.fx.AbstractDragDrop.prototype.createDragElement = function(sourceEl) {
820
var dragEl = this.createDragElementInternal(sourceEl);
821
goog.asserts.assert(dragEl);
822
if (this.dragClass_) {
823
goog.dom.classlist.add(dragEl, this.dragClass_);
824
}
825
826
return dragEl;
827
};
828
829
830
/**
831
* Returns the position for the drag element.
832
*
833
* @param {Element} el Drag source element.
834
* @param {Element} dragEl The dragged element created by createDragElement().
835
* @param {goog.events.BrowserEvent} event Mouse down event for start of drag.
836
* @return {!goog.math.Coordinate} The position for the drag element.
837
*/
838
goog.fx.AbstractDragDrop.prototype.getDragElementPosition = function(
839
el, dragEl, event) {
840
var pos = goog.style.getPageOffset(el);
841
842
// Subtract margin from drag element position twice, once to adjust the
843
// position given by the original node and once for the drag node.
844
var marginBox = goog.style.getMarginBox(el);
845
pos.x -= (marginBox.left || 0) * 2;
846
pos.y -= (marginBox.top || 0) * 2;
847
848
return pos;
849
};
850
851
852
/**
853
* Returns the dragger object.
854
*
855
* @return {goog.fx.Dragger} The dragger object used by this drag and drop
856
* instance.
857
*/
858
goog.fx.AbstractDragDrop.prototype.getDragger = function() {
859
return this.dragger_;
860
};
861
862
863
/**
864
* Creates copy of node being dragged.
865
*
866
* @param {Element} sourceEl Element to copy.
867
* @return {!Element} The clone of {@code sourceEl}.
868
* @deprecated Use goog.fx.Dragger.cloneNode().
869
* @private
870
*/
871
goog.fx.AbstractDragDrop.prototype.cloneNode_ = function(sourceEl) {
872
return goog.fx.Dragger.cloneNode(sourceEl);
873
};
874
875
876
/**
877
* Generates an element to follow the cursor during dragging, given a drag
878
* source element. The default behavior is simply to clone the source element,
879
* but this may be overridden in subclasses. This method is called by
880
* {@code createDragElement()} before the drag class is added.
881
*
882
* @param {Element} sourceEl Drag source element.
883
* @return {!Element} The new drag element.
884
* @protected
885
* @suppress {deprecated}
886
*/
887
goog.fx.AbstractDragDrop.prototype.createDragElementInternal = function(
888
sourceEl) {
889
return this.cloneNode_(sourceEl);
890
};
891
892
893
/**
894
* Add possible drop target for current drag operation.
895
*
896
* @param {goog.fx.AbstractDragDrop} target Drag handler.
897
* @param {goog.fx.DragDropItem} item Item that's being dragged.
898
* @private
899
*/
900
goog.fx.AbstractDragDrop.prototype.addDragTarget_ = function(target, item) {
901
902
// Get all the draggable elements and add each one.
903
var draggableElements = item.getDraggableElements();
904
for (var i = 0; i < draggableElements.length; i++) {
905
var draggableElement = draggableElements[i];
906
907
// Determine target position and dimension
908
var box = this.getElementBox(item, draggableElement);
909
910
this.targetList_.push(
911
new goog.fx.ActiveDropTarget_(box, target, item, draggableElement));
912
913
this.calculateTargetBox_(box);
914
}
915
};
916
917
918
/**
919
* Calculates the position and dimension of a draggable element.
920
*
921
* @param {goog.fx.DragDropItem} item Item that's being dragged.
922
* @param {Element} element The element to calculate the box.
923
*
924
* @return {!goog.math.Box} Box describing the position and dimension
925
* of element.
926
* @protected
927
*/
928
goog.fx.AbstractDragDrop.prototype.getElementBox = function(item, element) {
929
var pos = goog.style.getPageOffset(element);
930
var size = goog.style.getSize(element);
931
return new goog.math.Box(
932
pos.y, pos.x + size.width, pos.y + size.height, pos.x);
933
};
934
935
936
/**
937
* Calculate the outer bounds (the region all targets are inside).
938
*
939
* @param {goog.math.Box} box Box describing the position and dimension
940
* of a drag target.
941
* @private
942
*/
943
goog.fx.AbstractDragDrop.prototype.calculateTargetBox_ = function(box) {
944
if (this.targetList_.length == 1) {
945
this.targetBox_ =
946
new goog.math.Box(box.top, box.right, box.bottom, box.left);
947
} else {
948
var tb = this.targetBox_;
949
tb.left = Math.min(box.left, tb.left);
950
tb.right = Math.max(box.right, tb.right);
951
tb.top = Math.min(box.top, tb.top);
952
tb.bottom = Math.max(box.bottom, tb.bottom);
953
}
954
};
955
956
957
/**
958
* Creates a dummy target for the given cursor position. The assumption is to
959
* create as big dummy target box as possible, the only constraints are:
960
* - The dummy target box cannot overlap any of real target boxes.
961
* - The dummy target has to contain a point with current mouse coordinates.
962
*
963
* NOTE: For performance reasons the box construction algorithm is kept simple
964
* and it is not optimal (see example below). Currently it is O(n) in regard to
965
* the number of real drop target boxes, but its result depends on the order
966
* of those boxes being processed (the order in which they're added to the
967
* targetList_ collection).
968
*
969
* The algorithm.
970
* a) Assumptions
971
* - Mouse pointer is in the bounding box of real target boxes.
972
* - None of the boxes have negative coordinate values.
973
* - Mouse pointer is not contained by any of "real target" boxes.
974
* - For targets inside a scrollable container, the box used is the
975
* intersection of the scrollable container's box and the target's box.
976
* This is because the part of the target that extends outside the scrollable
977
* container should not be used in the clipping calculations.
978
*
979
* b) Outline
980
* - Initialize the fake target to the bounding box of real targets.
981
* - For each real target box - clip the fake target box so it does not contain
982
* that target box, but does contain the mouse pointer.
983
* -- Project the real target box, mouse pointer and fake target box onto
984
* both axes and calculate the clipping coordinates.
985
* -- Only one coordinate is used to clip the fake target box to keep the
986
* fake target as big as possible.
987
* -- If the projection of the real target box contains the mouse pointer,
988
* clipping for a given axis is not possible.
989
* -- If both clippings are possible, the clipping more distant from the
990
* mouse pointer is selected to keep bigger fake target area.
991
* - Save the created fake target only if it has a big enough area.
992
*
993
*
994
* c) Example
995
* <pre>
996
* Input: Algorithm created box: Maximum box:
997
* +---------------------+ +---------------------+ +---------------------+
998
* | B1 | B2 | | B1 B2 | | B1 B2 |
999
* | | | | +-------------+ | |+-------------------+|
1000
* |---------x-----------| | | | | || ||
1001
* | | | | | | | || ||
1002
* | | | | | | | || ||
1003
* | | | | | | | || ||
1004
* | | | | | | | || ||
1005
* | | | | +-------------+ | |+-------------------+|
1006
* | B4 | B3 | | B4 B3 | | B4 B3 |
1007
* +---------------------+ +---------------------+ +---------------------+
1008
* </pre>
1009
*
1010
* @param {number} x Cursor position on the x-axis.
1011
* @param {number} y Cursor position on the y-axis.
1012
* @return {goog.fx.ActiveDropTarget_} Dummy drop target.
1013
* @private
1014
*/
1015
goog.fx.AbstractDragDrop.prototype.maybeCreateDummyTargetForPosition_ =
1016
function(x, y) {
1017
if (!this.dummyTarget_) {
1018
this.dummyTarget_ = new goog.fx.ActiveDropTarget_(this.targetBox_.clone());
1019
}
1020
var fakeTargetBox = this.dummyTarget_.box_;
1021
1022
// Initialize the fake target box to the bounding box of DnD targets.
1023
fakeTargetBox.top = this.targetBox_.top;
1024
fakeTargetBox.right = this.targetBox_.right;
1025
fakeTargetBox.bottom = this.targetBox_.bottom;
1026
fakeTargetBox.left = this.targetBox_.left;
1027
1028
// Clip the fake target based on mouse position and DnD target boxes.
1029
for (var i = 0, target; target = this.targetList_[i]; i++) {
1030
var box = target.box_;
1031
1032
if (target.scrollableContainer_) {
1033
// If the target has a scrollable container, use the intersection of that
1034
// container's box and the target's box.
1035
var scrollBox = target.scrollableContainer_.box_;
1036
1037
box = new goog.math.Box(
1038
Math.max(box.top, scrollBox.top),
1039
Math.min(box.right, scrollBox.right),
1040
Math.min(box.bottom, scrollBox.bottom),
1041
Math.max(box.left, scrollBox.left));
1042
}
1043
1044
// Calculate clipping coordinates for horizontal and vertical axis.
1045
// The clipping coordinate is calculated by projecting fake target box,
1046
// the mouse pointer and DnD target box onto an axis and checking how
1047
// box projections overlap and if the projected DnD target box contains
1048
// mouse pointer. The clipping coordinate cannot be computed and is set to
1049
// a negative value if the projected DnD target contains the mouse pointer.
1050
1051
var horizontalClip = null; // Assume mouse is above or below the DnD box.
1052
if (x >= box.right) { // Mouse is to the right of the DnD box.
1053
// Clip the fake box only if the DnD box overlaps it.
1054
horizontalClip =
1055
box.right > fakeTargetBox.left ? box.right : fakeTargetBox.left;
1056
} else if (x < box.left) { // Mouse is to the left of the DnD box.
1057
// Clip the fake box only if the DnD box overlaps it.
1058
horizontalClip =
1059
box.left < fakeTargetBox.right ? box.left : fakeTargetBox.right;
1060
}
1061
var verticalClip = null;
1062
if (y >= box.bottom) {
1063
verticalClip =
1064
box.bottom > fakeTargetBox.top ? box.bottom : fakeTargetBox.top;
1065
} else if (y < box.top) {
1066
verticalClip =
1067
box.top < fakeTargetBox.bottom ? box.top : fakeTargetBox.bottom;
1068
}
1069
1070
// If both clippings are possible, choose one that gives us larger distance
1071
// to mouse pointer (mark the shorter clipping as impossible, by setting it
1072
// to null).
1073
if (!goog.isNull(horizontalClip) && !goog.isNull(verticalClip)) {
1074
if (Math.abs(horizontalClip - x) > Math.abs(verticalClip - y)) {
1075
verticalClip = null;
1076
} else {
1077
horizontalClip = null;
1078
}
1079
}
1080
1081
// Clip none or one of fake target box sides (at most one clipping
1082
// coordinate can be active).
1083
if (!goog.isNull(horizontalClip)) {
1084
if (horizontalClip <= x) {
1085
fakeTargetBox.left = horizontalClip;
1086
} else {
1087
fakeTargetBox.right = horizontalClip;
1088
}
1089
} else if (!goog.isNull(verticalClip)) {
1090
if (verticalClip <= y) {
1091
fakeTargetBox.top = verticalClip;
1092
} else {
1093
fakeTargetBox.bottom = verticalClip;
1094
}
1095
}
1096
}
1097
1098
// Only return the new fake target if it is big enough.
1099
return (fakeTargetBox.right - fakeTargetBox.left) *
1100
(fakeTargetBox.bottom - fakeTargetBox.top) >=
1101
goog.fx.AbstractDragDrop.DUMMY_TARGET_MIN_SIZE_ ?
1102
this.dummyTarget_ :
1103
null;
1104
};
1105
1106
1107
/**
1108
* Returns the target for a given cursor position.
1109
*
1110
* @param {goog.math.Coordinate} position Cursor position.
1111
* @return {goog.fx.ActiveDropTarget_} Target for position or null if no target
1112
* was defined for the given position.
1113
* @private
1114
*/
1115
goog.fx.AbstractDragDrop.prototype.getTargetFromPosition_ = function(position) {
1116
for (var target, i = 0; target = this.targetList_[i]; i++) {
1117
if (target.box_.contains(position)) {
1118
if (target.scrollableContainer_) {
1119
// If we have a scrollable container we will need to make sure
1120
// we account for clipping of the scroll area
1121
var box = target.scrollableContainer_.box_;
1122
if (box.contains(position)) {
1123
return target;
1124
}
1125
} else {
1126
return target;
1127
}
1128
}
1129
}
1130
1131
return null;
1132
};
1133
1134
1135
/**
1136
* Checks whatever a given point is inside a given box.
1137
*
1138
* @param {number} x Cursor position on the x-axis.
1139
* @param {number} y Cursor position on the y-axis.
1140
* @param {goog.math.Box} box Box to check position against.
1141
* @return {boolean} Whether the given point is inside {@code box}.
1142
* @protected
1143
* @deprecated Use goog.math.Box.contains.
1144
*/
1145
goog.fx.AbstractDragDrop.prototype.isInside = function(x, y, box) {
1146
return x >= box.left && x < box.right && y >= box.top && y < box.bottom;
1147
};
1148
1149
1150
/**
1151
* Gets the scroll distance as a coordinate object, using
1152
* the window of the current drag element's dom.
1153
* @return {!goog.math.Coordinate} Object with scroll offsets 'x' and 'y'.
1154
* @protected
1155
*/
1156
goog.fx.AbstractDragDrop.prototype.getScrollPos = function() {
1157
return goog.dom.getDomHelper(this.dragEl_).getDocumentScroll();
1158
};
1159
1160
1161
/**
1162
* Get the position of a drag event.
1163
* @param {goog.fx.DragEvent} event Drag event.
1164
* @return {!goog.math.Coordinate} Position of the event.
1165
* @protected
1166
*/
1167
goog.fx.AbstractDragDrop.prototype.getEventPosition = function(event) {
1168
var scroll = this.getScrollPos();
1169
return new goog.math.Coordinate(
1170
event.clientX + scroll.x, event.clientY + scroll.y);
1171
};
1172
1173
1174
/** @override */
1175
goog.fx.AbstractDragDrop.prototype.disposeInternal = function() {
1176
goog.fx.AbstractDragDrop.base(this, 'disposeInternal');
1177
this.removeItems();
1178
};
1179
1180
1181
1182
/**
1183
* Object representing a drag and drop event.
1184
*
1185
* @param {string} type Event type.
1186
* @param {goog.fx.AbstractDragDrop} source Source drag drop object.
1187
* @param {goog.fx.DragDropItem} sourceItem Source item.
1188
* @param {goog.fx.AbstractDragDrop=} opt_target Target drag drop object.
1189
* @param {goog.fx.DragDropItem=} opt_targetItem Target item.
1190
* @param {Element=} opt_targetElement Target element.
1191
* @param {number=} opt_clientX X-Position relative to the screen.
1192
* @param {number=} opt_clientY Y-Position relative to the screen.
1193
* @param {number=} opt_x X-Position relative to the viewport.
1194
* @param {number=} opt_y Y-Position relative to the viewport.
1195
* @param {Object=} opt_subtarget The currently active subtarget.
1196
* @param {goog.events.BrowserEvent=} opt_browserEvent The browser event
1197
* that caused this dragdrop event.
1198
* @extends {goog.events.Event}
1199
* @constructor
1200
* @struct
1201
*/
1202
goog.fx.DragDropEvent = function(
1203
type, source, sourceItem, opt_target, opt_targetItem, opt_targetElement,
1204
opt_clientX, opt_clientY, opt_x, opt_y, opt_subtarget, opt_browserEvent) {
1205
// TODO(eae): Get rid of all the optional parameters and have the caller set
1206
// the fields directly instead.
1207
goog.fx.DragDropEvent.base(this, 'constructor', type);
1208
1209
/**
1210
* Reference to the source goog.fx.AbstractDragDrop object.
1211
* @type {goog.fx.AbstractDragDrop}
1212
*/
1213
this.dragSource = source;
1214
1215
/**
1216
* Reference to the source goog.fx.DragDropItem object.
1217
* @type {goog.fx.DragDropItem}
1218
*/
1219
this.dragSourceItem = sourceItem;
1220
1221
/**
1222
* Reference to the target goog.fx.AbstractDragDrop object.
1223
* @type {goog.fx.AbstractDragDrop|undefined}
1224
*/
1225
this.dropTarget = opt_target;
1226
1227
/**
1228
* Reference to the target goog.fx.DragDropItem object.
1229
* @type {goog.fx.DragDropItem|undefined}
1230
*/
1231
this.dropTargetItem = opt_targetItem;
1232
1233
/**
1234
* The actual element of the drop target that is the target for this event.
1235
* @type {Element|undefined}
1236
*/
1237
this.dropTargetElement = opt_targetElement;
1238
1239
/**
1240
* X-Position relative to the screen.
1241
* @type {number|undefined}
1242
*/
1243
this.clientX = opt_clientX;
1244
1245
/**
1246
* Y-Position relative to the screen.
1247
* @type {number|undefined}
1248
*/
1249
this.clientY = opt_clientY;
1250
1251
/**
1252
* X-Position relative to the viewport.
1253
* @type {number|undefined}
1254
*/
1255
this.viewportX = opt_x;
1256
1257
/**
1258
* Y-Position relative to the viewport.
1259
* @type {number|undefined}
1260
*/
1261
this.viewportY = opt_y;
1262
1263
/**
1264
* The subtarget that is currently active if a subtargeting function
1265
* is supplied.
1266
* @type {Object|undefined}
1267
*/
1268
this.subtarget = opt_subtarget;
1269
1270
/**
1271
* The browser event that caused this dragdrop event.
1272
* @const
1273
*/
1274
this.browserEvent = opt_browserEvent;
1275
};
1276
goog.inherits(goog.fx.DragDropEvent, goog.events.Event);
1277
1278
1279
1280
/**
1281
* Class representing a source or target element for drag and drop operations.
1282
*
1283
* @param {Element|string} element Dom Node, or string representation of node
1284
* id, to be used as drag source/drop target.
1285
* @param {Object=} opt_data Data associated with the source/target.
1286
* @throws Error If no element argument is provided or if the type is invalid
1287
* @extends {goog.events.EventTarget}
1288
* @constructor
1289
* @struct
1290
*/
1291
goog.fx.DragDropItem = function(element, opt_data) {
1292
goog.fx.DragDropItem.base(this, 'constructor');
1293
1294
/**
1295
* Reference to drag source/target element
1296
* @type {Element}
1297
*/
1298
this.element = goog.dom.getElement(element);
1299
1300
/**
1301
* Data associated with element.
1302
* @type {Object|undefined}
1303
*/
1304
this.data = opt_data;
1305
1306
/**
1307
* Drag object the item belongs to.
1308
* @type {goog.fx.AbstractDragDrop?}
1309
* @private
1310
*/
1311
this.parent_ = null;
1312
1313
/**
1314
* Event handler for listeners on events that can initiate a drag.
1315
* @type {!goog.events.EventHandler<!goog.fx.DragDropItem>}
1316
* @private
1317
*/
1318
this.eventHandler_ = new goog.events.EventHandler(this);
1319
this.registerDisposable(this.eventHandler_);
1320
1321
/**
1322
* The current element being dragged. This is needed because a DragDropItem
1323
* can have multiple elements that can be dragged.
1324
* @private {?Element}
1325
*/
1326
this.currentDragElement_ = null;
1327
1328
/** @private {?goog.math.Coordinate} */
1329
this.startPosition_;
1330
1331
if (!this.element) {
1332
throw Error('Invalid argument');
1333
}
1334
};
1335
goog.inherits(goog.fx.DragDropItem, goog.events.EventTarget);
1336
1337
1338
/**
1339
* Get the data associated with the source/target.
1340
* @return {Object|null|undefined} Data associated with the source/target.
1341
*/
1342
goog.fx.DragDropItem.prototype.getData = function() {
1343
return this.data;
1344
};
1345
1346
1347
/**
1348
* Gets the element that is actually draggable given that the given target was
1349
* attempted to be dragged. This should be overriden when the element that was
1350
* given actually contains many items that can be dragged. From the target, you
1351
* can determine what element should actually be dragged.
1352
*
1353
* @param {Element} target The target that was attempted to be dragged.
1354
* @return {Element} The element that is draggable given the target. If
1355
* none are draggable, this will return null.
1356
*/
1357
goog.fx.DragDropItem.prototype.getDraggableElement = function(target) {
1358
return target;
1359
};
1360
1361
1362
/**
1363
* Gets the element that is currently being dragged.
1364
*
1365
* @return {Element} The element that is currently being dragged.
1366
*/
1367
goog.fx.DragDropItem.prototype.getCurrentDragElement = function() {
1368
return this.currentDragElement_;
1369
};
1370
1371
1372
/**
1373
* Gets all the elements of this item that are potentially draggable/
1374
*
1375
* @return {!Array<Element>} The draggable elements.
1376
*/
1377
goog.fx.DragDropItem.prototype.getDraggableElements = function() {
1378
return [this.element];
1379
};
1380
1381
1382
/**
1383
* Event handler for mouse down.
1384
*
1385
* @param {goog.events.BrowserEvent} event Mouse down event.
1386
* @private
1387
*/
1388
goog.fx.DragDropItem.prototype.mouseDown_ = function(event) {
1389
if (!event.isMouseActionButton()) {
1390
return;
1391
}
1392
1393
// Get the draggable element for the target.
1394
var element = this.getDraggableElement(/** @type {Element} */ (event.target));
1395
if (element) {
1396
this.maybeStartDrag_(event, element);
1397
}
1398
};
1399
1400
1401
/**
1402
* Sets the dragdrop to which this item belongs.
1403
* @param {goog.fx.AbstractDragDrop} parent The parent dragdrop.
1404
*/
1405
goog.fx.DragDropItem.prototype.setParent = function(parent) {
1406
this.parent_ = parent;
1407
};
1408
1409
1410
/**
1411
* Adds mouse move, mouse out and mouse up handlers.
1412
*
1413
* @param {goog.events.BrowserEvent} event Mouse down event.
1414
* @param {Element} element Element.
1415
* @private
1416
*/
1417
goog.fx.DragDropItem.prototype.maybeStartDrag_ = function(event, element) {
1418
var eventType = goog.events.EventType;
1419
this.eventHandler_
1420
.listen(element, eventType.MOUSEMOVE, this.mouseMove_, false)
1421
.listen(element, eventType.MOUSEOUT, this.mouseMove_, false);
1422
1423
// Capture the MOUSEUP on the document to ensure that we cancel the start
1424
// drag handlers even if the mouse up occurs on some other element. This can
1425
// happen for instance when the mouse down changes the geometry of the element
1426
// clicked on (e.g. through changes in activation styling) such that the mouse
1427
// up occurs outside the original element.
1428
var doc = goog.dom.getOwnerDocument(element);
1429
this.eventHandler_.listen(doc, eventType.MOUSEUP, this.mouseUp_, true);
1430
1431
this.currentDragElement_ = element;
1432
1433
this.startPosition_ = new goog.math.Coordinate(event.clientX, event.clientY);
1434
};
1435
1436
1437
/**
1438
* Event handler for mouse move. Starts drag operation if moved more than the
1439
* threshold value.
1440
*
1441
* @param {goog.events.BrowserEvent} event Mouse move or mouse out event.
1442
* @private
1443
*/
1444
goog.fx.DragDropItem.prototype.mouseMove_ = function(event) {
1445
var distance = Math.abs(event.clientX - this.startPosition_.x) +
1446
Math.abs(event.clientY - this.startPosition_.y);
1447
// Fire dragStart event if the drag distance exceeds the threshold or if the
1448
// mouse leave the dragged element.
1449
// TODO(user): Consider using the goog.fx.Dragger to track the distance
1450
// even after the mouse leaves the dragged element.
1451
var currentDragElement = this.currentDragElement_;
1452
var distanceAboveThreshold =
1453
distance > goog.fx.AbstractDragDrop.initDragDistanceThreshold;
1454
var mouseOutOnDragElement = event.type == goog.events.EventType.MOUSEOUT &&
1455
event.target == currentDragElement;
1456
if (distanceAboveThreshold || mouseOutOnDragElement) {
1457
this.eventHandler_.removeAll();
1458
this.parent_.startDrag(event, this);
1459
}
1460
1461
// Prevent text selection while dragging an element.
1462
event.preventDefault();
1463
};
1464
1465
1466
/**
1467
* Event handler for mouse up. Removes mouse move, mouse out and mouse up event
1468
* handlers.
1469
*
1470
* @param {goog.events.BrowserEvent} event Mouse up event.
1471
* @private
1472
*/
1473
goog.fx.DragDropItem.prototype.mouseUp_ = function(event) {
1474
this.eventHandler_.removeAll();
1475
delete this.startPosition_;
1476
this.currentDragElement_ = null;
1477
};
1478
1479
1480
1481
/**
1482
* Class representing an active drop target
1483
*
1484
* @param {goog.math.Box} box Box describing the position and dimension of the
1485
* target item.
1486
* @param {goog.fx.AbstractDragDrop=} opt_target Target that contains the item
1487
associated with position.
1488
* @param {goog.fx.DragDropItem=} opt_item Item associated with position.
1489
* @param {Element=} opt_element Element of item associated with position.
1490
* @constructor
1491
* @struct
1492
* @private
1493
*/
1494
goog.fx.ActiveDropTarget_ = function(box, opt_target, opt_item, opt_element) {
1495
1496
/**
1497
* Box describing the position and dimension of the target item
1498
* @type {goog.math.Box}
1499
* @private
1500
*/
1501
this.box_ = box;
1502
1503
/**
1504
* Target that contains the item associated with position
1505
* @type {goog.fx.AbstractDragDrop|undefined}
1506
* @private
1507
*/
1508
this.target_ = opt_target;
1509
1510
/**
1511
* Item associated with position
1512
* @type {goog.fx.DragDropItem|undefined}
1513
* @private
1514
*/
1515
this.item_ = opt_item;
1516
1517
/**
1518
* The draggable element of the item associated with position.
1519
* @type {Element}
1520
* @private
1521
*/
1522
this.element_ = opt_element || null;
1523
1524
/**
1525
* If this target is in a scrollable container this is it.
1526
* @private {?goog.fx.ScrollableContainer_}
1527
*/
1528
this.scrollableContainer_ = null;
1529
};
1530
1531
1532
1533
/**
1534
* Class for representing a scrollable container
1535
* @param {Element} element the scrollable element.
1536
* @constructor
1537
* @private
1538
*/
1539
goog.fx.ScrollableContainer_ = function(element) {
1540
1541
/**
1542
* The targets that lie within this container.
1543
* @type {Array<goog.fx.ActiveDropTarget_>}
1544
* @private
1545
*/
1546
this.containedTargets_ = [];
1547
1548
/**
1549
* The element that is this container
1550
* @type {Element}
1551
* @private
1552
*/
1553
this.element_ = element;
1554
1555
/**
1556
* The saved scroll left location for calculating deltas.
1557
* @type {number}
1558
* @private
1559
*/
1560
this.savedScrollLeft_ = 0;
1561
1562
/**
1563
* The saved scroll top location for calculating deltas.
1564
* @type {number}
1565
* @private
1566
*/
1567
this.savedScrollTop_ = 0;
1568
1569
/**
1570
* The space occupied by the container.
1571
* @type {goog.math.Box}
1572
* @private
1573
*/
1574
this.box_ = null;
1575
};
1576
1577