2
Copyright (c) 2004-2006, The Dojo Foundation
5
Licensed under the Academic Free License version 2.1 or above OR the
6
modified BSD license. For more information on Dojo licensing, see:
8
http://dojotoolkit.org/community/licensing.shtml
11
dojo.provide("dojo.dnd.HtmlDragManager");
12
dojo.require("dojo.dnd.DragAndDrop");
13
dojo.require("dojo.event.*");
14
dojo.require("dojo.lang.array");
15
dojo.require("dojo.html.common");
16
dojo.require("dojo.html.layout");
18
// NOTE: there will only ever be a single instance of HTMLDragManager, so it's
19
// safe to use prototype properties for book-keeping.
20
dojo.declare("dojo.dnd.HtmlDragManager", dojo.dnd.DragManager, {
22
* There are several sets of actions that the DnD code cares about in the
25
* (draggable selection)
26
* (dragObject generation)
28
* (draggable movement)
29
* (droppable detection)
33
* (inform/destroy dragObject)
36
* 2.) mouse-down -> mouse-down
37
* (click-hold context menu)
39
* (draggable selection)
40
* shift-mouse-click ->
41
* (augment draggable selection)
43
* (dragObject generation)
45
* (draggable movement)
46
* (droppable detection)
53
* (clobber draggable selection)
55
disabled: false, // to kill all dragging!
57
mouseDownTimer: null, // used for click-hold operations
59
dsPrefix: "dojoDragSource",
61
// dimension calculation cache for use durring drag
62
dropTargetDimensions: [],
64
currentDropTarget: null,
65
// currentDropTargetPoints: null,
66
previousDropTarget: null,
67
_dragTriggered: false,
72
// mouse position properties
81
dropAcceptable: false,
83
cancelEvent: function(e){ e.stopPropagation(); e.preventDefault();},
86
registerDragSource: function(ds){
87
//dojo.profile.start("register DragSource");
90
// FIXME: dragSource objects SHOULD have some sort of property that
91
// references their DOM node, we shouldn't just be passing nodes and
92
// expecting it to work.
93
//dojo.profile.start("register DragSource 1");
94
var dp = this.dsPrefix;
95
var dpIdx = dp+"Idx_"+(this.dsCounter++);
96
ds.dragSourceId = dpIdx;
97
this.dragSources[dpIdx] = ds;
98
ds.domNode.setAttribute(dp, dpIdx);
99
//dojo.profile.end("register DragSource 1");
101
//dojo.profile.start("register DragSource 2");
103
// so we can drag links
104
if(dojo.render.html.ie){
105
//dojo.profile.start("register DragSource IE");
107
dojo.event.browser.addListener(ds.domNode, "ondragstart", this.cancelEvent);
109
//dojo.event.connect(ds.domNode, "ondragstart", this.cancelEvent);
110
//dojo.profile.end("register DragSource IE");
113
//dojo.profile.end("register DragSource 2");
116
//dojo.profile.end("register DragSource");
119
unregisterDragSource: function(ds){
121
var dp = this.dsPrefix;
122
var dpIdx = ds.dragSourceId;
123
delete ds.dragSourceId;
124
delete this.dragSources[dpIdx];
125
ds.domNode.setAttribute(dp, null);
126
if(dojo.render.html.ie){
127
dojo.event.browser.removeListener(ds.domNode, "ondragstart", this.cancelEvent);
132
registerDropTarget: function(dt){
133
this.dropTargets.push(dt);
136
unregisterDropTarget: function(dt){
137
var index = dojo.lang.find(this.dropTargets, dt, true);
139
this.dropTargets.splice(index, 1);
144
* Get the DOM element that is meant to drag.
145
* Loop through the parent nodes of the event target until
146
* the element is found that was created as a DragSource and
149
* @param event object The event for which to get the drag source.
151
getDragSource: function(e){
153
if(tn === dojo.body()){ return; }
154
var ta = dojo.html.getAttribute(tn, this.dsPrefix);
157
if((!tn)||(tn === dojo.body())){ return; }
158
ta = dojo.html.getAttribute(tn, this.dsPrefix);
160
return this.dragSources[ta];
163
onKeyDown: function(e){
166
onMouseDown: function(e){
167
if(this.disabled) { return; }
169
// only begin on left click
170
if(dojo.render.html.ie) {
171
if(e.button != 1) { return; }
172
} else if(e.which != 1) {
176
var target = e.target.nodeType == dojo.html.TEXT_NODE ?
177
e.target.parentNode : e.target;
179
// do not start drag involvement if the user is interacting with
181
if(dojo.html.isTag(target, "button", "textarea", "input", "select", "option")) {
185
// find a selection object, if one is a parent of the source node
186
var ds = this.getDragSource(e);
188
// this line is important. if we aren't selecting anything then
189
// we need to return now, so preventDefault() isn't called, and thus
190
// the event is propogated to other handling code
193
if(!dojo.lang.inArray(this.selectedSources, ds)){
194
this.selectedSources.push(ds);
198
this.mouseDownX = e.pageX;
199
this.mouseDownY = e.pageY;
201
// Must stop the mouse down from being propogated, or otherwise can't
202
// drag links in firefox.
203
// WARNING: preventing the default action on all mousedown events
204
// prevents user interaction with the contents.
207
dojo.event.connect(document, "onmousemove", this, "onMouseMove");
210
onMouseUp: function(e, cancel){
211
// if we aren't dragging then ignore the mouse-up
212
// (in particular, don't call preventDefault(), because other
213
// code may need to process this event)
214
if(this.selectedSources.length==0){
218
this.mouseDownX = null;
219
this.mouseDownY = null;
220
this._dragTriggered = false;
221
// e.preventDefault();
222
e.dragSource = this.dragSource;
223
// let ctrl be used for multiselect or another action
224
// if I use same key to trigger treeV3 node selection and here,
225
// I have bugs with drag'n'drop. why ?? no idea..
226
if((!e.shiftKey)&&(!e.ctrlKey)){
228
if(this.currentDropTarget) {
229
this.currentDropTarget.onDropStart();
231
dojo.lang.forEach(this.dragObjects, function(tempDragObj){
233
if(!tempDragObj){ return; }
234
if(this.currentDropTarget) {
235
e.dragObject = tempDragObj;
237
// NOTE: we can't get anything but the current drop target
238
// here since the drag shadow blocks mouse-over events.
239
// This is probelematic for dropping "in" something
240
var ce = this.currentDropTarget.domNode.childNodes;
242
e.dropTarget = ce[0];
243
while(e.dropTarget == tempDragObj.domNode){
244
e.dropTarget = e.dropTarget.nextSibling;
247
e.dropTarget = this.currentDropTarget.domNode;
249
if(this.dropAcceptable){
250
ret = this.currentDropTarget.onDrop(e);
252
this.currentDropTarget.onDragOut(e);
256
e.dragStatus = this.dropAcceptable && ret ? "dropSuccess" : "dropFailure";
257
// decouple the calls for onDragEnd, so they don't block the execution here
258
// ie. if the onDragEnd would call an alert, the execution here is blocked until the
259
// user has confirmed the alert box and then the rest of the dnd code is executed
260
// while the mouse doesnt "hold" the dragged object anymore ... and so on
261
dojo.lang.delayThese([
263
// in FF1.5 this throws an exception, see
264
// http://dojotoolkit.org/pipermail/dojo-interest/2006-April/006751.html
266
tempDragObj.dragSource.onDragEnd(e)
268
// since the problem seems passing e, we just copy all
269
// properties and try the copy ...
272
if (i=="type") { // the type property contains the exception, no idea why...
273
ecopy.type = "mouseup";
278
tempDragObj.dragSource.onDragEnd(ecopy);
281
, function() {tempDragObj.onDragEnd(e)}]);
284
this.selectedSources = [];
285
this.dragObjects = [];
286
this.dragSource = null;
287
if(this.currentDropTarget) {
288
this.currentDropTarget.onDropEnd();
291
//dojo.debug("special click");
294
dojo.event.disconnect(document, "onmousemove", this, "onMouseMove");
295
this.currentDropTarget = null;
298
onScroll: function(){
299
//dojo.profile.start("DNDManager updateoffset");
300
for(var i = 0; i < this.dragObjects.length; i++) {
301
if(this.dragObjects[i].updateDragOffset) {
302
this.dragObjects[i].updateDragOffset();
305
//dojo.profile.end("DNDManager updateoffset");
307
// TODO: do not recalculate, only adjust coordinates
308
if (this.dragObjects.length) {
309
this.cacheTargetLocations();
313
_dragStartDistance: function(x, y){
314
if((!this.mouseDownX)||(!this.mouseDownX)){
317
var dx = Math.abs(x-this.mouseDownX);
319
var dy = Math.abs(y-this.mouseDownY);
321
return parseInt(Math.sqrt(dx2+dy2), 10);
324
cacheTargetLocations: function(){
325
dojo.profile.start("cacheTargetLocations");
327
this.dropTargetDimensions = [];
328
dojo.lang.forEach(this.dropTargets, function(tempTarget){
329
var tn = tempTarget.domNode;
330
//only cache dropTarget which can accept current dragSource
331
if(!tn || dojo.lang.find(tempTarget.acceptedTypes, this.dragSource.type) < 0){ return; }
332
var abs = dojo.html.getAbsolutePosition(tn, true);
333
var bb = dojo.html.getBorderBox(tn);
334
this.dropTargetDimensions.push([
335
[abs.x, abs.y], // upper-left
337
[ abs.x+bb.width, abs.y+bb.height ],
340
//dojo.debug("Cached for "+tempTarget)
343
dojo.profile.end("cacheTargetLocations");
345
//dojo.debug("Cache locations")
348
onMouseMove: function(e){
349
if((dojo.render.html.ie)&&(e.button != 1)){
350
// Oooops - mouse up occurred - e.g. when mouse was not over the
351
// window. I don't think we can detect this for FF - but at least
352
// we can be nice in IE.
353
this.currentDropTarget = null;
354
this.onMouseUp(e, true);
358
// if we've got some sources, but no drag objects, we need to send
359
// onDragStart to all the right parties and get things lined up for
360
// drop target detection
362
if( (this.selectedSources.length)&&
363
(!this.dragObjects.length) ){
366
if(!this._dragTriggered){
367
this._dragTriggered = (this._dragStartDistance(e.pageX, e.pageY) > this.threshold);
368
if(!this._dragTriggered){ return; }
369
dx = e.pageX - this.mouseDownX;
370
dy = e.pageY - this.mouseDownY;
373
// the first element is always our dragSource, if there are multiple
374
// selectedSources (elements that move along) then the first one is the master
375
// and for it the events will be fired etc.
376
this.dragSource = this.selectedSources[0];
378
dojo.lang.forEach(this.selectedSources, function(tempSource){
379
if(!tempSource){ return; }
380
var tdo = tempSource.onDragStart(e);
384
// "bump" the drag object to account for the drag threshold
385
tdo.dragOffset.y += dy;
386
tdo.dragOffset.x += dx;
387
tdo.dragSource = tempSource;
389
this.dragObjects.push(tdo);
393
/* clean previous drop target in dragStart */
394
this.previousDropTarget = null;
396
this.cacheTargetLocations();
399
// FIXME: we need to add dragSources and dragObjects to e
400
dojo.lang.forEach(this.dragObjects, function(dragObj){
401
if(dragObj){ dragObj.onDragMove(e); }
404
// if we have a current drop target, check to see if we're outside of
405
// it. If so, do all the actions that need doing.
406
if(this.currentDropTarget){
407
//dojo.debug(dojo.html.hasParent(this.currentDropTarget.domNode))
408
var c = dojo.html.toCoordinateObject(this.currentDropTarget.domNode, true);
409
// var dtp = this.currentDropTargetPoints;
411
[c.x,c.y], [c.x+c.width, c.y+c.height]
415
if((!this.nestedTargets)&&(dtp)&&(this.isInsideBox(e, dtp))){
416
if(this.dropAcceptable){
417
this.currentDropTarget.onDragMove(e, this.dragObjects);
420
// FIXME: need to fix the event object!
421
// see if we can find a better drop target
422
var bestBox = this.findBestTarget(e);
424
if(bestBox.target === null){
425
if(this.currentDropTarget){
426
this.currentDropTarget.onDragOut(e);
427
this.previousDropTarget = this.currentDropTarget;
428
this.currentDropTarget = null;
429
// this.currentDropTargetPoints = null;
431
this.dropAcceptable = false;
435
if(this.currentDropTarget !== bestBox.target){
436
if(this.currentDropTarget){
437
this.previousDropTarget = this.currentDropTarget;
438
this.currentDropTarget.onDragOut(e);
440
this.currentDropTarget = bestBox.target;
441
// this.currentDropTargetPoints = bestBox.points;
442
e.dragObjects = this.dragObjects;
443
this.dropAcceptable = this.currentDropTarget.onDragOver(e);
446
if(this.dropAcceptable){
447
this.currentDropTarget.onDragMove(e, this.dragObjects);
453
findBestTarget: function(e) {
455
var bestBox = new Object();
456
bestBox.target = null;
457
bestBox.points = null;
458
dojo.lang.every(this.dropTargetDimensions, function(tmpDA) {
459
if(!_this.isInsideBox(e, tmpDA)){
463
bestBox.target = tmpDA[2];
464
bestBox.points = tmpDA;
465
// continue iterating only if _this.nestedTargets == true
466
return Boolean(_this.nestedTargets);
472
isInsideBox: function(e, coords){
473
if( (e.pageX > coords[0][0])&&
474
(e.pageX < coords[1][0])&&
475
(e.pageY > coords[0][1])&&
476
(e.pageY < coords[1][1]) ){
482
onMouseOver: function(e){
485
onMouseOut: function(e){
489
dojo.dnd.dragManager = new dojo.dnd.HtmlDragManager();
491
// global namespace protection closure
494
var dm = dojo.dnd.dragManager;
495
//TODO: when focus manager is ready, dragManager should be rewritten to use it
496
// set up event handlers on the document (or no?)
497
dojo.event.connect(d, "onkeydown", dm, "onKeyDown");
498
dojo.event.connect(d, "onmouseover", dm, "onMouseOver");
499
dojo.event.connect(d, "onmouseout", dm, "onMouseOut");
500
dojo.event.connect(d, "onmousedown", dm, "onMouseDown");
501
dojo.event.connect(d, "onmouseup", dm, "onMouseUp");
502
// TODO: process scrolling of elements, not only window (focus manager would
503
// probably come to rescue here as well)
504
dojo.event.connect(window, "onscroll", dm, "onScroll");