/** * @class Ext.grid.Scroller * @extends Ext.Component * * Docked in an Ext.grid.Panel, controls virtualized scrolling and synchronization * across different sections. * * @private */ Ext.define('Ext.grid.Scroller', { extend: 'Ext.Component', alias: 'widget.gridscroller', weight: 110, cls: Ext.baseCSSPrefix + 'scroller', focusable: false, reservedSpace: 0, renderTpl: [ '<div class="' + Ext.baseCSSPrefix + 'scroller-ct" id="{baseId}_ct">', '<div class="' + Ext.baseCSSPrefix + 'stretcher" id="{baseId}_stretch"></div>', '</div>' ], initComponent: function() { var me = this, dock = me.dock, cls = Ext.baseCSSPrefix + 'scroller-vertical', sizeProp = 'width'; me.offsets = {bottom: 0}; me.scrollProp = 'scrollTop'; me.vertical = true; if (dock === 'top' || dock === 'bottom') { cls = Ext.baseCSSPrefix + 'scroller-horizontal'; sizeProp = 'height'; me.scrollProp = 'scrollLeft'; me.vertical = false; me.weight += 5; } me[sizeProp] = me.scrollerSize = Ext.getScrollbarSize()[sizeProp]; me.cls += (' ' + cls); Ext.applyIf(me.renderSelectors, { stretchEl: '.' + Ext.baseCSSPrefix + 'stretcher', scrollEl: '.' + Ext.baseCSSPrefix + 'scroller-ct' }); me.callParent(); }, initRenderData: function () { var me = this, ret = me.callParent(arguments) || {}; ret.baseId = me.id; return ret; }, afterRender: function() { var me = this; me.callParent(); me.mon(me.scrollEl, 'scroll', me.onElScroll, me); Ext.cache[me.el.id].skipGarbageCollection = true; }, onAdded: function(container) { // Capture the controlling grid Panel so that we can use it even when we are undocked, and don't have an ownerCt this.ownerGrid = container; this.callParent(arguments); }, getSizeCalculation: function() { var me = this, owner = me.getPanel(), width = 1, height = 1, view, tbl; if (!me.vertical) { // 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--; } } else { view = owner.down('tableview:not([lockableInjected])'); if (!view || !view.el) { return false; } tbl = view.el.child('table', true); if (!tbl) { return false; } // needs to also account for header and scroller (if still in picture) // should calculate from headerCt. height = tbl.offsetHeight; } if (isNaN(width)) { width = 1; } if (isNaN(height)) { height = 1; } return { width: width, height: height }; }, invalidate: function(firstPass) { var me = this, stretchEl = me.stretchEl; if (!stretchEl || !me.ownerCt) { return; } var size = me.getSizeCalculation(), scrollEl = me.scrollEl, elDom = scrollEl.dom, reservedSpace = me.reservedSpace, pos, extra = 5; if (size) { stretchEl.setSize(size); size = me.el.getSize(true); if (me.vertical) { size.width += extra; size.height -= reservedSpace; pos = 'left'; } else { size.width -= reservedSpace; size.height += extra; pos = 'top'; } scrollEl.setSize(size); elDom.style[pos] = (-extra) + 'px'; // BrowserBug: IE7 // This makes the scroller enabled, when initially rendering. elDom.scrollTop = elDom.scrollTop; } }, afterComponentLayout: function() { this.callParent(arguments); this.invalidate(); }, restoreScrollPos: function () { var me = this, el = this.scrollEl, elDom = el && el.dom; if (me._scrollPos !== null && elDom) { elDom[me.scrollProp] = me._scrollPos; me._scrollPos = null; } }, setReservedSpace: function (reservedSpace) { var me = this; if (me.reservedSpace !== reservedSpace) { me.reservedSpace = reservedSpace; me.invalidate(); } }, saveScrollPos: function () { var me = this, el = this.scrollEl, elDom = el && el.dom; me._scrollPos = elDom ? elDom[me.scrollProp] : null; }, /** * Sets the scrollTop and constrains the value between 0 and max. * @param {Number} scrollTop * @return {Number} The resulting scrollTop value after being constrained */ setScrollTop: function(scrollTop) { var el = this.scrollEl, elDom = el && el.dom; if (elDom) { return elDom.scrollTop = Ext.Number.constrain(scrollTop, 0, elDom.scrollHeight - elDom.clientHeight); } }, /** * Sets the scrollLeft and constrains the value between 0 and max. * @param {Number} scrollLeft * @return {Number} The resulting scrollLeft value after being constrained */ setScrollLeft: function(scrollLeft) { var el = this.scrollEl, elDom = el && el.dom; if (elDom) { return elDom.scrollLeft = Ext.Number.constrain(scrollLeft, 0, elDom.scrollWidth - elDom.clientWidth); } }, /** * Scroll by deltaY * @param {Number} delta * @return {Number} The resulting scrollTop value */ scrollByDeltaY: function(delta) { var el = this.scrollEl, elDom = el && el.dom; if (elDom) { return this.setScrollTop(elDom.scrollTop + delta); } }, /** * Scroll by deltaX * @param {Number} delta * @return {Number} The resulting scrollLeft value */ scrollByDeltaX: function(delta) { var el = this.scrollEl, elDom = el && el.dom; if (elDom) { return this.setScrollLeft(elDom.scrollLeft + delta); } }, /** * Scroll to the top. */ scrollToTop : function(){ this.setScrollTop(0); }, // synchronize the scroller with the bound gridviews onElScroll: function(event, target) { this.fireEvent('bodyscroll', event, target); }, getPanel: function() { var me = this; if (!me.panel) { me.panel = this.up('[scrollerOwner]'); } return me.panel; } });