X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/panel/Table.js diff --git a/src/panel/Table.js b/src/panel/Table.js new file mode 100644 index 00000000..7921fdce --- /dev/null +++ b/src/panel/Table.js @@ -0,0 +1,1202 @@ +/** + * @class Ext.panel.Table + * @extends Ext.panel.Panel + * @xtype tablepanel + * @private + * @author Nicolas Ferrero + * TablePanel is a private class and the basis of both TreePanel and GridPanel. + * + * TablePanel aggregates: + * + * - a Selection Model + * - a View + * - a Store + * - Scrollers + * - Ext.grid.header.Container + * + */ +Ext.define('Ext.panel.Table', { + extend: 'Ext.panel.Panel', + + alias: 'widget.tablepanel', + + uses: [ + 'Ext.selection.RowModel', + 'Ext.grid.Scroller', + 'Ext.grid.header.Container', + 'Ext.grid.Lockable' + ], + + cls: Ext.baseCSSPrefix + 'grid', + extraBodyCls: Ext.baseCSSPrefix + 'grid-body', + + layout: 'fit', + /** + * Boolean to indicate that a view has been injected into the panel. + * @property hasView + */ + hasView: false, + + // each panel should dictate what viewType and selType to use + viewType: null, + selType: 'rowmodel', + + /** + * @cfg {Number} scrollDelta + * Number of pixels to scroll when scrolling with mousewheel. + * Defaults to 40. + */ + scrollDelta: 40, + + /** + * @cfg {String/Boolean} scroll + * Valid values are 'both', 'horizontal' or 'vertical'. true implies 'both'. false implies 'none'. + * Defaults to true. + */ + scroll: true, + + /** + * @cfg {Array} columns + * An array of {@link Ext.grid.column.Column column} definition objects which define all columns that appear in this grid. Each + * column definition provides the header text for the column, and a definition of where the data for that column comes from. + */ + + /** + * @cfg {Boolean} forceFit + * Specify as true to force the columns to fit into the available width. Headers are first sized according to configuration, whether that be + * a specific width, or flex. Then they are all proportionally changed in width so that the entire content width is used.. + */ + + /** + * @cfg {Boolean} hideHeaders + * Specify as true to hide the headers. + */ + + /** + * @cfg {Boolean} sortableColumns + * Defaults to true. Set to false to disable column sorting via clicking the + * header and via the Sorting menu items. + */ + sortableColumns: true, + + verticalScrollDock: 'right', + verticalScrollerType: 'gridscroller', + + horizontalScrollerPresentCls: Ext.baseCSSPrefix + 'horizontal-scroller-present', + verticalScrollerPresentCls: Ext.baseCSSPrefix + 'vertical-scroller-present', + + // private property used to determine where to go down to find views + // this is here to support locking. + scrollerOwner: true, + + invalidateScrollerOnRefresh: true, + + enableColumnMove: true, + enableColumnResize: true, + + + initComponent: function() { + // + if (!this.viewType) { + Ext.Error.raise("You must specify a viewType config."); + } + if (!this.store) { + Ext.Error.raise("You must specify a store config"); + } + if (this.headers) { + Ext.Error.raise("The headers config is not supported. Please specify columns instead."); + } + // + + var me = this, + scroll = me.scroll, + vertical = false, + horizontal = false, + headerCtCfg = me.columns || me.colModel, + i = 0, + view, + border = me.border; + + // Set our determinScrollbars method to reference a buffered call to determinScrollbars which fires on a 30ms buffer. + me.determineScrollbars = Ext.Function.createBuffered(me.determineScrollbars, 30); + me.injectView = Ext.Function.createBuffered(me.injectView, 30); + + if (me.hideHeaders) { + border = false; + } + + // The columns/colModel config may be either a fully instantiated HeaderContainer, or an array of Column definitions, or a config object of a HeaderContainer + // Either way, we extract a columns property referencing an array of Column definitions. + if (headerCtCfg instanceof Ext.grid.header.Container) { + me.headerCt = headerCtCfg; + me.headerCt.border = border; + me.columns = me.headerCt.items.items; + } else { + if (Ext.isArray(headerCtCfg)) { + headerCtCfg = { + items: headerCtCfg, + border: border + }; + } + Ext.apply(headerCtCfg, { + forceFit: me.forceFit, + sortable: me.sortableColumns, + enableColumnMove: me.enableColumnMove, + enableColumnResize: me.enableColumnResize, + border: border + }); + me.columns = headerCtCfg.items; + + // If any of the Column objects contain a locked property, and are not processed, this is a lockable TablePanel, a + // special view will be injected by the Ext.grid.Lockable mixin, so no processing of . + if (Ext.ComponentQuery.query('{locked !== undefined}{processed != true}', me.columns).length) { + me.self.mixin('lockable', Ext.grid.Lockable); + me.injectLockable(); + } + } + + me.store = Ext.data.StoreManager.lookup(me.store); + me.addEvents( + /** + * @event scrollerhide + * Fires when a scroller is hidden + * @param {Ext.grid.Scroller} scroller + * @param {String} orientation Orientation, can be 'vertical' or 'horizontal' + */ + 'scrollerhide', + /** + * @event scrollershow + * Fires when a scroller is shown + * @param {Ext.grid.Scroller} scroller + * @param {String} orientation Orientation, can be 'vertical' or 'horizontal' + */ + 'scrollershow' + ); + + me.bodyCls = me.bodyCls || ''; + me.bodyCls += (' ' + me.extraBodyCls); + + // autoScroll is not a valid configuration + delete me.autoScroll; + + // If this TablePanel is lockable (Either configured lockable, or any of the defined columns has a 'locked' property) + // than a special lockable view containing 2 side-by-side grids will have been injected so we do not need to set up any UI. + if (!me.hasView) { + + // If we were not configured with a ready-made headerCt (either by direct config with a headerCt property, or by passing + // a HeaderContainer instance as the 'columns' property, then go ahead and create one from the config object created above. + if (!me.headerCt) { + me.headerCt = Ext.create('Ext.grid.header.Container', headerCtCfg); + } + + // Extract the array of Column objects + me.columns = me.headerCt.items.items; + + if (me.hideHeaders) { + me.headerCt.height = 0; + me.headerCt.border = false; + me.headerCt.addCls(Ext.baseCSSPrefix + 'grid-header-ct-hidden'); + me.addCls(Ext.baseCSSPrefix + 'grid-header-hidden'); + // IE Quirks Mode fix + // If hidden configuration option was used, several layout calculations will be bypassed. + if (Ext.isIEQuirks) { + me.headerCt.style = { + display: 'none' + }; + } + } + + // turn both on. + if (scroll === true || scroll === 'both') { + vertical = horizontal = true; + } else if (scroll === 'horizontal') { + horizontal = true; + } else if (scroll === 'vertical') { + vertical = true; + // All other values become 'none' or false. + } else { + me.headerCt.availableSpaceOffset = 0; + } + + if (vertical) { + me.verticalScroller = me.verticalScroller || {}; + Ext.applyIf(me.verticalScroller, { + dock: me.verticalScrollDock, + xtype: me.verticalScrollerType, + store: me.store + }); + me.verticalScroller = Ext.ComponentManager.create(me.verticalScroller); + me.mon(me.verticalScroller, { + bodyscroll: me.onVerticalScroll, + scope: me + }); + } + + if (horizontal) { + me.horizontalScroller = Ext.ComponentManager.create({ + xtype: 'gridscroller', + section: me, + dock: 'bottom', + store: me.store + }); + me.mon(me.horizontalScroller, { + bodyscroll: me.onHorizontalScroll, + scope: me + }); + } + + me.headerCt.on('columnresize', me.onHeaderResize, me); + me.relayEvents(me.headerCt, ['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange']); + me.features = me.features || []; + me.dockedItems = me.dockedItems || []; + me.dockedItems.unshift(me.headerCt); + me.viewConfig = me.viewConfig || {}; + me.viewConfig.invalidateScrollerOnRefresh = me.invalidateScrollerOnRefresh; + + // AbstractDataView will look up a Store configured as an object + // getView converts viewConfig into a View instance + view = me.getView(); + + if (view) { + me.mon(view.store, { + load: me.onStoreLoad, + scope: me + }); + me.mon(view, { + refresh: { + fn: this.onViewRefresh, + scope: me, + buffer: 50 + }, + itemupdate: me.onViewItemUpdate, + scope: me + }); + this.relayEvents(view, [ + /** + * @event beforeitemmousedown + * Fires before the mousedown event on an item is processed. Returns false to cancel the default action. + * @param {Ext.view.View} this + * @param {Ext.data.Model} record The record that belongs to the item + * @param {HTMLElement} item The item's element + * @param {Number} index The item's index + * @param {Ext.EventObject} e The raw event object + */ + 'beforeitemmousedown', + /** + * @event beforeitemmouseup + * Fires before the mouseup event on an item is processed. Returns false to cancel the default action. + * @param {Ext.view.View} this + * @param {Ext.data.Model} record The record that belongs to the item + * @param {HTMLElement} item The item's element + * @param {Number} index The item's index + * @param {Ext.EventObject} e The raw event object + */ + 'beforeitemmouseup', + /** + * @event beforeitemmouseenter + * Fires before the mouseenter event on an item is processed. Returns false to cancel the default action. + * @param {Ext.view.View} this + * @param {Ext.data.Model} record The record that belongs to the item + * @param {HTMLElement} item The item's element + * @param {Number} index The item's index + * @param {Ext.EventObject} e The raw event object + */ + 'beforeitemmouseenter', + /** + * @event beforeitemmouseleave + * Fires before the mouseleave event on an item is processed. Returns false to cancel the default action. + * @param {Ext.view.View} this + * @param {Ext.data.Model} record The record that belongs to the item + * @param {HTMLElement} item The item's element + * @param {Number} index The item's index + * @param {Ext.EventObject} e The raw event object + */ + 'beforeitemmouseleave', + /** + * @event beforeitemclick + * Fires before the click event on an item is processed. Returns false to cancel the default action. + * @param {Ext.view.View} this + * @param {Ext.data.Model} record The record that belongs to the item + * @param {HTMLElement} item The item's element + * @param {Number} index The item's index + * @param {Ext.EventObject} e The raw event object + */ + 'beforeitemclick', + /** + * @event beforeitemdblclick + * Fires before the dblclick event on an item is processed. Returns false to cancel the default action. + * @param {Ext.view.View} this + * @param {Ext.data.Model} record The record that belongs to the item + * @param {HTMLElement} item The item's element + * @param {Number} index The item's index + * @param {Ext.EventObject} e The raw event object + */ + 'beforeitemdblclick', + /** + * @event beforeitemcontextmenu + * Fires before the contextmenu event on an item is processed. Returns false to cancel the default action. + * @param {Ext.view.View} this + * @param {Ext.data.Model} record The record that belongs to the item + * @param {HTMLElement} item The item's element + * @param {Number} index The item's index + * @param {Ext.EventObject} e The raw event object + */ + 'beforeitemcontextmenu', + /** + * @event itemmousedown + * Fires when there is a mouse down on an item + * @param {Ext.view.View} this + * @param {Ext.data.Model} record The record that belongs to the item + * @param {HTMLElement} item The item's element + * @param {Number} index The item's index + * @param {Ext.EventObject} e The raw event object + */ + 'itemmousedown', + /** + * @event itemmouseup + * Fires when there is a mouse up on an item + * @param {Ext.view.View} this + * @param {Ext.data.Model} record The record that belongs to the item + * @param {HTMLElement} item The item's element + * @param {Number} index The item's index + * @param {Ext.EventObject} e The raw event object + */ + 'itemmouseup', + /** + * @event itemmouseenter + * Fires when the mouse enters an item. + * @param {Ext.view.View} this + * @param {Ext.data.Model} record The record that belongs to the item + * @param {HTMLElement} item The item's element + * @param {Number} index The item's index + * @param {Ext.EventObject} e The raw event object + */ + 'itemmouseenter', + /** + * @event itemmouseleave + * Fires when the mouse leaves an item. + * @param {Ext.view.View} this + * @param {Ext.data.Model} record The record that belongs to the item + * @param {HTMLElement} item The item's element + * @param {Number} index The item's index + * @param {Ext.EventObject} e The raw event object + */ + 'itemmouseleave', + /** + * @event itemclick + * Fires when an item is clicked. + * @param {Ext.view.View} this + * @param {Ext.data.Model} record The record that belongs to the item + * @param {HTMLElement} item The item's element + * @param {Number} index The item's index + * @param {Ext.EventObject} e The raw event object + */ + 'itemclick', + /** + * @event itemdblclick + * Fires when an item is double clicked. + * @param {Ext.view.View} this + * @param {Ext.data.Model} record The record that belongs to the item + * @param {HTMLElement} item The item's element + * @param {Number} index The item's index + * @param {Ext.EventObject} e The raw event object + */ + 'itemdblclick', + /** + * @event itemcontextmenu + * Fires when an item is right clicked. + * @param {Ext.view.View} this + * @param {Ext.data.Model} record The record that belongs to the item + * @param {HTMLElement} item The item's element + * @param {Number} index The item's index + * @param {Ext.EventObject} e The raw event object + */ + 'itemcontextmenu', + /** + * @event beforecontainermousedown + * Fires before the mousedown event on the container is processed. Returns false to cancel the default action. + * @param {Ext.view.View} this + * @param {Ext.EventObject} e The raw event object + */ + 'beforecontainermousedown', + /** + * @event beforecontainermouseup + * Fires before the mouseup event on the container is processed. Returns false to cancel the default action. + * @param {Ext.view.View} this + * @param {Ext.EventObject} e The raw event object + */ + 'beforecontainermouseup', + /** + * @event beforecontainermouseover + * Fires before the mouseover event on the container is processed. Returns false to cancel the default action. + * @param {Ext.view.View} this + * @param {Ext.EventObject} e The raw event object + */ + 'beforecontainermouseover', + /** + * @event beforecontainermouseout + * Fires before the mouseout event on the container is processed. Returns false to cancel the default action. + * @param {Ext.view.View} this + * @param {Ext.EventObject} e The raw event object + */ + 'beforecontainermouseout', + /** + * @event beforecontainerclick + * Fires before the click event on the container is processed. Returns false to cancel the default action. + * @param {Ext.view.View} this + * @param {Ext.EventObject} e The raw event object + */ + 'beforecontainerclick', + /** + * @event beforecontainerdblclick + * Fires before the dblclick event on the container is processed. Returns false to cancel the default action. + * @param {Ext.view.View} this + * @param {Ext.EventObject} e The raw event object + */ + 'beforecontainerdblclick', + /** + * @event beforecontainercontextmenu + * Fires before the contextmenu event on the container is processed. Returns false to cancel the default action. + * @param {Ext.view.View} this + * @param {Ext.EventObject} e The raw event object + */ + 'beforecontainercontextmenu', + /** + * @event containermouseup + * Fires when there is a mouse up on the container + * @param {Ext.view.View} this + * @param {Ext.EventObject} e The raw event object + */ + 'containermouseup', + /** + * @event containermouseover + * Fires when you move the mouse over the container. + * @param {Ext.view.View} this + * @param {Ext.EventObject} e The raw event object + */ + 'containermouseover', + /** + * @event containermouseout + * Fires when you move the mouse out of the container. + * @param {Ext.view.View} this + * @param {Ext.EventObject} e The raw event object + */ + 'containermouseout', + /** + * @event containerclick + * Fires when the container is clicked. + * @param {Ext.view.View} this + * @param {Ext.EventObject} e The raw event object + */ + 'containerclick', + /** + * @event containerdblclick + * Fires when the container is double clicked. + * @param {Ext.view.View} this + * @param {Ext.EventObject} e The raw event object + */ + 'containerdblclick', + /** + * @event containercontextmenu + * Fires when the container is right clicked. + * @param {Ext.view.View} this + * @param {Ext.EventObject} e The raw event object + */ + 'containercontextmenu', + + /** + * @event selectionchange + * Fires when the selected nodes change. Relayed event from the underlying selection model. + * @param {Ext.view.View} this + * @param {Array} selections Array of the selected nodes + */ + 'selectionchange', + /** + * @event beforeselect + * Fires before a selection is made. If any handlers return false, the selection is cancelled. + * @param {Ext.view.View} this + * @param {HTMLElement} node The node to be selected + * @param {Array} selections Array of currently selected nodes + */ + 'beforeselect' + ]); + } + } + me.callParent(arguments); + }, + + // state management + initStateEvents: function(){ + var events = this.stateEvents; + // push on stateEvents if they don't exist + Ext.each(['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange'], function(event){ + if (Ext.Array.indexOf(events, event)) { + events.push(event); + } + }); + this.callParent(); + }, + + getState: function(){ + var state = { + columns: [] + }, + sorter = this.store.sorters.first(); + + this.headerCt.items.each(function(header){ + state.columns.push({ + id: header.headerId, + width: header.flex ? undefined : header.width, + hidden: header.hidden, + sortable: header.sortable + }); + }); + + if (sorter) { + state.sort = { + property: sorter.property, + direction: sorter.direction + }; + } + return state; + }, + + applyState: function(state) { + var headers = state.columns, + length = headers ? headers.length : 0, + headerCt = this.headerCt, + items = headerCt.items, + sorter = state.sort, + store = this.store, + i = 0, + index, + headerState, + header; + + for (; i < length; ++i) { + headerState = headers[i]; + header = headerCt.down('gridcolumn[headerId=' + headerState.id + ']'); + index = items.indexOf(header); + if (i !== index) { + headerCt.moveHeader(index, i); + } + header.sortable = headerState.sortable; + if (Ext.isDefined(headerState.width)) { + delete header.flex; + if (header.rendered) { + header.setWidth(headerState.width); + } else { + header.minWidth = header.width = headerState.width; + } + } + header.hidden = headerState.hidden; + } + + if (sorter) { + if (store.remoteSort) { + store.sorters.add(Ext.create('Ext.util.Sorter', { + property: sorter.property, + direction: sorter.direction + })); + } + else { + store.sort(sorter.property, sorter.direction); + } + } + }, + + /** + * Returns the store associated with this Panel. + * @return {Ext.data.Store} The store + */ + getStore: function(){ + return this.store; + }, + + /** + * Gets the view for this panel. + * @return {Ext.view.Table} + */ + getView: function() { + var me = this, + sm; + + if (!me.view) { + sm = me.getSelectionModel(); + me.view = me.createComponent(Ext.apply({}, me.viewConfig, { + xtype: me.viewType, + store: me.store, + headerCt: me.headerCt, + selModel: sm, + features: me.features, + panel: me + })); + me.mon(me.view, { + uievent: me.processEvent, + scope: me + }); + sm.view = me.view; + me.headerCt.view = me.view; + me.relayEvents(me.view, ['cellclick', 'celldblclick']); + } + return me.view; + }, + + /** + * @private + * @override + * autoScroll is never valid for all classes which extend TablePanel. + */ + setAutoScroll: Ext.emptyFn, + + // This method hijacks Ext.view.Table's el scroll method. + // This enables us to keep the virtualized scrollbars in sync + // with the view. It currently does NOT support animation. + elScroll: function(direction, distance, animate) { + var me = this, + scroller; + + if (direction === "up" || direction === "left") { + distance = -distance; + } + + if (direction === "down" || direction === "up") { + scroller = me.getVerticalScroller(); + scroller.scrollByDeltaY(distance); + } else { + scroller = me.getHorizontalScroller(); + scroller.scrollByDeltaX(distance); + } + }, + + afterLayout: function() { + this.callParent(arguments); + this.injectView(); + }, + + + /** + * @private + * Called after this Component has achieved its correct initial size, after all layouts have done their thing. + * This is so we can add the View only after the initial size is known. This method is buffered 30ms. + */ + injectView: function() { + if (!this.hasView && !this.collapsed) { + var me = this, + view = me.getView(); + + me.hasView = true; + me.add(view); + + // hijack the view el's scroll method + view.el.scroll = Ext.Function.bind(me.elScroll, me); + // We use to listen to document.body wheel events, but that's a + // little much. We scope just to the view now. + me.mon(view.el, { + mousewheel: me.onMouseWheel, + scope: me + }); + } + }, + + afterExpand: function() { + this.callParent(arguments); + if (!this.hasView) { + this.injectView(); + } + }, + + /** + * @private + * Process UI events from the view. Propagate them to whatever internal Components need to process them + * @param {String} type Event type, eg 'click' + * @param {TableView} view TableView Component + * @param {HtmlElement} cell Cell HtmlElement the event took place within + * @param {Number} recordIndex Index of the associated Store Model (-1 if none) + * @param {Number} cellIndex Cell index within the row + * @param {EventObject} e Original event + */ + processEvent: function(type, view, cell, recordIndex, cellIndex, e) { + var me = this, + header; + + if (cellIndex !== -1) { + header = me.headerCt.getGridColumns()[cellIndex]; + return header.processEvent.apply(header, arguments); + } + }, + + /** + * Request a recalculation of scrollbars and put them in if they are needed. + */ + determineScrollbars: function() { + var me = this, + viewElDom, + centerScrollWidth, + centerClientWidth, + scrollHeight, + clientHeight; + + if (!me.collapsed && me.view && me.view.el) { + viewElDom = me.view.el.dom; + //centerScrollWidth = viewElDom.scrollWidth; + centerScrollWidth = me.headerCt.getFullWidth(); + /** + * clientWidth often returns 0 in IE resulting in an + * infinity result, here we use offsetWidth bc there are + * no possible scrollbars and we don't care about margins + */ + centerClientWidth = viewElDom.offsetWidth; + if (me.verticalScroller && me.verticalScroller.el) { + scrollHeight = me.verticalScroller.getSizeCalculation().height; + } else { + scrollHeight = viewElDom.scrollHeight; + } + + clientHeight = viewElDom.clientHeight; + + if (!me.collapsed && scrollHeight > clientHeight) { + me.showVerticalScroller(); + } else { + me.hideVerticalScroller(); + } + + if (!me.collapsed && centerScrollWidth > (centerClientWidth + Ext.getScrollBarWidth() - 2)) { + me.showHorizontalScroller(); + } else { + me.hideHorizontalScroller(); + } + } + }, + + onHeaderResize: function() { + if (this.view && this.view.rendered) { + this.determineScrollbars(); + this.invalidateScroller(); + } + }, + + /** + * Hide the verticalScroller and remove the horizontalScrollerPresentCls. + */ + hideHorizontalScroller: function() { + var me = this; + + if (me.horizontalScroller && me.horizontalScroller.ownerCt === me) { + me.verticalScroller.offsets.bottom = 0; + me.removeDocked(me.horizontalScroller, false); + me.removeCls(me.horizontalScrollerPresentCls); + me.fireEvent('scrollerhide', me.horizontalScroller, 'horizontal'); + } + + }, + + /** + * Show the horizontalScroller and add the horizontalScrollerPresentCls. + */ + showHorizontalScroller: function() { + var me = this; + + if (me.verticalScroller) { + me.verticalScroller.offsets.bottom = Ext.getScrollBarWidth() - 2; + } + if (me.horizontalScroller && me.horizontalScroller.ownerCt !== me) { + me.addDocked(me.horizontalScroller); + me.addCls(me.horizontalScrollerPresentCls); + me.fireEvent('scrollershow', me.horizontalScroller, 'horizontal'); + } + }, + + /** + * Hide the verticalScroller and remove the verticalScrollerPresentCls. + */ + hideVerticalScroller: function() { + var me = this, + headerCt = me.headerCt; + + // only trigger a layout when reserveOffset is changing + if (headerCt && headerCt.layout.reserveOffset) { + headerCt.layout.reserveOffset = false; + headerCt.doLayout(); + } + if (me.verticalScroller && me.verticalScroller.ownerCt === me) { + me.removeDocked(me.verticalScroller, false); + me.removeCls(me.verticalScrollerPresentCls); + me.fireEvent('scrollerhide', me.verticalScroller, 'vertical'); + } + }, + + /** + * Show the verticalScroller and add the verticalScrollerPresentCls. + */ + showVerticalScroller: function() { + var me = this, + headerCt = me.headerCt; + + // only trigger a layout when reserveOffset is changing + if (headerCt && !headerCt.layout.reserveOffset) { + headerCt.layout.reserveOffset = true; + headerCt.doLayout(); + } + if (me.verticalScroller && me.verticalScroller.ownerCt !== me) { + me.addDocked(me.verticalScroller); + me.addCls(me.verticalScrollerPresentCls); + me.fireEvent('scrollershow', me.verticalScroller, 'vertical'); + } + }, + + /** + * Invalides scrollers that are present and forces a recalculation. + * (Not related to showing/hiding the scrollers) + */ + invalidateScroller: function() { + var me = this, + vScroll = me.verticalScroller, + hScroll = me.horizontalScroller; + + if (vScroll) { + vScroll.invalidate(); + } + if (hScroll) { + hScroll.invalidate(); + } + }, + + // refresh the view when a header moves + onHeaderMove: function(headerCt, header, fromIdx, toIdx) { + this.view.refresh(); + }, + + // Section onHeaderHide is invoked after view. + onHeaderHide: function(headerCt, header) { + this.invalidateScroller(); + }, + + onHeaderShow: function(headerCt, header) { + this.invalidateScroller(); + }, + + getVerticalScroller: function() { + return this.getScrollerOwner().down('gridscroller[dock=' + this.verticalScrollDock + ']'); + }, + + getHorizontalScroller: function() { + return this.getScrollerOwner().down('gridscroller[dock=bottom]'); + }, + + onMouseWheel: function(e) { + var me = this, + browserEvent = e.browserEvent, + vertScroller = me.getVerticalScroller(), + horizScroller = me.getHorizontalScroller(), + scrollDelta = me.scrollDelta, + deltaY, deltaX, + vertScrollerEl, horizScrollerEl, + origScrollLeft, origScrollTop, + newScrollLeft, newScrollTop; + + // Track original scroll values, so we can see if we've + // reached the end of our scroll height/width. + if (horizScroller) { + horizScrollerEl = horizScroller.el; + if (horizScrollerEl) { + origScrollLeft = horizScrollerEl.dom.scrollLeft; + } + } + if (vertScroller) { + vertScrollerEl = vertScroller.el; + if (vertScrollerEl) { + origScrollTop = vertScrollerEl.dom.scrollTop; + } + } + + // Webkit Horizontal Axis + if (browserEvent.wheelDeltaX || browserEvent.wheelDeltaY) { + deltaX = -browserEvent.wheelDeltaX / 120 * scrollDelta / 3; + deltaY = -browserEvent.wheelDeltaY / 120 * scrollDelta / 3; + if (horizScroller) { + newScrollLeft = horizScroller.scrollByDeltaX(deltaX); + } + if (vertScroller) { + newScrollTop = vertScroller.scrollByDeltaY(deltaY); + } + } else { + // Gecko Horizontal Axis + if (browserEvent.axis && browserEvent.axis === 1) { + if (horizScroller) { + deltaX = -(scrollDelta * e.getWheelDelta()) / 3; + newScrollLeft = horizScroller.scrollByDeltaX(deltaX); + } + } else { + if (vertScroller) { + + deltaY = -(scrollDelta * e.getWheelDelta() / 3); + newScrollTop = vertScroller.scrollByDeltaY(deltaY); + } + } + } + + // If after given our delta, the scroller has not progressed, then we're + // at the end of our scroll range and shouldn't stop the browser event. + if ((deltaX !== 0 && newScrollLeft !== origScrollLeft) || + (deltaY !== 0 && newScrollTop !== origScrollTop)) { + e.stopEvent(); + } + }, + + /** + * @private + * Determine and invalidate scrollers on view refresh + */ + onViewRefresh: function() { + if (Ext.isIE) { + this.syncCellHeight(); + } + this.determineScrollbars(); + if (this.invalidateScrollerOnRefresh) { + this.invalidateScroller(); + } + }, + + onViewItemUpdate: function(record, index, tr) { + if (Ext.isIE) { + this.syncCellHeight([tr]); + } + }, + + // BrowserBug: IE will not stretch the td to fit the height of the entire + // tr, so manually sync cellheights on refresh and when an item has been + // updated. + syncCellHeight: function(trs) { + var me = this, + i = 0, + tds, + j, tdsLn, + tr, td, + trsLn, + rowHeights = [], + cellHeights, + cellClsSelector = ('.' + Ext.baseCSSPrefix + 'grid-cell'); + + trs = trs || me.view.getNodes(); + + trsLn = trs.length; + // Reading loop + for (; i < trsLn; i++) { + tr = trs[i]; + tds = Ext.fly(tr).query(cellClsSelector); + tdsLn = tds.length; + cellHeights = []; + for (j = 0; j < tdsLn; j++) { + td = tds[j]; + cellHeights.push(td.clientHeight); + } + rowHeights.push(Ext.Array.max(cellHeights)); + } + + // Setting loop + for (i = 0; i < trsLn; i++) { + tr = trs[i]; + tdsLn = tr.childNodes.length; + for (j = 0; j < tdsLn; j++) { + td = Ext.fly(tr.childNodes[j]); + if (rowHeights[i]) { + if (td.is(cellClsSelector)) { + td.setHeight(rowHeights[i]); + } else { + td.down(cellClsSelector).setHeight(rowHeights[i]); + } + } + + } + } + }, + + /** + * Sets the scrollTop of the TablePanel. + * @param {Number} deltaY + */ + setScrollTop: function(top) { + var me = this, + rootCmp = me.getScrollerOwner(), + verticalScroller = me.getVerticalScroller(); + + rootCmp.virtualScrollTop = top; + if (verticalScroller) { + verticalScroller.setScrollTop(top); + } + + }, + + getScrollerOwner: function() { + var rootCmp = this; + if (!this.scrollerOwner) { + rootCmp = this.up('[scrollerOwner]'); + } + return rootCmp; + }, + + /** + * Scrolls the TablePanel by deltaY + * @param {Number} deltaY + */ + scrollByDeltaY: function(deltaY) { + var rootCmp = this.getScrollerOwner(), + scrollerRight; + scrollerRight = rootCmp.down('gridscroller[dock=' + this.verticalScrollDock + ']'); + if (scrollerRight) { + scrollerRight.scrollByDeltaY(deltaY); + } + }, + + + /** + * Scrolls the TablePanel by deltaX + * @param {Number} deltaY + */ + scrollByDeltaX: function(deltaX) { + this.horizontalScroller.scrollByDeltaX(deltaX); + }, + + /** + * Get left hand side marker for header resizing. + * @private + */ + getLhsMarker: function() { + var me = this; + + if (!me.lhsMarker) { + me.lhsMarker = Ext.core.DomHelper.append(me.el, { + cls: Ext.baseCSSPrefix + 'grid-resize-marker' + }, true); + } + return me.lhsMarker; + }, + + /** + * Get right hand side marker for header resizing. + * @private + */ + getRhsMarker: function() { + var me = this; + + if (!me.rhsMarker) { + me.rhsMarker = Ext.core.DomHelper.append(me.el, { + cls: Ext.baseCSSPrefix + 'grid-resize-marker' + }, true); + } + return me.rhsMarker; + }, + + /** + * Returns the selection model being used and creates it via the configuration + * if it has not been created already. + * @return {Ext.selection.Model} selModel + */ + getSelectionModel: function(){ + if (!this.selModel) { + this.selModel = {}; + } + + var mode = 'SINGLE', + type; + if (this.simpleSelect) { + mode = 'SIMPLE'; + } else if (this.multiSelect) { + mode = 'MULTI'; + } + + Ext.applyIf(this.selModel, { + allowDeselect: this.allowDeselect, + mode: mode + }); + + if (!this.selModel.events) { + type = this.selModel.selType || this.selType; + this.selModel = Ext.create('selection.' + type, this.selModel); + } + + if (!this.selModel.hasRelaySetup) { + this.relayEvents(this.selModel, ['selectionchange', 'select', 'deselect']); + this.selModel.hasRelaySetup = true; + } + + // lock the selection model if user + // has disabled selection + if (this.disableSelection) { + this.selModel.locked = true; + } + return this.selModel; + }, + + onVerticalScroll: function(event, target) { + var owner = this.getScrollerOwner(), + items = owner.query('tableview'), + i = 0, + len = items.length; + + for (; i < len; i++) { + items[i].el.dom.scrollTop = target.scrollTop; + } + }, + + onHorizontalScroll: function(event, target) { + var owner = this.getScrollerOwner(), + items = owner.query('tableview'), + i = 0, + len = items.length, + center, + centerEl, + centerScrollWidth, + centerClientWidth, + width; + + center = items[1] || items[0]; + centerEl = center.el.dom; + centerScrollWidth = centerEl.scrollWidth; + centerClientWidth = centerEl.offsetWidth; + width = this.horizontalScroller.getWidth(); + + centerEl.scrollLeft = target.scrollLeft; + this.headerCt.el.dom.scrollLeft = target.scrollLeft; + }, + + // template method meant to be overriden + onStoreLoad: Ext.emptyFn, + + getEditorParent: function() { + return this.body; + }, + + bindStore: function(store) { + var me = this; + me.store = store; + me.getView().bindStore(store); + }, + + reconfigure: function(store, columns) { + var me = this; + + if (me.lockable) { + me.reconfigureLockable(store, columns); + return; + } + + if (columns) { + me.headerCt.removeAll(); + me.headerCt.add(columns); + } + if (store) { + store = Ext.StoreManager.lookup(store); + me.bindStore(store); + } else { + me.getView().refresh(); + } + }, + + afterComponentLayout: function() { + this.callParent(arguments); + this.determineScrollbars(); + this.invalidateScroller(); + } +}); \ No newline at end of file