/**
* @private
*/
Ext.define('Ext.view.DropZone', {
extend: 'Ext.dd.DropZone',
indicatorCls: Ext.baseCSSPrefix + 'grid-drop-indicator',
indicatorHtml: [
'<div class="', Ext.baseCSSPrefix, 'grid-drop-indicator-left" role="presentation"></div>',
'<div class="' + Ext.baseCSSPrefix + 'grid-drop-indicator-right" role="presentation"></div>'
].join(''),
constructor: function(config) {
var me = this;
Ext.apply(me, config);
// Create a ddGroup unless one has been configured.
// User configuration of ddGroups allows users to specify which
// DD instances can interact with each other. Using one
// based on the id of the View would isolate it and mean it can only
// interact with a DragZone on the same View also using a generated ID.
if (!me.ddGroup) {
me.ddGroup = 'view-dd-zone-' + me.view.id;
}
// The DropZone's encapsulating element is the View's main element.
// It must be this because drop gestures may require scrolling on hover near a scrolling
// boundary. In Ext 4.x two DD instances may not use the same element, so a DragZone
// on this same View must use the View's parent element as its element.
me.callParent([me.view.el]);
},
// Fire an event through the client DataView. Lock this DropZone during the event processing
// so that its data does not become corrupted by processing mouse events.
fireViewEvent: function() {
var me = this,
result;
me.lock();
result = me.view.fireEvent.apply(me.view, arguments);
me.unlock();
return result;
},
getTargetFromEvent: function(e) {
var node = e.getTarget(this.view.getItemSelector()),
mouseY, nodeList, testNode, i, len, box;
// Not over a row node: The content may be narrower than the View's encapsulating element,
// so return the closest. If we fall through because the mouse is below the nodes
// (or there are no nodes), we'll get an onContainerOver call.
if (!node) {
mouseY = e.getY();
for (i = 0, nodeList = this.view.getNodes(), len = nodeList.length; i < len; i++) {
testNode = nodeList[i];
box = Ext.fly(testNode).getBox();
if (mouseY <= box.bottom) {
return testNode;
}
}
}
return node;
},
getIndicator: function() {
var me = this;
if (!me.indicator) {
me.indicator = new Ext.Component({
ariaRole: 'presentation',
html: me.indicatorHtml,
cls: me.indicatorCls,
ownerCt: me.view,
floating: true,
alignOnScroll: false,
shadow: false
});
}
return me.indicator;
},
getPosition: function(e, node) {
var y = e.getXY()[1],
region = Ext.fly(node).getRegion(),
pos;
if ((region.bottom - y) >= (region.bottom - region.top) / 2) {
pos = "before";
}
else {
pos = "after";
}
return pos;
},
/**
* @private
* Determines whether the record at the specified offset from the passed record
* is in the drag payload.
* @param records
* @param record
* @param offset
* @return {Boolean} True if the targeted record is in the drag payload
*/
containsRecordAtOffset: function(records, record, offset) {
if (!record) {
return false;
}
// eslint-disable-next-line vars-on-top
var view = this.view,
recordIndex = view.indexOf(record),
nodeBefore = view.getNode(recordIndex + offset),
recordBefore = nodeBefore ? view.getRecord(nodeBefore) : null;
return recordBefore && Ext.Array.contains(records, recordBefore);
},
positionIndicator: function(node, data, e) {
var me = this,
view = me.view,
pos = me.getPosition(e, node),
overRecord = view.getRecord(node),
draggingRecords = data.records,
indicatorY, scrollable, scrollableEl, container, containerY;
if (!Ext.Array.contains(draggingRecords, overRecord) && (
pos === 'before' && !me.containsRecordAtOffset(draggingRecords, overRecord, -1) ||
pos === 'after' && !me.containsRecordAtOffset(draggingRecords, overRecord, 1))) {
me.valid = true;
if (me.overRecord !== overRecord || me.currentPosition !== pos) {
scrollable = me.view.getScrollable();
scrollableEl = scrollable && scrollable.getElement();
container = (scrollableEl && scrollableEl.isScrollable())
? scrollableEl
: Ext.fly(view.getNodeContainer());
containerY = container.getY();
indicatorY = Ext.fly(node).getY() - containerY - 1;
if (pos === 'after') {
indicatorY += Ext.fly(node).getHeight();
}
me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, indicatorY);
// Cache the overRecord and the 'before' or 'after' indicator.
me.overRecord = overRecord;
me.currentPosition = pos;
}
}
else {
me.invalidateDrop();
}
},
invalidateDrop: function() {
if (this.valid) {
this.valid = false;
this.getIndicator().hide();
}
},
// The mouse is over a View node
onNodeOver: function(node, dragZone, e, data) {
var me = this;
if (!Ext.Array.contains(data.records, me.view.getRecord(node))) {
me.positionIndicator(node, data, e);
}
return me.valid ? me.dropAllowed : me.dropNotAllowed;
},
// Moved out of the DropZone without dropping.
// Remove drop position indicator
notifyOut: function(node, dragZone, e, data) {
var me = this;
me.callParent([node, dragZone, e, data]);
me.overRecord = me.currentPosition = null;
me.valid = false;
if (me.indicator) {
me.indicator.hide();
}
},
// The mouse is past the end of all nodes (or there are no nodes)
onContainerOver: function(dd, e, data) {
var me = this,
view = me.view,
count = view.dataSource.getCount();
// There are records, so position after the last one
if (count) {
me.positionIndicator(view.all.last(), data, e);
}
// No records, position the indicator at the top
else {
me.overRecord = me.currentPosition = null;
me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, 0);
me.valid = true;
}
return me.dropAllowed;
},
onContainerDrop: function(dd, e, data) {
return this.onNodeDrop(dd, null, e, data);
},
onNodeDrop: function(targetNode, dragZone, e, data) {
var me = this,
dropHandled = false,
overRecord = me.overRecord,
currentPosition = me.currentPosition,
// Create a closure to perform the operation which the event handler may use.
// Users may now set the wait parameter in the beforedrop handler, and perform any kind
// of asynchronous processing such as an Ext.Msg.confirm, or an Ajax request,
// and complete the drop gesture at some point in the future by calling either the
// processDrop or cancelDrop methods.
dropHandlers = {
wait: false,
processDrop: function() {
me.invalidateDrop();
me.handleNodeDrop(data, overRecord, currentPosition);
dropHandled = true;
me.fireViewEvent('drop', targetNode, data, overRecord, currentPosition);
},
cancelDrop: function() {
me.invalidateDrop();
dropHandled = true;
}
},
performOperation = false;
if (me.valid) {
performOperation = me.fireViewEvent(
'beforedrop', targetNode, data, overRecord, currentPosition, dropHandlers
);
if (dropHandlers.wait) {
return;
}
if (performOperation !== false) {
// If either of the drop handlers were called in the event handler,
// do not do it again.
if (!dropHandled) {
dropHandlers.processDrop();
}
}
}
return performOperation;
},
destroy: function() {
this.indicator = Ext.destroy(this.indicator);
this.callParent();
}
});