</script>
</head>
<body onload="prettyPrint(); highlight();">
- <pre class="prettyprint lang-js"><span id='Ext-grid-header-DropZone'>/**
-</span> * @class Ext.grid.header.DropZone
+ <pre class="prettyprint lang-js"><span id='Ext-view-DropZone'>/**
+</span> * @class Ext.view.DropZone
* @extends Ext.dd.DropZone
* @private
*/
-Ext.define('Ext.grid.header.DropZone', {
+Ext.define('Ext.view.DropZone', {
extend: 'Ext.dd.DropZone',
- colHeaderCls: Ext.baseCSSPrefix + 'column-header',
- proxyOffsets: [-4, -9],
- constructor: function(headerCt){
- this.headerCt = headerCt;
- this.ddGroup = this.getDDGroup();
- this.callParent([headerCt.el]);
- },
+ indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
+ indicatorCls: 'x-grid-drop-indicator',
+
+ 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;
+ }
- getDDGroup: function() {
- return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').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]);
},
- getTargetFromEvent : function(e){
- return e.getTarget('.' + this.colHeaderCls);
+// 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;
},
- getTopIndicator: function() {
- if (!this.topIndicator) {
- this.topIndicator = Ext.core.DomHelper.append(Ext.getBody(), {
- cls: "col-move-top",
- html: "&#160;"
- }, true);
+ 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.getPageY();
+ 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 this.topIndicator;
+ return node;
},
- getBottomIndicator: function() {
- if (!this.bottomIndicator) {
- this.bottomIndicator = Ext.core.DomHelper.append(Ext.getBody(), {
- cls: "col-move-bottom",
- html: "&#160;"
- }, true);
+ getIndicator: function() {
+ var me = this;
+
+ if (!me.indicator) {
+ me.indicator = Ext.createWidget('component', {
+ html: me.indicatorHtml,
+ cls: me.indicatorCls,
+ ownerCt: me.view,
+ floating: true,
+ shadow: false
+ });
}
- return this.bottomIndicator;
+ return me.indicator;
},
- getLocation: function(e, t) {
- var x = e.getXY()[0],
- region = Ext.fly(t).getRegion(),
- pos, header;
+ getPosition: function(e, node) {
+ var y = e.getXY()[1],
+ region = Ext.fly(node).getRegion(),
+ pos;
- if ((region.right - x) <= (region.right - region.left) / 2) {
- pos = "after";
- } else {
+ if ((region.bottom - y) >= (region.bottom - region.top) / 2) {
pos = "before";
+ } else {
+ pos = "after";
}
- return {
- pos: pos,
- header: Ext.getCmp(t.id),
- node: t
- };
+ return pos;
},
- positionIndicator: function(draggedHeader, node, e){
- var location = this.getLocation(e, node),
- header = location.header,
- pos = location.pos,
- nextHd = draggedHeader.nextSibling('gridcolumn:not([hidden])'),
- prevHd = draggedHeader.previousSibling('gridcolumn:not([hidden])'),
- region, topIndicator, bottomIndicator, topAnchor, bottomAnchor,
- topXY, bottomXY, headerCtEl, minX, maxX;
-
- // Cannot drag beyond non-draggable start column
- if (!header.draggable && header.getIndex() == 0) {
+<span id='Ext-view-DropZone-method-containsRecordAtOffset'> /**
+</span> * @private Determines whether the record at the specified offset from the passed record
+ * is in the drag payload.
+ * @param records
+ * @param record
+ * @param offset
+ * @returns {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;
- this.lastLocation = location;
-
- if ((draggedHeader !== header) &&
- ((pos === "before" && nextHd !== header) ||
- (pos === "after" && prevHd !== header)) &&
- !header.isDescendantOf(draggedHeader)) {
-
- // 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.
- var allDropZones = Ext.dd.DragDropManager.getRelated(this),
- ln = allDropZones.length,
- i = 0,
- dropZone;
+ return recordBefore && Ext.Array.contains(records, recordBefore);
+ },
- for (; i < ln; i++) {
- dropZone = allDropZones[i];
- if (dropZone !== this && dropZone.invalidateDrop) {
- dropZone.invalidateDrop();
+ positionIndicator: function(node, data, e) {
+ var me = this,
+ view = me.view,
+ pos = me.getPosition(e, node),
+ overRecord = view.getRecord(node),
+ draggingRecords = data.records,
+ indicator, indicatorY;
+
+ 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) {
+
+ indicatorY = Ext.fly(node).getY() - view.el.getY() - 1;
+ if (pos == 'after') {
+ indicatorY += Ext.fly(node).getHeight();
}
- }
-
+ me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, indicatorY);
- this.valid = true;
- topIndicator = this.getTopIndicator();
- bottomIndicator = this.getBottomIndicator();
- if (pos === 'before') {
- topAnchor = 'tl';
- bottomAnchor = 'bl';
- } else {
- topAnchor = 'tr';
- bottomAnchor = 'br';
+ // Cache the overRecord and the 'before' or 'after' indicator.
+ me.overRecord = overRecord;
+ me.currentPosition = pos;
}
- topXY = header.el.getAnchorXY(topAnchor);
- bottomXY = header.el.getAnchorXY(bottomAnchor);
-
- // constrain the indicators to the viewable section
- headerCtEl = this.headerCt.el;
- minX = headerCtEl.getLeft();
- maxX = headerCtEl.getRight();
-
- topXY[0] = Ext.Number.constrain(topXY[0], minX, maxX);
- bottomXY[0] = Ext.Number.constrain(bottomXY[0], minX, maxX);
-
- // adjust by offsets, this is to center the arrows so that they point
- // at the split point
- topXY[0] -= 4;
- topXY[1] -= 9;
- bottomXY[0] -= 4;
-
- // position and show indicators
- topIndicator.setXY(topXY);
- bottomIndicator.setXY(bottomXY);
- topIndicator.show();
- bottomIndicator.show();
- // invalidate drop operation and hide indicators
} else {
- this.invalidateDrop();
+ me.invalidateDrop();
}
},
invalidateDrop: function() {
- this.valid = false;
- this.hideIndicators();
+ if (this.valid) {
+ this.valid = false;
+ this.getIndicator().hide();
+ }
},
+ // The mouse is over a View node
onNodeOver: function(node, dragZone, e, data) {
- if (data.header.el.dom !== node) {
- this.positionIndicator(data.header, node, e);
+ var me = this;
+
+ if (!Ext.Array.contains(data.records, me.view.getRecord(node))) {
+ me.positionIndicator(node, data, e);
}
- return this.valid ? this.dropAllowed : this.dropNotAllowed;
- },
-
- hideIndicators: function() {
- this.getTopIndicator().hide();
- this.getBottomIndicator().hide();
+ return me.valid ? me.dropAllowed : me.dropNotAllowed;
},
- onNodeOut: function() {
- this.hideIndicators();
+ // Moved out of the DropZone without dropping.
+ // Remove drop position indicator
+ notifyOut: function(node, dragZone, e, data) {
+ var me = this;
+
+ me.callParent(arguments);
+ delete me.overRecord;
+ delete me.currentPosition;
+ if (me.indicator) {
+ me.indicator.hide();
+ }
},
- onNodeDrop: function(node, dragZone, e, data) {
- if (this.valid) {
- this.invalidateDrop();
- var hd = data.header,
- lastLocation = this.lastLocation,
- fromCt = hd.ownerCt,
- fromIdx = fromCt.items.indexOf(hd), // Container.items is a MixedCollection
- toCt = lastLocation.header.ownerCt,
- toIdx = toCt.items.indexOf(lastLocation.header),
- headerCt = this.headerCt,
- groupCt,
- scrollerOwner;
+ // 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.store.getCount();
- if (lastLocation.pos === 'after') {
- toIdx++;
- }
-
- // If we are dragging in between two HeaderContainers that have had the lockable
- // mixin injected we will lock/unlock headers in between sections. Note that lockable
- // does NOT currently support grouped headers.
- if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && toCt.lockedCt) {
- scrollerOwner = fromCt.up('[scrollerOwner]');
- scrollerOwner.lock(hd, toIdx);
- } else if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && fromCt.lockedCt) {
- scrollerOwner = fromCt.up('[scrollerOwner]');
- scrollerOwner.unlock(hd, toIdx);
- } else {
- // If dragging rightwards, then after removal, the insertion index will be one less when moving
- // in between the same container.
- if ((fromCt === toCt) && (toIdx > fromCt.items.indexOf(hd))) {
- toIdx--;
- }
-
- // Remove dragged header from where it was without destroying it or relaying its Container
- if (fromCt !== toCt) {
- fromCt.suspendLayout = true;
- fromCt.remove(hd, false);
- fromCt.suspendLayout = false;
- }
-
- // Dragged the last header out of the fromCt group... The fromCt group must die
- if (fromCt.isGroupHeader) {
- if (!fromCt.items.getCount()) {
- groupCt = fromCt.ownerCt;
- groupCt.suspendLayout = true;
- groupCt.remove(fromCt, false);
- fromCt.el.dom.parentNode.removeChild(fromCt.el.dom);
- groupCt.suspendLayout = false;
- } else {
- fromCt.minWidth = fromCt.getWidth() - hd.getWidth();
- fromCt.setWidth(fromCt.minWidth);
- }
- }
-
- // Move dragged header into its drop position
- toCt.suspendLayout = true;
- if (fromCt === toCt) {
- toCt.move(fromIdx, toIdx);
- } else {
- toCt.insert(toIdx, hd);
- }
- toCt.suspendLayout = false;
+ // There are records, so position after the last one
+ if (count) {
+ me.positionIndicator(view.getNode(count - 1), data, e);
+ }
- // Group headers acquire the aggregate width of 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
- if (toCt.isGroupHeader) {
- hd.savedFlex = hd.flex;
- delete hd.flex;
- hd.width = hd.getWidth();
- // When there was previously a flex, we need to ensure we don't count for the
- // border twice.
- toCt.minWidth = toCt.getWidth() + hd.getWidth() - (hd.savedFlex ? 1 : 0);
- toCt.setWidth(toCt.minWidth);
- } else {
- if (hd.savedFlex) {
- hd.flex = hd.savedFlex;
- delete hd.width;
- }
- }
+ // No records, position the indicator at the top
+ else {
+ delete me.overRecord;
+ delete me.currentPosition;
+ 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);
+ },
- // Refresh columns cache in case we remove an emptied group column
- headerCt.purgeCache();
- headerCt.doLayout();
- headerCt.onHeaderMoved(hd, fromIdx, toIdx);
- // Emptied group header can only be destroyed after the header and grid have been refreshed
- if (!fromCt.items.getCount()) {
- fromCt.destroy();
+ onNodeDrop: function(node, dragZone, e, data) {
+ var me = this,
+ dropped = false,
+
+ // Create a closure to perform the operation which the event handler may use.
+ // Users may now return <code>false</code> from 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 this function.
+ processDrop = function () {
+ me.invalidateDrop();
+ me.handleNodeDrop(data, me.overRecord, me.currentPosition);
+ dropped = true;
+ me.fireViewEvent('drop', node, data, me.overRecord, me.currentPosition);
+ },
+ performOperation = false;
+
+ if (me.valid) {
+ performOperation = me.fireViewEvent('beforedrop', node, data, me.overRecord, me.currentPosition, processDrop);
+ if (performOperation !== false) {
+ // If the processDrop function was called in the event handler, do not do it again.
+ if (!dropped) {
+ processDrop();
}
}
}
+ return performOperation;
}
});
</pre>