</script>
</head>
<body onload="prettyPrint(); highlight();">
- <pre class="prettyprint lang-js"><span id='Ext-view-DropZone'>/**
-</span> * @class Ext.view.DropZone
+ <pre class="prettyprint lang-js"><span id='Ext-grid-header-DropZone'>/**
+</span> * @class Ext.grid.header.DropZone
* @extends Ext.dd.DropZone
* @private
*/
-Ext.define('Ext.view.DropZone', {
+Ext.define('Ext.grid.header.DropZone', {
extend: 'Ext.dd.DropZone',
+ colHeaderCls: Ext.baseCSSPrefix + 'column-header',
+ proxyOffsets: [-4, -9],
- 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;
- }
+ constructor: function(headerCt){
+ this.headerCt = headerCt;
+ this.ddGroup = this.getDDGroup();
+ this.callParent([headerCt.el]);
+ },
- // 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]);
+ getDDGroup: function() {
+ return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
},
-// 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){
+ return e.getTarget('.' + this.colHeaderCls);
},
- 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;
- }
- }
+ getTopIndicator: function() {
+ if (!this.topIndicator) {
+ this.topIndicator = Ext.core.DomHelper.append(Ext.getBody(), {
+ cls: "col-move-top",
+ html: "&#160;"
+ }, true);
}
- return node;
+ return this.topIndicator;
},
- 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
- });
+ getBottomIndicator: function() {
+ if (!this.bottomIndicator) {
+ this.bottomIndicator = Ext.core.DomHelper.append(Ext.getBody(), {
+ cls: "col-move-bottom",
+ html: "&#160;"
+ }, true);
}
- return me.indicator;
+ return this.bottomIndicator;
},
- getPosition: function(e, node) {
- var y = e.getXY()[1],
- region = Ext.fly(node).getRegion(),
- pos;
+ getLocation: function(e, t) {
+ var x = e.getXY()[0],
+ region = Ext.fly(t).getRegion(),
+ pos, header;
- if ((region.bottom - y) >= (region.bottom - region.top) / 2) {
- pos = "before";
- } else {
+ if ((region.right - x) <= (region.right - region.left) / 2) {
pos = "after";
+ } else {
+ pos = "before";
}
- return pos;
+ return {
+ pos: pos,
+ header: Ext.getCmp(t.id),
+ node: t
+ };
},
-<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) {
+ 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) {
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);
- },
+ 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;
- 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();
+ for (; i < ln; i++) {
+ dropZone = allDropZones[i];
+ if (dropZone !== this && dropZone.invalidateDrop) {
+ dropZone.invalidateDrop();
}
- 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;
+ this.valid = true;
+ topIndicator = this.getTopIndicator();
+ bottomIndicator = this.getBottomIndicator();
+ if (pos === 'before') {
+ topAnchor = 'tl';
+ bottomAnchor = 'bl';
+ } else {
+ topAnchor = 'tr';
+ bottomAnchor = 'br';
}
+ 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 {
- me.invalidateDrop();
+ this.invalidateDrop();
}
},
invalidateDrop: function() {
- if (this.valid) {
- this.valid = false;
- this.getIndicator().hide();
- }
+ this.valid = false;
+ this.hideIndicators();
},
- // 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);
+ if (data.header.el.dom !== node) {
+ this.positionIndicator(data.header, node, e);
}
- return me.valid ? me.dropAllowed : me.dropNotAllowed;
+ return this.valid ? this.dropAllowed : this.dropNotAllowed;
},
- // 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();
- }
+ hideIndicators: function() {
+ this.getTopIndicator().hide();
+ this.getBottomIndicator().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.store.getCount();
+ onNodeOut: function() {
+ this.hideIndicators();
+ },
- // There are records, so position after the last one
- if (count) {
- me.positionIndicator(view.getNode(count - 1), data, e);
- }
+ 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;
- // 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;
- },
+ if (lastLocation.pos === 'after') {
+ toIdx++;
+ }
- onContainerDrop : function(dd, e, data) {
- return this.onNodeDrop(dd, null, e, data);
- },
+ // 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--;
+ }
- 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();
+ // 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;
+
+ // 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;
+ }
+ }
+
+
+ // 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();
}
}
}
- return performOperation;
}
});
</pre>