]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**\r |
2 | * @private\r | |
3 | */\r | |
4 | Ext.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 | }); |