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