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.