| /* |
| Copyright (c) 2004-2006, The Dojo Foundation |
| All Rights Reserved. |
| |
| Licensed under the Academic Free License version 2.1 or above OR the |
| modified BSD license. For more information on Dojo licensing, see: |
| |
| http://dojotoolkit.org/community/licensing.shtml |
| */ |
| |
| dojo.provide("dojo.dnd.HtmlDragManager"); |
| dojo.require("dojo.dnd.DragAndDrop"); |
| dojo.require("dojo.event.*"); |
| dojo.require("dojo.lang.array"); |
| dojo.require("dojo.html.common"); |
| dojo.require("dojo.html.layout"); |
| |
| // NOTE: there will only ever be a single instance of HTMLDragManager, so it's |
| // safe to use prototype properties for book-keeping. |
| dojo.declare("dojo.dnd.HtmlDragManager", dojo.dnd.DragManager, { |
| /** |
| * There are several sets of actions that the DnD code cares about in the |
| * HTML context: |
| * 1.) mouse-down -> |
| * (draggable selection) |
| * (dragObject generation) |
| * mouse-move -> |
| * (draggable movement) |
| * (droppable detection) |
| * (inform droppable) |
| * (inform dragObject) |
| * mouse-up |
| * (inform/destroy dragObject) |
| * (inform draggable) |
| * (inform droppable) |
| * 2.) mouse-down -> mouse-down |
| * (click-hold context menu) |
| * 3.) mouse-click -> |
| * (draggable selection) |
| * shift-mouse-click -> |
| * (augment draggable selection) |
| * mouse-down -> |
| * (dragObject generation) |
| * mouse-move -> |
| * (draggable movement) |
| * (droppable detection) |
| * (inform droppable) |
| * (inform dragObject) |
| * mouse-up |
| * (inform draggable) |
| * (inform droppable) |
| * 4.) mouse-up |
| * (clobber draggable selection) |
| */ |
| disabled: false, // to kill all dragging! |
| nestedTargets: false, |
| mouseDownTimer: null, // used for click-hold operations |
| dsCounter: 0, |
| dsPrefix: "dojoDragSource", |
| |
| // dimension calculation cache for use durring drag |
| dropTargetDimensions: [], |
| |
| currentDropTarget: null, |
| // currentDropTargetPoints: null, |
| previousDropTarget: null, |
| _dragTriggered: false, |
| |
| selectedSources: [], |
| dragObjects: [], |
| dragSources: [], |
| |
| // mouse position properties |
| currentX: null, |
| currentY: null, |
| lastX: null, |
| lastY: null, |
| mouseDownX: null, |
| mouseDownY: null, |
| threshold: 7, |
| |
| dropAcceptable: false, |
| |
| cancelEvent: function(e){ e.stopPropagation(); e.preventDefault();}, |
| |
| // method over-rides |
| registerDragSource: function(ds){ |
| //dojo.profile.start("register DragSource"); |
| |
| if(ds["domNode"]){ |
| // FIXME: dragSource objects SHOULD have some sort of property that |
| // references their DOM node, we shouldn't just be passing nodes and |
| // expecting it to work. |
| //dojo.profile.start("register DragSource 1"); |
| var dp = this.dsPrefix; |
| var dpIdx = dp+"Idx_"+(this.dsCounter++); |
| ds.dragSourceId = dpIdx; |
| this.dragSources[dpIdx] = ds; |
| ds.domNode.setAttribute(dp, dpIdx); |
| //dojo.profile.end("register DragSource 1"); |
| |
| //dojo.profile.start("register DragSource 2"); |
| |
| // so we can drag links |
| if(dojo.render.html.ie){ |
| //dojo.profile.start("register DragSource IE"); |
| |
| dojo.event.browser.addListener(ds.domNode, "ondragstart", this.cancelEvent); |
| // terribly slow |
| //dojo.event.connect(ds.domNode, "ondragstart", this.cancelEvent); |
| //dojo.profile.end("register DragSource IE"); |
| |
| } |
| //dojo.profile.end("register DragSource 2"); |
| |
| } |
| //dojo.profile.end("register DragSource"); |
| }, |
| |
| unregisterDragSource: function(ds){ |
| if (ds["domNode"]){ |
| var dp = this.dsPrefix; |
| var dpIdx = ds.dragSourceId; |
| delete ds.dragSourceId; |
| delete this.dragSources[dpIdx]; |
| ds.domNode.setAttribute(dp, null); |
| if(dojo.render.html.ie){ |
| dojo.event.browser.removeListener(ds.domNode, "ondragstart", this.cancelEvent); |
| } |
| } |
| }, |
| |
| registerDropTarget: function(dt){ |
| this.dropTargets.push(dt); |
| }, |
| |
| unregisterDropTarget: function(dt){ |
| var index = dojo.lang.find(this.dropTargets, dt, true); |
| if (index>=0) { |
| this.dropTargets.splice(index, 1); |
| } |
| }, |
| |
| /** |
| * Get the DOM element that is meant to drag. |
| * Loop through the parent nodes of the event target until |
| * the element is found that was created as a DragSource and |
| * return it. |
| * |
| * @param event object The event for which to get the drag source. |
| */ |
| getDragSource: function(e){ |
| var tn = e.target; |
| if(tn === dojo.body()){ return; } |
| var ta = dojo.html.getAttribute(tn, this.dsPrefix); |
| while((!ta)&&(tn)){ |
| tn = tn.parentNode; |
| if((!tn)||(tn === dojo.body())){ return; } |
| ta = dojo.html.getAttribute(tn, this.dsPrefix); |
| } |
| return this.dragSources[ta]; |
| }, |
| |
| onKeyDown: function(e){ |
| }, |
| |
| onMouseDown: function(e){ |
| if(this.disabled) { return; } |
| |
| // only begin on left click |
| if(dojo.render.html.ie) { |
| if(e.button != 1) { return; } |
| } else if(e.which != 1) { |
| return; |
| } |
| |
| var target = e.target.nodeType == dojo.html.TEXT_NODE ? |
| e.target.parentNode : e.target; |
| |
| // do not start drag involvement if the user is interacting with |
| // a form element. |
| if(dojo.html.isTag(target, "button", "textarea", "input", "select", "option")) { |
| return; |
| } |
| |
| // find a selection object, if one is a parent of the source node |
| var ds = this.getDragSource(e); |
| |
| // this line is important. if we aren't selecting anything then |
| // we need to return now, so preventDefault() isn't called, and thus |
| // the event is propogated to other handling code |
| if(!ds){ return; } |
| |
| if(!dojo.lang.inArray(this.selectedSources, ds)){ |
| this.selectedSources.push(ds); |
| ds.onSelected(); |
| } |
| |
| this.mouseDownX = e.pageX; |
| this.mouseDownY = e.pageY; |
| |
| // Must stop the mouse down from being propogated, or otherwise can't |
| // drag links in firefox. |
| // WARNING: preventing the default action on all mousedown events |
| // prevents user interaction with the contents. |
| e.preventDefault(); |
| |
| dojo.event.connect(document, "onmousemove", this, "onMouseMove"); |
| }, |
| |
| onMouseUp: function(e, cancel){ |
| // if we aren't dragging then ignore the mouse-up |
| // (in particular, don't call preventDefault(), because other |
| // code may need to process this event) |
| if(this.selectedSources.length==0){ |
| return; |
| } |
| |
| this.mouseDownX = null; |
| this.mouseDownY = null; |
| this._dragTriggered = false; |
| // e.preventDefault(); |
| e.dragSource = this.dragSource; |
| // let ctrl be used for multiselect or another action |
| // if I use same key to trigger treeV3 node selection and here, |
| // I have bugs with drag'n'drop. why ?? no idea.. |
| if((!e.shiftKey)&&(!e.ctrlKey)){ |
| //if(!e.shiftKey){ |
| if(this.currentDropTarget) { |
| this.currentDropTarget.onDropStart(); |
| } |
| dojo.lang.forEach(this.dragObjects, function(tempDragObj){ |
| var ret = null; |
| if(!tempDragObj){ return; } |
| if(this.currentDropTarget) { |
| e.dragObject = tempDragObj; |
| |
| // NOTE: we can't get anything but the current drop target |
| // here since the drag shadow blocks mouse-over events. |
| // This is probelematic for dropping "in" something |
| var ce = this.currentDropTarget.domNode.childNodes; |
| if(ce.length > 0){ |
| e.dropTarget = ce[0]; |
| while(e.dropTarget == tempDragObj.domNode){ |
| e.dropTarget = e.dropTarget.nextSibling; |
| } |
| }else{ |
| e.dropTarget = this.currentDropTarget.domNode; |
| } |
| if(this.dropAcceptable){ |
| ret = this.currentDropTarget.onDrop(e); |
| }else{ |
| this.currentDropTarget.onDragOut(e); |
| } |
| } |
| |
| e.dragStatus = this.dropAcceptable && ret ? "dropSuccess" : "dropFailure"; |
| // decouple the calls for onDragEnd, so they don't block the execution here |
| // ie. if the onDragEnd would call an alert, the execution here is blocked until the |
| // user has confirmed the alert box and then the rest of the dnd code is executed |
| // while the mouse doesnt "hold" the dragged object anymore ... and so on |
| dojo.lang.delayThese([ |
| function() { |
| // in FF1.5 this throws an exception, see |
| // http://dojotoolkit.org/pipermail/dojo-interest/2006-April/006751.html |
| try{ |
| tempDragObj.dragSource.onDragEnd(e) |
| } catch(err) { |
| // since the problem seems passing e, we just copy all |
| // properties and try the copy ... |
| var ecopy = {}; |
| for (var i in e) { |
| if (i=="type") { // the type property contains the exception, no idea why... |
| ecopy.type = "mouseup"; |
| continue; |
| } |
| ecopy[i] = e[i]; |
| } |
| tempDragObj.dragSource.onDragEnd(ecopy); |
| } |
| } |
| , function() {tempDragObj.onDragEnd(e)}]); |
| }, this); |
| |
| this.selectedSources = []; |
| this.dragObjects = []; |
| this.dragSource = null; |
| if(this.currentDropTarget) { |
| this.currentDropTarget.onDropEnd(); |
| } |
| } else { |
| //dojo.debug("special click"); |
| } |
| |
| dojo.event.disconnect(document, "onmousemove", this, "onMouseMove"); |
| this.currentDropTarget = null; |
| }, |
| |
| onScroll: function(){ |
| //dojo.profile.start("DNDManager updateoffset"); |
| for(var i = 0; i < this.dragObjects.length; i++) { |
| if(this.dragObjects[i].updateDragOffset) { |
| this.dragObjects[i].updateDragOffset(); |
| } |
| } |
| //dojo.profile.end("DNDManager updateoffset"); |
| |
| // TODO: do not recalculate, only adjust coordinates |
| if (this.dragObjects.length) { |
| this.cacheTargetLocations(); |
| } |
| }, |
| |
| _dragStartDistance: function(x, y){ |
| if((!this.mouseDownX)||(!this.mouseDownX)){ |
| return; |
| } |
| var dx = Math.abs(x-this.mouseDownX); |
| var dx2 = dx*dx; |
| var dy = Math.abs(y-this.mouseDownY); |
| var dy2 = dy*dy; |
| return parseInt(Math.sqrt(dx2+dy2), 10); |
| }, |
| |
| cacheTargetLocations: function(){ |
| dojo.profile.start("cacheTargetLocations"); |
| |
| this.dropTargetDimensions = []; |
| dojo.lang.forEach(this.dropTargets, function(tempTarget){ |
| var tn = tempTarget.domNode; |
| //only cache dropTarget which can accept current dragSource |
| if(!tn || !tempTarget.accepts([this.dragSource])){ return; } |
| var abs = dojo.html.getAbsolutePosition(tn, true); |
| var bb = dojo.html.getBorderBox(tn); |
| this.dropTargetDimensions.push([ |
| [abs.x, abs.y], // upper-left |
| // lower-right |
| [ abs.x+bb.width, abs.y+bb.height ], |
| tempTarget |
| ]); |
| //dojo.debug("Cached for "+tempTarget) |
| }, this); |
| |
| dojo.profile.end("cacheTargetLocations"); |
| |
| //dojo.debug("Cache locations") |
| }, |
| |
| onMouseMove: function(e){ |
| if((dojo.render.html.ie)&&(e.button != 1)){ |
| // Oooops - mouse up occurred - e.g. when mouse was not over the |
| // window. I don't think we can detect this for FF - but at least |
| // we can be nice in IE. |
| this.currentDropTarget = null; |
| this.onMouseUp(e, true); |
| return; |
| } |
| |
| // if we've got some sources, but no drag objects, we need to send |
| // onDragStart to all the right parties and get things lined up for |
| // drop target detection |
| |
| if( (this.selectedSources.length)&& |
| (!this.dragObjects.length) ){ |
| var dx; |
| var dy; |
| if(!this._dragTriggered){ |
| this._dragTriggered = (this._dragStartDistance(e.pageX, e.pageY) > this.threshold); |
| if(!this._dragTriggered){ return; } |
| dx = e.pageX - this.mouseDownX; |
| dy = e.pageY - this.mouseDownY; |
| } |
| |
| // the first element is always our dragSource, if there are multiple |
| // selectedSources (elements that move along) then the first one is the master |
| // and for it the events will be fired etc. |
| this.dragSource = this.selectedSources[0]; |
| |
| dojo.lang.forEach(this.selectedSources, function(tempSource){ |
| if(!tempSource){ return; } |
| var tdo = tempSource.onDragStart(e); |
| if(tdo){ |
| tdo.onDragStart(e); |
| |
| // "bump" the drag object to account for the drag threshold |
| tdo.dragOffset.y += dy; |
| tdo.dragOffset.x += dx; |
| tdo.dragSource = tempSource; |
| |
| this.dragObjects.push(tdo); |
| } |
| }, this); |
| |
| /* clean previous drop target in dragStart */ |
| this.previousDropTarget = null; |
| |
| this.cacheTargetLocations(); |
| } |
| |
| // FIXME: we need to add dragSources and dragObjects to e |
| dojo.lang.forEach(this.dragObjects, function(dragObj){ |
| if(dragObj){ dragObj.onDragMove(e); } |
| }); |
| |
| // if we have a current drop target, check to see if we're outside of |
| // it. If so, do all the actions that need doing. |
| if(this.currentDropTarget){ |
| //dojo.debug(dojo.html.hasParent(this.currentDropTarget.domNode)) |
| var c = dojo.html.toCoordinateObject(this.currentDropTarget.domNode, true); |
| // var dtp = this.currentDropTargetPoints; |
| var dtp = [ |
| [c.x,c.y], [c.x+c.width, c.y+c.height] |
| ]; |
| } |
| |
| if((!this.nestedTargets)&&(dtp)&&(this.isInsideBox(e, dtp))){ |
| if(this.dropAcceptable){ |
| this.currentDropTarget.onDragMove(e, this.dragObjects); |
| } |
| }else{ |
| // FIXME: need to fix the event object! |
| // see if we can find a better drop target |
| var bestBox = this.findBestTarget(e); |
| |
| if(bestBox.target === null){ |
| if(this.currentDropTarget){ |
| this.currentDropTarget.onDragOut(e); |
| this.previousDropTarget = this.currentDropTarget; |
| this.currentDropTarget = null; |
| // this.currentDropTargetPoints = null; |
| } |
| this.dropAcceptable = false; |
| return; |
| } |
| |
| if(this.currentDropTarget !== bestBox.target){ |
| if(this.currentDropTarget){ |
| this.previousDropTarget = this.currentDropTarget; |
| this.currentDropTarget.onDragOut(e); |
| } |
| this.currentDropTarget = bestBox.target; |
| // this.currentDropTargetPoints = bestBox.points; |
| e.dragObjects = this.dragObjects; |
| this.dropAcceptable = this.currentDropTarget.onDragOver(e); |
| |
| }else{ |
| if(this.dropAcceptable){ |
| this.currentDropTarget.onDragMove(e, this.dragObjects); |
| } |
| } |
| } |
| }, |
| |
| findBestTarget: function(e) { |
| var _this = this; |
| var bestBox = new Object(); |
| bestBox.target = null; |
| bestBox.points = null; |
| dojo.lang.every(this.dropTargetDimensions, function(tmpDA) { |
| if(!_this.isInsideBox(e, tmpDA)){ |
| return true; |
| } |
| |
| bestBox.target = tmpDA[2]; |
| bestBox.points = tmpDA; |
| // continue iterating only if _this.nestedTargets == true |
| return Boolean(_this.nestedTargets); |
| }); |
| |
| return bestBox; |
| }, |
| |
| isInsideBox: function(e, coords){ |
| if( (e.pageX > coords[0][0])&& |
| (e.pageX < coords[1][0])&& |
| (e.pageY > coords[0][1])&& |
| (e.pageY < coords[1][1]) ){ |
| return true; |
| } |
| return false; |
| }, |
| |
| onMouseOver: function(e){ |
| }, |
| |
| onMouseOut: function(e){ |
| } |
| }); |
| |
| dojo.dnd.dragManager = new dojo.dnd.HtmlDragManager(); |
| |
| // global namespace protection closure |
| (function(){ |
| var d = document; |
| var dm = dojo.dnd.dragManager; |
| //TODO: when focus manager is ready, dragManager should be rewritten to use it |
| // set up event handlers on the document (or no?) |
| dojo.event.connect(d, "onkeydown", dm, "onKeyDown"); |
| dojo.event.connect(d, "onmouseover", dm, "onMouseOver"); |
| dojo.event.connect(d, "onmouseout", dm, "onMouseOut"); |
| dojo.event.connect(d, "onmousedown", dm, "onMouseDown"); |
| dojo.event.connect(d, "onmouseup", dm, "onMouseUp"); |
| // TODO: process scrolling of elements, not only window (focus manager would |
| // probably come to rescue here as well) |
| dojo.event.connect(window, "onscroll", dm, "onScroll"); |
| })(); |