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