Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / src / widgets / layout / box / ScrollerOverflow.js
1 /*!
2  * Ext JS Library 3.3.1
3  * Copyright(c) 2006-2010 Sencha Inc.
4  * licensing@sencha.com
5  * http://www.sencha.com/license
6  */
7 /**
8  * @class Ext.layout.boxOverflow.Scroller
9  * @extends Ext.layout.boxOverflow.None
10  * Description
11  */
12 Ext.layout.boxOverflow.Scroller = Ext.extend(Ext.layout.boxOverflow.None, {
13     /**
14      * @cfg animateScroll
15      * @type Boolean
16      * True to animate the scrolling of items within the layout (defaults to true, ignored if enableScroll is false)
17      */
18     animateScroll: true,
19     
20     /**
21      * @cfg scrollIncrement
22      * @type Number
23      * The number of pixels to scroll by on scroller click (defaults to 100)
24      */
25     scrollIncrement: 100,
26     
27     /**
28      * @cfg wheelIncrement
29      * @type Number
30      * The number of pixels to increment on mouse wheel scrolling (defaults to <tt>3</tt>).
31      */
32     wheelIncrement: 3,
33     
34     /**
35      * @cfg scrollRepeatInterval
36      * @type Number
37      * Number of milliseconds between each scroll while a scroller button is held down (defaults to 400)
38      */
39     scrollRepeatInterval: 400,
40     
41     /**
42      * @cfg scrollDuration
43      * @type Number
44      * Number of seconds that each scroll animation lasts (defaults to 0.4)
45      */
46     scrollDuration: 0.4,
47     
48     /**
49      * @cfg beforeCls
50      * @type String
51      * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers,
52      * which must always be present at the leftmost edge of the Container
53      */
54     beforeCls: 'x-strip-left',
55     
56     /**
57      * @cfg afterCls
58      * @type String
59      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
60      * which must always be present at the rightmost edge of the Container
61      */
62     afterCls: 'x-strip-right',
63     
64     /**
65      * @cfg scrollerCls
66      * @type String
67      * CSS class added to both scroller elements if enableScroll is used
68      */
69     scrollerCls: 'x-strip-scroller',
70     
71     /**
72      * @cfg beforeScrollerCls
73      * @type String
74      * CSS class added to the left scroller element if enableScroll is used
75      */
76     beforeScrollerCls: 'x-strip-scroller-left',
77     
78     /**
79      * @cfg afterScrollerCls
80      * @type String
81      * CSS class added to the right scroller element if enableScroll is used
82      */
83     afterScrollerCls: 'x-strip-scroller-right',
84     
85     /**
86      * @private
87      * Sets up an listener to scroll on the layout's innerCt mousewheel event
88      */
89     createWheelListener: function() {
90         this.layout.innerCt.on({
91             scope     : this,
92             mousewheel: function(e) {
93                 e.stopEvent();
94
95                 this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
96             }
97         });
98     },
99     
100     /**
101      * @private
102      * Most of the heavy lifting is done in the subclasses
103      */
104     handleOverflow: function(calculations, targetSize) {
105         this.createInnerElements();
106         this.showScrollers();
107     },
108     
109     /**
110      * @private
111      */
112     clearOverflow: function() {
113         this.hideScrollers();
114     },
115     
116     /**
117      * @private
118      * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
119      * present. 
120      */
121     showScrollers: function() {
122         this.createScrollers();
123         
124         this.beforeScroller.show();
125         this.afterScroller.show();
126         
127         this.updateScrollButtons();
128     },
129     
130     /**
131      * @private
132      * Hides the scroller elements in the beforeCt and afterCt
133      */
134     hideScrollers: function() {
135         if (this.beforeScroller != undefined) {
136             this.beforeScroller.hide();
137             this.afterScroller.hide();          
138         }
139     },
140     
141     /**
142      * @private
143      * Creates the clickable scroller elements and places them into the beforeCt and afterCt
144      */
145     createScrollers: function() {
146         if (!this.beforeScroller && !this.afterScroller) {
147             var before = this.beforeCt.createChild({
148                 cls: String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls)
149             });
150             
151             var after = this.afterCt.createChild({
152                 cls: String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls)
153             });
154             
155             before.addClassOnOver(this.beforeScrollerCls + '-hover');
156             after.addClassOnOver(this.afterScrollerCls + '-hover');
157             
158             before.setVisibilityMode(Ext.Element.DISPLAY);
159             after.setVisibilityMode(Ext.Element.DISPLAY);
160             
161             this.beforeRepeater = new Ext.util.ClickRepeater(before, {
162                 interval: this.scrollRepeatInterval,
163                 handler : this.scrollLeft,
164                 scope   : this
165             });
166             
167             this.afterRepeater = new Ext.util.ClickRepeater(after, {
168                 interval: this.scrollRepeatInterval,
169                 handler : this.scrollRight,
170                 scope   : this
171             });
172             
173             /**
174              * @property beforeScroller
175              * @type Ext.Element
176              * The left scroller element. Only created when needed.
177              */
178             this.beforeScroller = before;
179             
180             /**
181              * @property afterScroller
182              * @type Ext.Element
183              * The left scroller element. Only created when needed.
184              */
185             this.afterScroller = after;
186         }
187     },
188     
189     /**
190      * @private
191      */
192     destroy: function() {
193         Ext.destroy(this.beforeScroller, this.afterScroller, this.beforeRepeater, this.afterRepeater, this.beforeCt, this.afterCt);
194     },
195     
196     /**
197      * @private
198      * Scrolls left or right by the number of pixels specified
199      * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
200      */
201     scrollBy: function(delta, animate) {
202         this.scrollTo(this.getScrollPosition() + delta, animate);
203     },
204     
205     /**
206      * @private
207      * Normalizes an item reference, string id or numerical index into a reference to the item
208      * @param {Ext.Component|String|Number} item The item reference, id or index
209      * @return {Ext.Component} The item
210      */
211     getItem: function(item) {
212         if (Ext.isString(item)) {
213             item = Ext.getCmp(item);
214         } else if (Ext.isNumber(item)) {
215             item = this.items[item];
216         }
217         
218         return item;
219     },
220     
221     /**
222      * @private
223      * @return {Object} Object passed to scrollTo when scrolling
224      */
225     getScrollAnim: function() {
226         return {
227             duration: this.scrollDuration, 
228             callback: this.updateScrollButtons, 
229             scope   : this
230         };
231     },
232     
233     /**
234      * @private
235      * Enables or disables each scroller button based on the current scroll position
236      */
237     updateScrollButtons: function() {
238         if (this.beforeScroller == undefined || this.afterScroller == undefined) {
239             return;
240         }
241         
242         var beforeMeth = this.atExtremeBefore()  ? 'addClass' : 'removeClass',
243             afterMeth  = this.atExtremeAfter() ? 'addClass' : 'removeClass',
244             beforeCls  = this.beforeScrollerCls + '-disabled',
245             afterCls   = this.afterScrollerCls  + '-disabled';
246         
247         this.beforeScroller[beforeMeth](beforeCls);
248         this.afterScroller[afterMeth](afterCls);
249         this.scrolling = false;
250     },
251     
252     /**
253      * @private
254      * Returns true if the innerCt scroll is already at its left-most point
255      * @return {Boolean} True if already at furthest left point
256      */
257     atExtremeBefore: function() {
258         return this.getScrollPosition() === 0;
259     },
260     
261     /**
262      * @private
263      * Scrolls to the left by the configured amount
264      */
265     scrollLeft: function(animate) {
266         this.scrollBy(-this.scrollIncrement, animate);
267     },
268     
269     /**
270      * @private
271      * Scrolls to the right by the configured amount
272      */
273     scrollRight: function(animate) {
274         this.scrollBy(this.scrollIncrement, animate);
275     },
276     
277     /**
278      * Scrolls to the given component.
279      * @param {String|Number|Ext.Component} item The item to scroll to. Can be a numerical index, component id 
280      * or a reference to the component itself.
281      * @param {Boolean} animate True to animate the scrolling
282      */
283     scrollToItem: function(item, animate) {
284         item = this.getItem(item);
285         
286         if (item != undefined) {
287             var visibility = this.getItemVisibility(item);
288             
289             if (!visibility.fullyVisible) {
290                 var box  = item.getBox(true, true),
291                     newX = box.x;
292                     
293                 if (visibility.hiddenRight) {
294                     newX -= (this.layout.innerCt.getWidth() - box.width);
295                 }
296                 
297                 this.scrollTo(newX, animate);
298             }
299         }
300     },
301     
302     /**
303      * @private
304      * For a given item in the container, return an object with information on whether the item is visible
305      * with the current innerCt scroll value.
306      * @param {Ext.Component} item The item
307      * @return {Object} Values for fullyVisible, hiddenLeft and hiddenRight
308      */
309     getItemVisibility: function(item) {
310         var box         = this.getItem(item).getBox(true, true),
311             itemLeft    = box.x,
312             itemRight   = box.x + box.width,
313             scrollLeft  = this.getScrollPosition(),
314             scrollRight = this.layout.innerCt.getWidth() + scrollLeft;
315         
316         return {
317             hiddenLeft  : itemLeft < scrollLeft,
318             hiddenRight : itemRight > scrollRight,
319             fullyVisible: itemLeft > scrollLeft && itemRight < scrollRight
320         };
321     }
322 });
323
324 Ext.layout.boxOverflow.scroller = Ext.layout.boxOverflow.Scroller;
325
326
327 /**\r
328  * @class Ext.layout.boxOverflow.VerticalScroller\r
329  * @extends Ext.layout.boxOverflow.Scroller\r
330  * Description\r
331  */\r
332 Ext.layout.boxOverflow.VerticalScroller = Ext.extend(Ext.layout.boxOverflow.Scroller, {
333     scrollIncrement: 75,
334     wheelIncrement : 2,
335     
336     handleOverflow: function(calculations, targetSize) {
337         Ext.layout.boxOverflow.VerticalScroller.superclass.handleOverflow.apply(this, arguments);
338         
339         return {
340             targetSize: {
341                 height: targetSize.height - (this.beforeCt.getHeight() + this.afterCt.getHeight()),
342                 width : targetSize.width
343             }
344         };
345     },
346     
347     /**
348      * @private
349      * Creates the beforeCt and afterCt elements if they have not already been created
350      */
351     createInnerElements: function() {
352         var target = this.layout.innerCt;
353         
354         //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
355         //special items such as scrollers or dropdown menu triggers
356         if (!this.beforeCt) {
357             this.beforeCt = target.insertSibling({cls: this.beforeCls}, 'before');
358             this.afterCt  = target.insertSibling({cls: this.afterCls},  'after');
359
360             this.createWheelListener();
361         }
362     },
363     
364     /**
365      * @private
366      * Scrolls to the given position. Performs bounds checking.
367      * @param {Number} position The position to scroll to. This is constrained.
368      * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
369      */
370     scrollTo: function(position, animate) {
371         var oldPosition = this.getScrollPosition(),
372             newPosition = position.constrain(0, this.getMaxScrollBottom());
373         
374         if (newPosition != oldPosition && !this.scrolling) {
375             if (animate == undefined) {
376                 animate = this.animateScroll;
377             }
378             
379             this.layout.innerCt.scrollTo('top', newPosition, animate ? this.getScrollAnim() : false);
380             
381             if (animate) {
382                 this.scrolling = true;
383             } else {
384                 this.scrolling = false;
385                 this.updateScrollButtons();
386             }
387         }
388     },
389     
390     /**
391      * Returns the current scroll position of the innerCt element
392      * @return {Number} The current scroll position
393      */
394     getScrollPosition: function(){
395         return parseInt(this.layout.innerCt.dom.scrollTop, 10) || 0;
396     },
397     
398     /**
399      * @private
400      * Returns the maximum value we can scrollTo
401      * @return {Number} The max scroll value
402      */
403     getMaxScrollBottom: function() {
404         return this.layout.innerCt.dom.scrollHeight - this.layout.innerCt.getHeight();
405     },
406     
407     /**
408      * @private
409      * Returns true if the innerCt scroll is already at its right-most point
410      * @return {Boolean} True if already at furthest right point
411      */
412     atExtremeAfter: function() {
413         return this.getScrollPosition() >= this.getMaxScrollBottom();
414     }
415 });
416
417 Ext.layout.boxOverflow.scroller.vbox = Ext.layout.boxOverflow.VerticalScroller;
418
419
420 /**
421  * @class Ext.layout.boxOverflow.HorizontalScroller
422  * @extends Ext.layout.boxOverflow.Scroller
423  * Description
424  */
425 Ext.layout.boxOverflow.HorizontalScroller = Ext.extend(Ext.layout.boxOverflow.Scroller, {
426     handleOverflow: function(calculations, targetSize) {
427         Ext.layout.boxOverflow.HorizontalScroller.superclass.handleOverflow.apply(this, arguments);
428         
429         return {
430             targetSize: {
431                 height: targetSize.height,
432                 width : targetSize.width - (this.beforeCt.getWidth() + this.afterCt.getWidth())
433             }
434         };
435     },
436     
437     /**
438      * @private
439      * Creates the beforeCt and afterCt elements if they have not already been created
440      */
441     createInnerElements: function() {
442         var target = this.layout.innerCt;
443         
444         //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
445         //special items such as scrollers or dropdown menu triggers
446         if (!this.beforeCt) {
447             this.afterCt  = target.insertSibling({cls: this.afterCls},  'before');
448             this.beforeCt = target.insertSibling({cls: this.beforeCls}, 'before');
449             
450             this.createWheelListener();
451         }
452     },
453     
454     /**
455      * @private
456      * Scrolls to the given position. Performs bounds checking.
457      * @param {Number} position The position to scroll to. This is constrained.
458      * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
459      */
460     scrollTo: function(position, animate) {
461         var oldPosition = this.getScrollPosition(),
462             newPosition = position.constrain(0, this.getMaxScrollRight());
463         
464         if (newPosition != oldPosition && !this.scrolling) {
465             if (animate == undefined) {
466                 animate = this.animateScroll;
467             }
468             
469             this.layout.innerCt.scrollTo('left', newPosition, animate ? this.getScrollAnim() : false);
470             
471             if (animate) {
472                 this.scrolling = true;
473             } else {
474                 this.scrolling = false;
475                 this.updateScrollButtons();
476             }
477         }
478     },
479     
480     /**
481      * Returns the current scroll position of the innerCt element
482      * @return {Number} The current scroll position
483      */
484     getScrollPosition: function(){
485         return parseInt(this.layout.innerCt.dom.scrollLeft, 10) || 0;
486     },
487     
488     /**
489      * @private
490      * Returns the maximum value we can scrollTo
491      * @return {Number} The max scroll value
492      */
493     getMaxScrollRight: function() {
494         return this.layout.innerCt.dom.scrollWidth - this.layout.innerCt.getWidth();
495     },
496     
497     /**
498      * @private
499      * Returns true if the innerCt scroll is already at its right-most point
500      * @return {Boolean} True if already at furthest right point
501      */
502     atExtremeAfter: function() {
503         return this.getScrollPosition() >= this.getMaxScrollRight();
504     }
505 });
506
507 Ext.layout.boxOverflow.scroller.hbox = Ext.layout.boxOverflow.HorizontalScroller;