Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / docs / source / Scroller.html
index 14ecf7a..4d6da8c 100644 (file)
   </script>
 </head>
 <body onload="prettyPrint(); highlight();">
-  <pre class="prettyprint lang-js"><span id='Ext-grid-Scroller'>/**
-</span> * @class Ext.grid.Scroller
- * @extends Ext.Component
- *
- * Docked in an Ext.grid.Panel, controls virtualized scrolling and synchronization
- * across different sections.
- *
+  <pre class="prettyprint lang-js"><span id='Ext-layout-container-boxOverflow-Scroller'>/**
+</span> * @class Ext.layout.container.boxOverflow.Scroller
+ * @extends Ext.layout.container.boxOverflow.None
  * @private
  */
-Ext.define('Ext.grid.Scroller', {
-    extend: 'Ext.Component',
-    alias: 'widget.gridscroller',
-    weight: 110,
-    cls: Ext.baseCSSPrefix + 'scroller',
-    focusable: false,
+Ext.define('Ext.layout.container.boxOverflow.Scroller', {
+
+    /* Begin Definitions */
+
+    extend: 'Ext.layout.container.boxOverflow.None',
+    requires: ['Ext.util.ClickRepeater', 'Ext.core.Element'],
+    alternateClassName: 'Ext.layout.boxOverflow.Scroller',
+    mixins: {
+        observable: 'Ext.util.Observable'
+    },
     
-    renderTpl: ['&lt;div class=&quot;' + Ext.baseCSSPrefix + 'stretcher&quot;&gt;&lt;/div&gt;'],
+    /* End Definitions */
+
+<span id='Ext-layout-container-boxOverflow-Scroller-cfg-animateScroll'>    /**
+</span>     * @cfg {Boolean} animateScroll
+     * True to animate the scrolling of items within the layout (defaults to true, ignored if enableScroll is false)
+     */
+    animateScroll: false,
+
+<span id='Ext-layout-container-boxOverflow-Scroller-cfg-scrollIncrement'>    /**
+</span>     * @cfg {Number} scrollIncrement
+     * The number of pixels to scroll by on scroller click (defaults to 24)
+     */
+    scrollIncrement: 20,
+
+<span id='Ext-layout-container-boxOverflow-Scroller-cfg-wheelIncrement'>    /**
+</span>     * @cfg {Number} wheelIncrement
+     * The number of pixels to increment on mouse wheel scrolling (defaults to &lt;tt&gt;3&lt;/tt&gt;).
+     */
+    wheelIncrement: 10,
+
+<span id='Ext-layout-container-boxOverflow-Scroller-cfg-scrollRepeatInterval'>    /**
+</span>     * @cfg {Number} scrollRepeatInterval
+     * Number of milliseconds between each scroll while a scroller button is held down (defaults to 20)
+     */
+    scrollRepeatInterval: 60,
+
+<span id='Ext-layout-container-boxOverflow-Scroller-cfg-scrollDuration'>    /**
+</span>     * @cfg {Number} scrollDuration
+     * Number of milliseconds that each scroll animation lasts (defaults to 400)
+     */
+    scrollDuration: 400,
+
+<span id='Ext-layout-container-boxOverflow-Scroller-cfg-beforeCtCls'>    /**
+</span>     * @cfg {String} beforeCtCls
+     * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers,
+     * which must always be present at the leftmost edge of the Container
+     */
+
+<span id='Ext-layout-container-boxOverflow-Scroller-cfg-afterCtCls'>    /**
+</span>     * @cfg {String} afterCtCls
+     * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
+     * which must always be present at the rightmost edge of the Container
+     */
+
+<span id='Ext-layout-container-boxOverflow-Scroller-cfg-scrollerCls'>    /**
+</span>     * @cfg {String} scrollerCls
+     * CSS class added to both scroller elements if enableScroll is used
+     */
+    scrollerCls: Ext.baseCSSPrefix + 'box-scroller',
+
+<span id='Ext-layout-container-boxOverflow-Scroller-cfg-beforeScrollerCls'>    /**
+</span>     * @cfg {String} beforeScrollerCls
+     * CSS class added to the left scroller element if enableScroll is used
+     */
+
+<span id='Ext-layout-container-boxOverflow-Scroller-cfg-afterScrollerCls'>    /**
+</span>     * @cfg {String} afterScrollerCls
+     * CSS class added to the right scroller element if enableScroll is used
+     */
     
-    initComponent: function() {
-        var me       = this,
-            dock     = me.dock,
-            cls      = Ext.baseCSSPrefix + 'scroller-vertical',
-            sizeProp = 'width',
-            // Subtracting 2px would give us a perfect fit of the scroller
-            // however, some browsers wont allow us to scroll content thats not
-            // visible, therefore we use 1px.
-            // Note: This 1px offset matches code in Ext.grid.ColumnLayout when
-            // reserving room for the scrollbar
-            scrollbarWidth = Ext.getScrollBarWidth() + (Ext.isIE ? 1 : -1);
-
-        me.offsets = {bottom: 0};
-
-        if (dock === 'top' || dock === 'bottom') {
-            cls = Ext.baseCSSPrefix + 'scroller-horizontal';
-            sizeProp = 'height';
-        }
-        me[sizeProp] = scrollbarWidth;
-        
-        me.cls += (' ' + cls);
+    constructor: function(layout, config) {
+        this.layout = layout;
+        Ext.apply(this, config || {});
         
-        Ext.applyIf(me.renderSelectors, {
-            stretchEl: '.' + Ext.baseCSSPrefix + 'stretcher'
-        });
-        me.callParent();
+        this.addEvents(
+<span id='Ext-layout-container-boxOverflow-Scroller-event-scroll'>            /**
+</span>             * @event scroll
+             * @param {Ext.layout.container.boxOverflow.Scroller} scroller The layout scroller
+             * @param {Number} newPosition The new position of the scroller
+             * @param {Boolean/Object} animate If animating or not. If true, it will be a animation configuration, else it will be false
+             */
+            'scroll'
+        );
     },
     
-    
-    afterRender: function() {
-        var me = this;
-        me.callParent();
-        me.ownerCt.on('afterlayout', me.onOwnerAfterLayout, me);
-        me.mon(me.el, 'scroll', me.onElScroll, me);
-        Ext.cache[me.el.id].skipGarbageCollection = true;
+    initCSSClasses: function() {
+        var me = this,
+        layout = me.layout;
+
+        if (!me.CSSinitialized) {
+            me.beforeCtCls = me.beforeCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelBefore;
+            me.afterCtCls  = me.afterCtCls  || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelAfter;
+            me.beforeScrollerCls = me.beforeScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelBefore;
+            me.afterScrollerCls  = me.afterScrollerCls  || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelAfter;
+            me.CSSinitializes = true;
+        }
     },
-    
-    getSizeCalculation: function() {
-        var owner  = this.getPanel(),
-            dock   = this.dock,
-            elDom  = this.el.dom,
-            width  = 1,
-            height = 1,
-            view, tbl;
-            
-        if (dock === 'top' || dock === 'bottom') {
-            // TODO: Must gravitate to a single region..
-            // Horizontal scrolling only scrolls virtualized region
-            var items  = owner.query('tableview'),
-                center = items[1] || items[0];
-            
-            if (!center) {
-                return false;
-            }
-            // center is not guaranteed to have content, such as when there
-            // are zero rows in the grid/tree. We read the width from the
-            // headerCt instead.
-            width = center.headerCt.getFullWidth();
-            
-            if (Ext.isIEQuirks) {
-                width--;
-            }
-            // Account for the 1px removed in Scroller.
-            width--;
-        } else {            
-            view = owner.down('tableview:not([lockableInjected])');
-            if (!view) {
-                return false;
-            }
-            tbl = view.el;
-            if (!tbl) {
-                return false;
+
+    handleOverflow: function(calculations, targetSize) {
+        var me = this,
+            layout = me.layout,
+            methodName = 'get' + layout.parallelPrefixCap,
+            newSize = {};
+
+        me.initCSSClasses();
+        me.callParent(arguments);
+        this.createInnerElements();
+        this.showScrollers();
+        newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
+        newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - (me.beforeCt[methodName]() + me.afterCt[methodName]());
+        return { targetSize: newSize };
+    },
+
+<span id='Ext-layout-container-boxOverflow-Scroller-method-createInnerElements'>    /**
+</span>     * @private
+     * Creates the beforeCt and afterCt elements if they have not already been created
+     */
+    createInnerElements: function() {
+        var me = this,
+            target = me.layout.getRenderTarget();
+
+        //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
+        //special items such as scrollers or dropdown menu triggers
+        if (!me.beforeCt) {
+            target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
+            me.beforeCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.beforeCtCls}, 'before');
+            me.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.afterCtCls},  'after');
+            me.createWheelListener();
+        }
+    },
+
+<span id='Ext-layout-container-boxOverflow-Scroller-method-createWheelListener'>    /**
+</span>     * @private
+     * Sets up an listener to scroll on the layout's innerCt mousewheel event
+     */
+    createWheelListener: function() {
+        this.layout.innerCt.on({
+            scope     : this,
+            mousewheel: function(e) {
+                e.stopEvent();
+
+                this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
             }
+        });
+    },
+
+<span id='Ext-layout-container-boxOverflow-Scroller-method-clearOverflow'>    /**
+</span>     * @private
+     */
+    clearOverflow: function() {
+        this.hideScrollers();
+    },
+
+<span id='Ext-layout-container-boxOverflow-Scroller-method-showScrollers'>    /**
+</span>     * @private
+     * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
+     * present. 
+     */
+    showScrollers: function() {
+        this.createScrollers();
+        this.beforeScroller.show();
+        this.afterScroller.show();
+        this.updateScrollButtons();
+        
+        this.layout.owner.addClsWithUI('scroller');
+    },
+
+<span id='Ext-layout-container-boxOverflow-Scroller-method-hideScrollers'>    /**
+</span>     * @private
+     * Hides the scroller elements in the beforeCt and afterCt
+     */
+    hideScrollers: function() {
+        if (this.beforeScroller != undefined) {
+            this.beforeScroller.hide();
+            this.afterScroller.hide();
             
-            // needs to also account for header and scroller (if still in picture)
-            // should calculate from headerCt.
-            height = tbl.dom.scrollHeight;
-        }
-        if (isNaN(width)) {
-            width = 1;
+            this.layout.owner.removeClsWithUI('scroller');
         }
-        if (isNaN(height)) {
-            height = 1;
+    },
+
+<span id='Ext-layout-container-boxOverflow-Scroller-method-createScrollers'>    /**
+</span>     * @private
+     * Creates the clickable scroller elements and places them into the beforeCt and afterCt
+     */
+    createScrollers: function() {
+        if (!this.beforeScroller &amp;&amp; !this.afterScroller) {
+            var before = this.beforeCt.createChild({
+                cls: Ext.String.format(&quot;{0} {1} &quot;, this.scrollerCls, this.beforeScrollerCls)
+            });
+
+            var after = this.afterCt.createChild({
+                cls: Ext.String.format(&quot;{0} {1}&quot;, this.scrollerCls, this.afterScrollerCls)
+            });
+
+            before.addClsOnOver(this.beforeScrollerCls + '-hover');
+            after.addClsOnOver(this.afterScrollerCls + '-hover');
+
+            before.setVisibilityMode(Ext.core.Element.DISPLAY);
+            after.setVisibilityMode(Ext.core.Element.DISPLAY);
+
+            this.beforeRepeater = Ext.create('Ext.util.ClickRepeater', before, {
+                interval: this.scrollRepeatInterval,
+                handler : this.scrollLeft,
+                scope   : this
+            });
+
+            this.afterRepeater = Ext.create('Ext.util.ClickRepeater', after, {
+                interval: this.scrollRepeatInterval,
+                handler : this.scrollRight,
+                scope   : this
+            });
+
+<span id='Ext-layout-container-boxOverflow-Scroller-property-beforeScroller'>            /**
+</span>             * @property beforeScroller
+             * @type Ext.core.Element
+             * The left scroller element. Only created when needed.
+             */
+            this.beforeScroller = before;
+
+<span id='Ext-layout-container-boxOverflow-Scroller-property-afterScroller'>            /**
+</span>             * @property afterScroller
+             * @type Ext.core.Element
+             * The left scroller element. Only created when needed.
+             */
+            this.afterScroller = after;
         }
+    },
+
+<span id='Ext-layout-container-boxOverflow-Scroller-method-destroy'>    /**
+</span>     * @private
+     */
+    destroy: function() {
+        Ext.destroy(this.beforeRepeater, this.afterRepeater, this.beforeScroller, this.afterScroller, this.beforeCt, this.afterCt);
+    },
+
+<span id='Ext-layout-container-boxOverflow-Scroller-method-scrollBy'>    /**
+</span>     * @private
+     * Scrolls left or right by the number of pixels specified
+     * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
+     */
+    scrollBy: function(delta, animate) {
+        this.scrollTo(this.getScrollPosition() + delta, animate);
+    },
+
+<span id='Ext-layout-container-boxOverflow-Scroller-method-getScrollAnim'>    /**
+</span>     * @private
+     * @return {Object} Object passed to scrollTo when scrolling
+     */
+    getScrollAnim: function() {
         return {
-            width: width,
-            height: height
+            duration: this.scrollDuration, 
+            callback: this.updateScrollButtons, 
+            scope   : this
         };
     },
-    
-    invalidate: function(firstPass) {
-        if (!this.stretchEl || !this.ownerCt) {
+
+<span id='Ext-layout-container-boxOverflow-Scroller-method-updateScrollButtons'>    /**
+</span>     * @private
+     * Enables or disables each scroller button based on the current scroll position
+     */
+    updateScrollButtons: function() {
+        if (this.beforeScroller == undefined || this.afterScroller == undefined) {
             return;
         }
-        var size  = this.getSizeCalculation(),
-            elDom = this.el.dom;
-        if (size) {
-            this.stretchEl.setSize(size);
+
+        var beforeMeth = this.atExtremeBefore()  ? 'addCls' : 'removeCls',
+            afterMeth  = this.atExtremeAfter() ? 'addCls' : 'removeCls',
+            beforeCls  = this.beforeScrollerCls + '-disabled',
+            afterCls   = this.afterScrollerCls  + '-disabled';
         
-            // BrowserBug: IE7
-            // This makes the scroller enabled, when initially rendering.
-            elDom.scrollTop = elDom.scrollTop;
-        }
+        this.beforeScroller[beforeMeth](beforeCls);
+        this.afterScroller[afterMeth](afterCls);
+        this.scrolling = false;
     },
 
-    onOwnerAfterLayout: function(owner, layout) {
-        this.invalidate();
+<span id='Ext-layout-container-boxOverflow-Scroller-method-atExtremeBefore'>    /**
+</span>     * @private
+     * Returns true if the innerCt scroll is already at its left-most point
+     * @return {Boolean} True if already at furthest left point
+     */
+    atExtremeBefore: function() {
+        return this.getScrollPosition() === 0;
     },
 
-<span id='Ext-grid-Scroller-method-setScrollTop'>    /**
-</span>     * Sets the scrollTop and constrains the value between 0 and max.
-     * @param {Number} scrollTop
-     * @return {Number} The resulting scrollTop value after being constrained
+<span id='Ext-layout-container-boxOverflow-Scroller-method-scrollLeft'>    /**
+</span>     * @private
+     * Scrolls to the left by the configured amount
      */
-    setScrollTop: function(scrollTop) {
-        if (this.el) {
-            var elDom = this.el.dom;
-            return elDom.scrollTop = Ext.Number.constrain(scrollTop, 0, elDom.scrollHeight - elDom.clientHeight);
-        }
+    scrollLeft: function() {
+        this.scrollBy(-this.scrollIncrement, false);
     },
 
-<span id='Ext-grid-Scroller-method-setScrollLeft'>    /**
-</span>     * Sets the scrollLeft and constrains the value between 0 and max.
-     * @param {Number} scrollLeft
-     * @return {Number} The resulting scrollLeft value after being constrained
+<span id='Ext-layout-container-boxOverflow-Scroller-method-scrollRight'>    /**
+</span>     * @private
+     * Scrolls to the right by the configured amount
      */
-    setScrollLeft: function(scrollLeft) {
-        if (this.el) {
-            var elDom = this.el.dom;
-            return elDom.scrollLeft = Ext.Number.constrain(scrollLeft, 0, elDom.scrollWidth - elDom.clientWidth);
-        }
+    scrollRight: function() {
+        this.scrollBy(this.scrollIncrement, false);
     },
 
-<span id='Ext-grid-Scroller-method-scrollByDeltaY'>    /**
-</span>     * Scroll by deltaY
-     * @param {Number} delta
-     * @return {Number} The resulting scrollTop value
+<span id='Ext-layout-container-boxOverflow-Scroller-method-getScrollPosition'>    /**
+</span>     * Returns the current scroll position of the innerCt element
+     * @return {Number} The current scroll position
      */
-    scrollByDeltaY: function(delta) {
-        if (this.el) {
-            var elDom = this.el.dom;
-            return this.setScrollTop(elDom.scrollTop + delta);
-        }
+    getScrollPosition: function(){
+        var layout = this.layout;
+        return parseInt(layout.innerCt.dom['scroll' + layout.parallelBeforeCap], 10) || 0;
     },
 
-<span id='Ext-grid-Scroller-method-scrollByDeltaX'>    /**
-</span>     * Scroll by deltaX
-     * @param {Number} delta
-     * @return {Number} The resulting scrollLeft value
+<span id='Ext-layout-container-boxOverflow-Scroller-method-getMaxScrollPosition'>    /**
+</span>     * @private
+     * Returns the maximum value we can scrollTo
+     * @return {Number} The max scroll value
      */
-    scrollByDeltaX: function(delta) {
-        if (this.el) {
-            var elDom = this.el.dom;
-            return this.setScrollLeft(elDom.scrollLeft + delta);
-        }
+    getMaxScrollPosition: function() {
+        var layout = this.layout;
+        return layout.innerCt.dom['scroll' + layout.parallelPrefixCap] - this.layout.innerCt['get' + layout.parallelPrefixCap]();
     },
-    
-    
-<span id='Ext-grid-Scroller-method-scrollToTop'>    /**
-</span>     * Scroll to the top.
+
+<span id='Ext-layout-container-boxOverflow-Scroller-method-atExtremeAfter'>    /**
+</span>     * @private
+     * Returns true if the innerCt scroll is already at its right-most point
+     * @return {Boolean} True if already at furthest right point
      */
-    scrollToTop : function(){
-        this.setScrollTop(0);
+    atExtremeAfter: function() {
+        return this.getScrollPosition() &gt;= this.getMaxScrollPosition();
     },
-    
-    // synchronize the scroller with the bound gridviews
-    onElScroll: function(event, target) {
-        this.fireEvent('bodyscroll', event, target);
+
+<span id='Ext-layout-container-boxOverflow-Scroller-method-scrollTo'>    /**
+</span>     * @private
+     * Scrolls to the given position. Performs bounds checking.
+     * @param {Number} position The position to scroll to. This is constrained.
+     * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
+     */
+    scrollTo: function(position, animate) {
+        var me = this,
+            layout = me.layout,
+            oldPosition = me.getScrollPosition(),
+            newPosition = Ext.Number.constrain(position, 0, me.getMaxScrollPosition());
+
+        if (newPosition != oldPosition &amp;&amp; !me.scrolling) {
+            if (animate == undefined) {
+                animate = me.animateScroll;
+            }
+
+            layout.innerCt.scrollTo(layout.parallelBefore, newPosition, animate ? me.getScrollAnim() : false);
+            if (animate) {
+                me.scrolling = true;
+            } else {
+                me.scrolling = false;
+                me.updateScrollButtons();
+            }
+            
+            me.fireEvent('scroll', me, newPosition, animate ? me.getScrollAnim() : false);
+        }
     },
 
-    getPanel: function() {
-        var me = this;
-        if (!me.panel) {
-            me.panel = this.up('[scrollerOwner]');
+<span id='Ext-layout-container-boxOverflow-Scroller-method-scrollToItem'>    /**
+</span>     * Scrolls to the given component.
+     * @param {String|Number|Ext.Component} item The item to scroll to. Can be a numerical index, component id 
+     * or a reference to the component itself.
+     * @param {Boolean} animate True to animate the scrolling
+     */
+    scrollToItem: function(item, animate) {
+        var me = this,
+            layout = me.layout,
+            visibility,
+            box,
+            newPos;
+
+        item = me.getItem(item);
+        if (item != undefined) {
+            visibility = this.getItemVisibility(item);
+            if (!visibility.fullyVisible) {
+                box  = item.getBox(true, true);
+                newPos = box[layout.parallelPosition];
+                if (visibility.hiddenEnd) {
+                    newPos -= (this.layout.innerCt['get' + layout.parallelPrefixCap]() - box[layout.parallelPrefix]);
+                }
+                this.scrollTo(newPos, animate);
+            }
         }
-        return me.panel;
-    }
-});
+    },
+
+<span id='Ext-layout-container-boxOverflow-Scroller-method-getItemVisibility'>    /**
+</span>     * @private
+     * For a given item in the container, return an object with information on whether the item is visible
+     * with the current innerCt scroll value.
+     * @param {Ext.Component} item The item
+     * @return {Object} Values for fullyVisible, hiddenStart and hiddenEnd
+     */
+    getItemVisibility: function(item) {
+        var me          = this,
+            box         = me.getItem(item).getBox(true, true),
+            layout      = me.layout,
+            itemStart   = box[layout.parallelPosition],
+            itemEnd     = itemStart + box[layout.parallelPrefix],
+            scrollStart = me.getScrollPosition(),
+            scrollEnd   = scrollStart + layout.innerCt['get' + layout.parallelPrefixCap]();
 
-</pre>
+        return {
+            hiddenStart : itemStart &lt; scrollStart,
+            hiddenEnd   : itemEnd &gt; scrollEnd,
+            fullyVisible: itemStart &gt; scrollStart &amp;&amp; itemEnd &lt; scrollEnd
+        };
+    }
+});</pre>
 </body>
 </html>