Upgrade to ExtJS 4.0.7 - Released 10/19/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 Ext.define('Ext.grid.plugin.HeaderResizer', {
23     extend: 'Ext.util.Observable',
24     requires: ['Ext.dd.DragTracker', 'Ext.util.Region'],
25     alias: 'plugin.gridheaderresizer',
26
27     disabled: false,
28
29     /**
30      * @cfg {Boolean} dynamic
31      * Set to true to resize on the fly rather than using a proxy marker. Defaults to false.
32      */
33     configs: {
34         dynamic: true
35     },
36
37     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
38
39     minColWidth: 40,
40     maxColWidth: 1000,
41     wResizeCursor: 'col-resize',
42     eResizeCursor: 'col-resize',
43     // not using w and e resize bc we are only ever resizing one
44     // column
45     //wResizeCursor: Ext.isWebKit ? 'w-resize' : 'col-resize',
46     //eResizeCursor: Ext.isWebKit ? 'e-resize' : 'col-resize',
47
48     init: function(headerCt) {
49         this.headerCt = headerCt;
50         headerCt.on('render', this.afterHeaderRender, this, {single: true});
51     },
52
53     /**
54      * @private
55      * AbstractComponent calls destroy on all its plugins at destroy time.
56      */
57     destroy: function() {
58         if (this.tracker) {
59             this.tracker.destroy();
60         }
61     },
62
63     afterHeaderRender: function() {
64         var headerCt = this.headerCt,
65             el = headerCt.el;
66
67         headerCt.mon(el, 'mousemove', this.onHeaderCtMouseMove, this);
68
69         this.tracker = Ext.create('Ext.dd.DragTracker', {
70             disabled: this.disabled,
71             onBeforeStart: Ext.Function.bind(this.onBeforeStart, this),
72             onStart: Ext.Function.bind(this.onStart, this),
73             onDrag: Ext.Function.bind(this.onDrag, this),
74             onEnd: Ext.Function.bind(this.onEnd, this),
75             tolerance: 3,
76             autoStart: 300,
77             el: el
78         });
79     },
80
81     // As we mouse over individual headers, change the cursor to indicate
82     // that resizing is available, and cache the resize target header for use
83     // if/when they mousedown.
84     onHeaderCtMouseMove: function(e, t) {
85         if (this.headerCt.dragging) {
86             if (this.activeHd) {
87                 this.activeHd.el.dom.style.cursor = '';
88                 delete this.activeHd;
89             }
90         } else {
91             var headerEl = e.getTarget('.' + this.colHeaderCls, 3, true),
92                 overHeader, resizeHeader;
93
94             if (headerEl){
95                 overHeader = Ext.getCmp(headerEl.id);
96
97                 // On left edge, go back to the previous non-hidden header.
98                 if (overHeader.isOnLeftEdge(e)) {
99                     resizeHeader = overHeader.previousNode('gridcolumn:not([hidden])');
100
101                 }
102                 // Else, if on the right edge, we're resizing the column we are over
103                 else if (overHeader.isOnRightEdge(e)) {
104                     resizeHeader = overHeader;
105                 }
106                 // Between the edges: we are not resizing
107                 else {
108                     resizeHeader = null;
109                 }
110
111                 // We *are* resizing
112                 if (resizeHeader) {
113                     // If we're attempting to resize a group header, that cannot be resized,
114                     // so find its last visible leaf header; Group headers are sized
115                     // by the size of their child headers.
116                     if (resizeHeader.isGroupHeader) {
117                         resizeHeader = resizeHeader.down(':not([isGroupHeader]):not([hidden]):last');
118                     }
119
120                     // Check if the header is resizable. Continue checking the old "fixed" property, bug also
121                     // check whether the resizablwe property is set to false.
122                     if (resizeHeader && !(resizeHeader.fixed || (resizeHeader.resizable === false) || this.disabled)) {
123                         this.activeHd = resizeHeader;
124                         overHeader.el.dom.style.cursor = this.eResizeCursor;
125                     }
126                 // reset
127                 } else {
128                     overHeader.el.dom.style.cursor = '';
129                     delete this.activeHd;
130                 }
131             }
132         }
133     },
134
135     // only start when there is an activeHd
136     onBeforeStart : function(e){
137         var t = e.getTarget();
138         // cache the activeHd because it will be cleared.
139         this.dragHd = this.activeHd;
140
141         if (!!this.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger') && !this.headerCt.dragging) {
142             //this.headerCt.dragging = true;
143             this.tracker.constrainTo = this.getConstrainRegion();
144             return true;
145         } else {
146             this.headerCt.dragging = false;
147             return false;
148         }
149     },
150
151     // get the region to constrain to, takes into account max and min col widths
152     getConstrainRegion: function() {
153         var dragHdEl = this.dragHd.el,
154             region   = Ext.util.Region.getRegion(dragHdEl);
155
156         return region.adjust(
157             0,
158             this.maxColWidth - dragHdEl.getWidth(),
159             0,
160             this.minColWidth
161         );
162     },
163
164     // initialize the left and right hand side markers around
165     // the header that we are resizing
166     onStart: function(e){
167         var me       = this,
168             dragHd   = me.dragHd,
169             dragHdEl = dragHd.el,
170             width    = dragHdEl.getWidth(),
171             headerCt = me.headerCt,
172             t        = e.getTarget();
173
174         if (me.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger')) {
175             headerCt.dragging = true;
176         }
177
178         me.origWidth = width;
179
180         // setup marker proxies
181         if (!me.dynamic) {
182             var xy           = dragHdEl.getXY(),
183                 gridSection  = headerCt.up('[scrollerOwner]'),
184                 dragHct      = me.dragHd.up(':not([isGroupHeader])'),
185                 firstSection = dragHct.up(),
186                 lhsMarker    = gridSection.getLhsMarker(),
187                 rhsMarker    = gridSection.getRhsMarker(),
188                 el           = rhsMarker.parent(),
189                 offsetLeft   = el.getLeft(true),
190                 offsetTop    = el.getTop(true),
191                 topLeft      = el.translatePoints(xy),
192                 markerHeight = firstSection.body.getHeight() + headerCt.getHeight(),
193                 top = topLeft.top - offsetTop;
194
195             lhsMarker.setTop(top);
196             rhsMarker.setTop(top);
197             lhsMarker.setHeight(markerHeight);
198             rhsMarker.setHeight(markerHeight);
199             lhsMarker.setLeft(topLeft.left - offsetLeft);
200             rhsMarker.setLeft(topLeft.left + width - offsetLeft);
201         }
202     },
203
204     // synchronize the rhsMarker with the mouse movement
205     onDrag: function(e){
206         if (!this.dynamic) {
207             var xy          = this.tracker.getXY('point'),
208                 gridSection = this.headerCt.up('[scrollerOwner]'),
209                 rhsMarker   = gridSection.getRhsMarker(),
210                 el          = rhsMarker.parent(),
211                 topLeft     = el.translatePoints(xy),
212                 offsetLeft  = el.getLeft(true);
213
214             rhsMarker.setLeft(topLeft.left - offsetLeft);
215         // Resize as user interacts
216         } else {
217             this.doResize();
218         }
219     },
220
221     onEnd: function(e){
222         this.headerCt.dragging = false;
223         if (this.dragHd) {
224             if (!this.dynamic) {
225                 var dragHd      = this.dragHd,
226                     gridSection = this.headerCt.up('[scrollerOwner]'),
227                     lhsMarker   = gridSection.getLhsMarker(),
228                     rhsMarker   = gridSection.getRhsMarker(),
229                     currWidth   = dragHd.getWidth(),
230                     offset      = this.tracker.getOffset('point'),
231                     offscreen   = -9999;
232
233                 // hide markers
234                 lhsMarker.setLeft(offscreen);
235                 rhsMarker.setLeft(offscreen);
236             }
237             this.doResize();
238         }
239     },
240
241     doResize: function() {
242         if (this.dragHd) {
243             var dragHd = this.dragHd,
244                 nextHd,
245                 offset = this.tracker.getOffset('point');
246
247             // resize the dragHd
248             if (dragHd.flex) {
249                 delete dragHd.flex;
250             }
251
252             this.headerCt.suspendLayout = true;
253             dragHd.setWidth(this.origWidth + offset[0], false);
254
255             // In the case of forceFit, change the following Header width.
256             // Then apply the two width changes by laying out the owning HeaderContainer
257             // If HeaderContainer is configured forceFit, inhibit upstream layout notification, so that
258             // we can also shrink the following Header by an equal amount, and *then* inform the upstream layout.
259             if (this.headerCt.forceFit) {
260                 nextHd = dragHd.nextNode('gridcolumn:not([hidden]):not([isGroupHeader])');
261                 if (nextHd) {
262                     delete nextHd.flex;
263                     nextHd.setWidth(nextHd.getWidth() - offset[0], false);
264                 }
265             }
266             this.headerCt.suspendLayout = false;
267             this.headerCt.doComponentLayout(this.headerCt.getFullWidth());
268         }
269     },
270
271     disable: function() {
272         this.disabled = true;
273         if (this.tracker) {
274             this.tracker.disable();
275         }
276     },
277
278     enable: function() {
279         this.disabled = false;
280         if (this.tracker) {
281             this.tracker.enable();
282         }
283     }
284 });