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