blob: 884f966c1a9526627d237dd544befb109a476e04 [file] [log] [blame]
/*
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.require("dojo.dnd.HtmlDragManager");
dojo.require("dojo.dnd.DragAndDrop");
dojo.require("dojo.html.*");
dojo.require("dojo.html.display");
dojo.require("dojo.html.util");
dojo.require("dojo.html.selection");
dojo.require("dojo.html.iframe");
dojo.require("dojo.lang.extras");
dojo.require("dojo.lfx.*");
dojo.require("dojo.event.*");
dojo.declare("dojo.dnd.HtmlDragSource", dojo.dnd.DragSource, {
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.dragObjects.length; i++){
dojo.dnd.dragManager.selectedSources.push(new dojo.dnd.HtmlDragSource(this.dragObjects[i]));
}
},
/**
* Register elements that should be dragged along with
* the actual DragSource.
*
* Example usage:
* var dragSource = new dojo.dnd.HtmlDragSource(...);
* // add a single element
* dragSource.addDragObjects(dojo.byId('id1'));
* // add multiple elements to drag along
* dragSource.addDragObjects('id2', dojo.byId('id3'));
*
* el A dom node to add to the drag list.
*/
addDragObjects: function(/*DOMNode*/ el){
for(var i=0; i<arguments.length; i++){
this.dragObjects.push(dojo.byId(arguments[i]));
}
}
},
function(node, type){
node = dojo.byId(node);
this.dragObjects = [];
this.constrainToContainer = false;
if(node){
this.domNode = node;
this.dragObject = node;
// set properties that might have been clobbered by the mixin
this.type = (type)||(this.domNode.nodeName.toLowerCase());
dojo.dnd.DragSource.prototype.reregister.call(this);
}
}
);
dojo.declare("dojo.dnd.HtmlDragObject",
dojo.dnd.DragObject,
{
dragClass: "",
opacity: 0.5,
createIframe: true, // workaround IE6 bug
// if true, node will not move in X and/or Y direction
disableX: false,
disableY: false,
createDragNode: function() {
var node = this.domNode.cloneNode(true);
if(this.dragClass) { dojo.html.addClass(node, this.dragClass); }
if(this.opacity < 1) { dojo.html.setOpacity(node, this.opacity); }
var ltn = node.tagName.toLowerCase();
var isTr = (ltn == "tr");
if((isTr)||(ltn == "tbody")){
// dojo.debug("Dragging table row")
// Create a table for the cloned row
var doc = this.domNode.ownerDocument;
var table = doc.createElement("table");
if(isTr){
var tbody = doc.createElement("tbody");
table.appendChild(tbody);
tbody.appendChild(node);
}else{
table.appendChild(node);
}
// Set a fixed width to the cloned TDs
var tmpSrcTr = ((isTr) ? this.domNode : this.domNode.firstChild);
var tmpDstTr = ((isTr) ? node : node.firstChild);
var domTds = tdp.childNodes;
var cloneTds = tmpDstTr.childNodes;
for(var i = 0; i < domTds.length; i++){
if((cloneTds[i])&&(cloneTds[i].style)){
cloneTds[i].style.width = dojo.html.getContentBox(domTds[i]).width + "px";
}
}
node = table;
}
if((dojo.render.html.ie55||dojo.render.html.ie60) && this.createIframe){
with(node.style) {
top="0px";
left="0px";
}
var outer = document.createElement("div");
outer.appendChild(node);
this.bgIframe = new dojo.html.BackgroundIframe(outer);
outer.appendChild(this.bgIframe.iframe);
node = outer;
}
node.style.zIndex = 999;
return node;
},
onDragStart: function(e){
dojo.html.clearSelection();
this.scrollOffset = dojo.html.getScroll().offset;
this.dragStartPosition = dojo.html.getAbsolutePosition(this.domNode, true);
this.dragOffset = {y: this.dragStartPosition.y - e.pageY,
x: this.dragStartPosition.x - e.pageX};
this.dragClone = this.createDragNode();
this.containingBlockPosition = this.domNode.offsetParent ?
dojo.html.getAbsolutePosition(this.domNode.offsetParent, true) : {x:0, y:0};
if(this.constrainToContainer){
this.constraints = this.getConstraints();
}
// set up for dragging
with(this.dragClone.style){
position = "absolute";
top = this.dragOffset.y + e.pageY + "px";
left = this.dragOffset.x + e.pageX + "px";
}
dojo.body().appendChild(this.dragClone);
dojo.event.topic.publish('dragStart', { source: this } );
},
/** Return min/max x/y (relative to document.body) for this object) **/
getConstraints: function(){
if(this.constrainingContainer.nodeName.toLowerCase() == 'body'){
var viewport = dojo.html.getViewport();
var width = viewport.width;
var height = viewport.height;
var scroll = dojo.html.getScroll().offset;
var x = scroll.x;
var y = scroll.y;
}else{
var content = dojo.html.getContentBox(this.constrainingContainer);
width = content.width;
height = content.height;
x =
this.containingBlockPosition.x +
dojo.html.getPixelValue(this.constrainingContainer, "padding-left", true) +
dojo.html.getBorderExtent(this.constrainingContainer, "left");
y =
this.containingBlockPosition.y +
dojo.html.getPixelValue(this.constrainingContainer, "padding-top", true) +
dojo.html.getBorderExtent(this.constrainingContainer, "top");
}
// TODO: should handle left/top/right/bottom margin separately; left/top should affect minX/minY
var mb = dojo.html.getMarginBox(this.domNode);
return {
minX: x,
minY: y,
maxX: x + width - mb.width,
maxY: y + height - mb.height
}
},
updateDragOffset: function(){
var scroll = dojo.html.getScroll().offset;
if(scroll.y != this.scrollOffset.y){
var diff = scroll.y - this.scrollOffset.y;
this.dragOffset.y += diff;
this.scrollOffset.y = scroll.y;
}
if(scroll.x != this.scrollOffset.x){
var diff = scroll.x - this.scrollOffset.x;
this.dragOffset.x += diff;
this.scrollOffset.x = scroll.x;
}
},
/** Moves the node to follow the mouse */
onDragMove: function(e){
this.updateDragOffset();
var x = this.dragOffset.x + e.pageX;
var y = this.dragOffset.y + e.pageY;
if (this.constrainToContainer) {
if (x < this.constraints.minX) { x = this.constraints.minX; }
if (y < this.constraints.minY) { y = this.constraints.minY; }
if (x > 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 <body>.
*/
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 remove 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.html.removeNode(this.dragClone);
this.dragClone = null;
break;
case "dropFailure": // slide back to the start
var startCoords = dojo.html.getAbsolutePosition(this.dragClone, true);
// offset the end so the effect can be seen
var endCoords = { left: this.dragStartPosition.x + 1,
top: this.dragStartPosition.y + 1};
// animate
var anim = dojo.lfx.slideTo(this.dragClone, endCoords, 300);
var dragObject = this;
dojo.event.connect(anim, "onEnd", function(e){
// pause for a second (not literally) and disappear
// dojo.lang.setTimeout(function() {
dojo.html.removeNode(dragObject.dragClone);
// Allow drag clone to be gc'ed
dragObject.dragClone = null;
// },
// 50);
});
anim.play();
break;
}
dojo.event.topic.publish('dragEnd', { source: this } );
},
constrainTo: function(container){
this.constrainToContainer=true;
if(container){
this.constrainingContainer = container;
}else{
this.constrainingContainer = this.domNode.parentNode;
}
}
},
function(node, type){
this.domNode = dojo.byId(node);
this.type = type;
this.constrainToContainer = false;
this.dragSource = null;
// this.register();
dojo.dnd.DragObject.prototype.register.call(this);
}
);
dojo.declare("dojo.dnd.HtmlDropTarget",
dojo.dnd.DropTarget,
{
vertical: false,
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.html.ELEMENT_NODE){ continue; }
var pos = dojo.html.getAbsolutePosition(child, true);
var inner = dojo.html.getBorderBox(child);
this.childBoxes.push({top: pos.y, bottom: pos.y+inner.height,
left: pos.x, right: pos.x+inner.width, height: inner.height,
width: inner.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;
if(this.vertical){
borderLeftWidth = "1px";
borderLeftColor = "black";
borderLeftStyle = "solid";
height = dojo.html.getBorderBox(this.domNode).height + "px";
top = dojo.html.getAbsolutePosition(this.domNode, true).y + "px";
}else{
borderTopWidth = "1px";
borderTopColor = "black";
borderTopStyle = "solid";
width = dojo.html.getBorderBox(this.domNode).width + "px";
left = dojo.html.getAbsolutePosition(this.domNode, true).x + "px";
}
}
},
onDragMove: function(e, dragObjects){
var i = this._getNodeUnderMouse(e);
if(!this.dropIndicator){
this.createDropIndicator();
}
var gravity = this.vertical ? dojo.html.gravity.WEST : dojo.html.gravity.NORTH;
var hide = false;
if(i < 0){
if(this.childBoxes.length){
var before = (dojo.html.gravity(this.childBoxes[0].node, e) & gravity);
if(before){ hide = true; }
}else{
var before = true;
}
}else{
var child = this.childBoxes[i];
var before = (dojo.html.gravity(child.node, e) & gravity);
if(child.node === dragObjects[0].dragSource.domNode){
hide = true;
}else{
var currentPosChild = before ?
(i>0?this.childBoxes[i-1]:child) :
(i<this.childBoxes.length-1?this.childBoxes[i+1]:child);
if(currentPosChild.node === dragObjects[0].dragSource.domNode){
hide = true;
}
}
}
if(hide){
this.dropIndicator.style.display="none";
return;
}else{
this.dropIndicator.style.display="";
}
this.placeIndicator(e, dragObjects, i, before);
if(!dojo.html.hasParent(this.dropIndicator)) {
dojo.body().appendChild(this.dropIndicator);
}
},
/**
* Position the horizontal line that indicates "insert between these two items"
*/
placeIndicator: function(e, dragObjects, boxIndex, before) {
var targetProperty = this.vertical ? "left" : "top";
var child;
if(boxIndex < 0){
if(this.childBoxes.length){
child = before ? this.childBoxes[0]
: this.childBoxes[this.childBoxes.length - 1];
}else{
this.dropIndicator.style[targetProperty] = dojo.html.getAbsolutePosition(this.domNode, true)[this.vertical?"x":"y"] + "px";
}
}else{
child = this.childBoxes[boxIndex];
}
if(child){
this.dropIndicator.style[targetProperty] = (before ? child[targetProperty] : child[this.vertical?"right":"bottom"]) + "px";
if(this.vertical){
this.dropIndicator.style.height = child.height + "px";
this.dropIndicator.style.top = child.top + "px";
}else{
this.dropIndicator.style.width = child.width + "px";
this.dropIndicator.style.left = child.left + "px";
}
}
},
onDragOut: function(e) {
if(this.dropIndicator) {
dojo.html.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);
var gravity = this.vertical ? dojo.html.gravity.WEST : dojo.html.gravity.NORTH;
if(i < 0){
if(this.childBoxes.length){
if(dojo.html.gravity(this.childBoxes[0].node, e) & gravity){
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) & gravity){
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;
}
},
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.dnd.dragManager.registerDropTarget(this);
}
);