Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / src / grid / header / DropZone.js
1 /**
2  * @class Ext.grid.header.DropZone
3  * @extends Ext.dd.DropZone
4  * @private
5  */
6 Ext.define('Ext.grid.header.DropZone', {
7     extend: 'Ext.dd.DropZone',
8     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
9     proxyOffsets: [-4, -9],
10
11     constructor: function(headerCt){
12         this.headerCt = headerCt;
13         this.ddGroup = this.getDDGroup();
14         this.callParent([headerCt.el]);
15     },
16
17     getDDGroup: function() {
18         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
19     },
20
21     getTargetFromEvent : function(e){
22         return e.getTarget('.' + this.colHeaderCls);
23     },
24
25     getTopIndicator: function() {
26         if (!this.topIndicator) {
27             this.topIndicator = Ext.core.DomHelper.append(Ext.getBody(), {
28                 cls: "col-move-top",
29                 html: " "
30             }, true);
31         }
32         return this.topIndicator;
33     },
34
35     getBottomIndicator: function() {
36         if (!this.bottomIndicator) {
37             this.bottomIndicator = Ext.core.DomHelper.append(Ext.getBody(), {
38                 cls: "col-move-bottom",
39                 html: " "
40             }, true);
41         }
42         return this.bottomIndicator;
43     },
44
45     getLocation: function(e, t) {
46         var x      = e.getXY()[0],
47             region = Ext.fly(t).getRegion(),
48             pos, header;
49
50         if ((region.right - x) <= (region.right - region.left) / 2) {
51             pos = "after";
52         } else {
53             pos = "before";
54         }
55         return {
56             pos: pos,
57             header: Ext.getCmp(t.id),
58             node: t
59         };
60     },
61
62     positionIndicator: function(draggedHeader, node, e){
63         var location = this.getLocation(e, node),
64             header = location.header,
65             pos    = location.pos,
66             nextHd = draggedHeader.nextSibling('gridcolumn:not([hidden])'),
67             prevHd = draggedHeader.previousSibling('gridcolumn:not([hidden])'),
68             region, topIndicator, bottomIndicator, topAnchor, bottomAnchor,
69             topXY, bottomXY, headerCtEl, minX, maxX;
70
71         // Cannot drag beyond non-draggable start column
72         if (!header.draggable && header.getIndex() == 0) {
73             return false;
74         }
75
76         this.lastLocation = location;
77
78         if ((draggedHeader !== header) &&
79             ((pos === "before" && nextHd !== header) ||
80             (pos === "after" && prevHd !== header)) &&
81             !header.isDescendantOf(draggedHeader)) {
82
83             // As we move in between different DropZones that are in the same
84             // group (such as the case when in a locked grid), invalidateDrop
85             // on the other dropZones.
86             var allDropZones = Ext.dd.DragDropManager.getRelated(this),
87                 ln = allDropZones.length,
88                 i  = 0,
89                 dropZone;
90
91             for (; i < ln; i++) {
92                 dropZone = allDropZones[i];
93                 if (dropZone !== this && dropZone.invalidateDrop) {
94                     dropZone.invalidateDrop();
95                 }
96             }
97
98
99             this.valid = true;
100             topIndicator = this.getTopIndicator();
101             bottomIndicator = this.getBottomIndicator();
102             if (pos === 'before') {
103                 topAnchor = 'tl';
104                 bottomAnchor = 'bl';
105             } else {
106                 topAnchor = 'tr';
107                 bottomAnchor = 'br';
108             }
109             topXY = header.el.getAnchorXY(topAnchor);
110             bottomXY = header.el.getAnchorXY(bottomAnchor);
111
112             // constrain the indicators to the viewable section
113             headerCtEl = this.headerCt.el;
114             minX = headerCtEl.getLeft();
115             maxX = headerCtEl.getRight();
116
117             topXY[0] = Ext.Number.constrain(topXY[0], minX, maxX);
118             bottomXY[0] = Ext.Number.constrain(bottomXY[0], minX, maxX);
119
120             // adjust by offsets, this is to center the arrows so that they point
121             // at the split point
122             topXY[0] -= 4;
123             topXY[1] -= 9;
124             bottomXY[0] -= 4;
125
126             // position and show indicators
127             topIndicator.setXY(topXY);
128             bottomIndicator.setXY(bottomXY);
129             topIndicator.show();
130             bottomIndicator.show();
131         // invalidate drop operation and hide indicators
132         } else {
133             this.invalidateDrop();
134         }
135     },
136
137     invalidateDrop: function() {
138         this.valid = false;
139         this.hideIndicators();
140     },
141
142     onNodeOver: function(node, dragZone, e, data) {
143         if (data.header.el.dom !== node) {
144             this.positionIndicator(data.header, node, e);
145         }
146         return this.valid ? this.dropAllowed : this.dropNotAllowed;
147     },
148
149     hideIndicators: function() {
150         this.getTopIndicator().hide();
151         this.getBottomIndicator().hide();
152     },
153
154     onNodeOut: function() {
155         this.hideIndicators();
156     },
157
158     onNodeDrop: function(node, dragZone, e, data) {
159         if (this.valid) {
160             this.invalidateDrop();
161             var hd = data.header,
162                 lastLocation = this.lastLocation,
163                 fromCt  = hd.ownerCt,
164                 fromIdx = fromCt.items.indexOf(hd), // Container.items is a MixedCollection
165                 toCt    = lastLocation.header.ownerCt,
166                 toIdx   = toCt.items.indexOf(lastLocation.header),
167                 headerCt = this.headerCt,
168                 groupCt,
169                 scrollerOwner;
170
171             if (lastLocation.pos === 'after') {
172                 toIdx++;
173             }
174
175             // If we are dragging in between two HeaderContainers that have had the lockable
176             // mixin injected we will lock/unlock headers in between sections. Note that lockable
177             // does NOT currently support grouped headers.
178             if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && toCt.lockedCt) {
179                 scrollerOwner = fromCt.up('[scrollerOwner]');
180                 scrollerOwner.lock(hd, toIdx);
181             } else if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && fromCt.lockedCt) {
182                 scrollerOwner = fromCt.up('[scrollerOwner]');
183                 scrollerOwner.unlock(hd, toIdx);
184             } else {
185                 // If dragging rightwards, then after removal, the insertion index will be one less when moving
186                 // in between the same container.
187                 if ((fromCt === toCt) && (toIdx > fromCt.items.indexOf(hd))) {
188                     toIdx--;
189                 }
190
191                 // Remove dragged header from where it was without destroying it or relaying its Container
192                 if (fromCt !== toCt) {
193                     fromCt.suspendLayout = true;
194                     fromCt.remove(hd, false);
195                     fromCt.suspendLayout = false;
196                 }
197
198                 // Dragged the last header out of the fromCt group... The fromCt group must die
199                 if (fromCt.isGroupHeader) {
200                     if (!fromCt.items.getCount()) {
201                         groupCt = fromCt.ownerCt;
202                         groupCt.suspendLayout = true;
203                         groupCt.remove(fromCt, false);
204                         fromCt.el.dom.parentNode.removeChild(fromCt.el.dom);
205                         groupCt.suspendLayout = false;
206                     } else {
207                         fromCt.minWidth = fromCt.getWidth() - hd.getWidth();
208                         fromCt.setWidth(fromCt.minWidth);
209                     }
210                 }
211
212                 // Move dragged header into its drop position
213                 toCt.suspendLayout = true;
214                 if (fromCt === toCt) {
215                     toCt.move(fromIdx, toIdx);
216                 } else {
217                     toCt.insert(toIdx, hd);
218                 }
219                 toCt.suspendLayout = false;
220
221                 // Group headers acquire the aggregate width of their child headers
222                 // Therefore a child header may not flex; it must contribute a fixed width.
223                 // But we restore the flex value when moving back into the main header container
224                 if (toCt.isGroupHeader) {
225                     hd.savedFlex = hd.flex;
226                     delete hd.flex;
227                     hd.width = hd.getWidth();
228                     // When there was previously a flex, we need to ensure we don't count for the
229                     // border twice.
230                     toCt.minWidth = toCt.getWidth() + hd.getWidth() - (hd.savedFlex ? 1 : 0);
231                     toCt.setWidth(toCt.minWidth);
232                 } else {
233                     if (hd.savedFlex) {
234                         hd.flex = hd.savedFlex;
235                         delete hd.width;
236                     }
237                 }
238
239
240                 // Refresh columns cache in case we remove an emptied group column
241                 headerCt.purgeCache();
242                 headerCt.doLayout();
243                 headerCt.onHeaderMoved(hd, fromIdx, toIdx);
244                 // Emptied group header can only be destroyed after the header and grid have been refreshed
245                 if (!fromCt.items.getCount()) {
246                     fromCt.destroy();
247                 }
248             }
249         }
250     }
251 });