Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / grid / plugin / HeaderResizer.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.grid.plugin.HeaderResizer
17  * @extends Ext.util.Observable
18  * 
19  * Plugin to add header resizing functionality to a HeaderContainer.
20  * Always resizing header to the left of the splitter you are resizing.
21  * 
22  * Todo: Consider RTL support, columns would always calculate to the right of
23  *    the splitter instead of to the left.
24  */
25 Ext.define('Ext.grid.plugin.HeaderResizer', {
26     extend: 'Ext.util.Observable',
27     requires: ['Ext.dd.DragTracker', 'Ext.util.Region'],
28     alias: 'plugin.gridheaderresizer',
29     
30     disabled: false,
31
32     /**
33      * @cfg {Boolean} dynamic
34      * Set to true to resize on the fly rather than using a proxy marker. Defaults to false.
35      */
36     configs: {
37         dynamic: true
38     },
39
40     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
41
42     minColWidth: 40,
43     maxColWidth: 1000,
44     wResizeCursor: 'col-resize',
45     eResizeCursor: 'col-resize',
46     // not using w and e resize bc we are only ever resizing one
47     // column
48     //wResizeCursor: Ext.isWebKit ? 'w-resize' : 'col-resize',
49     //eResizeCursor: Ext.isWebKit ? 'e-resize' : 'col-resize',
50
51     init: function(headerCt) {
52         this.headerCt = headerCt;
53         headerCt.on('render', this.afterHeaderRender, this, {single: true});
54     },
55
56     /**
57      * @private
58      * AbstractComponent calls destroy on all its plugins at destroy time.
59      */
60     destroy: function() {
61         if (this.tracker) {
62             this.tracker.destroy();
63         }
64     },
65
66     afterHeaderRender: function() {
67         var headerCt = this.headerCt,
68             el = headerCt.el;
69
70         headerCt.mon(el, 'mousemove', this.onHeaderCtMouseMove, this);
71
72         this.tracker = Ext.create('Ext.dd.DragTracker', {
73             disabled: this.disabled,
74             onBeforeStart: Ext.Function.bind(this.onBeforeStart, this),
75             onStart: Ext.Function.bind(this.onStart, this),
76             onDrag: Ext.Function.bind(this.onDrag, this),
77             onEnd: Ext.Function.bind(this.onEnd, this),
78             tolerance: 3,
79             autoStart: 300,
80             el: el
81         });
82     },
83
84     // As we mouse over individual headers, change the cursor to indicate
85     // that resizing is available, and cache the resize target header for use
86     // if/when they mousedown.
87     onHeaderCtMouseMove: function(e, t) {
88         if (this.headerCt.dragging) {
89             if (this.activeHd) {
90                 this.activeHd.el.dom.style.cursor = '';
91                 delete this.activeHd;
92             }
93         } else {
94             var headerEl = e.getTarget('.' + this.colHeaderCls, 3, true),
95                 overHeader, resizeHeader;
96
97             if (headerEl){
98                 overHeader = Ext.getCmp(headerEl.id);
99
100                 // On left edge, we are resizing the previous non-hidden, base level column.
101                 if (overHeader.isOnLeftEdge(e)) {
102                     resizeHeader = overHeader.previousNode('gridcolumn:not([hidden]):not([isGroupHeader])');
103                 }
104                 // Else, if on the right edge, we're resizing the column we are over
105                 else if (overHeader.isOnRightEdge(e)) {
106                     resizeHeader = overHeader;
107                 }
108                 // Between the edges: we are not resizing
109                 else {
110                     resizeHeader = null;
111                 }
112
113                 // We *are* resizing
114                 if (resizeHeader) {
115                     // If we're attempting to resize a group header, that cannot be resized,
116                     // so find its last base level column header; Group headers are sized
117                     // by the size of their child headers.
118                     if (resizeHeader.isGroupHeader) {
119                         resizeHeader = resizeHeader.getVisibleGridColumns();
120                         resizeHeader = resizeHeader[resizeHeader.length - 1];
121                     }
122
123                     if (resizeHeader && !(resizeHeader.fixed || this.disabled)) {
124                         this.activeHd = resizeHeader;
125                         overHeader.el.dom.style.cursor = this.eResizeCursor;
126                     }
127                 // reset
128                 } else {
129                     overHeader.el.dom.style.cursor = '';
130                     delete this.activeHd;
131                 }
132             }
133         }
134     },
135
136     // only start when there is an activeHd
137     onBeforeStart : function(e){
138         var t = e.getTarget();
139         // cache the activeHd because it will be cleared.
140         this.dragHd = this.activeHd;
141
142         if (!!this.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger') && !this.headerCt.dragging) {
143             //this.headerCt.dragging = true;
144             this.tracker.constrainTo = this.getConstrainRegion();
145             return true;
146         } else {
147             this.headerCt.dragging = false;
148             return false;
149         }
150     },
151
152     // get the region to constrain to, takes into account max and min col widths
153     getConstrainRegion: function() {
154         var dragHdEl = this.dragHd.el,
155             region   = Ext.util.Region.getRegion(dragHdEl);
156
157         return region.adjust(
158             0,
159             this.maxColWidth - dragHdEl.getWidth(),
160             0,
161             this.minColWidth
162         );
163     },
164
165     // initialize the left and right hand side markers around
166     // the header that we are resizing
167     onStart: function(e){
168         var me       = this,
169             dragHd   = me.dragHd,
170             dragHdEl = dragHd.el,
171             width    = dragHdEl.getWidth(),
172             headerCt = me.headerCt,
173             t        = e.getTarget();
174
175         if (me.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger')) {
176             headerCt.dragging = true;
177         }
178
179         me.origWidth = width;
180
181         // setup marker proxies
182         if (!me.dynamic) {
183             var xy           = dragHdEl.getXY(),
184                 gridSection  = headerCt.up('[scrollerOwner]'),
185                 dragHct      = me.dragHd.up(':not([isGroupHeader])'),
186                 firstSection = dragHct.up(),
187                 lhsMarker    = gridSection.getLhsMarker(),
188                 rhsMarker    = gridSection.getRhsMarker(),
189                 el           = rhsMarker.parent(),
190                 offsetLeft   = el.getLeft(true),
191                 offsetTop    = el.getTop(true),
192                 topLeft      = el.translatePoints(xy),
193                 markerHeight = firstSection.body.getHeight() + headerCt.getHeight(),
194                 top = topLeft.top - offsetTop;
195
196             lhsMarker.setTop(top);
197             rhsMarker.setTop(top);
198             lhsMarker.setHeight(markerHeight);
199             rhsMarker.setHeight(markerHeight);
200             lhsMarker.setLeft(topLeft.left - offsetLeft);
201             rhsMarker.setLeft(topLeft.left + width - offsetLeft);
202         }
203     },
204
205     // synchronize the rhsMarker with the mouse movement
206     onDrag: function(e){
207         if (!this.dynamic) {
208             var xy          = this.tracker.getXY('point'),
209                 gridSection = this.headerCt.up('[scrollerOwner]'),
210                 rhsMarker   = gridSection.getRhsMarker(),
211                 el          = rhsMarker.parent(),
212                 topLeft     = el.translatePoints(xy),
213                 offsetLeft  = el.getLeft(true);
214
215             rhsMarker.setLeft(topLeft.left - offsetLeft);
216         // Resize as user interacts
217         } else {
218             this.doResize();
219         }
220     },
221
222     onEnd: function(e){
223         this.headerCt.dragging = false;
224         if (this.dragHd) {
225             if (!this.dynamic) {
226                 var dragHd      = this.dragHd,
227                     gridSection = this.headerCt.up('[scrollerOwner]'),
228                     lhsMarker   = gridSection.getLhsMarker(),
229                     rhsMarker   = gridSection.getRhsMarker(),
230                     currWidth   = dragHd.getWidth(),
231                     offset      = this.tracker.getOffset('point'),
232                     offscreen   = -9999;
233
234                 // hide markers
235                 lhsMarker.setLeft(offscreen);
236                 rhsMarker.setLeft(offscreen);
237             }
238             this.doResize();
239         }
240     },
241
242     doResize: function() {
243         if (this.dragHd) {
244             var dragHd = this.dragHd,
245                 nextHd,
246                 offset = this.tracker.getOffset('point');
247
248             // resize the dragHd
249             if (dragHd.flex) {
250                 delete dragHd.flex;
251             }
252
253             // If HeaderContainer is configured forceFit, inhibit upstream layout notification, so that
254             // we can also shrink the following Header by an equal amount, and *then* inform the upstream layout.
255             if (this.headerCt.forceFit) {
256                 nextHd = dragHd.nextNode('gridcolumn:not([hidden]):not([isGroupHeader])');
257                 if (nextHd) {
258                     this.headerCt.componentLayout.layoutBusy = true;
259                 }
260             }
261
262             // Non-flexed Headers may never be squeezed in the event of a shortfall so
263             // always set the minWidth to their current width.
264             dragHd.minWidth = this.origWidth + offset[0];
265             dragHd.setWidth(dragHd.minWidth);
266
267             // In the case of forceFit, change the following Header width.
268             // Then apply the two width changes by laying out the owning HeaderContainer
269             if (nextHd) {
270                 delete nextHd.flex;
271                 nextHd.setWidth(nextHd.getWidth() - offset[0]);
272                 this.headerCt.componentLayout.layoutBusy = false;
273                 this.headerCt.doComponentLayout();
274             }
275         }
276     },
277     
278     disable: function() {
279         this.disabled = true;
280         if (this.tracker) {
281             this.tracker.disable();
282         }
283     },
284     
285     enable: function() {
286         this.disabled = false;
287         if (this.tracker) {
288             this.tracker.enable();
289         }
290     }
291 });