tweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloud
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
341 lines
12 KiB
341 lines
12 KiB
/** |
|
* @private |
|
*/ |
|
Ext.define('Ext.tree.ViewDropZone', { |
|
extend: 'Ext.view.DropZone', |
|
|
|
/** |
|
* @cfg {Boolean} allowParentInserts |
|
* Allow inserting a dragged node between an expanded parent node and its first child that will become a |
|
* sibling of the parent when dropped. |
|
*/ |
|
allowParentInserts: false, |
|
|
|
/** |
|
* @cfg {Boolean} allowContainerDrops |
|
* True if drops on the tree container (outside of a specific tree node) are allowed. |
|
* |
|
* These are treated as appends to the root node. |
|
*/ |
|
allowContainerDrops: false, |
|
|
|
/** |
|
* @cfg {Boolean} appendOnly |
|
* True if the tree should only allow append drops (use for trees which are sorted). |
|
*/ |
|
appendOnly: false, |
|
|
|
/** |
|
* @cfg {Number} expandDelay |
|
* The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node |
|
* over the target. |
|
*/ |
|
expandDelay : 500, |
|
|
|
indicatorCls: Ext.baseCSSPrefix + 'tree-ddindicator', |
|
|
|
// @private |
|
expandNode : function(node) { |
|
var view = this.view; |
|
this.expandProcId = false; |
|
if (!node.isLeaf() && !node.isExpanded()) { |
|
view.expand(node); |
|
this.expandProcId = false; |
|
} |
|
}, |
|
|
|
// @private |
|
queueExpand : function(node) { |
|
this.expandProcId = Ext.Function.defer(this.expandNode, this.expandDelay, this, [node]); |
|
}, |
|
|
|
// @private |
|
cancelExpand : function() { |
|
if (this.expandProcId) { |
|
clearTimeout(this.expandProcId); |
|
this.expandProcId = false; |
|
} |
|
}, |
|
|
|
getPosition: function(e, node) { |
|
var view = this.view, |
|
record = view.getRecord(node), |
|
y = e.getY(), |
|
noAppend = record.isLeaf(), |
|
noBelow = false, |
|
region = Ext.fly(node).getRegion(), |
|
fragment; |
|
|
|
// If we are dragging on top of the root node of the tree, we always want to append. |
|
if (record.isRoot()) { |
|
return 'append'; |
|
} |
|
|
|
// Return 'append' if the node we are dragging on top of is not a leaf else return false. |
|
if (this.appendOnly) { |
|
return noAppend ? false : 'append'; |
|
} |
|
|
|
if (!this.allowParentInserts) { |
|
noBelow = record.hasChildNodes() && record.isExpanded(); |
|
} |
|
|
|
fragment = (region.bottom - region.top) / (noAppend ? 2 : 3); |
|
if (y >= region.top && y < (region.top + fragment)) { |
|
return 'before'; |
|
} |
|
else if (!noBelow && (noAppend || (y >= (region.bottom - fragment) && y <= region.bottom))) { |
|
return 'after'; |
|
} |
|
else { |
|
return 'append'; |
|
} |
|
}, |
|
|
|
isValidDropPoint : function(node, position, dragZone, e, data) { |
|
if (!node || !data.item) { |
|
return false; |
|
} |
|
|
|
var view = this.view, |
|
targetNode = view.getRecord(node), |
|
draggedRecords = data.records, |
|
dataLength = draggedRecords.length, |
|
ln = draggedRecords.length, |
|
i, record; |
|
|
|
// No drop position, or dragged records: invalid drop point |
|
if (!(targetNode && position && dataLength)) { |
|
return false; |
|
} |
|
|
|
// If the targetNode is within the folder we are dragging |
|
for (i = 0; i < ln; i++) { |
|
record = draggedRecords[i]; |
|
if (record.isNode && record.contains(targetNode)) { |
|
return false; |
|
} |
|
} |
|
|
|
// Respect the allowDrop field on Tree nodes |
|
if (position === 'append' && targetNode.get('allowDrop') === false) { |
|
return false; |
|
} |
|
else if (position !== 'append' && targetNode.parentNode.get('allowDrop') === false) { |
|
return false; |
|
} |
|
|
|
// If the target record is in the dragged dataset, then invalid drop |
|
if (Ext.Array.contains(draggedRecords, targetNode)) { |
|
return false; |
|
} |
|
return view.fireEvent('nodedragover', targetNode, position, data, e) !== false; |
|
}, |
|
|
|
onNodeOver : function(node, dragZone, e, data) { |
|
var position = this.getPosition(e, node), |
|
returnCls = this.dropNotAllowed, |
|
view = this.view, |
|
targetNode = view.getRecord(node), |
|
indicator = this.getIndicator(), |
|
indicatorY = 0; |
|
|
|
// auto node expand check |
|
this.cancelExpand(); |
|
if (position === 'append' && !this.expandProcId && !Ext.Array.contains(data.records, targetNode) && !targetNode.isLeaf() && !targetNode.isExpanded()) { |
|
this.queueExpand(targetNode); |
|
} |
|
|
|
|
|
if (this.isValidDropPoint(node, position, dragZone, e, data)) { |
|
this.valid = true; |
|
this.currentPosition = position; |
|
this.overRecord = targetNode; |
|
|
|
indicator.setWidth(Ext.fly(node).getWidth()); |
|
indicatorY = Ext.fly(node).getY() - Ext.fly(view.el).getY() - 1; |
|
|
|
// If view is scrolled using CSS translate, account for then when positioning the indicator |
|
if (view.touchScroll === 2) { |
|
indicatorY += view.getScrollY(); |
|
} |
|
|
|
/* |
|
* In the code below we show the proxy again. The reason for doing this is showing the indicator will |
|
* call toFront, causing it to get a new z-index which can sometimes push the proxy behind it. We always |
|
* want the proxy to be above, so calling show on the proxy will call toFront and bring it forward. |
|
*/ |
|
if (position === 'before') { |
|
returnCls = targetNode.isFirst() ? Ext.baseCSSPrefix + 'tree-drop-ok-above' : Ext.baseCSSPrefix + 'tree-drop-ok-between'; |
|
indicator.showAt(0, indicatorY); |
|
dragZone.proxy.show(); |
|
} else if (position === 'after') { |
|
returnCls = targetNode.isLast() ? Ext.baseCSSPrefix + 'tree-drop-ok-below' : Ext.baseCSSPrefix + 'tree-drop-ok-between'; |
|
indicatorY += Ext.fly(node).getHeight(); |
|
indicator.showAt(0, indicatorY); |
|
dragZone.proxy.show(); |
|
} else { |
|
returnCls = Ext.baseCSSPrefix + 'tree-drop-ok-append'; |
|
// @TODO: set a class on the parent folder node to be able to style it |
|
indicator.hide(); |
|
} |
|
} else { |
|
this.valid = false; |
|
} |
|
|
|
this.currentCls = returnCls; |
|
return returnCls; |
|
}, |
|
|
|
// The mouse is no longer over a tree node, so dropping is not valid |
|
onNodeOut : function(n, dd, e, data){ |
|
this.valid = false; |
|
this.getIndicator().hide(); |
|
}, |
|
|
|
onContainerOver : function(dd, e, data) { |
|
return this.allowContainerDrops ? this.dropAllowed : e.getTarget('.' + this.indicatorCls) ? this.currentCls : this.dropNotAllowed; |
|
}, |
|
|
|
// This will be called is allowContainerDrops is set. |
|
// The target node is the root |
|
onContainerDrop: function(dragZone, e, data) { |
|
if (this.allowContainerDrops) { |
|
this.valid = true; |
|
this.currentPosition = 'append'; |
|
this.overRecord = this.view.store.getRoot(); |
|
this.onNodeDrop(this.overRecord, dragZone, e, data); |
|
} |
|
}, |
|
|
|
notifyOut: function() { |
|
this.callParent(arguments); |
|
this.cancelExpand(); |
|
}, |
|
|
|
handleNodeDrop : function(data, targetNode, position) { |
|
var me = this, |
|
targetView = me.view, |
|
parentNode = targetNode ? targetNode.parentNode : targetView.panel.getRootNode(), |
|
Model = targetView.store.getModel(), |
|
records, i, len, record, |
|
insertionMethod, argList, |
|
needTargetExpand, |
|
transferData; |
|
|
|
// If the copy flag is set, create a copy of the models |
|
if (data.copy) { |
|
records = data.records; |
|
data.records = []; |
|
for (i = 0, len = records.length; i < len; i++) { |
|
record = records[i]; |
|
if (record.isNode) { |
|
data.records.push(record.copy()); |
|
} else { |
|
// If it's not a node, make a node copy |
|
data.records.push(new Model(Ext.apply({}, record.data))); |
|
} |
|
} |
|
} |
|
|
|
// Cancel any pending expand operation |
|
me.cancelExpand(); |
|
|
|
// Grab a reference to the correct node insertion method. |
|
// Create an arg list array intended for the apply method of the |
|
// chosen node insertion method. |
|
// Ensure the target object for the method is referenced by 'targetNode' |
|
if (position === 'before') { |
|
insertionMethod = parentNode.insertBefore; |
|
argList = [null, targetNode]; |
|
targetNode = parentNode; |
|
} |
|
else if (position === 'after') { |
|
if (targetNode.nextSibling) { |
|
insertionMethod = parentNode.insertBefore; |
|
argList = [null, targetNode.nextSibling]; |
|
} |
|
else { |
|
insertionMethod = parentNode.appendChild; |
|
argList = [null]; |
|
} |
|
targetNode = parentNode; |
|
} |
|
else { |
|
if (!(targetNode.isExpanded() || targetNode.isLoading())) { |
|
needTargetExpand = true; |
|
} |
|
insertionMethod = targetNode.appendChild; |
|
argList = [null]; |
|
} |
|
|
|
// A function to transfer the data into the destination tree |
|
transferData = function() { |
|
var color, |
|
n; |
|
|
|
// Coalesce layouts caused by node removal, appending and sorting |
|
Ext.suspendLayouts(); |
|
|
|
// Insert the records into the target node |
|
for (i = 0, len = data.records.length; i < len; i++) { |
|
record = data.records[i]; |
|
if (!record.isNode) { |
|
if (record.isModel) { |
|
record = new Model(record.data, record.getId()); |
|
} else { |
|
record = new Model(record); |
|
} |
|
data.records[i] = record; |
|
} |
|
argList[0] = record; |
|
insertionMethod.apply(targetNode, argList); |
|
|
|
// Focus the dropped node. |
|
targetView.getNavigationModel().setPosition(record); |
|
} |
|
|
|
// If configured to sort on drop, do it according to the TreeStore's comparator |
|
if (me.sortOnDrop) { |
|
targetNode.sort(targetNode.getOwnerTree().store.getSorters().sortFn); |
|
} |
|
|
|
Ext.resumeLayouts(true); |
|
|
|
// Kick off highlights after everything's been inserted, so they are |
|
// more in sync without insertion/render overhead. |
|
// Element.highlight can handle highlighting table nodes. |
|
if (Ext.enableFx && me.dropHighlight) { |
|
color = me.dropHighlightColor; |
|
|
|
for (i = 0; i < len; i++) { |
|
n = targetView.getNode(data.records[i]); |
|
if (n) { |
|
Ext.fly(n).highlight(color); |
|
} |
|
} |
|
} |
|
}; |
|
|
|
// If dropping right on an unexpanded node, transfer the data after it is expanded. |
|
if (needTargetExpand) { |
|
targetNode.expand(false, transferData); |
|
} |
|
// If the node is waiting for its children, we must transfer the data after the expansion. |
|
// The expand event does NOT signal UI expansion, it is the SIGNAL for UI expansion. |
|
// It's listened for by the NodeStore on the root node. Which means that listeners on the target |
|
// node get notified BEFORE UI expansion. So we need a delay. |
|
// TODO: Refactor NodeInterface.expand/collapse to notify its owning tree directly when it needs to expand/collapse. |
|
else if (targetNode.isLoading()) { |
|
targetNode.on({ |
|
expand: transferData, |
|
delay: 1, |
|
single: true |
|
}); |
|
} |
|
// Otherwise, call the data transfer function immediately |
|
else { |
|
transferData(); |
|
} |
|
} |
|
}); |