Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / docs / source / Table3.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5   <title>The source code</title>
6   <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
7   <script type="text/javascript" src="../prettify/prettify.js"></script>
8   <style type="text/css">
9     .highlight { display: block; background-color: #ddd; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
17 <body onload="prettyPrint(); highlight();">
18   <pre class="prettyprint lang-js"><span id='Ext-view-Table'>/**
19 </span> * @class Ext.view.Table
20  * @extends Ext.view.View
21
22 This class encapsulates the user interface for a tabular data set.
23 It acts as a centralized manager for controlling the various interface
24 elements of the view. This includes handling events, such as row and cell
25 level based DOM events. It also reacts to events from the underlying {@link Ext.selection.Model}
26 to provide visual feedback to the user.
27
28 This class does not provide ways to manipulate the underlying data of the configured
29 {@link Ext.data.Store}.
30
31 This is the base class for both {@link Ext.grid.View} and {@link Ext.tree.View} and is not
32 to be used directly.
33
34  * @markdown
35  * @abstract
36  * @author Nicolas Ferrero
37  */
38 Ext.define('Ext.view.Table', {
39     extend: 'Ext.view.View',
40     alias: 'widget.tableview',
41     uses: [
42         'Ext.view.TableChunker',
43         'Ext.util.DelayedTask',
44         'Ext.util.MixedCollection'
45     ],
46
47     cls: Ext.baseCSSPrefix + 'grid-view',
48
49     // row
50     itemSelector: '.' + Ext.baseCSSPrefix + 'grid-row',
51     // cell
52     cellSelector: '.' + Ext.baseCSSPrefix + 'grid-cell',
53
54     selectedItemCls: Ext.baseCSSPrefix + 'grid-row-selected',
55     selectedCellCls: Ext.baseCSSPrefix + 'grid-cell-selected',
56     focusedItemCls: Ext.baseCSSPrefix + 'grid-row-focused',
57     overItemCls: Ext.baseCSSPrefix + 'grid-row-over',
58     altRowCls:   Ext.baseCSSPrefix + 'grid-row-alt',
59     rowClsRe: /(?:^|\s*)grid-row-(first|last|alt)(?:\s+|$)/g,
60     cellRe: new RegExp('x-grid-cell-([^\\s]+) ', ''),
61
62     // cfg docs inherited
63     trackOver: true,
64
65 <span id='Ext-view-Table-method-getRowClass'>    /**
66 </span>     * Override this function to apply custom CSS classes to rows during rendering.  You can also supply custom
67      * parameters to the row template for the current row to customize how it is rendered using the &lt;b&gt;rowParams&lt;/b&gt;
68      * parameter.  This function should return the CSS class name (or empty string '' for none) that will be added
69      * to the row's wrapping div.  To apply multiple class names, simply return them space-delimited within the string
70      * (e.g., 'my-class another-class'). Example usage:
71     &lt;pre&gt;&lt;code&gt;
72 viewConfig: {
73     forceFit: true,
74     showPreview: true, // custom property
75     enableRowBody: true, // required to create a second, full-width row to show expanded Record data
76     getRowClass: function(record, rowIndex, rp, ds){ // rp = rowParams
77         if(this.showPreview){
78             rp.body = '&amp;lt;p&gt;'+record.data.excerpt+'&amp;lt;/p&gt;';
79             return 'x-grid3-row-expanded';
80         }
81         return 'x-grid3-row-collapsed';
82     }
83 },
84     &lt;/code&gt;&lt;/pre&gt;
85      * @param {Model} model The {@link Ext.data.Model} corresponding to the current row.
86      * @param {Number} index The row index.
87      * @param {Object} rowParams (DEPRECATED) A config object that is passed to the row template during rendering that allows
88      * customization of various aspects of a grid row.
89      * &lt;p&gt;If {@link #enableRowBody} is configured &lt;b&gt;&lt;tt&gt;&lt;/tt&gt;true&lt;/b&gt;, then the following properties may be set
90      * by this function, and will be used to render a full-width expansion row below each grid row:&lt;/p&gt;
91      * &lt;ul&gt;
92      * &lt;li&gt;&lt;code&gt;body&lt;/code&gt; : String &lt;div class=&quot;sub-desc&quot;&gt;An HTML fragment to be used as the expansion row's body content (defaults to '').&lt;/div&gt;&lt;/li&gt;
93      * &lt;li&gt;&lt;code&gt;bodyStyle&lt;/code&gt; : String &lt;div class=&quot;sub-desc&quot;&gt;A CSS style specification that will be applied to the expansion row's &amp;lt;tr&gt; element. (defaults to '').&lt;/div&gt;&lt;/li&gt;
94      * &lt;/ul&gt;
95      * The following property will be passed in, and may be appended to:
96      * &lt;ul&gt;
97      * &lt;li&gt;&lt;code&gt;tstyle&lt;/code&gt; : String &lt;div class=&quot;sub-desc&quot;&gt;A CSS style specification that willl be applied to the &amp;lt;table&gt; element which encapsulates
98      * both the standard grid row, and any expansion row.&lt;/div&gt;&lt;/li&gt;
99      * &lt;/ul&gt;
100      * @param {Store} store The {@link Ext.data.Store} this grid is bound to
101      * @method getRowClass
102      * @return {String} a CSS class name to add to the row.
103      */
104     getRowClass: null,
105
106     initComponent: function() {
107         var me = this;
108
109         if (me.deferRowRender !== false) {
110             me.refresh = function() {
111                 delete me.refresh;
112                 setTimeout(function() {
113                     me.refresh();
114                 }, 0);
115             };
116         }
117
118         me.scrollState = {};
119         me.selModel.view = me;
120         me.headerCt.view = me;
121         me.initFeatures();
122         me.tpl = '&lt;div&gt;&lt;/div&gt;';
123         me.callParent();
124         me.mon(me.store, {
125             load: me.onStoreLoad,
126             scope: me
127         });
128
129         // this.addEvents(
130         //     /**
131         //      * @event rowfocus
132         //      * @param {Ext.data.Record} record
133         //      * @param {HTMLElement} row
134         //      * @param {Number} rowIdx
135         //      */
136         //     'rowfocus'
137         // );
138     },
139
140     // scroll to top of the grid when store loads
141     onStoreLoad: function(){
142         var me = this;
143
144         if (me.invalidateScrollerOnRefresh) {
145             if (Ext.isGecko) {
146                 if (!me.scrollToTopTask) {
147                     me.scrollToTopTask = Ext.create('Ext.util.DelayedTask', me.scrollToTop, me);
148                 }
149                 me.scrollToTopTask.delay(1);
150             } else {
151                 me    .scrollToTop();
152             }
153         }
154     },
155
156     // scroll the view to the top
157     scrollToTop: Ext.emptyFn,
158
159 <span id='Ext-view-Table-method-addElListener'>    /**
160 </span>     * Add a listener to the main view element. It will be destroyed with the view.
161      * @private
162      */
163     addElListener: function(eventName, fn, scope){
164         this.mon(this, eventName, fn, scope, {
165             element: 'el'
166         });
167     },
168
169 <span id='Ext-view-Table-method-getGridColumns'>    /**
170 </span>     * Get the columns used for generating a template via TableChunker.
171      * See {@link Ext.grid.header.Container#getGridColumns}.
172      * @private
173      */
174     getGridColumns: function() {
175         return this.headerCt.getGridColumns();
176     },
177
178 <span id='Ext-view-Table-method-getHeaderAtIndex'>    /**
179 </span>     * Get a leaf level header by index regardless of what the nesting
180      * structure is.
181      * @private
182      * @param {Number} index The index
183      */
184     getHeaderAtIndex: function(index) {
185         return this.headerCt.getHeaderAtIndex(index);
186     },
187
188 <span id='Ext-view-Table-method-getCell'>    /**
189 </span>     * Get the cell (td) for a particular record and column.
190      * @param {Ext.data.Model} record
191      * @param {Ext.grid.column.Colunm} column
192      * @private
193      */
194     getCell: function(record, column) {
195         var row = this.getNode(record);
196         return Ext.fly(row).down(column.getCellSelector());
197     },
198
199 <span id='Ext-view-Table-method-getFeature'>    /**
200 </span>     * Get a reference to a feature
201      * @param {String} id The id of the feature
202      * @return {Ext.grid.feature.Feature} The feature. Undefined if not found
203      */
204     getFeature: function(id) {
205         var features = this.featuresMC;
206         if (features) {
207             return features.get(id);
208         }
209     },
210
211 <span id='Ext-view-Table-method-initFeatures'>    /**
212 </span>     * Initializes each feature and bind it to this view.
213      * @private
214      */
215     initFeatures: function() {
216         var me = this,
217             i = 0,
218             features,
219             len;
220
221         me.features = me.features || [];
222         features = me.features;
223         len = features.length;
224
225         me.featuresMC = Ext.create('Ext.util.MixedCollection');
226         for (; i &lt; len; i++) {
227             // ensure feature hasnt already been instantiated
228             if (!features[i].isFeature) {
229                 features[i] = Ext.create('feature.' + features[i].ftype, features[i]);
230             }
231             // inject a reference to view
232             features[i].view = me;
233             me.featuresMC.add(features[i]);
234         }
235     },
236
237 <span id='Ext-view-Table-method-attachEventsForFeatures'>    /**
238 </span>     * Gives features an injection point to attach events to the markup that
239      * has been created for this view.
240      * @private
241      */
242     attachEventsForFeatures: function() {
243         var features = this.features,
244             ln       = features.length,
245             i        = 0;
246
247         for (; i &lt; ln; i++) {
248             if (features[i].isFeature) {
249                 features[i].attachEvents();
250             }
251         }
252     },
253
254     afterRender: function() {
255         var me = this;
256
257         me.callParent();
258         me.mon(me.el, {
259             scroll: me.fireBodyScroll,
260             scope: me
261         });
262         me.el.unselectable();
263         me.attachEventsForFeatures();
264     },
265
266     fireBodyScroll: function(e, t) {
267         this.fireEvent('bodyscroll', e, t);
268     },
269
270     // TODO: Refactor headerCt dependency here to colModel
271 <span id='Ext-view-Table-method-prepareData'>    /**
272 </span>     * Uses the headerCt to transform data from dataIndex keys in a record to
273      * headerId keys in each header and then run them through each feature to
274      * get additional data for variables they have injected into the view template.
275      * @private
276      */
277     prepareData: function(data, idx, record) {
278         var me       = this,
279             orig     = me.headerCt.prepareData(data, idx, record, me, me.ownerCt),
280             features = me.features,
281             ln       = features.length,
282             i        = 0,
283             node, feature;
284
285         for (; i &lt; ln; i++) {
286             feature = features[i];
287             if (feature.isFeature) {
288                 Ext.apply(orig, feature.getAdditionalData(data, idx, record, orig, me));
289             }
290         }
291
292         return orig;
293     },
294
295     // TODO: Refactor headerCt dependency here to colModel
296     collectData: function(records, startIndex) {
297         var preppedRecords = this.callParent(arguments),
298             headerCt  = this.headerCt,
299             fullWidth = headerCt.getFullWidth(),
300             features  = this.features,
301             ln = features.length,
302             o = {
303                 rows: preppedRecords,
304                 fullWidth: fullWidth
305             },
306             i  = 0,
307             feature,
308             j = 0,
309             jln,
310             rowParams;
311
312         jln = preppedRecords.length;
313         // process row classes, rowParams has been deprecated and has been moved
314         // to the individual features that implement the behavior.
315         if (this.getRowClass) {
316             for (; j &lt; jln; j++) {
317                 rowParams = {};
318                 preppedRecords[j]['rowCls'] = this.getRowClass(records[j], j, rowParams, this.store);
319                 //&lt;debug&gt;
320                 if (rowParams.alt) {
321                     Ext.Error.raise(&quot;The getRowClass alt property is no longer supported.&quot;);
322                 }
323                 if (rowParams.tstyle) {
324                     Ext.Error.raise(&quot;The getRowClass tstyle property is no longer supported.&quot;);
325                 }
326                 if (rowParams.cells) {
327                     Ext.Error.raise(&quot;The getRowClass cells property is no longer supported.&quot;);
328                 }
329                 if (rowParams.body) {
330                     Ext.Error.raise(&quot;The getRowClass body property is no longer supported. Use the getAdditionalData method of the rowbody feature.&quot;);
331                 }
332                 if (rowParams.bodyStyle) {
333                     Ext.Error.raise(&quot;The getRowClass bodyStyle property is no longer supported.&quot;);
334                 }
335                 if (rowParams.cols) {
336                     Ext.Error.raise(&quot;The getRowClass cols property is no longer supported.&quot;);
337                 }
338                 //&lt;/debug&gt;
339             }
340         }
341         // currently only one feature may implement collectData. This is to modify
342         // what's returned to the view before its rendered
343         for (; i &lt; ln; i++) {
344             feature = features[i];
345             if (feature.isFeature &amp;&amp; feature.collectData &amp;&amp; !feature.disabled) {
346                 o = feature.collectData(records, preppedRecords, startIndex, fullWidth, o);
347                 break;
348             }
349         }
350         return o;
351     },
352
353     // TODO: Refactor header resizing to column resizing
354 <span id='Ext-view-Table-method-onHeaderResize'>    /**
355 </span>     * When a header is resized, setWidth on the individual columns resizer class,
356      * the top level table, save/restore scroll state, generate a new template and
357      * restore focus to the grid view's element so that keyboard navigation
358      * continues to work.
359      * @private
360      */
361     onHeaderResize: function(header, w, suppressFocus) {
362         var me = this,
363             el = me.el;
364
365         if (el) {
366             me.saveScrollState();
367             // Grab the col and set the width, css
368             // class is generated in TableChunker.
369             // Select composites because there may be several chunks.
370
371             // IE6 and IE7 bug.
372             // Setting the width of the first TD does not work - ends up with a 1 pixel discrepancy.
373             // We need to increment the passed with in this case.
374             if (Ext.isIE6 || Ext.isIE7) {
375                 if (header.el.hasCls(Ext.baseCSSPrefix + 'column-header-first')) {
376                     w += 1;
377                 }
378             }
379             el.select('.' + Ext.baseCSSPrefix + 'grid-col-resizer-'+header.id).setWidth(w);
380             el.select('.' + Ext.baseCSSPrefix + 'grid-table-resizer').setWidth(me.headerCt.getFullWidth());
381             me.restoreScrollState();
382             me.setNewTemplate();
383             if (!suppressFocus) {
384                 me.el.focus();
385             }
386         }
387     },
388
389 <span id='Ext-view-Table-method-onHeaderShow'>    /**
390 </span>     * When a header is shown restore its oldWidth if it was previously hidden.
391      * @private
392      */
393     onHeaderShow: function(headerCt, header, suppressFocus) {
394         // restore headers that were dynamically hidden
395         if (header.oldWidth) {
396             this.onHeaderResize(header, header.oldWidth, suppressFocus);
397             delete header.oldWidth;
398         // flexed headers will have a calculated size set
399         // this additional check has to do with the fact that
400         // defaults: {width: 100} will fight with a flex value
401         } else if (header.width &amp;&amp; !header.flex) {
402             this.onHeaderResize(header, header.width, suppressFocus);
403         }
404         this.setNewTemplate();
405     },
406
407 <span id='Ext-view-Table-method-onHeaderHide'>    /**
408 </span>     * When the header hides treat it as a resize to 0.
409      * @private
410      */
411     onHeaderHide: function(headerCt, header, suppressFocus) {
412         this.onHeaderResize(header, 0, suppressFocus);
413     },
414
415 <span id='Ext-view-Table-method-setNewTemplate'>    /**
416 </span>     * Set a new template based on the current columns displayed in the
417      * grid.
418      * @private
419      */
420     setNewTemplate: function() {
421         var me = this,
422             columns = me.headerCt.getColumnsForTpl(true);
423
424         me.tpl = me.getTableChunker().getTableTpl({
425             columns: columns,
426             features: me.features
427         });
428     },
429
430 <span id='Ext-view-Table-method-getTableChunker'>    /**
431 </span>     * Get the configured chunker or default of Ext.view.TableChunker
432      */
433     getTableChunker: function() {
434         return this.chunker || Ext.view.TableChunker;
435     },
436
437 <span id='Ext-view-Table-method-addRowCls'>    /**
438 </span>     * Add a CSS Class to a specific row.
439      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model representing this row
440      * @param {String} cls
441      */
442     addRowCls: function(rowInfo, cls) {
443         var row = this.getNode(rowInfo);
444         if (row) {
445             Ext.fly(row).addCls(cls);
446         }
447     },
448
449 <span id='Ext-view-Table-method-removeRowCls'>    /**
450 </span>     * Remove a CSS Class from a specific row.
451      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model representing this row
452      * @param {String} cls
453      */
454     removeRowCls: function(rowInfo, cls) {
455         var row = this.getNode(rowInfo);
456         if (row) {
457             Ext.fly(row).removeCls(cls);
458         }
459     },
460
461     // GridSelectionModel invokes onRowSelect as selection changes
462     onRowSelect : function(rowIdx) {
463         this.addRowCls(rowIdx, this.selectedItemCls);
464     },
465
466     // GridSelectionModel invokes onRowDeselect as selection changes
467     onRowDeselect : function(rowIdx) {
468         var me = this;
469
470         me.removeRowCls(rowIdx, me.selectedItemCls);
471         me.removeRowCls(rowIdx, me.focusedItemCls);
472     },
473
474     onCellSelect: function(position) {
475         var cell = this.getCellByPosition(position);
476         if (cell) {
477             cell.addCls(this.selectedCellCls);
478         }
479     },
480
481     onCellDeselect: function(position) {
482         var cell = this.getCellByPosition(position);
483         if (cell) {
484             cell.removeCls(this.selectedCellCls);
485         }
486
487     },
488
489     onCellFocus: function(position) {
490         //var cell = this.getCellByPosition(position);
491         this.focusCell(position);
492     },
493
494     getCellByPosition: function(position) {
495         var row    = position.row,
496             column = position.column,
497             store  = this.store,
498             node   = this.getNode(row),
499             header = this.headerCt.getHeaderAtIndex(column),
500             cellSelector,
501             cell = false;
502
503         if (header &amp;&amp; node) {
504             cellSelector = header.getCellSelector();
505             cell = Ext.fly(node).down(cellSelector);
506         }
507         return cell;
508     },
509
510     // GridSelectionModel invokes onRowFocus to 'highlight'
511     // the last row focused
512     onRowFocus: function(rowIdx, highlight, supressFocus) {
513         var me = this,
514             row = me.getNode(rowIdx);
515
516         if (highlight) {
517             me.addRowCls(rowIdx, me.focusedItemCls);
518             if (!supressFocus) {
519                 me.focusRow(rowIdx);
520             }
521             //this.el.dom.setAttribute('aria-activedescendant', row.id);
522         } else {
523             me.removeRowCls(rowIdx, me.focusedItemCls);
524         }
525     },
526
527 <span id='Ext-view-Table-method-focusRow'>    /**
528 </span>     * Focus a particular row and bring it into view. Will fire the rowfocus event.
529      * @param {Mixed} rowIdx An HTMLElement template node, index of a template node, the
530      * id of a template node or the record associated with the node.
531      */
532     focusRow: function(rowIdx) {
533         var me         = this,
534             row        = me.getNode(rowIdx),
535             el         = me.el,
536             adjustment = 0,
537             panel      = me.ownerCt,
538             rowRegion,
539             elRegion,
540             record;
541
542         if (row &amp;&amp; el) {
543             elRegion  = el.getRegion();
544             rowRegion = Ext.fly(row).getRegion();
545             // row is above
546             if (rowRegion.top &lt; elRegion.top) {
547                 adjustment = rowRegion.top - elRegion.top;
548             // row is below
549             } else if (rowRegion.bottom &gt; elRegion.bottom) {
550                 adjustment = rowRegion.bottom - elRegion.bottom;
551             }
552             record = me.getRecord(row);
553             rowIdx = me.store.indexOf(record);
554
555             if (adjustment) {
556                 // scroll the grid itself, so that all gridview's update.
557                 panel.scrollByDeltaY(adjustment);
558             }
559             me.fireEvent('rowfocus', record, row, rowIdx);
560         }
561     },
562
563     focusCell: function(position) {
564         var me          = this,
565             cell        = me.getCellByPosition(position),
566             el          = me.el,
567             adjustmentY = 0,
568             adjustmentX = 0,
569             elRegion    = el.getRegion(),
570             panel       = me.ownerCt,
571             cellRegion,
572             record;
573
574         if (cell) {
575             cellRegion = cell.getRegion();
576             // cell is above
577             if (cellRegion.top &lt; elRegion.top) {
578                 adjustmentY = cellRegion.top - elRegion.top;
579             // cell is below
580             } else if (cellRegion.bottom &gt; elRegion.bottom) {
581                 adjustmentY = cellRegion.bottom - elRegion.bottom;
582             }
583
584             // cell is left
585             if (cellRegion.left &lt; elRegion.left) {
586                 adjustmentX = cellRegion.left - elRegion.left;
587             // cell is right
588             } else if (cellRegion.right &gt; elRegion.right) {
589                 adjustmentX = cellRegion.right - elRegion.right;
590             }
591
592             if (adjustmentY) {
593                 // scroll the grid itself, so that all gridview's update.
594                 panel.scrollByDeltaY(adjustmentY);
595             }
596             if (adjustmentX) {
597                 panel.scrollByDeltaX(adjustmentX);
598             }
599             el.focus();
600             me.fireEvent('cellfocus', record, cell, position);
601         }
602     },
603
604 <span id='Ext-view-Table-method-scrollByDelta'>    /**
605 </span>     * Scroll by delta. This affects this individual view ONLY and does not
606      * synchronize across views or scrollers.
607      * @param {Number} delta
608      * @param {String} dir (optional) Valid values are scrollTop and scrollLeft. Defaults to scrollTop.
609      * @private
610      */
611     scrollByDelta: function(delta, dir) {
612         dir = dir || 'scrollTop';
613         var elDom = this.el.dom;
614         elDom[dir] = (elDom[dir] += delta);
615     },
616
617     onUpdate: function(ds, index) {
618         this.callParent(arguments);
619     },
620
621 <span id='Ext-view-Table-method-saveScrollState'>    /**
622 </span>     * Save the scrollState in a private variable.
623      * Must be used in conjunction with restoreScrollState
624      */
625     saveScrollState: function() {
626         var dom = this.el.dom,
627             state = this.scrollState;
628
629         state.left = dom.scrollLeft;
630         state.top = dom.scrollTop;
631     },
632
633 <span id='Ext-view-Table-method-restoreScrollState'>    /**
634 </span>     * Restore the scrollState.
635      * Must be used in conjunction with saveScrollState
636      * @private
637      */
638     restoreScrollState: function() {
639         var dom = this.el.dom,
640             state = this.scrollState,
641             headerEl = this.headerCt.el.dom;
642
643         headerEl.scrollLeft = dom.scrollLeft = state.left;
644         dom.scrollTop = state.top;
645     },
646
647 <span id='Ext-view-Table-method-refresh'>    /**
648 </span>     * Refresh the grid view.
649      * Saves and restores the scroll state, generates a new template, stripes rows
650      * and invalidates the scrollers.
651      */
652     refresh: function() {
653         this.setNewTemplate();
654         this.callParent(arguments);
655     },
656
657     processItemEvent: function(record, row, rowIndex, e) {
658         var me = this,
659             cell = e.getTarget(me.cellSelector, row),
660             cellIndex = cell ? cell.cellIndex : -1,
661             map = me.statics().EventMap,
662             selModel = me.getSelectionModel(),
663             type = e.type,
664             result;
665
666         if (type == 'keydown' &amp;&amp; !cell &amp;&amp; selModel.getCurrentPosition) {
667             // CellModel, otherwise we can't tell which cell to invoke
668             cell = me.getCellByPosition(selModel.getCurrentPosition());
669             if (cell) {
670                 cell = cell.dom;
671                 cellIndex = cell.cellIndex;
672             }
673         }
674
675         result = me.fireEvent('uievent', type, me, cell, rowIndex, cellIndex, e);
676
677         if (result === false || me.callParent(arguments) === false) {
678             return false;
679         }
680
681         // Don't handle cellmouseenter and cellmouseleave events for now
682         if (type == 'mouseover' || type == 'mouseout') {
683             return true;
684         }
685
686         return !(
687             // We are adding cell and feature events
688             (me['onBeforeCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
689             (me.fireEvent('beforecell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false) ||
690             (me['onCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
691             (me.fireEvent('cell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false)
692         );
693     },
694
695     processSpecialEvent: function(e) {
696         var me = this,
697             map = me.statics().EventMap,
698             features = me.features,
699             ln = features.length,
700             type = e.type,
701             i, feature, prefix, featureTarget,
702             beforeArgs, args,
703             panel = me.ownerCt;
704
705         me.callParent(arguments);
706
707         if (type == 'mouseover' || type == 'mouseout') {
708             return;
709         }
710
711         for (i = 0; i &lt; ln; i++) {
712             feature = features[i];
713             if (feature.hasFeatureEvent) {
714                 featureTarget = e.getTarget(feature.eventSelector, me.getTargetEl());
715                 if (featureTarget) {
716                     prefix = feature.eventPrefix;
717                     // allows features to implement getFireEventArgs to change the
718                     // fireEvent signature
719                     beforeArgs = feature.getFireEventArgs('before' + prefix + type, me, featureTarget, e);
720                     args = feature.getFireEventArgs(prefix + type, me, featureTarget, e);
721
722                     if (
723                         // before view event
724                         (me.fireEvent.apply(me, beforeArgs) === false) ||
725                         // panel grid event
726                         (panel.fireEvent.apply(panel, beforeArgs) === false) ||
727                         // view event
728                         (me.fireEvent.apply(me, args) === false) ||
729                         // panel event
730                         (panel.fireEvent.apply(panel, args) === false)
731                     ) {
732                         return false;
733                     }
734                 }
735             }
736         }
737         return true;
738     },
739
740     onCellMouseDown: Ext.emptyFn,
741     onCellMouseUp: Ext.emptyFn,
742     onCellClick: Ext.emptyFn,
743     onCellDblClick: Ext.emptyFn,
744     onCellContextMenu: Ext.emptyFn,
745     onCellKeyDown: Ext.emptyFn,
746     onBeforeCellMouseDown: Ext.emptyFn,
747     onBeforeCellMouseUp: Ext.emptyFn,
748     onBeforeCellClick: Ext.emptyFn,
749     onBeforeCellDblClick: Ext.emptyFn,
750     onBeforeCellContextMenu: Ext.emptyFn,
751     onBeforeCellKeyDown: Ext.emptyFn,
752
753 <span id='Ext-view-Table-method-expandToFit'>    /**
754 </span>     * Expand a particular header to fit the max content width.
755      * This will ONLY expand, not contract.
756      * @private
757      */
758     expandToFit: function(header) {
759         if (header) {
760             var maxWidth = this.getMaxContentWidth(header);
761             delete header.flex;
762             header.setWidth(maxWidth);
763         }
764     },
765
766 <span id='Ext-view-Table-method-getMaxContentWidth'>    /**
767 </span>     * Get the max contentWidth of the header's text and all cells
768      * in the grid under this header.
769      * @private
770      */
771     getMaxContentWidth: function(header) {
772         var cellSelector = header.getCellInnerSelector(),
773             cells        = this.el.query(cellSelector),
774             i = 0,
775             ln = cells.length,
776             maxWidth = header.el.dom.scrollWidth,
777             scrollWidth;
778
779         for (; i &lt; ln; i++) {
780             scrollWidth = cells[i].scrollWidth;
781             if (scrollWidth &gt; maxWidth) {
782                 maxWidth = scrollWidth;
783             }
784         }
785         return maxWidth;
786     },
787
788     getPositionByEvent: function(e) {
789         var me       = this,
790             cellNode = e.getTarget(me.cellSelector),
791             rowNode  = e.getTarget(me.itemSelector),
792             record   = me.getRecord(rowNode),
793             header   = me.getHeaderByCell(cellNode);
794
795         return me.getPosition(record, header);
796     },
797
798     getHeaderByCell: function(cell) {
799         if (cell) {
800             var m = cell.className.match(this.cellRe);
801             if (m &amp;&amp; m[1]) {
802                 return Ext.getCmp(m[1]);
803             }
804         }
805         return false;
806     },
807
808 <span id='Ext-view-Table-method-walkCells'>    /**
809 </span>     * @param {Object} position The current row and column: an object containing the following properties:&lt;ul&gt;
810      * &lt;li&gt;row&lt;div class=&quot;sub-desc&quot;&gt; The row &lt;b&gt;index&lt;/b&gt;&lt;/div&gt;&lt;/li&gt;
811      * &lt;li&gt;column&lt;div class=&quot;sub-desc&quot;&gt;The column &lt;b&gt;index&lt;/b&gt;&lt;/div&gt;&lt;/li&gt;
812      * &lt;/ul&gt;
813      * @param {String} direction 'up', 'down', 'right' and 'left'
814      * @param {Ext.EventObject} e event
815      * @param {Boolean} preventWrap Set to true to prevent wrap around to the next or previous row.
816      * @param {Function} verifierFn A function to verify the validity of the calculated position. When using this function, you must return true to allow the newPosition to be returned.
817      * @param {Scope} scope Scope to run the verifierFn in
818      * @returns {Object} newPosition An object containing the following properties:&lt;ul&gt;
819      * &lt;li&gt;row&lt;div class=&quot;sub-desc&quot;&gt; The row &lt;b&gt;index&lt;/b&gt;&lt;/div&gt;&lt;/li&gt;
820      * &lt;li&gt;column&lt;div class=&quot;sub-desc&quot;&gt;The column &lt;b&gt;index&lt;/b&gt;&lt;/div&gt;&lt;/li&gt;
821      * &lt;/ul&gt;
822      * @private
823      */
824     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
825         var me       = this,
826             row      = pos.row,
827             column   = pos.column,
828             rowCount = me.store.getCount(),
829             firstCol = me.getFirstVisibleColumnIndex(),
830             lastCol  = me.getLastVisibleColumnIndex(),
831             newPos   = {row: row, column: column},
832             activeHeader = me.headerCt.getHeaderAtIndex(column);
833
834         // no active header or its currently hidden
835         if (!activeHeader || activeHeader.hidden) {
836             return false;
837         }
838
839         e = e || {};
840         direction = direction.toLowerCase();
841         switch (direction) {
842             case 'right':
843                 // has the potential to wrap if its last
844                 if (column === lastCol) {
845                     // if bottom row and last column, deny right
846                     if (preventWrap || row === rowCount - 1) {
847                         return false;
848                     }
849                     if (!e.ctrlKey) {
850                         // otherwise wrap to nextRow and firstCol
851                         newPos.row = row + 1;
852                         newPos.column = firstCol;
853                     }
854                 // go right
855                 } else {
856                     if (!e.ctrlKey) {
857                         newPos.column = column + me.getRightGap(activeHeader);
858                     } else {
859                         newPos.column = lastCol;
860                     }
861                 }
862                 break;
863
864             case 'left':
865                 // has the potential to wrap
866                 if (column === firstCol) {
867                     // if top row and first column, deny left
868                     if (preventWrap || row === 0) {
869                         return false;
870                     }
871                     if (!e.ctrlKey) {
872                         // otherwise wrap to prevRow and lastCol
873                         newPos.row = row - 1;
874                         newPos.column = lastCol;
875                     }
876                 // go left
877                 } else {
878                     if (!e.ctrlKey) {
879                         newPos.column = column + me.getLeftGap(activeHeader);
880                     } else {
881                         newPos.column = firstCol;
882                     }
883                 }
884                 break;
885
886             case 'up':
887                 // if top row, deny up
888                 if (row === 0) {
889                     return false;
890                 // go up
891                 } else {
892                     if (!e.ctrlKey) {
893                         newPos.row = row - 1;
894                     } else {
895                         newPos.row = 0;
896                     }
897                 }
898                 break;
899
900             case 'down':
901                 // if bottom row, deny down
902                 if (row === rowCount - 1) {
903                     return false;
904                 // go down
905                 } else {
906                     if (!e.ctrlKey) {
907                         newPos.row = row + 1;
908                     } else {
909                         newPos.row = rowCount - 1;
910                     }
911                 }
912                 break;
913         }
914
915         if (verifierFn &amp;&amp; verifierFn.call(scope || window, newPos) !== true) {
916             return false;
917         } else {
918             return newPos;
919         }
920     },
921     getFirstVisibleColumnIndex: function() {
922         var headerCt   = this.getHeaderCt(),
923             allColumns = headerCt.getGridColumns(),
924             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
925             firstHeader = visHeaders[0];
926
927         return headerCt.getHeaderIndex(firstHeader);
928     },
929
930     getLastVisibleColumnIndex: function() {
931         var headerCt   = this.getHeaderCt(),
932             allColumns = headerCt.getGridColumns(),
933             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
934             lastHeader = visHeaders[visHeaders.length - 1];
935
936         return headerCt.getHeaderIndex(lastHeader);
937     },
938
939     getHeaderCt: function() {
940         return this.headerCt;
941     },
942
943     getPosition: function(record, header) {
944         var me = this,
945             store = me.store,
946             gridCols = me.headerCt.getGridColumns();
947
948         return {
949             row: store.indexOf(record),
950             column: Ext.Array.indexOf(gridCols, header)
951         };
952     },
953
954 <span id='Ext-view-Table-method-getRightGap'>    /**
955 </span>     * Determines the 'gap' between the closest adjacent header to the right
956      * that is not hidden.
957      * @private
958      */
959     getRightGap: function(activeHeader) {
960         var headerCt        = this.getHeaderCt(),
961             headers         = headerCt.getGridColumns(),
962             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
963             i               = activeHeaderIdx + 1,
964             nextIdx;
965
966         for (; i &lt;= headers.length; i++) {
967             if (!headers[i].hidden) {
968                 nextIdx = i;
969                 break;
970             }
971         }
972
973         return nextIdx - activeHeaderIdx;
974     },
975
976     beforeDestroy: function() {
977         if (this.rendered) {
978             this.el.removeAllListeners();
979         }
980         this.callParent(arguments);
981     },
982
983 <span id='Ext-view-Table-method-getLeftGap'>    /**
984 </span>     * Determines the 'gap' between the closest adjacent header to the left
985      * that is not hidden.
986      * @private
987      */
988     getLeftGap: function(activeHeader) {
989         var headerCt        = this.getHeaderCt(),
990             headers         = headerCt.getGridColumns(),
991             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
992             i               = activeHeaderIdx - 1,
993             prevIdx;
994
995         for (; i &gt;= 0; i--) {
996             if (!headers[i].hidden) {
997                 prevIdx = i;
998                 break;
999             }
1000         }
1001
1002         return prevIdx - activeHeaderIdx;
1003     }
1004 });</pre>
1005 </body>
1006 </html>