Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / grid / Scroller.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  * Docked in an Ext.grid.Panel, controls virtualized scrolling and synchronization
17  * across different sections.
18  */
19 Ext.define('Ext.grid.Scroller', {
20     extend: 'Ext.Component',
21     alias: 'widget.gridscroller',
22     weight: 110,
23     baseCls: Ext.baseCSSPrefix + 'scroller',
24     focusable: false,
25     reservedSpace: 0,
26
27     renderTpl: [
28         '<div class="' + Ext.baseCSSPrefix + 'scroller-ct" id="{baseId}_ct">',
29             '<div class="' + Ext.baseCSSPrefix + 'stretcher" id="{baseId}_stretch"></div>',
30         '</div>'
31     ],
32
33     initComponent: function() {
34         var me       = this,
35             dock     = me.dock,
36             cls      = Ext.baseCSSPrefix + 'scroller-vertical';
37
38         me.offsets = {bottom: 0};
39         me.scrollProp = 'scrollTop';
40         me.vertical = true;
41         me.sizeProp = 'width';
42
43         if (dock === 'top' || dock === 'bottom') {
44             cls = Ext.baseCSSPrefix + 'scroller-horizontal';
45             me.sizeProp = 'height';
46             me.scrollProp = 'scrollLeft';
47             me.vertical = false;
48             me.weight += 5;
49         }
50
51         me.cls += (' ' + cls);
52
53         Ext.applyIf(me.renderSelectors, {
54             stretchEl: '.' + Ext.baseCSSPrefix + 'stretcher',
55             scrollEl: '.' + Ext.baseCSSPrefix + 'scroller-ct'
56         });
57         me.callParent();
58     },
59     
60     ensureDimension: function(){
61         var me = this,
62             sizeProp = me.sizeProp;
63             
64         me[sizeProp] = me.scrollerSize = Ext.getScrollbarSize()[sizeProp];  
65     },
66
67     initRenderData: function () {
68         var me = this,
69             ret = me.callParent(arguments) || {};
70
71         ret.baseId = me.id;
72
73         return ret;
74     },
75
76     afterRender: function() {
77         var me = this;
78         me.callParent();
79         
80         me.mon(me.scrollEl, 'scroll', me.onElScroll, me);
81         Ext.cache[me.el.id].skipGarbageCollection = true;
82     },
83
84     onAdded: function(container) {
85         // Capture the controlling grid Panel so that we can use it even when we are undocked, and don't have an ownerCt
86         this.ownerGrid = container;
87         this.callParent(arguments);
88     },
89
90     getSizeCalculation: function() {
91         var me     = this,
92             owner  = me.getPanel(),
93             width  = 1,
94             height = 1,
95             view, tbl;
96
97         if (!me.vertical) {
98             // TODO: Must gravitate to a single region..
99             // Horizontal scrolling only scrolls virtualized region
100             var items  = owner.query('tableview'),
101                 center = items[1] || items[0];
102
103             if (!center) {
104                 return false;
105             }
106             // center is not guaranteed to have content, such as when there
107             // are zero rows in the grid/tree. We read the width from the
108             // headerCt instead.
109             width = center.headerCt.getFullWidth();
110
111             if (Ext.isIEQuirks) {
112                 width--;
113             }
114         } else {
115             view = owner.down('tableview:not([lockableInjected])');
116             if (!view || !view.el) {
117                 return false;
118             }
119             tbl = view.el.child('table', true);
120             if (!tbl) {
121                 return false;
122             }
123
124             // needs to also account for header and scroller (if still in picture)
125             // should calculate from headerCt.
126             height = tbl.offsetHeight;
127         }
128         if (isNaN(width)) {
129             width = 1;
130         }
131         if (isNaN(height)) {
132             height = 1;
133         }
134         return {
135             width: width,
136             height: height
137         };
138     },
139
140     invalidate: function(firstPass) {
141         var me = this,
142             stretchEl = me.stretchEl;
143
144         if (!stretchEl || !me.ownerCt) {
145             return;
146         }
147
148         var size  = me.getSizeCalculation(),
149             scrollEl = me.scrollEl,
150             elDom = scrollEl.dom,
151             reservedSpace = me.reservedSpace,
152             pos,
153             extra = 5;
154
155         if (size) {
156             stretchEl.setSize(size);
157
158             size = me.el.getSize(true);
159
160             if (me.vertical) {
161                 size.width += extra;
162                 size.height -= reservedSpace;
163                 pos = 'left';
164             } else {
165                 size.width -= reservedSpace;
166                 size.height += extra;
167                 pos = 'top';
168             }
169
170             scrollEl.setSize(size);
171             elDom.style[pos] = (-extra) + 'px';
172
173             // BrowserBug: IE7
174             // This makes the scroller enabled, when initially rendering.
175             elDom.scrollTop = elDom.scrollTop;
176         }
177     },
178
179     afterComponentLayout: function() {
180         this.callParent(arguments);
181         this.invalidate();
182     },
183
184     restoreScrollPos: function () {
185         var me = this,
186             el = this.scrollEl,
187             elDom = el && el.dom;
188
189         if (me._scrollPos !== null && elDom) {
190             elDom[me.scrollProp] = me._scrollPos;
191             me._scrollPos = null;
192         }
193     },
194
195     setReservedSpace: function (reservedSpace) {
196         var me = this;
197         if (me.reservedSpace !== reservedSpace) {
198             me.reservedSpace = reservedSpace;
199             me.invalidate();
200         }
201     },
202
203     saveScrollPos: function () {
204         var me = this,
205             el = this.scrollEl,
206             elDom = el && el.dom;
207
208         me._scrollPos = elDom ? elDom[me.scrollProp] : null;
209     },
210
211     /**
212      * Sets the scrollTop and constrains the value between 0 and max.
213      * @param {Number} scrollTop
214      * @return {Number} The resulting scrollTop value after being constrained
215      */
216     setScrollTop: function(scrollTop) {
217         var el = this.scrollEl,
218             elDom = el && el.dom;
219
220         if (elDom) {
221             return elDom.scrollTop = Ext.Number.constrain(scrollTop, 0, elDom.scrollHeight - elDom.clientHeight);
222         }
223     },
224
225     /**
226      * Sets the scrollLeft and constrains the value between 0 and max.
227      * @param {Number} scrollLeft
228      * @return {Number} The resulting scrollLeft value after being constrained
229      */
230     setScrollLeft: function(scrollLeft) {
231         var el = this.scrollEl,
232             elDom = el && el.dom;
233
234         if (elDom) {
235             return elDom.scrollLeft = Ext.Number.constrain(scrollLeft, 0, elDom.scrollWidth - elDom.clientWidth);
236         }
237     },
238
239     /**
240      * Scroll by deltaY
241      * @param {Number} delta
242      * @return {Number} The resulting scrollTop value
243      */
244     scrollByDeltaY: function(delta) {
245         var el = this.scrollEl,
246             elDom = el && el.dom;
247
248         if (elDom) {
249             return this.setScrollTop(elDom.scrollTop + delta);
250         }
251     },
252
253     /**
254      * Scroll by deltaX
255      * @param {Number} delta
256      * @return {Number} The resulting scrollLeft value
257      */
258     scrollByDeltaX: function(delta) {
259         var el = this.scrollEl,
260             elDom = el && el.dom;
261
262         if (elDom) {
263             return this.setScrollLeft(elDom.scrollLeft + delta);
264         }
265     },
266
267
268     /**
269      * Scroll to the top.
270      */
271     scrollToTop : function(){
272         this.setScrollTop(0);
273     },
274
275     // synchronize the scroller with the bound gridviews
276     onElScroll: function(event, target) {
277         this.fireEvent('bodyscroll', event, target);
278     },
279
280     getPanel: function() {
281         var me = this;
282         if (!me.panel) {
283             me.panel = this.up('[scrollerOwner]');
284         }
285         return me.panel;
286     }
287 });
288
289