Login Register

dnd - nested targets patch

I have written a patch that allows nested targets for drag and drop. The nested targets can accept different source types. I ran the test suite but I don't think that the dnd test suite is included in the default setup.

Index: dnd/Manager.js
===================================================================
--- dnd/Manager.js      (revision 12205)
+++ dnd/Manager.js      (working copy)
@@ -14,6 +14,9 @@
               this.target = null;
               this.canDropFlag = false;
               this.events = [];
+              this.nestedTargets = false;
+              this.sources = new dojo.NodeList();
+              this.leftSource = false;
        },
 
        // avatar's offset from the mouse
@@ -159,6 +162,17 @@
               this.updateAvatar();
               dojo.removeClass(dojo.body(), "dojoDnd" + (this.copy ? "Move" : "Copy"));
               dojo.addClass(dojo.body(), "dojoDnd" + (this.copy ? "Copy" : "Move"));
+       },
+       registerSource: function(source){
+              this.sources.push(source);
+       },
+       sourceById: function(id){
+              for (var i = 0; i < this.sources.length; i++){
+                     if (this.sources[i].node.id == id){
+                            return this.sources[i];
+                     }
+              }
+              return null;
        }
 });
 
Index: dnd/Source.js
===================================================================
--- dnd/Source.js       (revision 12205)
+++ dnd/Source.js       (working copy)
@@ -78,6 +78,9 @@
                      dojo.subscribe("/dnd/drop",   this, "onDndDrop"),
                      dojo.subscribe("/dnd/cancel", this, "onDndCancel")
               ];
+
+              var m = dojo.dnd.manager();
+              m.registerSource(this); //nestedTargets will be set by the client after this so always register the source
        },
        
        // methods
@@ -129,6 +132,13 @@
               dojo.dnd.Source.superclass.onMouseMove.call(this, e);
               var m = dojo.dnd.manager();
               if(this.isDragging){
+           
+                     if (m.leftSource){
+                            m.leftSource = false;
+                            m.overSource(this);
+                            return;
+                     }
+
                      // calculate before/after
                      var before = false;
                      if(this.current){
@@ -210,8 +220,27 @@
               // source: Object: the source which provides items
               // nodes: Array: the list of transferred items
               // copy: Boolean: copy items, if true, move items otherwise
+       
+              var m = dojo.dnd.manager();
+
               do{ //break box
                      if(this.containerState != "Over"){ break; }
+
+                     /*If this source, contains any sources itself that have
+                     their "containerState" set to "Over" then break*/
+                     if (m.nestedTargets){
+                            var sources = m.sources;
+                            if (dojo.some(sources,function(item){
+                                   return Boolean(
+                                   item.node.id != this.node.id
+                                   && item.containerState == 'Over'
+                                   && dojo.isDescendant(item.node,this.node)
+                            );
+                            },this)){
+                                   break;
+                            }
+                     }
+           
                      var oldCreator = this._normalizedCreator;
                      if(this != source){
                             // transferring nodes from the source to the target
@@ -314,6 +343,10 @@
        },
        onOutEvent: function(){
               // summary: this function is called once, when mouse is out of our container
+              var m = dojo.dnd.manager();
+              if (m.nestedTargets){
+                     m.leftSource = true;
+              }
               dojo.dnd.Source.superclass.onOutEvent.call(this);
               dojo.dnd.manager().outSource(this);
        },

Please file a CLA first

Please file a CLA first (http://dojotoolkit.org/cla). Before that we cannot review your code. Mark your ticket with [CLA] in the subject line, when you are done.

One reason we don't support nested targets right now is due to complications in the selection UI, while the linear container is easier to implement ⇒ less code in the base. Still we need to know some extra stuff like the orientation (vertical/horizontal).

But I am getting ahead of myself — CLA goes first, next we discuss merits of the implementation.

I am in need of nested

I am in need of nested targets and am getting very odd results when I use them.

I have a source with a list of items. Within the source there are two more sources but they don't accept items from the outer source, only from each other. The outer sources do not accept items from the inner source. When I try to drag and drop from inner source to inner source, the item gets placed in the outer source, the avatar gets stuck on the screen and I get the javascript error "t has no properties" I assume this is because of the complications with the selection UI?

I attempted to apply this patch but the results are the same. ( I know the patch isn't official but I wanted to see if it would work =) )

Any help on a work-around would be greatly appreciated. Thanks.

Latest patch

jbasinger, below is my latest patch (this is against 1.1.0)

You have to enable nested targets

dojo.dnd.manager().nestedTargets = true
Index: Manager.js
===================================================================
--- Manager.js	(revision 13152)
+++ Manager.js	(working copy)
@@ -14,6 +14,9 @@
 		this.target = null;
 		this.canDropFlag = false;
 		this.events = [];
+		this.nestedTargets = false;
+		this.sources = new dojo.NodeList();
+		this.leftSource = false;
 	},
 
 	// avatar's offset from the mouse
@@ -25,6 +28,10 @@
 		// summary: called when a source detected a mouse-over conditiion
 		// source: Object: the reporter
 		if(this.avatar){
+			if (this.nestedTargets && this.target){
+				this.target._unmarkTargetAnchor();
+			}
+
 			this.target = (source && source.targetState != "Disabled") ? source : null;
 			this.avatar.update();
 		}
@@ -161,7 +168,68 @@
 		this.updateAvatar();
 		dojo.removeClass(dojo.body(), "dojoDnd" + (this.copy ? "Move" : "Copy"));
 		dojo.addClass(dojo.body(), "dojoDnd" + (this.copy ? "Copy" : "Move"));
+	},
+	//Register the source with the manager.
+	registerSource: function(source){
+		this.sources.push(source);
+	},
+	//Returns the source with the specified node id
+	sourceById: function(id){
+		if (! id){
+			return;
+		}
+        	
+		for (var i = 0; i < this.sources.length; i++){
+			if (this.sources[i].node.id == id){
+				return this.sources[i];
+			}
+		}
+		return null;
+	},
+	//Returns the ultimate parent source of the specified source
+	ultimate_parent: function(source){
+		//return the ultimate parent source of this nested source.
+		var current_parent = source;
+		var temp_parent = source;
+		var parent_node = source.node.parentNode;
+		while (parent_node){
+			temp_parent = this.sourceById(parent_node.id);
+			parent_node = parent_node.parentNode;
+			if (temp_parent){
+				current_parent = temp_parent;
+			}
+		}
+		return current_parent;
+	},
+	//Deselects all selected nodes in the same tree as the passed source
+	deselectItemsInTree: function(source){
+		if (! this.nestedTargets){
+			return;
+		}
+		var parent_source = this.ultimate_parent(source);
+		dojo.forEach(this.sources,function(source){
+			if (
+				this.node.id != source.node.id
+				&& dojo.isDescendant(source.node,parent_source.node)
+			){
+				source.selectNone();
+			}
+		},source);
 	}
+      //Returns a nodeList of sources which are inside the specified source.
+    ,nestedSources: function(source){
+        var list = new dojo.NodeList();
+        dojo.forEach(this.sources,function(s){
+            if (
+                this.node.id != s.node.id
+                && dojo.isDescendant(s.node,this.node)
+            ){
+                list.push(s);
+            }
+        },source);
+
+        return list;
+    }
 });
 
 // summary: the manager singleton variable, can be overwritten, if needed
Index: Selector.js
===================================================================
--- Selector.js	(revision 13152)
+++ Selector.js	(working copy)
@@ -121,6 +121,9 @@
 			dojo.stopEvent(e);
 			return;
 		}
+
+		dojo.dnd.manager().deselectItemsInTree(this);
+
 		if(!this.singular && e.shiftKey){
 			if(!dojo.dnd.getCopyKeyState(e)){
 				this._removeSelection();
Index: Source.js
===================================================================
--- Source.js	(revision 13152)
+++ Source.js	(working copy)
@@ -96,6 +96,9 @@
 			dojo.subscribe("/dnd/drop",   this, "onDndDrop"),
 			dojo.subscribe("/dnd/cancel", this, "onDndCancel")
 		];
+
+		var m = dojo.dnd.manager();
+		m.registerSource(this); //nestedTargets will be set by the client after this so always register the source
 	},
 	
 	// methods
@@ -104,6 +107,18 @@
 		// source: Object: the source which provides items
 		// nodes: Array: the list of transferred items
 		if(this == source){ return true; }
+        
+		var m = dojo.dnd.manager();
+		if (m.nestedTargets){
+			//If one of the nodes we are dragging, contains the source we are
+			//trying to drop into, then do not allow.
+			if (dojo.some(nodes,function(node){
+				return dojo.isDescendant(this.node,node);
+			},this)){
+				return false;
+			}
+		}
+
 		for(var i = 0; i < nodes.length; ++i){
 			var type = source.getItem(nodes[i].id).type;
 			// type instanceof Array
@@ -138,7 +153,6 @@
 		params._skipStartup = true;
 		return new dojo.dnd.Source(node, params);
 	},
-
 	// mouse event processors
 	onMouseMove: function(e){
 		// summary: event processor for onmousemove
@@ -147,6 +161,17 @@
 		dojo.dnd.Source.superclass.onMouseMove.call(this, e);
 		var m = dojo.dnd.manager();
 		if(this.isDragging){
+            
+			if (m.leftSource){
+				m.leftSource = false;
+				m.overSource(this);
+				return;
+			}
+
+			if (this._hasNestedOver()){
+				return;
+			}
+
 			// calculate before/after
 			var before = false;
 			if(this.current){
@@ -230,6 +255,12 @@
 		// copy: Boolean: copy items, if true, move items otherwise
 		do{ //break box
 			if(this.containerState != "Over"){ break; }
+
+			if (this._hasNestedOver()){
+				break;
+			}
+			dojo.dnd.manager().deselectItemsInTree(this);
+            
 			var oldCreator = this._normalizedCreator;
 			if(this != source){
 				// transferring nodes from the source to the target
@@ -308,6 +339,7 @@
 			}
 			this._normalizedCreator = oldCreator;
 		}while(false);
+
 		this.onDndCancel();
 	},
 	onDndCancel: function(){
@@ -332,6 +364,10 @@
 	},
 	onOutEvent: function(){
 		// summary: this function is called once, when mouse is out of our container
+		var m = dojo.dnd.manager();
+		if (m.nestedTargets){
+			m.leftSource = true;
+		}
 		dojo.dnd.Source.superclass.onOutEvent.call(this);
 		dojo.dnd.manager().outSource(this);
 	},
@@ -369,7 +405,26 @@
 			if(dojo.hasClass(node, "dojoDndHandle")){ return true; }
 		}
 		return false;	// Boolean
-	}
+	},
+	//If the source has a source within that currently is being moused over, return true
+	_hasNestedOver: function(){
+		/*If this source, contains any sources itself that have
+		their "containerState" set to "Over" then break*/
+		var m = dojo.dnd.manager();
+		if (m.nestedTargets){
+			var sources = m.sources;
+			if (dojo.some(sources,function(item){
+				return Boolean(
+				item.node.id != this.node.id
+				&& item.containerState == 'Over'
+				&& dojo.isDescendant(item.node,this.node)
+			);
+			},this)){
+				return true;
+			}
+		}
+		return false;
+	},
 });
 
 dojo.declare("dojo.dnd.Target", dojo.dnd.Source, {

Bug with latest patch

There is a trailing comma at the end of that patch, after the "_hasNestedOver" method in Source.js

Needed Nested Dnd function

Hi Master,

Nested Dnd only apply within a tree. How can i drag and drop a parent node with children to another tree? Please assist. Many Thanks.

Regards,
Wilson