]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/grid/header/DropZone.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / grid / header / DropZone.js
CommitLineData
6527f429
DM
1/**\r
2 * @private\r
3 */\r
4Ext.define('Ext.grid.header.DropZone', {\r
5 extend: 'Ext.dd.DropZone',\r
6 colHeaderCls: Ext.baseCSSPrefix + 'column-header',\r
7 proxyOffsets: [-4, -9],\r
8\r
9 constructor: function(headerCt) {\r
10 var me = this;\r
11\r
12 me.headerCt = headerCt;\r
13 me.ddGroup = me.getDDGroup();\r
14 me.autoGroup = true;\r
15 me.callParent([headerCt.el]);\r
16 },\r
17\r
18 destroy: function () {\r
19 this.callParent();\r
20 Ext.destroy(this.topIndicator, this.bottomIndicator);\r
21 },\r
22\r
23 getDDGroup: function() {\r
24 return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;\r
25 },\r
26\r
27 getTargetFromEvent : function(e){\r
28 return e.getTarget('.' + this.colHeaderCls);\r
29 },\r
30\r
31 getTopIndicator: function() {\r
32 if (!this.topIndicator) {\r
33 this.topIndicator = Ext.getBody().createChild({\r
34 role: 'presentation',\r
35 cls: Ext.baseCSSPrefix + "col-move-top",\r
36 //<debug>\r
37 // tell the spec runner to ignore this element when checking if the dom is clean\r
38 "data-sticky": true,\r
39 //</debug>\r
40 html: "&#160;"\r
41 });\r
42 this.indicatorXOffset = Math.floor((this.topIndicator.dom.offsetWidth + 1) / 2);\r
43 }\r
44 return this.topIndicator;\r
45 },\r
46\r
47 getBottomIndicator: function() {\r
48 if (!this.bottomIndicator) {\r
49 this.bottomIndicator = Ext.getBody().createChild({\r
50 role: 'presentation',\r
51 cls: Ext.baseCSSPrefix + "col-move-bottom",\r
52 //<debug>\r
53 // tell the spec runner to ignore this element when checking if the dom is clean\r
54 "data-sticky": true,\r
55 //</debug>\r
56 html: "&#160;"\r
57 });\r
58 }\r
59 return this.bottomIndicator;\r
60 },\r
61\r
62 getLocation: function(e, t) {\r
63 var x = e.getXY()[0],\r
64 region = Ext.fly(t).getRegion(),\r
65 pos;\r
66\r
67 if ((region.right - x) <= (region.right - region.left) / 2) {\r
68 pos = "after";\r
69 } else {\r
70 pos = "before";\r
71 }\r
72 return {\r
73 pos: pos,\r
74 header: Ext.getCmp(t.id),\r
75 node: t\r
76 };\r
77 },\r
78\r
79 positionIndicator: function(data, node, e){\r
80 var me = this,\r
81 dragHeader = data.header,\r
82 dropLocation = me.getLocation(e, node),\r
83 targetHeader = dropLocation.header,\r
84 pos = dropLocation.pos,\r
85 nextHd,\r
86 prevHd,\r
87 topIndicator, bottomIndicator, topAnchor, bottomAnchor,\r
88 topXY, bottomXY, headerCtEl, minX, maxX,\r
89 allDropZones, ln, i, dropZone;\r
90\r
91 // Avoid expensive CQ lookups and DOM calculations if dropPosition has not changed\r
92 if (targetHeader === me.lastTargetHeader && pos === me.lastDropPos) {\r
93 return;\r
94 }\r
95 nextHd = dragHeader.nextSibling('gridcolumn:not([hidden])');\r
96 prevHd = dragHeader.previousSibling('gridcolumn:not([hidden])');\r
97 me.lastTargetHeader = targetHeader;\r
98 me.lastDropPos = pos;\r
99\r
100 // Cannot drag to before non-draggable start column\r
101 if (!targetHeader.draggable && pos === 'before' && targetHeader.getIndex() === 0) {\r
102 return false;\r
103 }\r
104\r
105 data.dropLocation = dropLocation;\r
106\r
107 if ((dragHeader !== targetHeader) &&\r
108 ((pos === "before" && nextHd !== targetHeader) ||\r
109 (pos === "after" && prevHd !== targetHeader)) &&\r
110 !targetHeader.isDescendantOf(dragHeader)) {\r
111\r
112 // As we move in between different DropZones that are in the same\r
113 // group (such as the case when in a locked grid), invalidateDrop\r
114 // on the other dropZones.\r
115 allDropZones = Ext.dd.DragDropManager.getRelated(me);\r
116 ln = allDropZones.length;\r
117 i = 0;\r
118\r
119 for (; i < ln; i++) {\r
120 dropZone = allDropZones[i];\r
121 if (dropZone !== me && dropZone.invalidateDrop) {\r
122 dropZone.invalidateDrop();\r
123 }\r
124 }\r
125\r
126 me.valid = true;\r
127 topIndicator = me.getTopIndicator();\r
128 bottomIndicator = me.getBottomIndicator();\r
129 if (pos === 'before') {\r
130 topAnchor = 'bc-tl';\r
131 bottomAnchor = 'tc-bl';\r
132 } else {\r
133 topAnchor = 'bc-tr';\r
134 bottomAnchor = 'tc-br';\r
135 }\r
136\r
137 // Calculate arrow positions. Offset them to align exactly with column border line\r
138 topXY = topIndicator.getAlignToXY(targetHeader.el, topAnchor);\r
139 bottomXY = bottomIndicator.getAlignToXY(targetHeader.el, bottomAnchor);\r
140\r
141 // constrain the indicators to the viewable section\r
142 headerCtEl = me.headerCt.el;\r
143 minX = headerCtEl.getX() - me.indicatorXOffset;\r
144 maxX = headerCtEl.getX() + headerCtEl.getWidth();\r
145\r
146 topXY[0] = Ext.Number.constrain(topXY[0], minX, maxX);\r
147 bottomXY[0] = Ext.Number.constrain(bottomXY[0], minX, maxX);\r
148\r
149 // position and show indicators\r
150 topIndicator.setXY(topXY);\r
151 bottomIndicator.setXY(bottomXY);\r
152 topIndicator.show();\r
153 bottomIndicator.show();\r
154\r
155 // invalidate drop operation and hide indicators\r
156 } else {\r
157 me.invalidateDrop();\r
158 }\r
159 },\r
160\r
161 invalidateDrop: function() {\r
162 this.valid = false;\r
163 this.hideIndicators();\r
164 },\r
165\r
166 onNodeOver: function(node, dragZone, e, data) {\r
167 var me = this,\r
168 from = data.header,\r
169 doPosition,\r
170 to,\r
171 fromPanel,\r
172 toPanel;\r
173\r
174 if (data.header.el.dom === node) {\r
175 doPosition = false;\r
176 } else {\r
177 data.isLock = data.isUnlock = data.crossPanel = false;\r
178 to = me.getLocation(e, node).header;\r
179\r
180 // Dragging within the same container - always valid\r
181 doPosition = (from.ownerCt === to.ownerCt);\r
182\r
183 // If from different containers, and they are not sealed, then continue checking\r
184 if (!doPosition && (!from.ownerCt.sealed && !to.ownerCt.sealed)) {\r
185\r
186 doPosition = true;\r
187 fromPanel = from.up('tablepanel');\r
188 toPanel = to.up('tablepanel');\r
189 if (fromPanel !== toPanel) {\r
190 data.crossPanel = true;\r
191\r
192 // If it's a lock operation, check that it's allowable.\r
193 data.isLock = toPanel.isLocked && !fromPanel.isLocked;\r
194 data.isUnlock = !toPanel.isLocked && fromPanel.isLocked;\r
195 if ((data.isUnlock && from.lockable === false) || (data.isLock && !from.isLockable())) {\r
196 doPosition = false;\r
197 }\r
198 }\r
199 }\r
200 }\r
201\r
202 if (doPosition) {\r
203 me.positionIndicator(data, node, e);\r
204 } else {\r
205 me.valid = false;\r
206 }\r
207 return me.valid ? me.dropAllowed : me.dropNotAllowed;\r
208 },\r
209\r
210 hideIndicators: function() {\r
211 var me = this;\r
212\r
213 me.getTopIndicator().hide();\r
214 me.getBottomIndicator().hide();\r
215 me.lastTargetHeader = me.lastDropPos = null;\r
216 },\r
217\r
218 onNodeOut: function() {\r
219 this.hideIndicators();\r
220 },\r
221\r
222 /**\r
223 * @private\r
224 * Used to determine the move position for the view's data columns for nested headers at any level.\r
225 */\r
226 getNestedHeader: function (header, first) {\r
227 var items = header.items,\r
228 pos;\r
229\r
230 if (header.isGroupHeader && items.length) {\r
231 pos = !first ? 'first' : 'last';\r
232 header = this.getNestedHeader(items[pos](), first);\r
233 }\r
234\r
235 return header;\r
236 },\r
237\r
238 onNodeDrop: function(node, dragZone, e, data) {\r
239 // Do not process the upcoming click after this mouseup. It's not a click gesture\r
240 this.headerCt.blockNextEvent();\r
241\r
242 // Note that dropLocation.pos refers to whether the header is dropped before or after the target node!\r
243 if (!this.valid) {\r
244 return;\r
245 }\r
246 \r
247 var me = this,\r
248 dragHeader = data.header,\r
249 dropLocation = data.dropLocation,\r
250 dropPosition = dropLocation.pos,\r
251 targetHeader = dropLocation.header,\r
252 fromCt = dragHeader.ownerCt,\r
253 fromCtRoot = fromCt.getRootHeaderCt(),\r
254 toCt = targetHeader.ownerCt,\r
255 // Use the full column manager here, the indices we want are for moving the actual items in the container.\r
256 // The HeaderContainer translates this to visible columns for informing the view and firing events.\r
257 visibleColumnManager = me.headerCt.visibleColumnManager,\r
258 visibleFromIdx = visibleColumnManager.getHeaderIndex(dragHeader),\r
259 visibleToIdx, colsToMove, moveMethod, scrollerOwner, savedWidth;\r
260\r
261 // If we are dragging in between two HeaderContainers that have had the lockable mixin injected we will lock/unlock\r
262 // headers in between sections, and then continue with another execution of onNodeDrop to ensure the header is\r
263 // dropped into the correct group.\r
264 if (data.isLock || data.isUnlock) {\r
265 scrollerOwner = fromCt.up('[scrollerOwner]');\r
266 visibleToIdx = toCt.items.indexOf(targetHeader);\r
267\r
268 if (dropPosition === 'after') {\r
269 visibleToIdx++;\r
270 }\r
271\r
272 if (data.isLock) {\r
273 scrollerOwner.lock(dragHeader, visibleToIdx, toCt);\r
274 } else {\r
275 scrollerOwner.unlock(dragHeader, visibleToIdx, toCt);\r
276 }\r
277 }\r
278 // This is a drop within the same HeaderContainer.\r
279 else {\r
280 // For the after position, we need to update the visibleToIdx index. In case it's nested in one or more\r
281 // grouped headers, we need to get the last header (or the first, depending on the dropPosition) in the\r
282 // items collection for the most deeply-nested header, whether it be first or last in the collection.\r
283 // This will yield the header index in the visibleColumnManager, which will correctly maintain a list\r
284 // of all the headers.\r
285 visibleToIdx = dropPosition === 'after' ?\r
286 // Get the last header in the most deeply-nested header group and add one.\r
287 visibleColumnManager.getHeaderIndex(me.getNestedHeader(targetHeader, 1)) + 1 :\r
288 // Get the first header in the most deeply-nested header group.\r
289 visibleColumnManager.getHeaderIndex(me.getNestedHeader(targetHeader, 0));\r
290\r
291 me.invalidateDrop();\r
292 // Cache the width here, we need to get it before we removed it from the DOM\r
293 savedWidth = dragHeader.getWidth();\r
294\r
295 // Suspend layouts while we sort all this out.\r
296 Ext.suspendLayouts();\r
297\r
298 // When removing and then adding, the owning gridpanel will be informed of column mutation twice\r
299 // Both remove and add handling inform the owning grid.\r
300 // The isDDMoveInGrid flag will prevent the remove operation from doing this.\r
301 // See Ext.grid.header.Container#onRemove.\r
302 fromCt.isDDMoveInGrid = toCt.isDDMoveInGrid = !data.crossPanel;\r
303\r
304 // ***Move the headers***\r
305 //\r
306 // If both drag and target headers are groupHeaders, we have to check and see if they are nested, i.e.,\r
307 // there are multiple stacked group headers with only subheaders at the lowest level:\r
308 //\r
309 // +-----------------------------------+\r
310 // | Group 1 |\r
311 // |-----------------------------------|\r
312 // | Group 2 |\r
313 // other |-----------------------------------| other\r
314 // headers | Group 3 | headers\r
315 // |-----------------------------------|\r
316 // | Field3 | Field4 | Field5 | Field6 |\r
317 // |===================================|\r
318 // | view |\r
319 // +-----------------------------------+\r
320 //\r
321 // In these cases, we need to mark the groupHeader that is the ownerCt of the targetHeader and then only\r
322 // remove the headers up until that (removal of headers is recursive and assumes that any header with no\r
323 // children can be safely removed, which is not a safe assumption).\r
324 // See Ext.grid.header.Container#onRemove.\r
325 if (dragHeader.isGroupHeader && targetHeader.isGroupHeader) {\r
326 dragHeader.setNestedParent(targetHeader);\r
327 }\r
328\r
329 // We only need to be concerned with moving the dragHeader component before or after the targetHeader\r
330 // component rather than trying to pass indices, which is too ambiguous and could refer to any\r
331 // collection at any level of (grouped) header containers.\r
332 if (dropPosition === 'before') {\r
333 targetHeader.insertNestedHeader(dragHeader);\r
334 } else {\r
335 // Capitalize the first letter. This will call either ct.moveAfter() or ct.moveBefore().\r
336 moveMethod = 'move' + dropPosition.charAt(0).toUpperCase() + dropPosition.substr(1);\r
337 toCt[moveMethod](dragHeader, targetHeader);\r
338 }\r
339\r
340 // ***Move the view data columns***\r
341 // Refresh the view if it's not the last header in a group. If it is the last header, we don't need\r
342 // to refresh the view as the headers and the corrresponding data columns will already be correctly\r
343 // aligned (think of the group header sitting directly atop the last header in the group).\r
344 // Also, it's not necessary to refresh the view if the indices are the same.\r
345 // NOTE that targetHeader can be destroyed by this point if it was a group header\r
346 // and we just dragged the last column out of it; in that case header's items collection\r
347 // will be nulled.\r
348 if (visibleToIdx >= 0 && \r
349 !(targetHeader.isGroupHeader && (!targetHeader.items || !targetHeader.items.length)) &&\r
350 visibleFromIdx !== visibleToIdx)\r
351 {\r
352 colsToMove = dragHeader.isGroupHeader ?\r
353 dragHeader.query(':not([hidden]):not([isGroupHeader])').length :\r
354 1;\r
355\r
356 // We need to adjust the visibleToIdx when both of the following conditions are met:\r
357 // 1. The drag is forward, i.e., the dragHeader is being dragged to the right.\r
358 // 2. There is more than one column being dragged, i.e., an entire group.\r
359 if ((visibleFromIdx <= visibleToIdx) && colsToMove > 1) {\r
360 visibleToIdx -= colsToMove;\r
361 }\r
362\r
363 // It's necessary to lookup the ancestor grid of the grouped header b/c the header could be\r
364 // nested at any level.\r
365 toCt.getRootHeaderCt().grid.view.moveColumn(visibleFromIdx, visibleToIdx, colsToMove);\r
366 }\r
367\r
368 // We need to always fire a columnmove event. Check for an .ownerCt first in case this is a\r
369 // grouped header.\r
370 fromCtRoot.fireEvent('columnmove', fromCt, dragHeader, visibleFromIdx, visibleToIdx);\r
371\r
372 fromCt.isDDMoveInGrid = toCt.isDDMoveInGrid = false;\r
373\r
374 // Group headers skrinkwrap their child headers.\r
375 // Therefore a child header may not flex; it must contribute a fixed width.\r
376 // But we restore the flex value when moving back into the main header container\r
377 //\r
378 // Note that we don't need to save the flex if coming from another group header b/c it couldn't\r
379 // have had one!\r
380 if (toCt.isGroupHeader && !fromCt.isGroupHeader) {\r
381 // Adjust the width of the "to" group header only if we dragged in from somewhere else.\r
382 // If not within the same container.\r
383 if (fromCt !== toCt) {\r
384 dragHeader.savedFlex = dragHeader.flex;\r
385 delete dragHeader.flex;\r
386 dragHeader.width = savedWidth;\r
387 }\r
388 } else if (!fromCt.isGroupHeader) {\r
389 if (dragHeader.savedFlex) {\r
390 dragHeader.flex = dragHeader.savedFlex;\r
391 delete dragHeader.width;\r
392 }\r
393 }\r
394\r
395 Ext.resumeLayouts(true);\r
396 // Ext.grid.header.Container will handle the removal of empty groups, don't handle it here.\r
397 }\r
398 }\r
399});\r