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