X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/tree/ViewDropZone.js diff --git a/src/tree/ViewDropZone.js b/src/tree/ViewDropZone.js new file mode 100644 index 00000000..e54b21cd --- /dev/null +++ b/src/tree/ViewDropZone.js @@ -0,0 +1,278 @@ +/** + * @class Ext.tree.ViewDropZone + * @extends Ext.view.DropZone + * @private + */ +Ext.define('Ext.tree.ViewDropZone', { + extend: 'Ext.view.DropZone', + + /** + * @cfg {Boolean} allowParentInsert + * Allow inserting a dragged node between an expanded parent node and its first child that will become a + * sibling of the parent when dropped (defaults to false) + */ + allowParentInserts: false, + + /** + * @cfg {String} allowContainerDrop + * True if drops on the tree container (outside of a specific tree node) are allowed (defaults to false) + */ + allowContainerDrops: false, + + /** + * @cfg {String} appendOnly + * True if the tree should only allow append drops (use for trees which are sorted, defaults to false) + */ + appendOnly: false, + + /** + * @cfg {String} expandDelay + * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node + * over the target (defaults to 500) + */ + expandDelay : 500, + + indicatorCls: 'x-tree-ddindicator', + + // private + expandNode : function(node) { + var view = this.view; + 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.getPageY(), + 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.allowParentInsert) { + 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; + } + + // @TODO: fire some event to notify that there is a valid drop possible for the node you're dragging + // Yes: this.fireViewEvent(blah....) fires an event through the owning View. + return true; + }, + + 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(), + indicatorX = 0, + 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 (position == 'before') { + returnCls = targetNode.isFirst() ? Ext.baseCSSPrefix + 'tree-drop-ok-above' : Ext.baseCSSPrefix + 'tree-drop-ok-between'; + indicator.showAt(0, indicatorY); + indicator.toFront(); + } + 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); + indicator.toFront(); + } + 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; + }, + + onContainerOver : function(dd, e, data) { + return e.getTarget('.' + this.indicatorCls) ? this.currentCls : this.dropNotAllowed; + }, + + notifyOut: function() { + this.callParent(arguments); + this.cancelExpand(); + }, + + handleNodeDrop : function(data, targetNode, position) { + var me = this, + view = me.view, + parentNode = targetNode.parentNode, + store = view.getStore(), + recordDomNodes = [], + records, i, len, + insertionMethod, argList, + needTargetExpand, + transferData, + processDrop; + + // If the copy flag is set, create a copy of the Models with the same IDs + if (data.copy) { + records = data.records; + data.records = []; + for (i = 0, len = records.length; i < len; i++) { + data.records.push(Ext.apply({}, records[i].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()) { + needTargetExpand = true; + } + insertionMethod = targetNode.appendChild; + argList = [null]; + } + + // A function to transfer the data into the destination tree + transferData = function() { + var node; + for (i = 0, len = data.records.length; i < len; i++) { + argList[0] = data.records[i]; + node = insertionMethod.apply(targetNode, argList); + + if (Ext.enableFx && me.dropHighlight) { + recordDomNodes.push(view.getNode(node)); + } + } + + // Kick off highlights after everything's been inserted, so they are + // more in sync without insertion/render overhead. + if (Ext.enableFx && me.dropHighlight) { + //FIXME: the check for n.firstChild is not a great solution here. Ideally the line should simply read + //Ext.fly(n.firstChild) but this yields errors in IE6 and 7. See ticket EXTJSIV-1705 for more details + Ext.Array.forEach(recordDomNodes, function(n) { + Ext.fly(n.firstChild ? n.firstChild : n).highlight(me.dropHighlightColor); + }); + } + }; + + // If dropping right on an unexpanded node, transfer the data after it is expanded. + if (needTargetExpand) { + targetNode.expand(false, transferData); + } + // Otherwise, call the data transfer function immediately + else { + transferData(); + } + } +}); \ No newline at end of file