]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/tree/ViewDropZone.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / tree / ViewDropZone.js
CommitLineData
6527f429
DM
1/**\r
2 * @private\r
3 */\r
4Ext.define('Ext.tree.ViewDropZone', {\r
5 extend: 'Ext.view.DropZone',\r
6\r
7 /**\r
8 * @cfg {Boolean} allowParentInserts\r
9 * Allow inserting a dragged node between an expanded parent node and its first child that will become a\r
10 * sibling of the parent when dropped.\r
11 */\r
12 allowParentInserts: false,\r
13 \r
14 /**\r
15 * @cfg {Boolean} allowContainerDrops\r
16 * True if drops on the tree container (outside of a specific tree node) are allowed.\r
17 *\r
18 * These are treated as appends to the root node.\r
19 */\r
20 allowContainerDrops: false,\r
21\r
22 /**\r
23 * @cfg {Boolean} appendOnly\r
24 * True if the tree should only allow append drops (use for trees which are sorted).\r
25 */\r
26 appendOnly: false,\r
27\r
28 /**\r
29 * @cfg {Number} expandDelay\r
30 * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node\r
31 * over the target.\r
32 */\r
33 expandDelay : 500,\r
34\r
35 indicatorCls: Ext.baseCSSPrefix + 'tree-ddindicator',\r
36\r
37 /**\r
38 * @private\r
39 */\r
40 expandNode : function(node) {\r
41 var view = this.view;\r
42 this.expandProcId = false;\r
43 if (!node.isLeaf() && !node.isExpanded()) {\r
44 view.expand(node);\r
45 this.expandProcId = false;\r
46 }\r
47 },\r
48\r
49 /**\r
50 * @private\r
51 */\r
52 queueExpand : function(node) {\r
53 this.expandProcId = Ext.Function.defer(this.expandNode, this.expandDelay, this, [node]);\r
54 },\r
55\r
56 /**\r
57 * @private\r
58 */\r
59 cancelExpand : function() {\r
60 if (this.expandProcId) {\r
61 clearTimeout(this.expandProcId);\r
62 this.expandProcId = false;\r
63 }\r
64 },\r
65\r
66 getPosition: function(e, node) {\r
67 var view = this.view,\r
68 record = view.getRecord(node),\r
69 y = e.getY(),\r
70 noAppend = record.isLeaf(),\r
71 noBelow = false,\r
72 region = Ext.fly(node).getRegion(),\r
73 fragment;\r
74\r
75 // If we are dragging on top of the root node of the tree, we always want to append.\r
76 if (record.isRoot()) {\r
77 return 'append';\r
78 }\r
79\r
80 // Return 'append' if the node we are dragging on top of is not a leaf else return false.\r
81 if (this.appendOnly) {\r
82 return noAppend ? false : 'append';\r
83 }\r
84\r
85 if (!this.allowParentInserts) {\r
86 noBelow = record.hasChildNodes() && record.isExpanded();\r
87 }\r
88\r
89 fragment = (region.bottom - region.top) / (noAppend ? 2 : 3);\r
90 if (y >= region.top && y < (region.top + fragment)) {\r
91 return 'before';\r
92 }\r
93 else if (!noBelow && (noAppend || (y >= (region.bottom - fragment) && y <= region.bottom))) {\r
94 return 'after';\r
95 }\r
96 else {\r
97 return 'append';\r
98 }\r
99 },\r
100\r
101 isValidDropPoint : function(node, position, dragZone, e, data) {\r
102 if (!node || !data.item) {\r
103 return false;\r
104 }\r
105\r
106 var view = this.view,\r
107 targetNode = view.getRecord(node),\r
108 draggedRecords = data.records,\r
109 dataLength = draggedRecords.length,\r
110 ln = draggedRecords.length,\r
111 i, record;\r
112\r
113 // No drop position, or dragged records: invalid drop point\r
114 if (!(targetNode && position && dataLength)) {\r
115 return false;\r
116 }\r
117\r
118 // If the targetNode is within the folder we are dragging\r
119 for (i = 0; i < ln; i++) {\r
120 record = draggedRecords[i];\r
121 if (record.isNode && record.contains(targetNode)) {\r
122 return false;\r
123 }\r
124 }\r
125 \r
126 // Respect the allowDrop field on Tree nodes\r
127 if (position === 'append' && targetNode.get('allowDrop') === false) {\r
128 return false;\r
129 }\r
130 else if (position !== 'append' && targetNode.parentNode.get('allowDrop') === false) {\r
131 return false;\r
132 }\r
133\r
134 // If the target record is in the dragged dataset, then invalid drop\r
135 if (Ext.Array.contains(draggedRecords, targetNode)) {\r
136 return false;\r
137 }\r
138 return view.fireEvent('nodedragover', targetNode, position, data, e) !== false;\r
139 },\r
140\r
141 onNodeOver : function(node, dragZone, e, data) {\r
142 var position = this.getPosition(e, node),\r
143 returnCls = this.dropNotAllowed,\r
144 view = this.view,\r
145 targetNode = view.getRecord(node),\r
146 indicator = this.getIndicator(),\r
147 indicatorY = 0;\r
148\r
149 // auto node expand check\r
150 this.cancelExpand();\r
151 if (position === 'append' && !this.expandProcId && !Ext.Array.contains(data.records, targetNode) && !targetNode.isLeaf() && !targetNode.isExpanded()) {\r
152 this.queueExpand(targetNode);\r
153 }\r
154 \r
155 \r
156 if (this.isValidDropPoint(node, position, dragZone, e, data)) {\r
157 this.valid = true;\r
158 this.currentPosition = position;\r
159 this.overRecord = targetNode;\r
160\r
161 indicator.setWidth(Ext.fly(node).getWidth());\r
162 indicatorY = Ext.fly(node).getY() - Ext.fly(view.el).getY() - 1;\r
163\r
164 // If view is scrolled using CSS translate, account for then when positioning the indicator\r
165 if (view.touchScroll === 2) {\r
166 indicatorY += view.getScrollY();\r
167 }\r
168\r
169 /*\r
170 * In the code below we show the proxy again. The reason for doing this is showing the indicator will\r
171 * call toFront, causing it to get a new z-index which can sometimes push the proxy behind it. We always \r
172 * want the proxy to be above, so calling show on the proxy will call toFront and bring it forward.\r
173 */\r
174 if (position === 'before') {\r
175 returnCls = targetNode.isFirst() ? Ext.baseCSSPrefix + 'tree-drop-ok-above' : Ext.baseCSSPrefix + 'tree-drop-ok-between';\r
176 indicator.showAt(0, indicatorY);\r
177 dragZone.proxy.show();\r
178 } else if (position === 'after') {\r
179 returnCls = targetNode.isLast() ? Ext.baseCSSPrefix + 'tree-drop-ok-below' : Ext.baseCSSPrefix + 'tree-drop-ok-between';\r
180 indicatorY += Ext.fly(node).getHeight();\r
181 indicator.showAt(0, indicatorY);\r
182 dragZone.proxy.show();\r
183 } else {\r
184 returnCls = Ext.baseCSSPrefix + 'tree-drop-ok-append';\r
185 // @TODO: set a class on the parent folder node to be able to style it\r
186 indicator.hide();\r
187 }\r
188 } else {\r
189 this.valid = false;\r
190 }\r
191\r
192 this.currentCls = returnCls;\r
193 return returnCls;\r
194 },\r
195\r
196 // The mouse is no longer over a tree node, so dropping is not valid\r
197 onNodeOut : function(n, dd, e, data){\r
198 this.valid = false;\r
199 this.getIndicator().hide();\r
200 },\r
201\r
202 onContainerOver : function(dd, e, data) {\r
203 return this.allowContainerDrops ? this.dropAllowed : e.getTarget('.' + this.indicatorCls) ? this.currentCls : this.dropNotAllowed;\r
204 },\r
205\r
206 // This will be called is allowContainerDrops is set.\r
207 // The target node is the root\r
208 onContainerDrop: function(dragZone, e, data) {\r
209 if (this.allowContainerDrops) {\r
210 this.valid = true;\r
211 this.currentPosition = 'append';\r
212 this.overRecord = this.view.store.getRoot();\r
213 this.onNodeDrop(this.overRecord, dragZone, e, data);\r
214 }\r
215 },\r
216 \r
217 notifyOut: function() {\r
218 this.callParent(arguments);\r
219 this.cancelExpand();\r
220 },\r
221\r
222 handleNodeDrop : function(data, targetNode, position) {\r
223 var me = this,\r
224 targetView = me.view,\r
225 parentNode = targetNode ? targetNode.parentNode : targetView.panel.getRootNode(),\r
226 Model = targetView.store.getModel(),\r
227 records, i, len, record,\r
228 insertionMethod, argList,\r
229 needTargetExpand,\r
230 transferData;\r
231\r
232 // If the copy flag is set, create a copy of the models\r
233 if (data.copy) {\r
234 records = data.records;\r
235 data.records = [];\r
236 for (i = 0, len = records.length; i < len; i++) {\r
237 record = records[i];\r
238 if (record.isNode) {\r
239 data.records.push(record.copy());\r
240 } else {\r
241 // If it's not a node, make a node copy\r
242 data.records.push(new Model(Ext.apply({}, record.data)));\r
243 }\r
244 }\r
245 }\r
246\r
247 // Cancel any pending expand operation\r
248 me.cancelExpand();\r
249\r
250 // Grab a reference to the correct node insertion method.\r
251 // Create an arg list array intended for the apply method of the\r
252 // chosen node insertion method.\r
253 // Ensure the target object for the method is referenced by 'targetNode'\r
254 if (position === 'before') {\r
255 insertionMethod = parentNode.insertBefore;\r
256 argList = [null, targetNode];\r
257 targetNode = parentNode;\r
258 }\r
259 else if (position === 'after') {\r
260 if (targetNode.nextSibling) {\r
261 insertionMethod = parentNode.insertBefore;\r
262 argList = [null, targetNode.nextSibling];\r
263 }\r
264 else {\r
265 insertionMethod = parentNode.appendChild;\r
266 argList = [null];\r
267 }\r
268 targetNode = parentNode;\r
269 }\r
270 else {\r
271 if (!(targetNode.isExpanded() || targetNode.isLoading())) {\r
272 needTargetExpand = true;\r
273 }\r
274 insertionMethod = targetNode.appendChild;\r
275 argList = [null];\r
276 }\r
277 \r
278 // A function to transfer the data into the destination tree\r
279 transferData = function() {\r
280 var color,\r
281 n;\r
282\r
283 // Coalesce layouts caused by node removal, appending and sorting\r
284 Ext.suspendLayouts();\r
285\r
286 // Insert the records into the target node\r
287 for (i = 0, len = data.records.length; i < len; i++) {\r
288 record = data.records[i];\r
289 if (!record.isNode) {\r
290 if (record.isModel) {\r
291 record = new Model(record.data, record.getId());\r
292 } else {\r
293 record = new Model(record);\r
294 }\r
295 data.records[i] = record;\r
296 }\r
297 argList[0] = record;\r
298 insertionMethod.apply(targetNode, argList);\r
299 }\r
300\r
301 // If configured to sort on drop, do it according to the TreeStore's comparator\r
302 if (me.sortOnDrop) {\r
303 targetNode.sort(targetNode.getOwnerTree().store.getSorters().sortFn);\r
304 }\r
305 \r
306 Ext.resumeLayouts(true);\r
307 \r
308 // Focus the dropped node.\r
309 record = data.records[0];\r
310 targetView.ownerGrid.ensureVisible(record);\r
311 targetView.getNavigationModel().setPosition(record);\r
312\r
313 // Kick off highlights after everything's been inserted, so they are\r
314 // more in sync without insertion/render overhead.\r
315 // Element.highlight can handle highlighting table nodes.\r
316 if (Ext.enableFx && me.dropHighlight) {\r
317 color = me.dropHighlightColor;\r
318\r
319 for (i = 0; i < len; i++) {\r
320 n = targetView.getNode(data.records[i]);\r
321 if (n) {\r
322 Ext.fly(n).highlight(color);\r
323 }\r
324 }\r
325 }\r
326 };\r
327\r
328 // If dropping right on an unexpanded node, transfer the data after it is expanded.\r
329 if (needTargetExpand) {\r
330 targetNode.expand(false, transferData);\r
331 }\r
332 // If the node is waiting for its children, we must transfer the data after the expansion.\r
333 // The expand event does NOT signal UI expansion, it is the SIGNAL for UI expansion.\r
334 // It's listened for by the NodeStore on the root node. Which means that listeners on the target\r
335 // node get notified BEFORE UI expansion. So we need a delay.\r
336 // TODO: Refactor NodeInterface.expand/collapse to notify its owning tree directly when it needs to expand/collapse.\r
337 else if (targetNode.isLoading()) {\r
338 targetNode.on({\r
339 expand: transferData,\r
340 delay: 1,\r
341 single: true\r
342 });\r
343 }\r
344 // Otherwise, call the data transfer function immediately\r
345 else {\r
346 transferData();\r
347 }\r
348 } \r
349});