/**
* TreeDrag* specialized on managing subtree drags
* It selects nodes and visualises what's going on,
* but delegates real actions upon tree to the controller
*
* This code is considered a part of controller
*/
dojo.provide("dojo.dnd.TreeDragAndDrop");
dojo.require("dojo.dnd.HtmlDragAndDrop");
dojo.require("dojo.lang.func");
dojo.require("dojo.lang.array");
dojo.require("dojo.lang.extras");
dojo.require("dojo.html.layout");
dojo.dnd.TreeDragSource = function(node, syncController, type, treeNode){
this.controller = syncController;
this.treeNode = treeNode;
dojo.dnd.HtmlDragSource.call(this, node, type);
}
dojo.inherits(dojo.dnd.TreeDragSource, dojo.dnd.HtmlDragSource);
dojo.lang.extend(dojo.dnd.TreeDragSource, {
onDragStart: function(){
/* extend adds functions to prototype */
var dragObject = dojo.dnd.HtmlDragSource.prototype.onDragStart.call(this);
//dojo.debugShallow(dragObject)
dragObject.treeNode = this.treeNode;
dragObject.onDragStart = dojo.lang.hitch(dragObject, function(e) {
/* save selection */
this.savedSelectedNode = this.treeNode.tree.selector.selectedNode;
if (this.savedSelectedNode) {
this.savedSelectedNode.unMarkSelected();
}
var result = dojo.dnd.HtmlDragObject.prototype.onDragStart.apply(this, arguments);
/* remove background grid from cloned object */
var cloneGrid = this.dragClone.getElementsByTagName('img');
for(var i=0; i<cloneGrid.length; i++) {
cloneGrid.item(i).style.backgroundImage='url()';
}
return result;
});
dragObject.onDragEnd = function(e) {
/* restore selection */
if (this.savedSelectedNode) {
this.savedSelectedNode.markSelected();
}
//dojo.debug(e.dragStatus);
return dojo.dnd.HtmlDragObject.prototype.onDragEnd.apply(this, arguments);
}
//dojo.debug(dragObject.domNode.outerHTML)
return dragObject;
},
onDragEnd: function(e){
var res = dojo.dnd.HtmlDragSource.prototype.onDragEnd.call(this, e);
return res;
}
});
// .......................................
dojo.dnd.TreeDropTarget = function(domNode, controller, type, treeNode){
this.treeNode = treeNode;
this.controller = controller; // I will sync-ly process drops
dojo.dnd.HtmlDropTarget.apply(this, [domNode, type]);
}
dojo.inherits(dojo.dnd.TreeDropTarget, dojo.dnd.HtmlDropTarget);
dojo.lang.extend(dojo.dnd.TreeDropTarget, {
autoExpandDelay: 1500,
autoExpandTimer: null,
position: null,
indicatorStyle: "2px black solid",
showIndicator: function(position) {
// do not change style too often, cause of blinking possible
if (this.position == position) {
return;
}
//dojo.debug(position)
this.hideIndicator();
this.position = position;
if (position == "before") {
this.treeNode.labelNode.style.borderTop = this.indicatorStyle;
} else if (position == "after") {
this.treeNode.labelNode.style.borderBottom = this.indicatorStyle;
} else if (position == "onto") {
this.treeNode.markSelected();
}
},
hideIndicator: function() {
this.treeNode.labelNode.style.borderBottom="";
this.treeNode.labelNode.style.borderTop="";
this.treeNode.unMarkSelected();
this.position = null;
},
// is the target possibly ok ?
// This function is run on dragOver, but drop possibility is also determined by position over node
// that's why acceptsWithPosition is called
// doesnt take index into account ( can change while moving mouse w/o changing target )
/**
* Coarse (tree-level) access check.
* We can't determine real accepts status w/o position
*/
onDragOver: function(e){
//dojo.debug("onDragOver for "+e);
var accepts = dojo.dnd.HtmlDropTarget.prototype.onDragOver.apply(this, arguments);
//dojo.debug("TreeDropTarget.onDragOver accepts:"+accepts)
if (accepts && this.treeNode.isFolder && !this.treeNode.isExpanded) {
this.setAutoExpandTimer();
}
return accepts;
},
/* Parent.onDragOver calls this function to get accepts status */
accepts: function(dragObjects) {
var accepts = dojo.dnd.HtmlDropTarget.prototype.accepts.apply(this, arguments);
if (!accepts) return false;
var sourceTreeNode = dragObjects[0].treeNode;
if (dojo.lang.isUndefined(sourceTreeNode) || !sourceTreeNode || !sourceTreeNode.isTreeNode) {
dojo.raise("Source is not TreeNode or not found");
}
if (sourceTreeNode === this.treeNode) return false;
return true;
},
setAutoExpandTimer: function() {
// set up autoexpand timer
var _this = this;
var autoExpand = function () {
if (dojo.dnd.dragManager.currentDropTarget === _this) {
_this.controller.expand(_this.treeNode);
}
}
this.autoExpandTimer = dojo.lang.setTimeout(autoExpand, _this.autoExpandDelay);
},
getDNDMode: function() {
return this.treeNode.tree.DNDMode;
},
getAcceptPosition: function(e, sourceTreeNode) {
var DNDMode = this.getDNDMode();
if (DNDMode & dojo.widget.Tree.prototype.DNDModes.ONTO &&
// check if ONTO is allowed localy
!(
!this.treeNode.actionIsDisabled(dojo.widget.TreeNode.prototype.actions.ADDCHILD) // check dynamically cause may change w/o regeneration of dropTarget
&& sourceTreeNode.parent !== this.treeNode
&& this.controller.canMove(sourceTreeNode, this.treeNode)
)
) {
// disable ONTO if can't move
DNDMode &= ~dojo.widget.Tree.prototype.DNDModes.ONTO;
}
var position = this.getPosition(e, DNDMode);
//dojo.debug(DNDMode & +" : "+position);
// if onto is here => it was allowed before, no accept check is needed
if (position=="onto" ||
(!this.isAdjacentNode(sourceTreeNode, position)
&& this.controller.canMove(sourceTreeNode, this.treeNode.parent)
)
) {
return position;
} else {
return false;
}
},
onDragOut: function(e) {
this.clearAutoExpandTimer();
this.hideIndicator();
},
clearAutoExpandTimer: function() {
if (this.autoExpandTimer) {
clearTimeout(this.autoExpandTimer);
this.autoExpandTimer = null;
}
},
onDragMove: function(e, dragObjects){
var sourceTreeNode = dragObjects[0].treeNode;
var position = this.getAcceptPosition(e, sourceTreeNode);
if (position) {
this.showIndicator(position);
}
},
isAdjacentNode: function(sourceNode, position) {
if (sourceNode === this.treeNode) return true;
if (sourceNode.getNextSibling() === this.treeNode && position=="before") return true;
if (sourceNode.getPreviousSibling() === this.treeNode && position=="after") return true;
return false;
},
/* get DNDMode and see which position e fits */
getPosition: function(e, DNDMode) {
var node = dojo.byId(this.treeNode.labelNode);
var mousey = e.pageY || e.clientY + dojo.body().scrollTop;
var nodey = dojo.html.getAbsolutePosition(node).y;
var height = dojo.html.getBorderBox(node).height;
var relY = mousey - nodey;
var p = relY / height;
var position = ""; // "" <=> forbidden
if (DNDMode & dojo.widget.Tree.prototype.DNDModes.ONTO
&& DNDMode & dojo.widget.Tree.prototype.DNDModes.BETWEEN) {
if (p<=0.3) {
position = "before";
} else if (p<=0.7) {
position = "onto";
} else {
position = "after";
}
} else if (DNDMode & dojo.widget.Tree.prototype.DNDModes.BETWEEN) {
if (p<=0.5) {
position = "before";
} else {
position = "after";
}
}
else if (DNDMode & dojo.widget.Tree.prototype.DNDModes.ONTO) {
position = "onto";
}
return position;
},
getTargetParentIndex: function(sourceTreeNode, position) {
var index = position == "before" ? this.treeNode.getParentIndex() : this.treeNode.getParentIndex()+1;
if (this.treeNode.parent === sourceTreeNode.parent
&& this.treeNode.getParentIndex() > sourceTreeNode.getParentIndex()) {
index--; // dragging a node is different for simple move bacause of before-after issues
}
return index;
},
onDrop: function(e){
// onDragOut will clean position
var position = this.position;
//dojo.debug(position);
this.onDragOut(e);
var sourceTreeNode = e.dragObject.treeNode;
if (!dojo.lang.isObject(sourceTreeNode)) {
dojo.raise("TreeNode not found in dragObject")
}
if (position == "onto") {
return this.controller.move(sourceTreeNode, this.treeNode, 0);
} else {
var index = this.getTargetParentIndex(sourceTreeNode, position);
return this.controller.move(sourceTreeNode, this.treeNode.parent, index);
}
//dojo.debug('drop2');
}
});
dojo.dnd.TreeDNDController = function(treeController) {
// I use this controller to perform actions
this.treeController = treeController;
this.dragSources = {};
this.dropTargets = {};
}
dojo.lang.extend(dojo.dnd.TreeDNDController, {
listenTree: function(tree) {
//dojo.debug("Listen tree "+tree);
dojo.event.topic.subscribe(tree.eventNames.createDOMNode, this, "onCreateDOMNode");
dojo.event.topic.subscribe(tree.eventNames.moveFrom, this, "onMoveFrom");
dojo.event.topic.subscribe(tree.eventNames.moveTo, this, "onMoveTo");
dojo.event.topic.subscribe(tree.eventNames.addChild, this, "onAddChild");
dojo.event.topic.subscribe(tree.eventNames.removeNode, this, "onRemoveNode");
dojo.event.topic.subscribe(tree.eventNames.treeDestroy, this, "onTreeDestroy");
},
unlistenTree: function(tree) {
//dojo.debug("Listen tree "+tree);
dojo.event.topic.unsubscribe(tree.eventNames.createDOMNode, this, "onCreateDOMNode");
dojo.event.topic.unsubscribe(tree.eventNames.moveFrom, this, "onMoveFrom");
dojo.event.topic.unsubscribe(tree.eventNames.moveTo, this, "onMoveTo");
dojo.event.topic.unsubscribe(tree.eventNames.addChild, this, "onAddChild");
dojo.event.topic.unsubscribe(tree.eventNames.removeNode, this, "onRemoveNode");
dojo.event.topic.unsubscribe(tree.eventNames.treeDestroy, this, "onTreeDestroy");
},
onTreeDestroy: function(message) {
this.unlistenTree(message.source);
// I'm not widget so don't use destroy() call and dieWithTree
},
onCreateDOMNode: function(message) {
this.registerDNDNode(message.source);
},
onAddChild: function(message) {
this.registerDNDNode(message.child);
},
onMoveFrom: function(message) {
var _this = this;
dojo.lang.forEach(
message.child.getDescendants(),
function(node) { _this.unregisterDNDNode(node); }
);
},
onMoveTo: function(message) {
var _this = this;
dojo.lang.forEach(
message.child.getDescendants(),
function(node) { _this.registerDNDNode(node); }
);
},
/**
* Controller(node model) creates DNDNodes because it passes itself to node for synchroneous drops processing
* I can't process DnD with events cause an event can't return result success/false
*/
registerDNDNode: function(node) {
if (!node.tree.DNDMode) return;
//dojo.debug("registerDNDNode "+node);
/* I drag label, not domNode, because large domNodes are very slow to copy and large to drag */
var source = null;
var target = null;
if (!node.actionIsDisabled(node.actions.MOVE)) {
//dojo.debug("reg source")
var source = new dojo.dnd.TreeDragSource(node.labelNode, this, node.tree.widgetId, node);
this.dragSources[node.widgetId] = source;
}
var target = new dojo.dnd.TreeDropTarget(node.labelNode, this.treeController, node.tree.DNDAcceptTypes, node);
this.dropTargets[node.widgetId] = target;
},
unregisterDNDNode: function(node) {
if (this.dragSources[node.widgetId]) {
dojo.dnd.dragManager.unregisterDragSource(this.dragSources[node.widgetId]);
delete this.dragSources[node.widgetId];
}
if (this.dropTargets[node.widgetId]) {
dojo.dnd.dragManager.unregisterDropTarget(this.dropTargets[node.widgetId]);
delete this.dropTargets[node.widgetId];
}
}
});