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