+ /**
+ * Called to get grid's drag proxy text, by default returns this.ddText.
+ * @return {String} The text
+ */
+ getDragDropText : function(){
+ var count = this.selModel.getCount();
+ return String.format(this.ddText, count, count == 1 ? '' : 's');
+ }
+
+ /**
+ * @cfg {String/Number} activeItem
+ * @hide
+ */
+ /**
+ * @cfg {Boolean} autoDestroy
+ * @hide
+ */
+ /**
+ * @cfg {Object/String/Function} autoLoad
+ * @hide
+ */
+ /**
+ * @cfg {Boolean} autoWidth
+ * @hide
+ */
+ /**
+ * @cfg {Boolean/Number} bufferResize
+ * @hide
+ */
+ /**
+ * @cfg {String} defaultType
+ * @hide
+ */
+ /**
+ * @cfg {Object} defaults
+ * @hide
+ */
+ /**
+ * @cfg {Boolean} hideBorders
+ * @hide
+ */
+ /**
+ * @cfg {Mixed} items
+ * @hide
+ */
+ /**
+ * @cfg {String} layout
+ * @hide
+ */
+ /**
+ * @cfg {Object} layoutConfig
+ * @hide
+ */
+ /**
+ * @cfg {Boolean} monitorResize
+ * @hide
+ */
+ /**
+ * @property items
+ * @hide
+ */
+ /**
+ * @method add
+ * @hide
+ */
+ /**
+ * @method cascade
+ * @hide
+ */
+ /**
+ * @method doLayout
+ * @hide
+ */
+ /**
+ * @method find
+ * @hide
+ */
+ /**
+ * @method findBy
+ * @hide
+ */
+ /**
+ * @method findById
+ * @hide
+ */
+ /**
+ * @method findByType
+ * @hide
+ */
+ /**
+ * @method getComponent
+ * @hide
+ */
+ /**
+ * @method getLayout
+ * @hide
+ */
+ /**
+ * @method getUpdater
+ * @hide
+ */
+ /**
+ * @method insert
+ * @hide
+ */
+ /**
+ * @method load
+ * @hide
+ */
+ /**
+ * @method remove
+ * @hide
+ */
+ /**
+ * @event add
+ * @hide
+ */
+ /**
+ * @event afterlayout
+ * @hide
+ */
+ /**
+ * @event beforeadd
+ * @hide
+ */
+ /**
+ * @event beforeremove
+ * @hide
+ */
+ /**
+ * @event remove
+ * @hide
+ */
+
+
+
+ /**
+ * @cfg {String} allowDomMove @hide
+ */
+ /**
+ * @cfg {String} autoEl @hide
+ */
+ /**
+ * @cfg {String} applyTo @hide
+ */
+ /**
+ * @cfg {String} autoScroll @hide
+ */
+ /**
+ * @cfg {String} bodyBorder @hide
+ */
+ /**
+ * @cfg {String} bodyStyle @hide
+ */
+ /**
+ * @cfg {String} contentEl @hide
+ */
+ /**
+ * @cfg {String} disabledClass @hide
+ */
+ /**
+ * @cfg {String} elements @hide
+ */
+ /**
+ * @cfg {String} html @hide
+ */
+ /**
+ * @cfg {Boolean} preventBodyReset
+ * @hide
+ */
+ /**
+ * @property disabled
+ * @hide
+ */
+ /**
+ * @method applyToMarkup
+ * @hide
+ */
+ /**
+ * @method enable
+ * @hide
+ */
+ /**
+ * @method disable
+ * @hide
+ */
+ /**
+ * @method setDisabled
+ * @hide
+ */
+});
+Ext.reg('grid', Ext.grid.GridPanel);/**
+ * @class Ext.grid.PivotGrid
+ * @extends Ext.grid.GridPanel
+ * <p>The PivotGrid component enables rapid summarization of large data sets. It provides a way to reduce a large set of
+ * data down into a format where trends and insights become more apparent. A classic example is in sales data; a company
+ * will often have a record of all sales it makes for a given period - this will often encompass thousands of rows of
+ * data. The PivotGrid allows you to see how well each salesperson performed, which cities generate the most revenue,
+ * how products perform between cities and so on.</p>
+ * <p>A PivotGrid is composed of two axes (left and top), one {@link #measure} and one {@link #aggregator aggregation}
+ * function. Each axis can contain one or more {@link #dimension}, which are ordered into a hierarchy. Dimensions on the
+ * left axis can also specify a width. Each dimension in each axis can specify its sort ordering, defaulting to "ASC",
+ * and must specify one of the fields in the {@link Ext.data.Record Record} used by the PivotGrid's
+ * {@link Ext.data.Store Store}.</p>
+<pre><code>
+// This is the record representing a single sale
+var SaleRecord = Ext.data.Record.create([
+ {name: 'person', type: 'string'},
+ {name: 'product', type: 'string'},
+ {name: 'city', type: 'string'},
+ {name: 'state', type: 'string'},
+ {name: 'year', type: 'int'},
+ {name: 'value', type: 'int'}
+]);
+
+// A simple store that loads SaleRecord data from a url
+var myStore = new Ext.data.Store({
+ url: 'data.json',
+ autoLoad: true,
+ reader: new Ext.data.JsonReader({
+ root: 'rows',
+ idProperty: 'id'
+ }, SaleRecord)
+});
+
+// Create the PivotGrid itself, referencing the store
+var pivot = new Ext.grid.PivotGrid({
+ store : myStore,
+ aggregator: 'sum',
+ measure : 'value',
+
+ leftAxis: [
+ {
+ width: 60,
+ dataIndex: 'product'
+ },
+ {
+ width: 120,
+ dataIndex: 'person',
+ direction: 'DESC'
+ }
+ ],
+
+ topAxis: [
+ {
+ dataIndex: 'year'
+ }
+ ]
+});
+</code></pre>
+ * <p>The specified {@link #measure} is the field from SaleRecord that is extracted from each combination
+ * of product and person (on the left axis) and year on the top axis. There may be several SaleRecords in the
+ * data set that share this combination, so an array of measure fields is produced. This array is then
+ * aggregated using the {@link #aggregator} function.</p>
+ * <p>The default aggregator function is sum, which simply adds up all of the extracted measure values. Other
+ * built-in aggregator functions are count, avg, min and max. In addition, you can specify your own function.
+ * In this example we show the code used to sum the measures, but you can return any value you like. See
+ * {@link #aggregator} for more details.</p>
+<pre><code>
+new Ext.grid.PivotGrid({
+ aggregator: function(records, measure) {
+ var length = records.length,
+ total = 0,
+ i;
+
+ for (i = 0; i < length; i++) {
+ total += records[i].get(measure);
+ }
+
+ return total;
+ },
+
+ renderer: function(value) {
+ return Math.round(value);
+ },
+
+ //your normal config here
+});
+</code></pre>
+ * <p><u>Renderers</u></p>
+ * <p>PivotGrid optionally accepts a {@link #renderer} function which can modify the data in each cell before it
+ * is rendered. The renderer is passed the value that would usually be placed in the cell and is expected to return
+ * the new value. For example let's imagine we had height data expressed as a decimal - here's how we might use a
+ * renderer to display the data in feet and inches notation:</p>
+<pre><code>
+new Ext.grid.PivotGrid({
+ //in each case the value is a decimal number of feet
+ renderer : function(value) {
+ var feet = Math.floor(value),
+ inches = Math.round((value - feet) * 12);
+
+ return String.format("{0}' {1}\"", feet, inches);
+ },
+ //normal config here
+});
+</code></pre>
+ * <p><u>Reconfiguring</u></p>
+ * <p>All aspects PivotGrid's configuration can be updated at runtime. It is easy to change the {@link #setMeasure measure},
+ * {@link #setAggregator aggregation function}, {@link #setLeftAxis left} and {@link #setTopAxis top} axes and refresh the grid.</p>
+ * <p>In this case we reconfigure the PivotGrid to have city and year as the top axis dimensions, rendering the average sale
+ * value into the cells:</p>
+<pre><code>
+//the left axis can also be changed
+pivot.topAxis.setDimensions([
+ {dataIndex: 'city', direction: 'DESC'},
+ {dataIndex: 'year', direction: 'ASC'}
+]);
+
+pivot.setMeasure('value');
+pivot.setAggregator('avg');
+
+pivot.view.refresh(true);
+</code></pre>
+ * <p>See the {@link Ext.grid.PivotAxis PivotAxis} documentation for further detail on reconfiguring axes.</p>
+ */
+Ext.grid.PivotGrid = Ext.extend(Ext.grid.GridPanel, {
+
+ /**
+ * @cfg {String|Function} aggregator The aggregation function to use to combine the measures extracted
+ * for each dimension combination. Can be any of the built-in aggregators (sum, count, avg, min, max).
+ * Can also be a function which accepts two arguments (an array of Records to aggregate, and the measure
+ * to aggregate them on) and should return a String.
+ */
+ aggregator: 'sum',
+
+ /**
+ * @cfg {Function} renderer Optional renderer to pass values through before they are rendered to the dom. This
+ * gives an opportunity to modify cell contents after the value has been computed.
+ */
+ renderer: undefined,
+
+ /**
+ * @cfg {String} measure The field to extract from each Record when pivoting around the two axes. See the class
+ * introduction docs for usage
+ */
+
+ /**
+ * @cfg {Array|Ext.grid.PivotAxis} leftAxis Either and array of {@link #dimension} to use on the left axis, or
+ * a {@link Ext.grid.PivotAxis} instance. If an array is passed, it is turned into a PivotAxis internally.
+ */
+
+ /**
+ * @cfg {Array|Ext.grid.PivotAxis} topAxis Either and array of {@link #dimension} to use on the top axis, or
+ * a {@link Ext.grid.PivotAxis} instance. If an array is passed, it is turned into a PivotAxis internally.
+ */
+
+ //inherit docs
+ initComponent: function() {
+ Ext.grid.PivotGrid.superclass.initComponent.apply(this, arguments);
+
+ this.initAxes();
+
+ //no resizing of columns is allowed yet in PivotGrid
+ this.enableColumnResize = false;
+
+ this.viewConfig = Ext.apply(this.viewConfig || {}, {
+ forceFit: true
+ });
+
+ //TODO: dummy col model that is never used - GridView is too tightly integrated with ColumnModel
+ //in 3.x to remove this altogether.
+ this.colModel = new Ext.grid.ColumnModel({});
+ },
+
+ /**
+ * Returns the function currently used to aggregate the records in each Pivot cell
+ * @return {Function} The current aggregator function
+ */
+ getAggregator: function() {
+ if (typeof this.aggregator == 'string') {
+ return Ext.grid.PivotAggregatorMgr.types[this.aggregator];
+ } else {
+ return this.aggregator;
+ }
+ },
+
+ /**
+ * Sets the function to use when aggregating data for each cell.
+ * @param {String|Function} aggregator The new aggregator function or named function string
+ */
+ setAggregator: function(aggregator) {
+ this.aggregator = aggregator;
+ },
+
+ /**
+ * Sets the field name to use as the Measure in this Pivot Grid
+ * @param {String} measure The field to make the measure
+ */
+ setMeasure: function(measure) {
+ this.measure = measure;
+ },
+
+ /**
+ * Sets the left axis of this pivot grid. Optionally refreshes the grid afterwards.
+ * @param {Ext.grid.PivotAxis} axis The pivot axis
+ * @param {Boolean} refresh True to immediately refresh the grid and its axes (defaults to false)
+ */
+ setLeftAxis: function(axis, refresh) {
+ /**
+ * The configured {@link Ext.grid.PivotAxis} used as the left Axis for this Pivot Grid
+ * @property leftAxis
+ * @type Ext.grid.PivotAxis
+ */
+ this.leftAxis = axis;
+
+ if (refresh) {
+ this.view.refresh();
+ }
+ },
+
+ /**
+ * Sets the top axis of this pivot grid. Optionally refreshes the grid afterwards.
+ * @param {Ext.grid.PivotAxis} axis The pivot axis
+ * @param {Boolean} refresh True to immediately refresh the grid and its axes (defaults to false)
+ */
+ setTopAxis: function(axis, refresh) {
+ /**
+ * The configured {@link Ext.grid.PivotAxis} used as the top Axis for this Pivot Grid
+ * @property topAxis
+ * @type Ext.grid.PivotAxis
+ */
+ this.topAxis = axis;
+
+ if (refresh) {
+ this.view.refresh();
+ }
+ },
+
+ /**
+ * @private
+ * Creates the top and left axes. Should usually only need to be called once from initComponent
+ */
+ initAxes: function() {
+ var PivotAxis = Ext.grid.PivotAxis;
+
+ if (!(this.leftAxis instanceof PivotAxis)) {
+ this.setLeftAxis(new PivotAxis({
+ orientation: 'vertical',
+ dimensions : this.leftAxis || [],
+ store : this.store
+ }));
+ };
+
+ if (!(this.topAxis instanceof PivotAxis)) {
+ this.setTopAxis(new PivotAxis({
+ orientation: 'horizontal',
+ dimensions : this.topAxis || [],
+ store : this.store
+ }));
+ };
+ },
+
+ /**
+ * @private
+ * @return {Array} 2-dimensional array of cell data
+ */
+ extractData: function() {
+ var records = this.store.data.items,
+ recCount = records.length,
+ cells = [],
+ record, i, j, k;
+
+ if (recCount == 0) {
+ return [];
+ }
+
+ var leftTuples = this.leftAxis.getTuples(),
+ leftCount = leftTuples.length,
+ topTuples = this.topAxis.getTuples(),
+ topCount = topTuples.length,
+ aggregator = this.getAggregator();
+
+ for (i = 0; i < recCount; i++) {
+ record = records[i];
+
+ for (j = 0; j < leftCount; j++) {
+ cells[j] = cells[j] || [];
+
+ if (leftTuples[j].matcher(record) === true) {
+ for (k = 0; k < topCount; k++) {
+ cells[j][k] = cells[j][k] || [];
+
+ if (topTuples[k].matcher(record)) {
+ cells[j][k].push(record);
+ }
+ }
+ }
+ }
+ }
+
+ var rowCount = cells.length,
+ colCount, row;
+
+ for (i = 0; i < rowCount; i++) {
+ row = cells[i];
+ colCount = row.length;
+
+ for (j = 0; j < colCount; j++) {
+ cells[i][j] = aggregator(cells[i][j], this.measure);
+ }
+ }
+
+ return cells;
+ },
+
+ /**
+ * Returns the grid's GridView object.
+ * @return {Ext.grid.PivotGridView} The grid view
+ */
+ getView: function() {
+ if (!this.view) {
+ this.view = new Ext.grid.PivotGridView(this.viewConfig);
+ }
+
+ return this.view;
+ }
+});
+
+Ext.reg('pivotgrid', Ext.grid.PivotGrid);
+
+
+Ext.grid.PivotAggregatorMgr = new Ext.AbstractManager();
+
+Ext.grid.PivotAggregatorMgr.registerType('sum', function(records, measure) {
+ var length = records.length,
+ total = 0,
+ i;
+
+ for (i = 0; i < length; i++) {
+ total += records[i].get(measure);
+ }
+
+ return total;
+});
+
+Ext.grid.PivotAggregatorMgr.registerType('avg', function(records, measure) {
+ var length = records.length,
+ total = 0,
+ i;
+
+ for (i = 0; i < length; i++) {
+ total += records[i].get(measure);
+ }
+
+ return (total / length) || 'n/a';
+});
+
+Ext.grid.PivotAggregatorMgr.registerType('min', function(records, measure) {
+ var data = [],
+ length = records.length,
+ i;
+
+ for (i = 0; i < length; i++) {
+ data.push(records[i].get(measure));
+ }
+
+ return Math.min.apply(this, data) || 'n/a';
+});
+
+Ext.grid.PivotAggregatorMgr.registerType('max', function(records, measure) {
+ var data = [],
+ length = records.length,
+ i;
+
+ for (i = 0; i < length; i++) {
+ data.push(records[i].get(measure));
+ }
+
+ return Math.max.apply(this, data) || 'n/a';
+});
+
+Ext.grid.PivotAggregatorMgr.registerType('count', function(records, measure) {
+ return records.length;
+});/**
+ * @class Ext.grid.GridView
+ * @extends Ext.util.Observable
+ * <p>This class encapsulates the user interface of an {@link Ext.grid.GridPanel}.
+ * Methods of this class may be used to access user interface elements to enable
+ * special display effects. Do not change the DOM structure of the user interface.</p>
+ * <p>This class does not provide ways to manipulate the underlying data. The data
+ * model of a Grid is held in an {@link Ext.data.Store}.</p>
+ * @constructor
+ * @param {Object} config
+ */
+Ext.grid.GridView = Ext.extend(Ext.util.Observable, {
+ /**
+ * Override this function to apply custom CSS classes to rows during rendering. You can also supply custom
+ * parameters to the row template for the current row to customize how it is rendered using the <b>rowParams</b>
+ * parameter. This function should return the CSS class name (or empty string '' for none) that will be added
+ * to the row's wrapping div. To apply multiple class names, simply return them space-delimited within the string
+ * (e.g., 'my-class another-class'). Example usage:
+ <pre><code>
+viewConfig: {
+ forceFit: true,
+ showPreview: true, // custom property
+ enableRowBody: true, // required to create a second, full-width row to show expanded Record data
+ getRowClass: function(record, rowIndex, rp, ds){ // rp = rowParams
+ if(this.showPreview){
+ rp.body = '<p>'+record.data.excerpt+'</p>';
+ return 'x-grid3-row-expanded';
+ }
+ return 'x-grid3-row-collapsed';
+ }
+},
+ </code></pre>
+ * @param {Record} record The {@link Ext.data.Record} corresponding to the current row.
+ * @param {Number} index The row index.
+ * @param {Object} rowParams A config object that is passed to the row template during rendering that allows
+ * customization of various aspects of a grid row.
+ * <p>If {@link #enableRowBody} is configured <b><tt></tt>true</b>, then the following properties may be set
+ * by this function, and will be used to render a full-width expansion row below each grid row:</p>
+ * <ul>
+ * <li><code>body</code> : String <div class="sub-desc">An HTML fragment to be used as the expansion row's body content (defaults to '').</div></li>
+ * <li><code>bodyStyle</code> : String <div class="sub-desc">A CSS style specification that will be applied to the expansion row's <tr> element. (defaults to '').</div></li>
+ * </ul>
+ * The following property will be passed in, and may be appended to:
+ * <ul>
+ * <li><code>tstyle</code> : String <div class="sub-desc">A CSS style specification that willl be applied to the <table> element which encapsulates
+ * both the standard grid row, and any expansion row.</div></li>
+ * </ul>
+ * @param {Store} store The {@link Ext.data.Store} this grid is bound to
+ * @method getRowClass
+ * @return {String} a CSS class name to add to the row.
+ */
+
+ /**
+ * @cfg {Boolean} enableRowBody True to add a second TR element per row that can be used to provide a row body
+ * that spans beneath the data row. Use the {@link #getRowClass} method's rowParams config to customize the row body.
+ */
+
+ /**
+ * @cfg {String} emptyText Default text (html tags are accepted) to display in the grid body when no rows
+ * are available (defaults to ''). This value will be used to update the <tt>{@link #mainBody}</tt>:
+ <pre><code>
+ this.mainBody.update('<div class="x-grid-empty">' + this.emptyText + '</div>');
+ </code></pre>
+ */
+
+ /**
+ * @cfg {Boolean} headersDisabled True to disable the grid column headers (defaults to <tt>false</tt>).
+ * Use the {@link Ext.grid.ColumnModel ColumnModel} <tt>{@link Ext.grid.ColumnModel#menuDisabled menuDisabled}</tt>
+ * config to disable the <i>menu</i> for individual columns. While this config is true the
+ * following will be disabled:<div class="mdetail-params"><ul>
+ * <li>clicking on header to sort</li>
+ * <li>the trigger to reveal the menu.</li>
+ * </ul></div>
+ */
+
+ /**
+ * <p>A customized implementation of a {@link Ext.dd.DragZone DragZone} which provides default implementations
+ * of the template methods of DragZone to enable dragging of the selected rows of a GridPanel.
+ * See {@link Ext.grid.GridDragZone} for details.</p>
+ * <p>This will <b>only</b> be present:<div class="mdetail-params"><ul>
+ * <li><i>if</i> the owning GridPanel was configured with {@link Ext.grid.GridPanel#enableDragDrop enableDragDrop}: <tt>true</tt>.</li>
+ * <li><i>after</i> the owning GridPanel has been rendered.</li>
+ * </ul></div>
+ * @property dragZone
+ * @type {Ext.grid.GridDragZone}
+ */
+
+ /**
+ * @cfg {Boolean} deferEmptyText True to defer <tt>{@link #emptyText}</tt> being applied until the store's
+ * first load (defaults to <tt>true</tt>).
+ */
+ deferEmptyText : true,
+
+ /**
+ * @cfg {Number} scrollOffset The amount of space to reserve for the vertical scrollbar
+ * (defaults to <tt>undefined</tt>). If an explicit value isn't specified, this will be automatically
+ * calculated.
+ */
+ scrollOffset : undefined,
+
+ /**
+ * @cfg {Boolean} autoFill
+ * Defaults to <tt>false</tt>. Specify <tt>true</tt> to have the column widths re-proportioned
+ * when the grid is <b>initially rendered</b>. The
+ * {@link Ext.grid.Column#width initially configured width}</tt> of each column will be adjusted
+ * to fit the grid width and prevent horizontal scrolling. If columns are later resized (manually
+ * or programmatically), the other columns in the grid will <b>not</b> be resized to fit the grid width.
+ * See <tt>{@link #forceFit}</tt> also.
+ */
+ autoFill : false,
+
+ /**
+ * @cfg {Boolean} forceFit
+ * <p>Defaults to <tt>false</tt>. Specify <tt>true</tt> to have the column widths re-proportioned
+ * at <b>all times</b>.</p>
+ * <p>The {@link Ext.grid.Column#width initially configured width}</tt> of each
+ * column will be adjusted to fit the grid width and prevent horizontal scrolling. If columns are
+ * later resized (manually or programmatically), the other columns in the grid <b>will</b> be resized
+ * to fit the grid width.</p>
+ * <p>Columns which are configured with <code>fixed: true</code> are omitted from being resized.</p>
+ * <p>See <tt>{@link #autoFill}</tt>.</p>
+ */
+ forceFit : false,
+
+ /**
+ * @cfg {Array} sortClasses The CSS classes applied to a header when it is sorted. (defaults to <tt>['sort-asc', 'sort-desc']</tt>)
+ */
+ sortClasses : ['sort-asc', 'sort-desc'],
+
+ /**
+ * @cfg {String} sortAscText The text displayed in the 'Sort Ascending' menu item (defaults to <tt>'Sort Ascending'</tt>)
+ */
+ sortAscText : 'Sort Ascending',
+
+ /**
+ * @cfg {String} sortDescText The text displayed in the 'Sort Descending' menu item (defaults to <tt>'Sort Descending'</tt>)
+ */
+ sortDescText : 'Sort Descending',
+
+ /**
+ * @cfg {String} columnsText The text displayed in the 'Columns' menu item (defaults to <tt>'Columns'</tt>)
+ */
+ columnsText : 'Columns',
+
+ /**
+ * @cfg {String} selectedRowClass The CSS class applied to a selected row (defaults to <tt>'x-grid3-row-selected'</tt>). An
+ * example overriding the default styling:
+ <pre><code>
+ .x-grid3-row-selected {background-color: yellow;}
+ </code></pre>
+ * Note that this only controls the row, and will not do anything for the text inside it. To style inner
+ * facets (like text) use something like:
+ <pre><code>
+ .x-grid3-row-selected .x-grid3-cell-inner {
+ color: #FFCC00;
+ }
+ </code></pre>
+ * @type String
+ */
+ selectedRowClass : 'x-grid3-row-selected',
+
+ // private
+ borderWidth : 2,
+ tdClass : 'x-grid3-cell',
+ hdCls : 'x-grid3-hd',
+
+
+ /**
+ * @cfg {Boolean} markDirty True to show the dirty cell indicator when a cell has been modified. Defaults to <tt>true</tt>.
+ */
+ markDirty : true,
+
+ /**
+ * @cfg {Number} cellSelectorDepth The number of levels to search for cells in event delegation (defaults to <tt>4</tt>)
+ */
+ cellSelectorDepth : 4,
+
+ /**
+ * @cfg {Number} rowSelectorDepth The number of levels to search for rows in event delegation (defaults to <tt>10</tt>)
+ */
+ rowSelectorDepth : 10,
+
+ /**
+ * @cfg {Number} rowBodySelectorDepth The number of levels to search for row bodies in event delegation (defaults to <tt>10</tt>)
+ */
+ rowBodySelectorDepth : 10,
+
+ /**
+ * @cfg {String} cellSelector The selector used to find cells internally (defaults to <tt>'td.x-grid3-cell'</tt>)
+ */
+ cellSelector : 'td.x-grid3-cell',
+
+ /**
+ * @cfg {String} rowSelector The selector used to find rows internally (defaults to <tt>'div.x-grid3-row'</tt>)
+ */
+ rowSelector : 'div.x-grid3-row',
+
+ /**
+ * @cfg {String} rowBodySelector The selector used to find row bodies internally (defaults to <tt>'div.x-grid3-row'</tt>)
+ */
+ rowBodySelector : 'div.x-grid3-row-body',
+
+ // private
+ firstRowCls: 'x-grid3-row-first',
+ lastRowCls: 'x-grid3-row-last',
+ rowClsRe: /(?:^|\s+)x-grid3-row-(first|last|alt)(?:\s+|$)/g,
+
+ /**
+ * @cfg {String} headerMenuOpenCls The CSS class to add to the header cell when its menu is visible. Defaults to 'x-grid3-hd-menu-open'
+ */
+ headerMenuOpenCls: 'x-grid3-hd-menu-open',
+
+ /**
+ * @cfg {String} rowOverCls The CSS class added to each row when it is hovered over. Defaults to 'x-grid3-row-over'
+ */
+ rowOverCls: 'x-grid3-row-over',
+
+ constructor : function(config) {
+ Ext.apply(this, config);
+
+ // These events are only used internally by the grid components
+ this.addEvents(
+ /**
+ * @event beforerowremoved
+ * Internal UI Event. Fired before a row is removed.
+ * @param {Ext.grid.GridView} view
+ * @param {Number} rowIndex The index of the row to be removed.
+ * @param {Ext.data.Record} record The Record to be removed
+ */
+ 'beforerowremoved',
+
+ /**
+ * @event beforerowsinserted
+ * Internal UI Event. Fired before rows are inserted.
+ * @param {Ext.grid.GridView} view
+ * @param {Number} firstRow The index of the first row to be inserted.
+ * @param {Number} lastRow The index of the last row to be inserted.
+ */
+ 'beforerowsinserted',
+
+ /**
+ * @event beforerefresh
+ * Internal UI Event. Fired before the view is refreshed.
+ * @param {Ext.grid.GridView} view
+ */
+ 'beforerefresh',
+
+ /**
+ * @event rowremoved
+ * Internal UI Event. Fired after a row is removed.
+ * @param {Ext.grid.GridView} view
+ * @param {Number} rowIndex The index of the row that was removed.
+ * @param {Ext.data.Record} record The Record that was removed
+ */
+ 'rowremoved',
+
+ /**
+ * @event rowsinserted
+ * Internal UI Event. Fired after rows are inserted.
+ * @param {Ext.grid.GridView} view
+ * @param {Number} firstRow The index of the first inserted.
+ * @param {Number} lastRow The index of the last row inserted.
+ */
+ 'rowsinserted',
+
+ /**
+ * @event rowupdated
+ * Internal UI Event. Fired after a row has been updated.
+ * @param {Ext.grid.GridView} view
+ * @param {Number} firstRow The index of the row updated.
+ * @param {Ext.data.record} record The Record backing the row updated.
+ */
+ 'rowupdated',
+
+ /**
+ * @event refresh
+ * Internal UI Event. Fired after the GridView's body has been refreshed.
+ * @param {Ext.grid.GridView} view
+ */
+ 'refresh'
+ );
+
+ Ext.grid.GridView.superclass.constructor.call(this);
+ },
+
+ /* -------------------------------- UI Specific ----------------------------- */
+
+ /**
+ * The master template to use when rendering the GridView. Has a default template
+ * @property Ext.Template
+ * @type masterTpl
+ */
+ masterTpl: new Ext.Template(
+ '<div class="x-grid3" hidefocus="true">',
+ '<div class="x-grid3-viewport">',
+ '<div class="x-grid3-header">',
+ '<div class="x-grid3-header-inner">',
+ '<div class="x-grid3-header-offset" style="{ostyle}">{header}</div>',
+ '</div>',
+ '<div class="x-clear"></div>',
+ '</div>',
+ '<div class="x-grid3-scroller">',
+ '<div class="x-grid3-body" style="{bstyle}">{body}</div>',
+ '<a href="#" class="x-grid3-focus" tabIndex="-1"></a>',
+ '</div>',
+ '</div>',
+ '<div class="x-grid3-resize-marker"> </div>',
+ '<div class="x-grid3-resize-proxy"> </div>',
+ '</div>'
+ ),
+
+ /**
+ * The template to use when rendering headers. Has a default template
+ * @property headerTpl
+ * @type Ext.Template
+ */
+ headerTpl: new Ext.Template(
+ '<table border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
+ '<thead>',
+ '<tr class="x-grid3-hd-row">{cells}</tr>',
+ '</thead>',
+ '</table>'
+ ),
+
+ /**
+ * The template to use when rendering the body. Has a default template
+ * @property bodyTpl
+ * @type Ext.Template
+ */
+ bodyTpl: new Ext.Template('{rows}'),
+
+ /**
+ * The template to use to render each cell. Has a default template
+ * @property cellTpl
+ * @type Ext.Template
+ */
+ cellTpl: new Ext.Template(
+ '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}" tabIndex="0" {cellAttr}>',
+ '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on" {attr}>{value}</div>',
+ '</td>'
+ ),
+
+ /**
+ * @private
+ * Provides default templates if they are not given for this particular instance. Most of the templates are defined on
+ * the prototype, the ones defined inside this function are done so because they are based on Grid or GridView configuration
+ */
+ initTemplates : function() {
+ var templates = this.templates || {},
+ template, name,
+
+ headerCellTpl = new Ext.Template(
+ '<td class="x-grid3-hd x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',
+ '<div {tooltip} {attr} class="x-grid3-hd-inner x-grid3-hd-{id}" unselectable="on" style="{istyle}">',
+ this.grid.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '',
+ '{value}',
+ '<img alt="" class="x-grid3-sort-icon" src="', Ext.BLANK_IMAGE_URL, '" />',
+ '</div>',
+ '</td>'
+ ),
+
+ rowBodyText = [
+ '<tr class="x-grid3-row-body-tr" style="{bodyStyle}">',
+ '<td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on">',
+ '<div class="x-grid3-row-body">{body}</div>',
+ '</td>',
+ '</tr>'
+ ].join(""),
+
+ innerText = [
+ '<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
+ '<tbody>',
+ '<tr>{cells}</tr>',
+ this.enableRowBody ? rowBodyText : '',
+ '</tbody>',
+ '</table>'
+ ].join("");
+
+ Ext.applyIf(templates, {
+ hcell : headerCellTpl,
+ cell : this.cellTpl,
+ body : this.bodyTpl,
+ header : this.headerTpl,
+ master : this.masterTpl,
+ row : new Ext.Template('<div class="x-grid3-row {alt}" style="{tstyle}">' + innerText + '</div>'),
+ rowInner: new Ext.Template(innerText)
+ });
+
+ for (name in templates) {
+ template = templates[name];
+
+ if (template && Ext.isFunction(template.compile) && !template.compiled) {
+ template.disableFormats = true;
+ template.compile();
+ }
+ }
+
+ this.templates = templates;
+ this.colRe = new RegExp('x-grid3-td-([^\\s]+)', '');
+ },
+
+ /**
+ * @private
+ * Each GridView has its own private flyweight, accessed through this method
+ */
+ fly : function(el) {
+ if (!this._flyweight) {
+ this._flyweight = new Ext.Element.Flyweight(document.body);
+ }
+ this._flyweight.dom = el;
+ return this._flyweight;
+ },
+
+ // private
+ getEditorParent : function() {
+ return this.scroller.dom;
+ },
+
+ /**
+ * @private
+ * Finds and stores references to important elements
+ */
+ initElements : function() {
+ var Element = Ext.Element,
+ el = Ext.get(this.grid.getGridEl().dom.firstChild),
+ mainWrap = new Element(el.child('div.x-grid3-viewport')),
+ mainHd = new Element(mainWrap.child('div.x-grid3-header')),
+ scroller = new Element(mainWrap.child('div.x-grid3-scroller'));
+
+ if (this.grid.hideHeaders) {
+ mainHd.setDisplayed(false);
+ }
+
+ if (this.forceFit) {
+ scroller.setStyle('overflow-x', 'hidden');
+ }
+
+ /**
+ * <i>Read-only</i>. The GridView's body Element which encapsulates all rows in the Grid.
+ * This {@link Ext.Element Element} is only available after the GridPanel has been rendered.
+ * @type Ext.Element
+ * @property mainBody
+ */
+
+ Ext.apply(this, {
+ el : el,
+ mainWrap: mainWrap,
+ scroller: scroller,
+ mainHd : mainHd,
+ innerHd : mainHd.child('div.x-grid3-header-inner').dom,
+ mainBody: new Element(Element.fly(scroller).child('div.x-grid3-body')),
+ focusEl : new Element(Element.fly(scroller).child('a')),
+
+ resizeMarker: new Element(el.child('div.x-grid3-resize-marker')),
+ resizeProxy : new Element(el.child('div.x-grid3-resize-proxy'))
+ });
+
+ this.focusEl.swallowEvent('click', true);
+ },
+
+ // private
+ getRows : function() {
+ return this.hasRows() ? this.mainBody.dom.childNodes : [];
+ },
+
+ // finder methods, used with delegation
+
+ // private
+ findCell : function(el) {
+ if (!el) {
+ return false;
+ }
+ return this.fly(el).findParent(this.cellSelector, this.cellSelectorDepth);
+ },
+
+ /**
+ * <p>Return the index of the grid column which contains the passed HTMLElement.</p>
+ * See also {@link #findRowIndex}
+ * @param {HTMLElement} el The target element
+ * @return {Number} The column index, or <b>false</b> if the target element is not within a row of this GridView.
+ */
+ findCellIndex : function(el, requiredCls) {
+ var cell = this.findCell(el),
+ hasCls;
+
+ if (cell) {
+ hasCls = this.fly(cell).hasClass(requiredCls);
+ if (!requiredCls || hasCls) {
+ return this.getCellIndex(cell);
+ }
+ }
+ return false;
+ },
+
+ // private
+ getCellIndex : function(el) {
+ if (el) {
+ var match = el.className.match(this.colRe);
+
+ if (match && match[1]) {
+ return this.cm.getIndexById(match[1]);
+ }
+ }
+ return false;
+ },
+
+ // private
+ findHeaderCell : function(el) {
+ var cell = this.findCell(el);
+ return cell && this.fly(cell).hasClass(this.hdCls) ? cell : null;
+ },
+
+ // private
+ findHeaderIndex : function(el){
+ return this.findCellIndex(el, this.hdCls);
+ },
+
+ /**
+ * Return the HtmlElement representing the grid row which contains the passed element.
+ * @param {HTMLElement} el The target HTMLElement
+ * @return {HTMLElement} The row element, or null if the target element is not within a row of this GridView.
+ */
+ findRow : function(el) {
+ if (!el) {
+ return false;
+ }
+ return this.fly(el).findParent(this.rowSelector, this.rowSelectorDepth);
+ },
+
+ /**
+ * Return the index of the grid row which contains the passed HTMLElement.
+ * See also {@link #findCellIndex}
+ * @param {HTMLElement} el The target HTMLElement
+ * @return {Number} The row index, or <b>false</b> if the target element is not within a row of this GridView.
+ */
+ findRowIndex : function(el) {
+ var row = this.findRow(el);
+ return row ? row.rowIndex : false;
+ },
+
+ /**
+ * Return the HtmlElement representing the grid row body which contains the passed element.
+ * @param {HTMLElement} el The target HTMLElement
+ * @return {HTMLElement} The row body element, or null if the target element is not within a row body of this GridView.
+ */
+ findRowBody : function(el) {
+ if (!el) {
+ return false;
+ }
+
+ return this.fly(el).findParent(this.rowBodySelector, this.rowBodySelectorDepth);
+ },
+
+ // getter methods for fetching elements dynamically in the grid
+
+ /**
+ * Return the <tt><div></tt> HtmlElement which represents a Grid row for the specified index.
+ * @param {Number} index The row index
+ * @return {HtmlElement} The div element.
+ */
+ getRow : function(row) {
+ return this.getRows()[row];
+ },
+
+ /**
+ * Returns the grid's <tt><td></tt> HtmlElement at the specified coordinates.
+ * @param {Number} row The row index in which to find the cell.
+ * @param {Number} col The column index of the cell.
+ * @return {HtmlElement} The td at the specified coordinates.
+ */
+ getCell : function(row, col) {
+ return Ext.fly(this.getRow(row)).query(this.cellSelector)[col];
+ },
+
+ /**
+ * Return the <tt><td></tt> HtmlElement which represents the Grid's header cell for the specified column index.
+ * @param {Number} index The column index
+ * @return {HtmlElement} The td element.
+ */
+ getHeaderCell : function(index) {
+ return this.mainHd.dom.getElementsByTagName('td')[index];
+ },
+
+ // manipulating elements
+
+ // private - use getRowClass to apply custom row classes
+ addRowClass : function(rowId, cls) {
+ var row = this.getRow(rowId);
+ if (row) {
+ this.fly(row).addClass(cls);
+ }
+ },
+
+ // private
+ removeRowClass : function(row, cls) {
+ var r = this.getRow(row);
+ if(r){
+ this.fly(r).removeClass(cls);
+ }
+ },
+
+ // private
+ removeRow : function(row) {
+ Ext.removeNode(this.getRow(row));
+ this.syncFocusEl(row);
+ },
+
+ // private
+ removeRows : function(firstRow, lastRow) {
+ var bd = this.mainBody.dom,
+ rowIndex;
+
+ for (rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
+ Ext.removeNode(bd.childNodes[firstRow]);
+ }
+
+ this.syncFocusEl(firstRow);
+ },
+
+ /* ----------------------------------- Scrolling functions -------------------------------------------*/
+
+ // private
+ getScrollState : function() {
+ var sb = this.scroller.dom;
+
+ return {
+ left: sb.scrollLeft,
+ top : sb.scrollTop
+ };
+ },
+
+ // private
+ restoreScroll : function(state) {
+ var sb = this.scroller.dom;
+ sb.scrollLeft = state.left;
+ sb.scrollTop = state.top;
+ },
+
+ /**
+ * Scrolls the grid to the top
+ */
+ scrollToTop : function() {
+ var dom = this.scroller.dom;
+
+ dom.scrollTop = 0;
+ dom.scrollLeft = 0;
+ },
+
+ // private
+ syncScroll : function() {
+ this.syncHeaderScroll();
+ var mb = this.scroller.dom;
+ this.grid.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop);
+ },
+
+ // private
+ syncHeaderScroll : function() {
+ var innerHd = this.innerHd,
+ scrollLeft = this.scroller.dom.scrollLeft;
+
+ innerHd.scrollLeft = scrollLeft;
+ innerHd.scrollLeft = scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
+ },
+
+ /**
+ * @private
+ * Ensures the given column has the given icon class
+ */
+ updateSortIcon : function(col, dir) {
+ var sortClasses = this.sortClasses,
+ sortClass = sortClasses[dir == "DESC" ? 1 : 0],
+ headers = this.mainHd.select('td').removeClass(sortClasses);
+
+ headers.item(col).addClass(sortClass);
+ },
+
+ /**
+ * @private
+ * Updates the size of every column and cell in the grid
+ */
+ updateAllColumnWidths : function() {
+ var totalWidth = this.getTotalWidth(),
+ colCount = this.cm.getColumnCount(),
+ rows = this.getRows(),
+ rowCount = rows.length,
+ widths = [],
+ row, rowFirstChild, trow, i, j;
+
+ for (i = 0; i < colCount; i++) {
+ widths[i] = this.getColumnWidth(i);
+ this.getHeaderCell(i).style.width = widths[i];
+ }
+
+ this.updateHeaderWidth();
+
+ for (i = 0; i < rowCount; i++) {
+ row = rows[i];
+ row.style.width = totalWidth;
+ rowFirstChild = row.firstChild;
+
+ if (rowFirstChild) {
+ rowFirstChild.style.width = totalWidth;
+ trow = rowFirstChild.rows[0];
+
+ for (j = 0; j < colCount; j++) {
+ trow.childNodes[j].style.width = widths[j];
+ }
+ }
+ }
+
+ this.onAllColumnWidthsUpdated(widths, totalWidth);
+ },
+
+ /**
+ * @private
+ * Called after a column's width has been updated, this resizes all of the cells for that column in each row
+ * @param {Number} column The column index
+ */
+ updateColumnWidth : function(column, width) {
+ var columnWidth = this.getColumnWidth(column),
+ totalWidth = this.getTotalWidth(),
+ headerCell = this.getHeaderCell(column),
+ nodes = this.getRows(),
+ nodeCount = nodes.length,
+ row, i, firstChild;
+
+ this.updateHeaderWidth();
+ headerCell.style.width = columnWidth;
+
+ for (i = 0; i < nodeCount; i++) {
+ row = nodes[i];
+ firstChild = row.firstChild;
+
+ row.style.width = totalWidth;
+ if (firstChild) {
+ firstChild.style.width = totalWidth;
+ firstChild.rows[0].childNodes[column].style.width = columnWidth;
+ }
+ }
+
+ this.onColumnWidthUpdated(column, columnWidth, totalWidth);
+ },
+
+ /**
+ * @private
+ * Sets the hidden status of a given column.
+ * @param {Number} col The column index
+ * @param {Boolean} hidden True to make the column hidden
+ */
+ updateColumnHidden : function(col, hidden) {
+ var totalWidth = this.getTotalWidth(),
+ display = hidden ? 'none' : '',
+ headerCell = this.getHeaderCell(col),
+ nodes = this.getRows(),
+ nodeCount = nodes.length,
+ row, rowFirstChild, i;
+
+ this.updateHeaderWidth();
+ headerCell.style.display = display;
+
+ for (i = 0; i < nodeCount; i++) {
+ row = nodes[i];
+ row.style.width = totalWidth;
+ rowFirstChild = row.firstChild;
+
+ if (rowFirstChild) {
+ rowFirstChild.style.width = totalWidth;
+ rowFirstChild.rows[0].childNodes[col].style.display = display;
+ }
+ }
+
+ this.onColumnHiddenUpdated(col, hidden, totalWidth);
+ delete this.lastViewWidth; //recalc
+ this.layout();
+ },
+
+ /**
+ * @private
+ * Renders all of the rows to a string buffer and returns the string. This is called internally
+ * by renderRows and performs the actual string building for the rows - it does not inject HTML into the DOM.
+ * @param {Array} columns The column data acquired from getColumnData.
+ * @param {Array} records The array of records to render
+ * @param {Ext.data.Store} store The store to render the rows from
+ * @param {Number} startRow The index of the first row being rendered. Sometimes we only render a subset of
+ * the rows so this is used to maintain logic for striping etc
+ * @param {Number} colCount The total number of columns in the column model
+ * @param {Boolean} stripe True to stripe the rows
+ * @return {String} A string containing the HTML for the rendered rows
+ */
+ doRender : function(columns, records, store, startRow, colCount, stripe) {
+ var templates = this.templates,
+ cellTemplate = templates.cell,
+ rowTemplate = templates.row,
+ last = colCount - 1,
+ tstyle = 'width:' + this.getTotalWidth() + ';',
+ // buffers
+ rowBuffer = [],
+ colBuffer = [],
+ rowParams = {tstyle: tstyle},
+ meta = {},
+ len = records.length,
+ alt,
+ column,
+ record, i, j, rowIndex;
+
+ //build up each row's HTML
+ for (j = 0; j < len; j++) {
+ record = records[j];
+ colBuffer = [];
+
+ rowIndex = j + startRow;
+
+ //build up each column's HTML
+ for (i = 0; i < colCount; i++) {
+ column = columns[i];
+
+ meta.id = column.id;
+ meta.css = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
+ meta.attr = meta.cellAttr = '';
+ meta.style = column.style;
+ meta.value = column.renderer.call(column.scope, record.data[column.name], meta, record, rowIndex, i, store);
+
+ if (Ext.isEmpty(meta.value)) {
+ meta.value = ' ';
+ }
+
+ if (this.markDirty && record.dirty && typeof record.modified[column.name] != 'undefined') {
+ meta.css += ' x-grid3-dirty-cell';
+ }
+
+ colBuffer[colBuffer.length] = cellTemplate.apply(meta);
+ }
+
+ alt = [];
+ //set up row striping and row dirtiness CSS classes
+ if (stripe && ((rowIndex + 1) % 2 === 0)) {
+ alt[0] = 'x-grid3-row-alt';
+ }
+
+ if (record.dirty) {
+ alt[1] = ' x-grid3-dirty-row';
+ }
+
+ rowParams.cols = colCount;
+
+ if (this.getRowClass) {
+ alt[2] = this.getRowClass(record, rowIndex, rowParams, store);
+ }
+
+ rowParams.alt = alt.join(' ');
+ rowParams.cells = colBuffer.join('');
+
+ rowBuffer[rowBuffer.length] = rowTemplate.apply(rowParams);
+ }
+
+ return rowBuffer.join('');
+ },
+
+ /**
+ * @private
+ * Adds CSS classes and rowIndex to each row
+ * @param {Number} startRow The row to start from (defaults to 0)
+ */
+ processRows : function(startRow, skipStripe) {
+ if (!this.ds || this.ds.getCount() < 1) {
+ return;
+ }
+
+ var rows = this.getRows(),
+ length = rows.length,
+ row, i;
+
+ skipStripe = skipStripe || !this.grid.stripeRows;
+ startRow = startRow || 0;
+
+ for (i = 0; i < length; i++) {
+ row = rows[i];
+ if (row) {
+ row.rowIndex = i;
+ if (!skipStripe) {
+ row.className = row.className.replace(this.rowClsRe, ' ');
+ if ((i + 1) % 2 === 0){
+ row.className += ' x-grid3-row-alt';
+ }
+ }
+ }
+ }
+
+ // add first/last-row classes
+ if (startRow === 0) {
+ Ext.fly(rows[0]).addClass(this.firstRowCls);
+ }
+
+ Ext.fly(rows[length - 1]).addClass(this.lastRowCls);
+ },
+
+ /**
+ * @private
+ */
+ afterRender : function() {
+ if (!this.ds || !this.cm) {
+ return;
+ }
+
+ this.mainBody.dom.innerHTML = this.renderBody() || ' ';
+ this.processRows(0, true);
+
+ if (this.deferEmptyText !== true) {
+ this.applyEmptyText();
+ }
+
+ this.grid.fireEvent('viewready', this.grid);
+ },
+
+ /**
+ * @private
+ * This is always intended to be called after renderUI. Sets up listeners on the UI elements
+ * and sets up options like column menus, moving and resizing.
+ */
+ afterRenderUI: function() {
+ var grid = this.grid;
+
+ this.initElements();
+
+ // get mousedowns early
+ Ext.fly(this.innerHd).on('click', this.handleHdDown, this);
+
+ this.mainHd.on({
+ scope : this,
+ mouseover: this.handleHdOver,
+ mouseout : this.handleHdOut,
+ mousemove: this.handleHdMove
+ });
+
+ this.scroller.on('scroll', this.syncScroll, this);
+
+ if (grid.enableColumnResize !== false) {
+ this.splitZone = new Ext.grid.GridView.SplitDragZone(grid, this.mainHd.dom);
+ }
+
+ if (grid.enableColumnMove) {
+ this.columnDrag = new Ext.grid.GridView.ColumnDragZone(grid, this.innerHd);
+ this.columnDrop = new Ext.grid.HeaderDropZone(grid, this.mainHd.dom);
+ }
+
+ if (grid.enableHdMenu !== false) {
+ this.hmenu = new Ext.menu.Menu({id: grid.id + '-hctx'});
+ this.hmenu.add(
+ {itemId:'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc'},
+ {itemId:'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'}
+ );
+
+ if (grid.enableColumnHide !== false) {
+ this.colMenu = new Ext.menu.Menu({id:grid.id + '-hcols-menu'});
+ this.colMenu.on({
+ scope : this,
+ beforeshow: this.beforeColMenuShow,
+ itemclick : this.handleHdMenuClick
+ });
+ this.hmenu.add('-', {
+ itemId:'columns',
+ hideOnClick: false,
+ text: this.columnsText,
+ menu: this.colMenu,
+ iconCls: 'x-cols-icon'
+ });
+ }
+
+ this.hmenu.on('itemclick', this.handleHdMenuClick, this);
+ }
+
+ if (grid.trackMouseOver) {
+ this.mainBody.on({
+ scope : this,
+ mouseover: this.onRowOver,
+ mouseout : this.onRowOut
+ });
+ }
+
+ if (grid.enableDragDrop || grid.enableDrag) {
+ this.dragZone = new Ext.grid.GridDragZone(grid, {
+ ddGroup : grid.ddGroup || 'GridDD'
+ });
+ }
+
+ this.updateHeaderSortState();
+ },
+
+ /**
+ * @private
+ * Renders each of the UI elements in turn. This is called internally, once, by this.render. It does not
+ * render rows from the store, just the surrounding UI elements.
+ */
+ renderUI : function() {
+ var templates = this.templates;
+
+ return templates.master.apply({
+ body : templates.body.apply({rows:' '}),
+ header: this.renderHeaders(),
+ ostyle: 'width:' + this.getOffsetWidth() + ';',
+ bstyle: 'width:' + this.getTotalWidth() + ';'
+ });
+ },
+