/** * @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; } 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(); }});