/**
* @private
*/
Ext.define('Ext.grid.header.DropZone', {
extend: 'Ext.dd.DropZone',
colHeaderCls: Ext.baseCSSPrefix + 'column-header',
proxyOffsets: [-4, -9],
constructor: function(headerCt) {
var me = this;
me.headerCt = headerCt;
me.ddGroup = me.getDDGroup();
me.autoGroup = true;
me.callParent([headerCt.el]);
},
destroy: function() {
Ext.destroy(this.topIndicator, this.bottomIndicator);
this.callParent();
},
getDDGroup: function() {
return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
},
getTargetFromEvent: function(e) {
return e.getTarget('.' + this.colHeaderCls);
},
getTopIndicator: function() {
if (!this.topIndicator) {
this.topIndicator = Ext.getBody().createChild({
role: 'presentation',
cls: Ext.baseCSSPrefix + "col-move-top",
//<debug>
// tell the spec runner to ignore this element when checking if the dom is clean
"data-sticky": true,
//</debug>
html: " "
});
this.indicatorXOffset = Math.floor((this.topIndicator.dom.offsetWidth + 1) / 2);
}
return this.topIndicator;
},
getBottomIndicator: function() {
if (!this.bottomIndicator) {
this.bottomIndicator = Ext.getBody().createChild({
role: 'presentation',
cls: Ext.baseCSSPrefix + "col-move-bottom",
//<debug>
// tell the spec runner to ignore this element when checking if the dom is clean
"data-sticky": true,
//</debug>
html: " "
});
}
return this.bottomIndicator;
},
getLocation: function(e, t) {
var x = e.getXY()[0],
region = Ext.fly(t).getRegion(),
pos;
if ((region.right - x) <= (region.right - region.left) / 2) {
pos = "after";
}
else {
pos = "before";
}
return {
pos: pos,
header: Ext.getCmp(t.id),
node: t
};
},
positionIndicator: function(data, node, e) {
var me = this,
dragHeader = data.header,
dropLocation = me.getLocation(e, node),
targetHeader = dropLocation.header,
pos = dropLocation.pos,
nextHd, prevHd, topIndicator, bottomIndicator, topAnchor, bottomAnchor,
topXY, bottomXY, headerCtEl, minX, maxX, allDropZones, ln, i, dropZone;
// Avoid expensive CQ lookups and DOM calculations if dropPosition has not changed
if (targetHeader === me.lastTargetHeader && pos === me.lastDropPos) {
return;
}
nextHd = dragHeader.nextSibling('gridcolumn:not([hidden])');
prevHd = dragHeader.previousSibling('gridcolumn:not([hidden])');
// can't move a column before the grouping column
if ((targetHeader.isGroupsColumn && pos === 'before') ||
(nextHd && nextHd.isGroupsColumn && pos === 'after')) {
return false;
}
me.lastTargetHeader = targetHeader;
me.lastDropPos = pos;
// Cannot drag to before non-draggable start column
if (!targetHeader.draggable && pos === 'before' && targetHeader.getIndex() === 0) {
return false;
}
data.dropLocation = dropLocation;
if ((dragHeader !== targetHeader) &&
((pos === "before" && nextHd !== targetHeader) ||
(pos === "after" && prevHd !== targetHeader)) &&
!targetHeader.isDescendantOf(dragHeader)) {
// As we move in between different DropZones that are in the same
// group (such as the case when in a locked grid), invalidateDrop
// on the other dropZones.
allDropZones = Ext.dd.DragDropManager.getRelated(me);
ln = allDropZones.length;
i = 0;
for (; i < ln; i++) {
dropZone = allDropZones[i];
if (dropZone !== me && dropZone.invalidateDrop) {
dropZone.invalidateDrop();
}
}
me.valid = true;
topIndicator = me.getTopIndicator();
bottomIndicator = me.getBottomIndicator();
if (pos === 'before') {
topAnchor = 'bc-tl';
bottomAnchor = 'tc-bl';
}
else {
topAnchor = 'bc-tr';
bottomAnchor = 'tc-br';
}
// Calculate arrow positions. Offset them to align exactly with column border line
topXY = topIndicator.getAlignToXY(targetHeader.el, topAnchor);
bottomXY = bottomIndicator.getAlignToXY(targetHeader.el, bottomAnchor);
// constrain the indicators to the viewable section
headerCtEl = me.headerCt.el;
minX = headerCtEl.getX() - me.indicatorXOffset;
maxX = headerCtEl.getX() + headerCtEl.getWidth();
topXY[0] = Ext.Number.constrain(topXY[0], minX, maxX);
bottomXY[0] = Ext.Number.constrain(bottomXY[0], minX, maxX);
// position and show indicators
topIndicator.setXY(topXY);
bottomIndicator.setXY(bottomXY);
topIndicator.show();
bottomIndicator.show();
}
// invalidate drop operation and hide indicators
else {
me.invalidateDrop();
}
},
invalidateDrop: function() {
this.valid = false;
this.hideIndicators();
},
onNodeOver: function(node, dragZone, e, data) {
var me = this,
from = data.header,
doPosition, fromPanel, to, toPanel;
if (data.header.el.dom === node) {
doPosition = false;
}
else {
data.isLock = data.isUnlock = data.crossPanel = false;
to = me.getLocation(e, node).header;
// Dragging within the same container - always valid
doPosition = (from.ownerCt === to.ownerCt);
// If from different containers, and they are not sealed, then continue checking
if (!doPosition && (!from.ownerCt.isSealed() && !to.ownerCt.isSealed())) {
doPosition = true;
fromPanel = from.up('tablepanel');
toPanel = to.up('tablepanel');
if (fromPanel !== toPanel) {
data.crossPanel = true;
// If it's a lock operation, check that it's allowable.
data.isLock = toPanel.isLocked && !fromPanel.isLocked;
data.isUnlock = !toPanel.isLocked && fromPanel.isLocked;
if ((data.isUnlock && from.lockable === false) ||
(data.isLock && !from.isLockable())) {
doPosition = false;
}
}
}
}
if (doPosition) {
me.positionIndicator(data, node, e);
}
else {
me.valid = false;
}
return me.valid ? me.dropAllowed : me.dropNotAllowed;
},
hideIndicators: function() {
var me = this;
me.getTopIndicator().hide();
me.getBottomIndicator().hide();
me.lastTargetHeader = me.lastDropPos = null;
},
onNodeOut: function() {
this.hideIndicators();
},
/**
* @private
* Used to determine the move position for the view's data columns for nested headers
* at any level.
*/
getNestedHeader: function(header, first) {
var items = header.items,
pos;
if (header.isGroupHeader && items.length) {
pos = !first ? 'first' : 'last';
header = this.getNestedHeader(items[pos](), first);
}
return header;
},
onNodeDrop: function(node, dragZone, e, data) {
// Do not process the upcoming click after this mouseup. It's not a click gesture
this.headerCt.blockNextEvent();
// Note that dropLocation.pos refers to whether the header is dropped
// before or after the target node!
if (!this.valid) {
return;
}
// eslint-disable-next-line vars-on-top
var me = this,
dragHeader = data.header,
dropLocation = data.dropLocation,
dropPosition = dropLocation.pos,
targetHeader = dropLocation.header,
fromCt = dragHeader.ownerCt,
fromCtRoot = fromCt.getRootHeaderCt(),
toCt = targetHeader.ownerCt,
// Use the full column manager here, the indices we want are for moving the actual items
// in the container. The HeaderContainer translates this to visible columns
// for informing the view and firing events.
visibleColumnManager = me.headerCt.visibleColumnManager,
visibleFromIdx = visibleColumnManager.getHeaderIndex(dragHeader),
visibleToIdx, colsToMove, scrollerOwner, savedWidth;
// If we are dragging in between two HeaderContainers that have had the lockable mixin
// injected we will lock/unlock headers in between sections, and then continue with another
// execution of onNodeDrop to ensure the header is dropped into the correct group.
if (data.isLock || data.isUnlock) {
scrollerOwner = fromCt.up('[scrollerOwner]');
visibleToIdx = toCt.items.indexOf(targetHeader);
if (dropPosition === 'after') {
visibleToIdx++;
}
if (data.isLock) {
scrollerOwner.lock(dragHeader, visibleToIdx, toCt);
}
else {
scrollerOwner.unlock(dragHeader, visibleToIdx, toCt);
}
}
// This is a drop within the same HeaderContainer.
else {
// For the after position, we need to update the visibleToIdx index. In case it's nested
// in one or more grouped headers, we need to get the last header (or the first,
// depending on the dropPosition) in the items collection for the most deeply-nested
// header, whether it be first or last in the collection. This will yield the header
// index in the visibleColumnManager, which will correctly maintain a list
// of all the headers.
visibleToIdx = dropPosition === 'after'
// Get the last header in the most deeply-nested header group and add one.
? visibleColumnManager.getHeaderIndex(me.getNestedHeader(targetHeader, 1)) + 1
// Get the first header in the most deeply-nested header group.
: visibleColumnManager.getHeaderIndex(me.getNestedHeader(targetHeader, 0));
me.invalidateDrop();
// Cache the width here, we need to get it before we removed it from the DOM
savedWidth = dragHeader.getWidth();
// Suspend layouts while we sort all this out.
Ext.suspendLayouts();
// When removing and then adding, the owning gridpanel will be informed of column
// mutation twice
// Both remove and add handling inform the owning grid.
// The isDDMoveInGrid flag will prevent the remove operation from doing this.
// See Ext.grid.header.Container#onRemove.
// It's enough to inform the root container about the move
fromCtRoot.isDDMoveInGrid = !data.crossPanel;
// ***Move the headers***
//
// If both drag and target headers are groupHeaders, we have to check and see if
// they are nested, i.e., there are multiple stacked group headers with only subheaders
// at the lowest level:
//
// +-----------------------------------+
// | Group 1 |
// |-----------------------------------|
// | Group 2 |
// other |-----------------------------------| other
// headers | Group 3 | headers
// |-----------------------------------|
// | Field3 | Field4 | Field5 | Field6 |
// |===================================|
// | view |
// +-----------------------------------+
//
// In these cases, we need to mark the groupHeader that is the ownerCt
// of the targetHeader and then only remove the headers up until that
// (removal of headers is recursive and assumes that any header with no children
// can be safely removed, which is not a safe assumption).
// See Ext.grid.header.Container#onRemove.
if (dragHeader.isGroupHeader && targetHeader.isGroupHeader) {
dragHeader.setNestedParent(targetHeader);
}
// We only need to be concerned with moving the dragHeader component before or after
// the targetHeader component rather than trying to pass indices, which is too ambiguous
// and could refer to any collection at any level of (grouped) header containers.
if (dropPosition === 'before') {
toCt.moveBefore(dragHeader, targetHeader);
}
else {
toCt.moveAfter(dragHeader, targetHeader);
}
// ***Move the view data columns***
// Refresh the view if it's not the last header in a group. If it is the last header,
// we don't need to refresh the view as the headers and the corrresponding data columns
// will already be correctly aligned (think of the group header sitting directly atop
// the last header in the group).
// Also, it's not necessary to refresh the view if the indices are the same.
// NOTE that targetHeader can be destroyed by this point if it was a group header
// and we just dragged the last column out of it; in that case header's items collection
// will be nulled.
if (visibleToIdx >= 0 &&
!(targetHeader.isGroupHeader &&
(!targetHeader.items || !targetHeader.items.length)) &&
visibleFromIdx !== visibleToIdx) {
colsToMove = dragHeader.isGroupHeader
? dragHeader.query('gridcolumn:not([hidden]):not([isGroupHeader])').length
: 1;
// We need to adjust the visibleToIdx when both of the following conditions are met:
// 1. The drag is forward, i.e., the dragHeader is being dragged to the right.
// 2. There is more than one column being dragged, i.e., an entire group.
if ((visibleFromIdx <= visibleToIdx) && colsToMove > 1) {
visibleToIdx -= colsToMove;
}
// It's necessary to lookup the ancestor grid of the grouped header b/c the header
// could be nested at any level.
toCt.getRootHeaderCt().grid.view.moveColumn(visibleFromIdx, visibleToIdx,
colsToMove);
}
// We need to always fire a columnmove event. Check for an .ownerCt first in case
// this is a grouped header.
fromCtRoot.fireEvent('columnmove', fromCt, dragHeader, visibleFromIdx, visibleToIdx);
fromCtRoot.isDDMoveInGrid = false;
// Group headers skrinkwrap their child headers.
// Therefore a child header may not flex; it must contribute a fixed width.
// But we restore the flex value when moving back into the main header container
//
// Note that we don't need to save the flex if coming from another group header
// b/c it couldn't have had one!
if (toCt.isGroupHeader && !fromCt.isGroupHeader) {
// Adjust the width of the "to" group header only if we dragged in
// from somewhere else. If not within the same container.
if (fromCt !== toCt) {
dragHeader.savedFlex = dragHeader.flex;
delete dragHeader.flex;
dragHeader.width = savedWidth;
}
}
else if (!fromCt.isGroupHeader) {
if (dragHeader.savedFlex) {
dragHeader.flex = dragHeader.savedFlex;
delete dragHeader.width;
}
}
Ext.resumeLayouts(true);
// The grid must lay out so that its headerCt lays out.
// It will not be thrown into the mix by BorderLayout#getLayoutItems
// if it's floated, so we have to force the issue.
if (me.headerCt.grid.floated) {
me.headerCt.grid.updateLayout();
}
// Ext.grid.header.Container will handle the removal of empty groups,
// don't handle it here.
}
}
});