2 * @class Ext.layout.container.boxOverflow.Scroller
3 * @extends Ext.layout.container.boxOverflow.None
6 Ext.define('Ext.layout.container.boxOverflow.Scroller', {
8 /* Begin Definitions */
10 extend: 'Ext.layout.container.boxOverflow.None',
11 requires: ['Ext.util.ClickRepeater', 'Ext.core.Element'],
12 alternateClassName: 'Ext.layout.boxOverflow.Scroller',
14 observable: 'Ext.util.Observable'
20 * @cfg {Boolean} animateScroll
21 * True to animate the scrolling of items within the layout (defaults to true, ignored if enableScroll is false)
26 * @cfg {Number} scrollIncrement
27 * The number of pixels to scroll by on scroller click (defaults to 24)
32 * @cfg {Number} wheelIncrement
33 * The number of pixels to increment on mouse wheel scrolling (defaults to <tt>3</tt>).
38 * @cfg {Number} scrollRepeatInterval
39 * Number of milliseconds between each scroll while a scroller button is held down (defaults to 20)
41 scrollRepeatInterval: 60,
44 * @cfg {Number} scrollDuration
45 * Number of milliseconds that each scroll animation lasts (defaults to 400)
50 * @cfg {String} beforeCtCls
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
56 * @cfg {String} afterCtCls
57 * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
58 * which must always be present at the rightmost edge of the Container
62 * @cfg {String} scrollerCls
63 * CSS class added to both scroller elements if enableScroll is used
65 scrollerCls: Ext.baseCSSPrefix + 'box-scroller',
68 * @cfg {String} beforeScrollerCls
69 * CSS class added to the left scroller element if enableScroll is used
73 * @cfg {String} afterScrollerCls
74 * CSS class added to the right scroller element if enableScroll is used
77 constructor: function(layout, config) {
79 Ext.apply(this, config || {});
84 * @param {Ext.layout.container.boxOverflow.Scroller} scroller The layout scroller
85 * @param {Number} newPosition The new position of the scroller
86 * @param {Boolean/Object} animate If animating or not. If true, it will be a animation configuration, else it will be false
92 initCSSClasses: function() {
96 if (!me.CSSinitialized) {
97 me.beforeCtCls = me.beforeCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelBefore;
98 me.afterCtCls = me.afterCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelAfter;
99 me.beforeScrollerCls = me.beforeScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelBefore;
100 me.afterScrollerCls = me.afterScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelAfter;
101 me.CSSinitializes = true;
105 handleOverflow: function(calculations, targetSize) {
108 methodName = 'get' + layout.parallelPrefixCap,
112 me.callParent(arguments);
113 this.createInnerElements();
114 this.showScrollers();
115 newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
116 newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - (me.beforeCt[methodName]() + me.afterCt[methodName]());
117 return { targetSize: newSize };
122 * Creates the beforeCt and afterCt elements if they have not already been created
124 createInnerElements: function() {
126 target = me.layout.getRenderTarget();
128 //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
129 //special items such as scrollers or dropdown menu triggers
131 target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
132 me.beforeCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.beforeCtCls}, 'before');
133 me.afterCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.afterCtCls}, 'after');
134 me.createWheelListener();
140 * Sets up an listener to scroll on the layout's innerCt mousewheel event
142 createWheelListener: function() {
143 this.layout.innerCt.on({
145 mousewheel: function(e) {
148 this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
156 clearOverflow: function() {
157 this.hideScrollers();
162 * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
165 showScrollers: function() {
166 this.createScrollers();
167 this.beforeScroller.show();
168 this.afterScroller.show();
169 this.updateScrollButtons();
171 this.layout.owner.addClsWithUI('scroller');
176 * Hides the scroller elements in the beforeCt and afterCt
178 hideScrollers: function() {
179 if (this.beforeScroller != undefined) {
180 this.beforeScroller.hide();
181 this.afterScroller.hide();
183 this.layout.owner.removeClsWithUI('scroller');
189 * Creates the clickable scroller elements and places them into the beforeCt and afterCt
191 createScrollers: function() {
192 if (!this.beforeScroller && !this.afterScroller) {
193 var before = this.beforeCt.createChild({
194 cls: Ext.String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls)
197 var after = this.afterCt.createChild({
198 cls: Ext.String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls)
201 before.addClsOnOver(this.beforeScrollerCls + '-hover');
202 after.addClsOnOver(this.afterScrollerCls + '-hover');
204 before.setVisibilityMode(Ext.core.Element.DISPLAY);
205 after.setVisibilityMode(Ext.core.Element.DISPLAY);
207 this.beforeRepeater = Ext.create('Ext.util.ClickRepeater', before, {
208 interval: this.scrollRepeatInterval,
209 handler : this.scrollLeft,
213 this.afterRepeater = Ext.create('Ext.util.ClickRepeater', after, {
214 interval: this.scrollRepeatInterval,
215 handler : this.scrollRight,
220 * @property beforeScroller
221 * @type Ext.core.Element
222 * The left scroller element. Only created when needed.
224 this.beforeScroller = before;
227 * @property afterScroller
228 * @type Ext.core.Element
229 * The left scroller element. Only created when needed.
231 this.afterScroller = after;
238 destroy: function() {
239 Ext.destroy(this.beforeRepeater, this.afterRepeater, this.beforeScroller, this.afterScroller, this.beforeCt, this.afterCt);
244 * Scrolls left or right by the number of pixels specified
245 * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
247 scrollBy: function(delta, animate) {
248 this.scrollTo(this.getScrollPosition() + delta, animate);
253 * @return {Object} Object passed to scrollTo when scrolling
255 getScrollAnim: function() {
257 duration: this.scrollDuration,
258 callback: this.updateScrollButtons,
265 * Enables or disables each scroller button based on the current scroll position
267 updateScrollButtons: function() {
268 if (this.beforeScroller == undefined || this.afterScroller == undefined) {
272 var beforeMeth = this.atExtremeBefore() ? 'addCls' : 'removeCls',
273 afterMeth = this.atExtremeAfter() ? 'addCls' : 'removeCls',
274 beforeCls = this.beforeScrollerCls + '-disabled',
275 afterCls = this.afterScrollerCls + '-disabled';
277 this.beforeScroller[beforeMeth](beforeCls);
278 this.afterScroller[afterMeth](afterCls);
279 this.scrolling = false;
284 * Returns true if the innerCt scroll is already at its left-most point
285 * @return {Boolean} True if already at furthest left point
287 atExtremeBefore: function() {
288 return this.getScrollPosition() === 0;
293 * Scrolls to the left by the configured amount
295 scrollLeft: function() {
296 this.scrollBy(-this.scrollIncrement, false);
301 * Scrolls to the right by the configured amount
303 scrollRight: function() {
304 this.scrollBy(this.scrollIncrement, false);
308 * Returns the current scroll position of the innerCt element
309 * @return {Number} The current scroll position
311 getScrollPosition: function(){
312 var layout = this.layout;
313 return parseInt(layout.innerCt.dom['scroll' + layout.parallelBeforeCap], 10) || 0;
318 * Returns the maximum value we can scrollTo
319 * @return {Number} The max scroll value
321 getMaxScrollPosition: function() {
322 var layout = this.layout;
323 return layout.innerCt.dom['scroll' + layout.parallelPrefixCap] - this.layout.innerCt['get' + layout.parallelPrefixCap]();
328 * Returns true if the innerCt scroll is already at its right-most point
329 * @return {Boolean} True if already at furthest right point
331 atExtremeAfter: function() {
332 return this.getScrollPosition() >= this.getMaxScrollPosition();
337 * Scrolls to the given position. Performs bounds checking.
338 * @param {Number} position The position to scroll to. This is constrained.
339 * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
341 scrollTo: function(position, animate) {
344 oldPosition = me.getScrollPosition(),
345 newPosition = Ext.Number.constrain(position, 0, me.getMaxScrollPosition());
347 if (newPosition != oldPosition && !me.scrolling) {
348 if (animate == undefined) {
349 animate = me.animateScroll;
352 layout.innerCt.scrollTo(layout.parallelBefore, newPosition, animate ? me.getScrollAnim() : false);
356 me.scrolling = false;
357 me.updateScrollButtons();
360 me.fireEvent('scroll', me, newPosition, animate ? me.getScrollAnim() : false);
365 * Scrolls to the given component.
366 * @param {String|Number|Ext.Component} item The item to scroll to. Can be a numerical index, component id
367 * or a reference to the component itself.
368 * @param {Boolean} animate True to animate the scrolling
370 scrollToItem: function(item, animate) {
377 item = me.getItem(item);
378 if (item != undefined) {
379 visibility = this.getItemVisibility(item);
380 if (!visibility.fullyVisible) {
381 box = item.getBox(true, true);
382 newPos = box[layout.parallelPosition];
383 if (visibility.hiddenEnd) {
384 newPos -= (this.layout.innerCt['get' + layout.parallelPrefixCap]() - box[layout.parallelPrefix]);
386 this.scrollTo(newPos, animate);
393 * For a given item in the container, return an object with information on whether the item is visible
394 * with the current innerCt scroll value.
395 * @param {Ext.Component} item The item
396 * @return {Object} Values for fullyVisible, hiddenStart and hiddenEnd
398 getItemVisibility: function(item) {
400 box = me.getItem(item).getBox(true, true),
402 itemStart = box[layout.parallelPosition],
403 itemEnd = itemStart + box[layout.parallelPrefix],
404 scrollStart = me.getScrollPosition(),
405 scrollEnd = scrollStart + layout.innerCt['get' + layout.parallelPrefixCap]();
408 hiddenStart : itemStart < scrollStart,
409 hiddenEnd : itemEnd > scrollEnd,
410 fullyVisible: itemStart > scrollStart && itemEnd < scrollEnd