1 if(!dojo._hasResource["dijit._tree.dndSource"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dijit._tree.dndSource"] = true;
3 dojo.provide("dijit._tree.dndSource");
5 dojo.require("dijit._tree.dndSelector");
6 dojo.require("dojo.dnd.Manager");
8 dojo.declare("dijit._tree.dndSource", dijit._tree.dndSelector, {
9 // summary: a Source object, which can be used as a DnD source, or a DnD target
11 // object attributes (for markup)
17 constructor: function(tree, params){
18 // summary: a constructor of the Source
19 // tree: dijit.Tree: the tree widget to build the source on
20 // params: Object: a dict of parameters, recognized parameters are:
21 // isSource: Boolean: can be used as a DnD source, if true; assumed to be "true" if omitted
22 // accept: Array: list of accepted types (text strings) for a target; assumed to be ["text"] if omitted
23 // horizontal: Boolean: a horizontal container, if true, vertical otherwise or when omitted
24 // copyOnly: Boolean: always copy items, if true, use a state of Ctrl key otherwise
25 // skipForm: Boolean: don't start the drag operation, if clicked on form elements
26 // the rest of parameters are passed to the selector
27 if(!params){ params = {}; }
28 dojo.mixin(this, params);
29 this.isSource = typeof params.isSource == "undefined" ? true : params.isSource;
30 var type = params.accept instanceof Array ? params.accept : ["text"];
34 for(var i = 0; i < type.length; ++i){
35 this.accept[type[i]] = 1;
39 // class-specific variables
40 this.isDragging = false;
41 this.mouseDown = false;
42 this.targetAnchor = null;
43 this.targetBox = null;
47 this.sourceState = "";
49 dojo.addClass(this.node, "dojoDndSource");
51 this.targetState = "";
53 dojo.addClass(this.node, "dojoDndTarget");
56 dojo.addClass(this.node, "dojoDndHorizontal");
60 dojo.subscribe("/dnd/source/over", this, "onDndSourceOver"),
61 dojo.subscribe("/dnd/start", this, "onDndStart"),
62 dojo.subscribe("/dnd/drop", this, "onDndDrop"),
63 dojo.subscribe("/dnd/cancel", this, "onDndCancel")
71 checkAcceptance: function(source, nodes){
72 // summary: checks, if the target can accept nodes from this source
73 // source: Object: the source which provides items
74 // nodes: Array: the list of transferred items
75 return true; // Boolean
77 copyState: function(keyPressed){
78 // summary: Returns true, if we need to copy items, false to move.
79 // It is separated to be overwritten dynamically, if needed.
80 // keyPressed: Boolean: the "copy" was pressed
81 return this.copyOnly || keyPressed; // Boolean
84 // summary: prepares the object to be garbage-collected
85 this.inherited("destroy",arguments);
86 dojo.forEach(this.topics, dojo.unsubscribe);
87 this.targetAnchor = null;
91 markupFactory: function(params, node){
92 params._skipStartup = true;
93 return new dijit._tree.dndSource(node, params);
96 // mouse event processors
97 onMouseMove: function(e){
98 // summary: event processor for onmousemove
99 // e: Event: mouse event
100 if(this.isDragging && this.targetState == "Disabled"){ return; }
101 this.inherited("onMouseMove", arguments);
102 var m = dojo.dnd.manager();
104 // calculate before/after
106 if (this.allowBetween){ // not implemented yet for tree since it has no concept of order
109 if(!this.targetBox || this.targetAnchor != this.current){
111 xy: dojo.coords(this.current, true),
112 w: this.current.offsetWidth,
113 h: this.current.offsetHeight
117 before = (e.pageX - this.targetBox.xy.x) < (this.targetBox.w / 2);
119 before = (e.pageY - this.targetBox.xy.y) < (this.targetBox.h / 2);
122 if(this.current != this.targetAnchor || before != this.before){
123 this._markTargetAnchor(before);
124 m.canDrop(!this.current || m.source != this || !(this.current.id in this.selection));
128 if(this.mouseDown && this.isSource){
129 var n = this.getSelectedNodes();
135 m.startDrag(this, nodes, this.copyState(dojo.dnd.getCopyKeyState(e)));
141 onMouseDown: function(e){
142 // summary: event processor for onmousedown
143 // e: Event: mouse event
144 this.mouseDown = true;
145 this.mouseButton = e.button;
146 this.inherited("onMouseDown",arguments);
149 onMouseUp: function(e){
150 // summary: event processor for onmouseup
151 // e: Event: mouse event
153 this.mouseDown = false;
154 this.inherited("onMouseUp",arguments);
158 onMouseOver: function(e){
159 // summary: event processor for onmouseover
160 // e: Event: mouse event
162 // handle when mouse has just moved over the Tree itself (not a TreeNode, but the Tree)
163 var rt = e.relatedTarget; // the previous location
165 if(rt == this.node){ break; }
173 this._changeState("Container", "Over");
177 // code below is for handling depending on which TreeNode we are over
178 var n = this._getChildByEvent(e); // the TreeNode
179 if(this.current == n){ return; }
180 if(this.current){ this._removeItemClass(this.current, "Over"); }
181 var m = dojo.dnd.manager();
183 this._addItemClass(n, "Over");
185 if(this.checkItemAcceptance(n,m.source)){
186 m.canDrop(this.targetState != "Disabled" && (!this.current || m.source != this || !(n in this.selection)));
193 if (m.source && this.checkAcceptance(m.source,m.source.getSelectedNodes())){
194 m.canDrop(this.targetState != "Disabled" && (!this.current || m.source != this || !(this.current.id in this.selection)));
203 checkItemAcceptance: function(node, source){
204 // summary: stub funciton to be overridden if one wants to check for the ability to drop at the node/item level
208 // topic event processors
209 onDndSourceOver: function(source){
210 // summary: topic event processor for /dnd/source/over, called when detected a current source
211 // source: Object: the source which has the mouse over it
213 this.mouseDown = false;
214 if(this.targetAnchor){
215 this._unmarkTargetAnchor();
217 }else if(this.isDragging){
218 var m = dojo.dnd.manager();
219 m.canDrop(this.targetState != "Disabled" && (!this.current || m.source != this || !(this.current.id in this.selection)));
222 onDndStart: function(source, nodes, copy){
223 // summary: topic event processor for /dnd/start, called to initiate the DnD operation
224 // source: Object: the source which provides items
225 // nodes: Array: the list of transferred items
226 // copy: Boolean: copy items, if true, move items otherwise
229 this._changeState("Source", this == source ? (copy ? "Copied" : "Moved") : "");
231 var accepted = this.checkAcceptance(source, nodes);
233 this._changeState("Target", accepted ? "" : "Disabled");
236 dojo.dnd.manager().overSource(this);
239 this.isDragging = true;
242 itemCreator: function(nodes){
243 return dojo.map(nodes, function(node){
246 "name": node.textContent || node.innerText || ""
251 onDndDrop: function(source, nodes, copy){
253 // Topic event processor for /dnd/drop, called to finish the DnD operation..
254 // Updates data store items according to where node was dragged from and dropped
255 // to. The tree will then respond to those data store updates and redraw itself.
256 // source: Object: the source which provides items
257 // nodes: Array: the list of transferred items
258 // copy: Boolean: copy items, if true, move items otherwise
260 if(this.containerState == "Over"){
261 var tree = this.tree,
263 target = this.current,
264 requeryRoot = false; // set to true iff top level items change
266 this.isDragging = false;
268 // Compute the new parent item
269 var targetWidget = dijit.getEnclosingWidget(target),
270 newParentItem = (targetWidget && targetWidget.item) || tree.item;
272 // If we are dragging from another source (or at least, another source
273 // that points to a different data store), then we need to make new data
274 // store items for each element in nodes[]. This call get the parameters
275 // to pass to store.newItem()
278 newItemsParams = this.itemCreator(nodes, target);
281 dojo.forEach(nodes, function(node, idx){
283 // This is a node from my own tree, and we are moving it, not copying.
284 // Remove item from old parent's children attribute.
285 // TODO: dijit._tree.dndSelector should implement deleteSelectedNodes()
286 // and this code should go there.
287 var childTreeNode = dijit.getEnclosingWidget(node),
288 childItem = childTreeNode.item,
289 oldParentItem = childTreeNode.getParent().item;
291 model.pasteItem(childItem, oldParentItem, newParentItem, copy);
293 model.newItem(newItemsParams[idx], newParentItem);
297 // Expand the target node (if it's currently collapsed) so the user can see
298 // where their node was dropped. In particular since that node is still selected.
299 this.tree._expandNode(targetWidget);
303 onDndCancel: function(){
304 // summary: topic event processor for /dnd/cancel, called to cancel the DnD operation
305 if(this.targetAnchor){
306 this._unmarkTargetAnchor();
307 this.targetAnchor = null;
310 this.isDragging = false;
311 this.mouseDown = false;
312 delete this.mouseButton;
313 this._changeState("Source", "");
314 this._changeState("Target", "");
319 onOverEvent: function(){
320 // summary: this function is called once, when mouse is over our container
321 this.inherited("onOverEvent",arguments);
322 dojo.dnd.manager().overSource(this);
324 onOutEvent: function(){
325 // summary: this function is called once, when mouse is out of our container
326 this.inherited("onOutEvent",arguments);
327 dojo.dnd.manager().outSource(this);
329 _markTargetAnchor: function(before){
330 // summary: assigns a class to the current target anchor based on "before" status
331 // before: Boolean: insert before, if true, after otherwise
332 if(this.current == this.targetAnchor && this.before == before){ return; }
333 if(this.targetAnchor){
334 this._removeItemClass(this.targetAnchor, this.before ? "Before" : "After");
336 this.targetAnchor = this.current;
337 this.targetBox = null;
338 this.before = before;
339 if(this.targetAnchor){
340 this._addItemClass(this.targetAnchor, this.before ? "Before" : "After");
343 _unmarkTargetAnchor: function(){
344 // summary: removes a class of the current target anchor based on "before" status
345 if(!this.targetAnchor){ return; }
346 this._removeItemClass(this.targetAnchor, this.before ? "Before" : "After");
347 this.targetAnchor = null;
348 this.targetBox = null;
351 _markDndStatus: function(copy){
352 // summary: changes source's state based on "copy" status
353 this._changeState("Source", copy ? "Copied" : "Moved");
357 dojo.declare("dijit._tree.dndTarget", dijit._tree.dndSource, {
358 // summary: a Target object, which can be used as a DnD target
360 constructor: function(node, params){
361 // summary: a constructor of the Target --- see the Source constructor for details
362 this.isSource = false;
363 dojo.removeClass(this.node, "dojoDndSource");
367 markupFactory: function(params, node){
368 params._skipStartup = true;
369 return new dijit._tree.dndTarget(node, params);