/* 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.HtmlDragAndDrop"); dojo.provide("dojo.dnd.HtmlDragSource"); dojo.provide("dojo.dnd.HtmlDropTarget"); dojo.provide("dojo.dnd.HtmlDragObject"); dojo.require("dojo.dnd.HtmlDragManager"); dojo.require("dojo.dnd.DragAndDrop"); dojo.require("dojo.dom"); dojo.require("dojo.style"); dojo.require("dojo.html"); dojo.require("dojo.html.extras"); dojo.require("dojo.lang.extras"); dojo.require("dojo.lfx.*"); dojo.require("dojo.event"); dojo.dnd.HtmlDragSource = function(node, type){ node = dojo.byId(node); this.dragObjects = []; this.constrainToContainer = false; if(node){ this.domNode = node; this.dragObject = node; // register us dojo.dnd.DragSource.call(this); // set properties that might have been clobbered by the mixin this.type = (type)||(this.domNode.nodeName.toLowerCase()); } } dojo.inherits(dojo.dnd.HtmlDragSource, dojo.dnd.DragSource); dojo.lang.extend(dojo.dnd.HtmlDragSource, { dragClass: "", // CSS classname(s) applied to node when it is being dragged onDragStart: function(){ var dragObj = new dojo.dnd.HtmlDragObject(this.dragObject, this.type); if(this.dragClass) { dragObj.dragClass = this.dragClass; } if (this.constrainToContainer) { dragObj.constrainTo(this.constrainingContainer || this.domNode.parentNode); } return dragObj; }, setDragHandle: function(node){ node = dojo.byId(node); dojo.dnd.dragManager.unregisterDragSource(this); this.domNode = node; dojo.dnd.dragManager.registerDragSource(this); }, setDragTarget: function(node){ this.dragObject = node; }, constrainTo: function(container) { this.constrainToContainer = true; if (container) { this.constrainingContainer = container; } }, /* * * see dojo.dnd.DragSource.onSelected */ onSelected: function() { for (var i=0; i this.constraints.maxX) { x = this.constraints.maxX; } if (y > this.constraints.maxY) { y = this.constraints.maxY; } } this.setAbsolutePosition(x, y); dojo.event.topic.publish('dragMove', { source: this } ); }, /** * Set the position of the drag clone. (x,y) is relative to . */ setAbsolutePosition: function(x, y){ // The drag clone is attached to document.body so this is trivial if(!this.disableY) { this.dragClone.style.top = y + "px"; } if(!this.disableX) { this.dragClone.style.left = x + "px"; } }, /** * If the drag operation returned a success we reomve the clone of * ourself from the original position. If the drag operation returned * failure we slide back over to where we came from and end the operation * with a little grace. */ onDragEnd: function(e){ switch(e.dragStatus){ case "dropSuccess": dojo.dom.removeNode(this.dragClone); this.dragClone = null; break; case "dropFailure": // slide back to the start var startCoords = dojo.style.getAbsolutePosition(this.dragClone, true); // offset the end so the effect can be seen var endCoords = [this.dragStartPosition.x + 1, this.dragStartPosition.y + 1]; // animate var line = new dojo.lfx.Line(startCoords, endCoords); var anim = new dojo.lfx.Animation(500, line, dojo.lfx.easeOut); var dragObject = this; dojo.event.connect(anim, "onAnimate", function(e) { dragObject.dragClone.style.left = e[0] + "px"; dragObject.dragClone.style.top = e[1] + "px"; }); dojo.event.connect(anim, "onEnd", function (e) { // pause for a second (not literally) and disappear dojo.lang.setTimeout(function() { dojo.dom.removeNode(dragObject.dragClone); // Allow drag clone to be gc'ed dragObject.dragClone = null; }, 200); }); anim.play(); break; } // shortly the browser will fire an onClick() event, // but since this was really a drag, just squelch it dojo.event.connect(this.domNode, "onclick", this, "squelchOnClick"); dojo.event.topic.publish('dragEnd', { source: this } ); }, squelchOnClick: function(e){ // squelch this onClick() event because it's the result of a drag (it's not a real click) e.preventDefault(); // but if a real click comes along, allow it dojo.event.disconnect(this.domNode, "onclick", this, "squelchOnClick"); }, constrainTo: function(container) { this.constrainToContainer=true; if (container) { this.constrainingContainer = container; } else { this.constrainingContainer = this.domNode.parentNode; } } }); dojo.dnd.HtmlDropTarget = function(node, types){ if (arguments.length == 0) { return; } this.domNode = dojo.byId(node); dojo.dnd.DropTarget.call(this); if(types && dojo.lang.isString(types)) { types = [types]; } this.acceptedTypes = types || []; } dojo.inherits(dojo.dnd.HtmlDropTarget, dojo.dnd.DropTarget); dojo.lang.extend(dojo.dnd.HtmlDropTarget, { onDragOver: function(e){ if(!this.accepts(e.dragObjects)){ return false; } // cache the positions of the child nodes this.childBoxes = []; for (var i = 0, child; i < this.domNode.childNodes.length; i++) { child = this.domNode.childNodes[i]; if (child.nodeType != dojo.dom.ELEMENT_NODE) { continue; } var pos = dojo.style.getAbsolutePosition(child, true); var height = dojo.style.getInnerHeight(child); var width = dojo.style.getInnerWidth(child); this.childBoxes.push({top: pos.y, bottom: pos.y+height, left: pos.x, right: pos.x+width, node: child}); } // TODO: use dummy node return true; }, _getNodeUnderMouse: function(e){ // find the child for (var i = 0, child; i < this.childBoxes.length; i++) { with (this.childBoxes[i]) { if (e.pageX >= left && e.pageX <= right && e.pageY >= top && e.pageY <= bottom) { return i; } } } return -1; }, createDropIndicator: function() { this.dropIndicator = document.createElement("div"); with (this.dropIndicator.style) { position = "absolute"; zIndex = 999; borderTopWidth = "1px"; borderTopColor = "black"; borderTopStyle = "solid"; width = dojo.style.getInnerWidth(this.domNode) + "px"; left = dojo.style.getAbsoluteX(this.domNode, true) + "px"; } }, onDragMove: function(e, dragObjects){ var i = this._getNodeUnderMouse(e); if(!this.dropIndicator){ this.createDropIndicator(); } if(i < 0) { if(this.childBoxes.length) { var before = (dojo.html.gravity(this.childBoxes[0].node, e) & dojo.html.gravity.NORTH); } else { var before = true; } } else { var child = this.childBoxes[i]; var before = (dojo.html.gravity(child.node, e) & dojo.html.gravity.NORTH); } this.placeIndicator(e, dragObjects, i, before); if(!dojo.html.hasParent(this.dropIndicator)) { document.body.appendChild(this.dropIndicator); } }, /** * Position the horizontal line that indicates "insert between these two items" */ placeIndicator: function(e, dragObjects, boxIndex, before) { with(this.dropIndicator.style){ if (boxIndex < 0) { if (this.childBoxes.length) { top = (before ? this.childBoxes[0].top : this.childBoxes[this.childBoxes.length - 1].bottom) + "px"; } else { top = dojo.style.getAbsoluteY(this.domNode, true) + "px"; } } else { var child = this.childBoxes[boxIndex]; top = (before ? child.top : child.bottom) + "px"; } } }, onDragOut: function(e) { if(this.dropIndicator) { dojo.dom.removeNode(this.dropIndicator); delete this.dropIndicator; } }, /** * Inserts the DragObject as a child of this node relative to the * position of the mouse. * * @return true if the DragObject was inserted, false otherwise */ onDrop: function(e){ this.onDragOut(e); var i = this._getNodeUnderMouse(e); if (i < 0) { if (this.childBoxes.length) { if (dojo.html.gravity(this.childBoxes[0].node, e) & dojo.html.gravity.NORTH) { return this.insert(e, this.childBoxes[0].node, "before"); } else { return this.insert(e, this.childBoxes[this.childBoxes.length-1].node, "after"); } } return this.insert(e, this.domNode, "append"); } var child = this.childBoxes[i]; if (dojo.html.gravity(child.node, e) & dojo.html.gravity.NORTH) { return this.insert(e, child.node, "before"); } else { return this.insert(e, child.node, "after"); } }, insert: function(e, refNode, position) { var node = e.dragObject.domNode; if(position == "before") { return dojo.html.insertBefore(node, refNode); } else if(position == "after") { return dojo.html.insertAfter(node, refNode); } else if(position == "append") { refNode.appendChild(node); return true; } return false; } });