]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/grid/header/DropZone.js
bump version to 7.0.0-4
[extjs.git] / extjs / classic / classic / src / grid / header / DropZone.js
CommitLineData
947f0963
TL
1/**
2 * @private
3 */
4Ext.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: "&#160;"
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: "&#160;"
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});