| /* |
| 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 |
| */ |
| |
| /** |
| * 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.TreeDragAndDropV3"); |
| |
| dojo.require("dojo.dnd.HtmlDragAndDrop"); |
| dojo.require("dojo.lang.func"); |
| dojo.require("dojo.lang.array"); |
| dojo.require("dojo.lang.extras"); |
| dojo.require("dojo.Deferred"); |
| dojo.require("dojo.html.layout"); |
| |
| // FIXME: if controller can't move then skip node on move start |
| dojo.dnd.TreeDragSourceV3 = function(node, syncController, type, treeNode){ |
| //dojo.profile.start("TreeDragSourceV3 "+treeNode); |
| this.controller = syncController; |
| this.treeNode = treeNode; |
| |
| dojo.dnd.HtmlDragSource.call(this, node, type); |
| //dojo.profile.end("TreeDragSourceV3 "+treeNode); |
| } |
| |
| dojo.inherits(dojo.dnd.TreeDragSourceV3, dojo.dnd.HtmlDragSource); |
| |
| |
| // ....................................... |
| |
| dojo.dnd.TreeDropTargetV3 = function(domNode, controller, type, treeNode){ |
| |
| this.treeNode = treeNode; |
| this.controller = controller; // I will sync-ly process drops |
| |
| dojo.dnd.HtmlDropTarget.call(this, domNode, type); |
| } |
| |
| dojo.inherits(dojo.dnd.TreeDropTargetV3, dojo.dnd.HtmlDropTarget); |
| |
| dojo.lang.extend(dojo.dnd.TreeDropTargetV3, { |
| |
| autoExpandDelay: 1500, |
| autoExpandTimer: null, |
| |
| |
| position: null, |
| |
| indicatorStyle: "2px black groove", |
| |
| showIndicator: function(position) { |
| |
| // do not change style too often, cause of blinking possible |
| if (this.position == position) { |
| return; |
| } |
| |
| //dojo.debug("set position for "+this.treeNode) |
| |
| this.hideIndicator(); |
| |
| this.position = position; |
| |
| var node = this.treeNode; |
| |
| |
| node.contentNode.style.width = dojo.html.getBorderBox(node.labelNode).width + "px"; |
| |
| if (position == "onto") { |
| node.contentNode.style.border = this.indicatorStyle; |
| } else { |
| // FIXME: bottom-top or highlight should cover ONLY top/bottom or div itself, |
| // not span whole line (try Dnd) |
| // FAILURE: Can't put span inside div: multiline bottom-top will span multiple lines |
| if (position == "before") { |
| node.contentNode.style.borderTop = this.indicatorStyle; |
| } else if (position == "after") { |
| node.contentNode.style.borderBottom = this.indicatorStyle; |
| } |
| } |
| }, |
| |
| hideIndicator: function() { |
| this.treeNode.contentNode.style.borderBottom = ""; |
| this.treeNode.contentNode.style.borderTop = ""; |
| this.treeNode.contentNode.style.border = ""; |
| this.treeNode.contentNode.style.width="" |
| 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(); |
| } |
| |
| if (accepts) { |
| this.cacheNodeCoords(); |
| } |
| |
| |
| return accepts; |
| }, |
| |
| /* Parent.onDragOver calls this function to get accepts status */ |
| accepts: function(dragObjects) { |
| |
| var accepts = dojo.dnd.HtmlDropTarget.prototype.accepts.apply(this, arguments); |
| |
| //dojo.debug("accepts "+accepts); |
| |
| if (!accepts) return false; |
| |
| for(var i=0; i<dragObjects.length; i++) { |
| // there may be NO treeNode |
| var sourceTreeNode = dragObjects[i].treeNode; |
| |
| 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); |
| // SLOW. Coordinates will not be recalculated if collapse occurs, or |
| // other (generic) resize. So that's a kind of hack. |
| dojo.dnd.dragManager.cacheTargetLocations(); |
| } |
| } |
| |
| this.autoExpandTimer = dojo.lang.setTimeout(autoExpand, _this.autoExpandDelay); |
| }, |
| |
| |
| |
| getAcceptPosition: function(e, dragObjects) { |
| |
| |
| var DndMode = this.treeNode.tree.DndMode; |
| |
| // disable ONTO mode possibility if impossible |
| if (DndMode & dojo.widget.TreeV3.prototype.DndModes.ONTO && |
| // check if ONTO is allowed localy |
| // check dynamically cause may change w/o regeneration of dropTarget |
| this.treeNode.actionIsDisabledNow(this.treeNode.actions.ADDCHILD) |
| ) { |
| // disable ONTO if can't move |
| DndMode &= ~dojo.widget.TreeV3.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") { |
| return position; |
| } |
| |
| for(var i=0; i<dragObjects.length; i++) { |
| var source = dragObjects[i].dragSource; |
| if (source.treeNode && this.isAdjacentNode(source.treeNode, position)) { // skip check if same parent |
| continue; |
| } |
| |
| if (!this.controller.canMove(source.treeNode ? source.treeNode : source, this.treeNode.parent)) { |
| return false; |
| } |
| } |
| |
| return position; |
| |
| }, |
| |
| |
| |
| onDropEnd: function(e) { |
| this.clearAutoExpandTimer(); |
| |
| this.hideIndicator(); |
| }, |
| |
| |
| onDragOut: function(e) { |
| this.clearAutoExpandTimer(); |
| |
| this.hideIndicator(); |
| }, |
| |
| clearAutoExpandTimer: function() { |
| if (this.autoExpandTimer) { |
| clearTimeout(this.autoExpandTimer); |
| this.autoExpandTimer = null; |
| } |
| }, |
| |
| |
| |
| onDragMove: function(e, dragObjects){ |
| |
| var position = this.getAcceptPosition(e, dragObjects); |
| |
| 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; |
| }, |
| |
| |
| /** |
| * cache node coordinates to speed up onDragMove |
| */ |
| cacheNodeCoords: function() { |
| var node = this.treeNode.contentNode; |
| |
| this.cachedNodeY = dojo.html.getAbsolutePosition(node).y; |
| this.cachedNodeHeight = dojo.html.getBorderBox(node).height; |
| }, |
| |
| |
| |
| /* get DndMode and see which position e fits */ |
| getPosition: function(e, DndMode) { |
| var mousey = e.pageY || e.clientY + dojo.body().scrollTop; |
| |
| var relY = mousey - this.cachedNodeY; |
| var p = relY / this.cachedNodeHeight; |
| |
| var position = ""; // "" <=> forbidden |
| if (DndMode & dojo.widget.TreeV3.prototype.DndModes.ONTO |
| && DndMode & dojo.widget.TreeV3.prototype.DndModes.BETWEEN) { |
| //dojo.debug("BOTH"); |
| if (p<=0.33) { |
| position = "before"; |
| // if children are expanded then I ignore understrike, cause it is confusing with firstChild |
| // but for last nodes I put understrike there |
| } else if (p<=0.66 || this.treeNode.isExpanded && this.treeNode.children.length && !this.treeNode.isLastChild()) { |
| position = "onto"; |
| } else { |
| position = "after"; |
| } |
| } else if (DndMode & dojo.widget.TreeV3.prototype.DndModes.BETWEEN) { |
| //dojo.debug("BETWEEN"); |
| if (p<=0.5 || this.treeNode.isExpanded && this.treeNode.children.length && !this.treeNode.isLastChild()) { |
| position = "before"; |
| } else { |
| position = "after"; |
| } |
| } |
| else if (DndMode & dojo.widget.TreeV3.prototype.DndModes.ONTO) { |
| //dojo.debug("ONTO"); |
| position = "onto"; |
| } |
| |
| //dojo.debug(position); |
| |
| return position; |
| }, |
| |
| |
| |
| getTargetParentIndex: function(source, position) { |
| |
| var index = position == "before" ? this.treeNode.getParentIndex() : this.treeNode.getParentIndex()+1; |
| if (source.treeNode |
| && this.treeNode.parent === source.treeNode.parent |
| && this.treeNode.getParentIndex() > source.treeNode.getParentIndex()) { |
| index--; // dragging a node is different for simple move bacause of before-after issues |
| } |
| |
| return index; |
| }, |
| |
| |
| onDrop: function(e) { |
| // onDropEnd will clean position |
| |
| |
| var position = this.position; |
| |
| //dojo.debug(position); |
| var source = e.dragObject.dragSource; |
| |
| //dojo.debug("onDrop "+source.treeNode+" " + position + " "+this.treeNode); |
| |
| |
| var targetParent, targetIndex; |
| if (position == "onto") { |
| targetParent = this.treeNode; |
| targetIndex = 0; |
| } else { |
| targetIndex = this.getTargetParentIndex(source, position); |
| targetParent = this.treeNode.parent; |
| } |
| |
| //dojo.profile.start("onDrop "+sourceTreeNode); |
| var r = this.getDropHandler(e, source, targetParent, targetIndex)(); |
| |
| //dojo.profile.end("onDrop "+sourceTreeNode); |
| |
| return r; |
| |
| }, |
| |
| /** |
| * determine, which action I should perform with nodes |
| * e.g move, clone.. |
| */ |
| getDropHandler: function(e, source, targetParent, targetIndex) { |
| var handler; |
| var _this = this; |
| handler = function () { |
| var result; |
| |
| //dojo.debug("Move "+source.treeNode+" to parent "+targetParent+":"+targetIndex); |
| if (source.treeNode) { |
| result = _this.controller.move(source.treeNode, targetParent, targetIndex, true); |
| //dojo.debug("moved "+result); |
| } else { |
| if (dojo.lang.isFunction(source.onDrop)) { |
| source.onDrop(targetParent, targetIndex); |
| } |
| |
| var treeNode = source.getTreeNode(); |
| if (treeNode) { |
| result = _this.controller.createChild(targetParent, targetIndex, treeNode, true); |
| } else { |
| result = true; |
| } |
| } |
| |
| if (result instanceof dojo.Deferred) { |
| // this Deferred is always sync |
| var isSuccess = result.fired == 0; |
| if (!isSuccess) { |
| _this.handleDropError(source, targetParent, targetIndex, result); |
| } |
| |
| return isSuccess; |
| |
| } else { |
| return result; |
| } |
| } |
| |
| return handler; |
| }, |
| |
| |
| handleDropError: function(source, parent, index, result) { |
| dojo.debug("TreeDropTargetV3.handleDropError: DND error occured"); |
| dojo.debugShallow(result); |
| } |
| |
| |
| }); |
| |