Login Register

[patch] DnD extension: 2D (i.e. non-linear) Container using absolute positioning

I extended the DnD module to support inserting items at the position where they are dropped. It is done by absolute positioning within the Container.
I created a simple demo at http://mikula.ic.cz/dnddemo/
Basically, when Manager publishes the "/dnd/drop" topic, it passes an additional argument -- mouse event that caused the drop.
Container constructor accepts an additional Boolean parameter absolutePositioning -- when true, the items are positioned absolutely within the container at the position calculated from the passed mouse event.
I provide the diff below.
If there is a will to incorporate this into Dojo, I'll submit the CLA.

There is an odd behavior when dragging more items at once -- they are all placed at the same position. Still I would rather have 2D positioning work fine for single items only than not have 2D positioning at all.

Regards,
Tomas

Index: trunk/dnd/Manager.js
===================================================================
--- trunk/dnd/Manager.js	(revision 14394)
+++ trunk/dnd/Manager.js	(working copy)
@@ -118,7 +118,7 @@
 					e.button == 0 : this.source.mouseButton == e.button))){
 			if(this.target && this.canDropFlag){
 				var copy = Boolean(this.source.copyState(dojo.dnd.getCopyKeyState(e))),
-				params = [this.source, this.nodes, copy, this.target];
+				params = [this.source, this.nodes, copy, this.target, e];
 				dojo.publish("/dnd/drop/before", params);
 				dojo.publish("/dnd/drop", params);
 			}else{
Index: trunk/dnd/Avatar.js
===================================================================
--- trunk/dnd/Avatar.js	(revision 14394)
+++ trunk/dnd/Avatar.js	(working copy)
@@ -40,6 +40,7 @@
 			}else{
 				// or just clone the node and hope it works
 				node = this.manager.nodes[i].cloneNode(true);
+				node.style.position = "static";
 				if(node.tagName.toLowerCase() == "tr"){
 					// insert extra table nodes
 					var table = dojo.doc.createElement("table"),
Index: trunk/dnd/Selector.js
===================================================================
--- trunk/dnd/Selector.js	(revision 14394)
+++ trunk/dnd/Selector.js	(working copy)
@@ -111,12 +111,17 @@
 		
 		return this;	// self
 	},
-	insertNodes: function(addSelected, data, before, anchor){
+	insertNodes: function(addSelected, data, where){
 		// summary: inserts new data items (see Container's insertNodes method for details)
 		// addSelected: Boolean: all new nodes will be added to selected items, if true, no selection change otherwise
 		// data: Array: a list of data items, which should be processed by the creator function
-		// before: Boolean: insert before the anchor, if true, and after the anchor otherwise
-		// anchor: Node: the anchor node to be used as a point of insertion
+		// where: Object: a dictionary of parameters, recognized params are:
+		//  if this Container uses absolute positioning:
+		//   pageX: X coordinate on the page where data should be inserted
+		//   pageY: Y coordinate on the page where data should be inserted
+		//  if this is a linear Container:
+		//   before: Boolean: insert before the anchor, if true, and after the anchor otherwise
+		//   anchor: Node: the anchor node to be used as a point of insertion
 		var oldCreator = this._normalizedCreator;
 		this._normalizedCreator = function(item, hint){
 			var t = oldCreator.call(this, item, hint);
@@ -136,7 +141,7 @@
 			}
 			return t;
 		};
-		dojo.dnd.Selector.superclass.insertNodes.call(this, data, before, anchor);
+		dojo.dnd.Selector.superclass.insertNodes.call(this, data, where);
 		this._normalizedCreator = oldCreator;
 		return this;	// self
 	},
Index: trunk/dnd/Source.js
===================================================================
--- trunk/dnd/Source.js	(revision 14394)
+++ trunk/dnd/Source.js	(working copy)
@@ -174,6 +174,10 @@
 		dojo.dnd.Source.superclass.onMouseMove.call(this, e);
 		var m = dojo.dnd.manager();
 		if(this.isDragging){
+			if(this.absolutePositioning){
+				// do nothing
+				return;
+			}
 			// calculate before/after
 			var before = false;
 			if(this.current){
@@ -232,7 +236,7 @@
 			}
 		}else if(this.isDragging){
 			var m = dojo.dnd.manager();
-			m.canDrop(this.targetState != "Disabled" && (!this.current || m.source != this || !(this.current.id in this.selection)));
+			m.canDrop(this.targetState != "Disabled" && (this.absolutePositioning || !this.current || m.source != this || !(this.current.id in this.selection)));
 		}
 	},
 	onDndStart: function(source, nodes, copy){
@@ -251,15 +255,16 @@
 		}
 		this.isDragging = true;
 	},
-	onDndDrop: function(source, nodes, copy, target){
+	onDndDrop: function(source, nodes, copy, target, e){
 		// summary: topic event processor for /dnd/drop, called to finish the DnD operation
 		// source: Object: the source which provides items
 		// nodes: Array: the list of transferred items
 		// copy: Boolean: copy items, if true, move items otherwise
 		// target: Object: the target which accepts items
+		// e: Event: mouse event that caused the drop
 		if(this == target){
 			// this one is for us => move nodes!
-			this.onDrop(source, nodes, copy);
+			this.onDrop(source, nodes, copy, e);
 		}
 		this.onDndCancel();
 	},
@@ -278,24 +283,26 @@
 	},
 	
 	// local events
-	onDrop: function(source, nodes, copy){
+	onDrop: function(source, nodes, copy, e){
 		// summary: called only on the current target, when drop is performed
 		// source: Object: the source which provides items
 		// nodes: Array: the list of transferred items
 		// copy: Boolean: copy items, if true, move items otherwise
+		// e: Event: mouse event that caused the drop
 		
 		if(this != source){
-			this.onDropExternal(source, nodes, copy);
+			this.onDropExternal(source, nodes, copy, e);
 		}else{
-			this.onDropInternal(nodes, copy);
+			this.onDropInternal(nodes, copy, e);
 		}
 	},
-	onDropExternal: function(source, nodes, copy){
+	onDropExternal: function(source, nodes, copy, e){
 		// summary: called only on the current target, when drop is performed
 		//	from an external source
 		// source: Object: the source which provides items
 		// nodes: Array: the list of transferred items
 		// copy: Boolean: copy items, if true, move items otherwise
+		// e: Event: mouse event that caused the drop
 		
 		var oldCreator = this._normalizedCreator;
 		// transferring nodes from the source to the target
@@ -327,21 +334,25 @@
 		if(!copy && !this.creator){
 			source.selectNone();
 		}
-		this.insertNodes(true, nodes, this.before, this.current);
+		var where = this.absolutePositioning ?
+				{ pageX: e.pageX, pageY: e.pageY } :
+				{ before: this.before, anchor: this.current };
+		this.insertNodes(true, nodes, where);
 		if(!copy && this.creator){
 			source.deleteSelectedNodes();
 		}
 		this._normalizedCreator = oldCreator;
 	},
-	onDropInternal: function(nodes, copy){
+	onDropInternal: function(nodes, copy, e){
 		// summary: called only on the current target, when drop is performed
 		//	from the same target/source
 		// nodes: Array: the list of transferred items
 		// copy: Boolean: copy items, if true, move items otherwise
+		// e: Event: mouse event that caused the drop
 		
 		var oldCreator = this._normalizedCreator;
 		// transferring nodes within the single source
-		if(this.current && this.current.id in this.selection){
+		if(this.current && this.current.id in this.selection && !this.absolutePositioning){
 			// do nothing
 			return;
 		}
@@ -354,7 +365,7 @@
 				};
 			}else{
 				// move nodes
-				if(!this.current){
+				if(!this.current && !this.absolutePositioning){
 					// do nothing
 					return;
 				}
@@ -375,7 +386,7 @@
 				};
 			}else{
 				// move nodes
-				if(!this.current){
+				if(!this.current && !this.absolutePositioning){
 					// do nothing
 					return;
 				}
@@ -386,7 +397,10 @@
 			}
 		}
 		this._removeSelection();
-		this.insertNodes(true, nodes, this.before, this.current);
+		var where = this.absolutePositioning ?
+				{ pageX: e.pageX, pageY: e.pageY } :
+				{ before: this.before, anchor: this.current };
+		this.insertNodes(true, nodes, where);
 		this._normalizedCreator = oldCreator;
 	},
 	onDraggingOver: function(){
Index: trunk/dnd/Container.js
===================================================================
--- trunk/dnd/Container.js	(revision 14394)
+++ trunk/dnd/Container.js	(working copy)
@@ -18,6 +18,7 @@
 	
 	// object attributes (for markup)
 	skipForm: false,
+	absolutePositioning: false,
 	
 	constructor: function(node, params){
 		// summary: a constructor of the Container
@@ -26,6 +27,7 @@
 		//	creator: Function: a creator function, which takes a data item, and returns an object like that:
 		//		{node: newNode, data: usedData, type: arrayOfStrings}
 		//	skipForm: Boolean: don't start the drag operation, if clicked on form elements
+		//	absolutePositioning: Boolean: if true, inserted items will be positioned absolutely within the container in the place where they are dropped
 		//	dropParent: Node: node or node's id to use as the parent node for dropped items
 		//		(must be underneath the 'node' parameter in the DOM)
 		//	_skipStartup: Boolean: skip startup(), which collects children, for deferred initialization
@@ -34,6 +36,7 @@
 		if(!params){ params = {}; }
 		this.creator = params.creator || null;
 		this.skipForm = params.skipForm;
+		this.absolutePositioning = params.absolutePositioning || false;
 		this.parent = params.dropParent && dojo.byId(params.dropParent);
 		
 		// class-specific variables
@@ -118,34 +121,61 @@
 		this.map = map;
 		return this;	// self
 	},
-	insertNodes: function(data, before, anchor){
+	insertNodes: function(data, where){
 		// summary: inserts an array of new nodes before/after an anchor node
 		// data: Array: a list of data items, which should be processed by the creator function
-		// before: Boolean: insert before the anchor, if true, and after the anchor otherwise
-		// anchor: Node: the anchor node to be used as a point of insertion
-		if(!this.parent.firstChild){
-			anchor = null;
-		}else if(before){
-			if(!anchor){
-				anchor = this.parent.firstChild;
-			}
-		}else{
-			if(anchor){
-				anchor = anchor.nextSibling;
-			}
-		}
-		if(anchor){
+		// where: Object: a dictionary of parameters, recognized params are:
+		//  if this Container uses absolute positioning:
+		//   pageX: X coordinate on the page where data should be inserted
+		//   pageY: Y coordinate on the page where data should be inserted
+		//  if this is a linear Container:
+		//   before: Boolean: insert before the anchor, if true, and after the anchor otherwise
+		//   anchor: Node: the anchor node to be used as a point of insertion
+		if(this.absolutePositioning){
+			var cs = dojo.getComputedStyle(this.parent);
+			var cb = dojo._getContentBox(this.parent, cs);
+			var pe = dojo._getPadExtents(this.parent, cs);
+			var m = dojo.dnd.manager();
+			var diffX = -cb.l + pe.l + m.OFFSET_X;
+			var diffY = -cb.t + pe.t + m.OFFSET_Y;
+			if(!where){ where = {pageX: cb.l, pageY: cb.t}; }
 			for(var i = 0; i < data.length; ++i){
 				var t = this._normalizedCreator(data[i]);
 				this.setItem(t.node.id, {data: t.data, type: t.type});
-				this.parent.insertBefore(t.node, anchor);
+				var s = t.node.style;
+				s.position = "absolute";
+				s.left = (where.pageX + diffX) + "px";
+				s.top = (where.pageY + diffY) + "px";
+				this.parent.appendChild(t.node);
 			}
 		}else{
-			for(var i = 0; i < data.length; ++i){
-				var t = this._normalizedCreator(data[i]);
-				this.setItem(t.node.id, {data: t.data, type: t.type});
-				this.parent.appendChild(t.node);
+			if(!where)
+				where = {};
+			var before = where.before, anchor = where.anchor;
+			if(!this.parent.firstChild){
+				anchor = null;
+			}else if(before){
+				if(!anchor){
+					anchor = this.parent.firstChild;
+				}
+			}else{
+				if(anchor){
+					anchor = anchor.nextSibling;
+				}
 			}
+			if(anchor){
+				for(var i = 0; i < data.length; ++i){
+					var t = this._normalizedCreator(data[i]);
+					this.setItem(t.node.id, {data: t.data, type: t.type});
+					this.parent.insertBefore(t.node, anchor);
+				}
+			}else{
+				for(var i = 0; i < data.length; ++i){
+					var t = this._normalizedCreator(data[i]);
+					this.setItem(t.node.id, {data: t.data, type: t.type});
+					this.parent.appendChild(t.node);
+				}
+			}
 		}
 		return this;	// self
 	},

Yes, there is an interest

Yes, there is an interest for such functionality. Please submit a CLA, and file an enhancement ticket in the trac. After opening the ticket attach your patch. Don't forget to put [CLA] and [Patch] in the subject line.

CLA submitted, ticked opened

CLA submitted, ticked opened (http://bugs.dojotoolkit.org/ticket/7164)

Thank you.

Thank you.