3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
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.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @class Ext.layout.container.boxOverflow.Scroller
17 * @extends Ext.layout.container.boxOverflow.None
20 Ext.define('Ext.layout.container.boxOverflow.Scroller', {
22 /* Begin Definitions */
24 extend: 'Ext.layout.container.boxOverflow.None',
25 requires: ['Ext.util.ClickRepeater', 'Ext.Element'],
26 alternateClassName: 'Ext.layout.boxOverflow.Scroller',
28 observable: 'Ext.util.Observable'
34 * @cfg {Boolean} animateScroll
35 * True to animate the scrolling of items within the layout (ignored if enableScroll is false)
40 * @cfg {Number} scrollIncrement
41 * The number of pixels to scroll by on scroller click
46 * @cfg {Number} wheelIncrement
47 * The number of pixels to increment on mouse wheel scrolling.
52 * @cfg {Number} scrollRepeatInterval
53 * Number of milliseconds between each scroll while a scroller button is held down
55 scrollRepeatInterval: 60,
58 * @cfg {Number} scrollDuration
59 * Number of milliseconds that each scroll animation lasts
64 * @cfg {String} beforeCtCls
65 * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers,
66 * which must always be present at the leftmost edge of the Container
70 * @cfg {String} afterCtCls
71 * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
72 * which must always be present at the rightmost edge of the Container
76 * @cfg {String} [scrollerCls='x-box-scroller']
77 * CSS class added to both scroller elements if enableScroll is used
79 scrollerCls: Ext.baseCSSPrefix + 'box-scroller',
82 * @cfg {String} beforeScrollerCls
83 * CSS class added to the left scroller element if enableScroll is used
87 * @cfg {String} afterScrollerCls
88 * CSS class added to the right scroller element if enableScroll is used
91 constructor: function(layout, config) {
93 Ext.apply(this, config || {});
98 * @param {Ext.layout.container.boxOverflow.Scroller} scroller The layout scroller
99 * @param {Number} newPosition The new position of the scroller
100 * @param {Boolean/Object} animate If animating or not. If true, it will be a animation configuration, else it will be false
106 initCSSClasses: function() {
110 if (!me.CSSinitialized) {
111 me.beforeCtCls = me.beforeCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelBefore;
112 me.afterCtCls = me.afterCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelAfter;
113 me.beforeScrollerCls = me.beforeScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelBefore;
114 me.afterScrollerCls = me.afterScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelAfter;
115 me.CSSinitializes = true;
119 handleOverflow: function(calculations, targetSize) {
122 methodName = 'get' + layout.parallelPrefixCap,
126 me.callParent(arguments);
127 this.createInnerElements();
128 this.showScrollers();
129 newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
130 newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - (me.beforeCt[methodName]() + me.afterCt[methodName]());
131 return { targetSize: newSize };
136 * Creates the beforeCt and afterCt elements if they have not already been created
138 createInnerElements: function() {
140 target = me.layout.getRenderTarget();
142 //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
143 //special items such as scrollers or dropdown menu triggers
145 target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
146 me.beforeCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.beforeCtCls}, 'before');
147 me.afterCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.afterCtCls}, 'after');
148 me.createWheelListener();
154 * Sets up an listener to scroll on the layout's innerCt mousewheel event
156 createWheelListener: function() {
157 this.layout.innerCt.on({
159 mousewheel: function(e) {
162 this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
170 clearOverflow: function() {
171 this.hideScrollers();
176 * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
179 showScrollers: function() {
180 this.createScrollers();
181 this.beforeScroller.show();
182 this.afterScroller.show();
183 this.updateScrollButtons();
185 this.layout.owner.addClsWithUI('scroller');
190 * Hides the scroller elements in the beforeCt and afterCt
192 hideScrollers: function() {
193 if (this.beforeScroller != undefined) {
194 this.beforeScroller.hide();
195 this.afterScroller.hide();
197 this.layout.owner.removeClsWithUI('scroller');
203 * Creates the clickable scroller elements and places them into the beforeCt and afterCt
205 createScrollers: function() {
206 if (!this.beforeScroller && !this.afterScroller) {
207 var before = this.beforeCt.createChild({
208 cls: Ext.String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls)
211 var after = this.afterCt.createChild({
212 cls: Ext.String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls)
215 before.addClsOnOver(this.beforeScrollerCls + '-hover');
216 after.addClsOnOver(this.afterScrollerCls + '-hover');
218 before.setVisibilityMode(Ext.Element.DISPLAY);
219 after.setVisibilityMode(Ext.Element.DISPLAY);
221 this.beforeRepeater = Ext.create('Ext.util.ClickRepeater', before, {
222 interval: this.scrollRepeatInterval,
223 handler : this.scrollLeft,
227 this.afterRepeater = Ext.create('Ext.util.ClickRepeater', after, {
228 interval: this.scrollRepeatInterval,
229 handler : this.scrollRight,
234 * @property beforeScroller
236 * The left scroller element. Only created when needed.
238 this.beforeScroller = before;
241 * @property afterScroller
243 * The left scroller element. Only created when needed.
245 this.afterScroller = after;
252 destroy: function() {
253 Ext.destroy(this.beforeRepeater, this.afterRepeater, this.beforeScroller, this.afterScroller, this.beforeCt, this.afterCt);
258 * Scrolls left or right by the number of pixels specified
259 * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
261 scrollBy: function(delta, animate) {
262 this.scrollTo(this.getScrollPosition() + delta, animate);
267 * @return {Object} Object passed to scrollTo when scrolling
269 getScrollAnim: function() {
271 duration: this.scrollDuration,
272 callback: this.updateScrollButtons,
279 * Enables or disables each scroller button based on the current scroll position
281 updateScrollButtons: function() {
282 if (this.beforeScroller == undefined || this.afterScroller == undefined) {
286 var beforeMeth = this.atExtremeBefore() ? 'addCls' : 'removeCls',
287 afterMeth = this.atExtremeAfter() ? 'addCls' : 'removeCls',
288 beforeCls = this.beforeScrollerCls + '-disabled',
289 afterCls = this.afterScrollerCls + '-disabled';
291 this.beforeScroller[beforeMeth](beforeCls);
292 this.afterScroller[afterMeth](afterCls);
293 this.scrolling = false;
298 * Returns true if the innerCt scroll is already at its left-most point
299 * @return {Boolean} True if already at furthest left point
301 atExtremeBefore: function() {
302 return this.getScrollPosition() === 0;
307 * Scrolls to the left by the configured amount
309 scrollLeft: function() {
310 this.scrollBy(-this.scrollIncrement, false);
315 * Scrolls to the right by the configured amount
317 scrollRight: function() {
318 this.scrollBy(this.scrollIncrement, false);
322 * Returns the current scroll position of the innerCt element
323 * @return {Number} The current scroll position
325 getScrollPosition: function(){
326 var layout = this.layout;
327 return parseInt(layout.innerCt.dom['scroll' + layout.parallelBeforeCap], 10) || 0;
332 * Returns the maximum value we can scrollTo
333 * @return {Number} The max scroll value
335 getMaxScrollPosition: function() {
336 var layout = this.layout;
337 return layout.innerCt.dom['scroll' + layout.parallelPrefixCap] - this.layout.innerCt['get' + layout.parallelPrefixCap]();
342 * Returns true if the innerCt scroll is already at its right-most point
343 * @return {Boolean} True if already at furthest right point
345 atExtremeAfter: function() {
346 return this.getScrollPosition() >= this.getMaxScrollPosition();
351 * Scrolls to the given position. Performs bounds checking.
352 * @param {Number} position The position to scroll to. This is constrained.
353 * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
355 scrollTo: function(position, animate) {
358 oldPosition = me.getScrollPosition(),
359 newPosition = Ext.Number.constrain(position, 0, me.getMaxScrollPosition());
361 if (newPosition != oldPosition && !me.scrolling) {
362 if (animate == undefined) {
363 animate = me.animateScroll;
366 layout.innerCt.scrollTo(layout.parallelBefore, newPosition, animate ? me.getScrollAnim() : false);
370 me.scrolling = false;
371 me.updateScrollButtons();
374 me.fireEvent('scroll', me, newPosition, animate ? me.getScrollAnim() : false);
379 * Scrolls to the given component.
380 * @param {String/Number/Ext.Component} item The item to scroll to. Can be a numerical index, component id
381 * or a reference to the component itself.
382 * @param {Boolean} animate True to animate the scrolling
384 scrollToItem: function(item, animate) {
391 item = me.getItem(item);
392 if (item != undefined) {
393 visibility = this.getItemVisibility(item);
394 if (!visibility.fullyVisible) {
395 box = item.getBox(true, true);
396 newPos = box[layout.parallelPosition];
397 if (visibility.hiddenEnd) {
398 newPos -= (this.layout.innerCt['get' + layout.parallelPrefixCap]() - box[layout.parallelPrefix]);
400 this.scrollTo(newPos, animate);
407 * For a given item in the container, return an object with information on whether the item is visible
408 * with the current innerCt scroll value.
409 * @param {Ext.Component} item The item
410 * @return {Object} Values for fullyVisible, hiddenStart and hiddenEnd
412 getItemVisibility: function(item) {
414 box = me.getItem(item).getBox(true, true),
416 itemStart = box[layout.parallelPosition],
417 itemEnd = itemStart + box[layout.parallelPrefix],
418 scrollStart = me.getScrollPosition(),
419 scrollEnd = scrollStart + layout.innerCt['get' + layout.parallelPrefixCap]();
422 hiddenStart : itemStart < scrollStart,
423 hiddenEnd : itemEnd > scrollEnd,
424 fullyVisible: itemStart > scrollStart && itemEnd < scrollEnd