Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / src / widgets / grid / ColumnModel.js
1 /*!
2  * Ext JS Library 3.3.1
3  * Copyright(c) 2006-2010 Sencha Inc.
4  * licensing@sencha.com
5  * http://www.sencha.com/license
6  */
7 /**
8  * @class Ext.grid.ColumnModel
9  * @extends Ext.util.Observable
10  * <p>After the data has been read into the client side cache (<b>{@link Ext.data.Store Store}</b>),
11  * the ColumnModel is used to configure how and what parts of that data will be displayed in the
12  * vertical slices (columns) of the grid. The Ext.grid.ColumnModel Class is the default implementation
13  * of a ColumnModel used by implentations of {@link Ext.grid.GridPanel GridPanel}.</p>
14  * <p>Data is mapped into the store's records and then indexed into the ColumnModel using the
15  * <tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt>:</p>
16  * <pre><code>
17 {data source} == mapping ==> {data store} == <b><tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt></b> ==> {ColumnModel}
18  * </code></pre>
19  * <p>Each {@link Ext.grid.Column Column} in the grid's ColumnModel is configured with a
20  * <tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt> to specify how the data within
21  * each record in the store is indexed into the ColumnModel.</p>
22  * <p>There are two ways to initialize the ColumnModel class:</p>
23  * <p><u>Initialization Method 1: an Array</u></p>
24 <pre><code>
25  var colModel = new Ext.grid.ColumnModel([
26     { header: "Ticker", width: 60, sortable: true},
27     { header: "Company Name", width: 150, sortable: true, id: 'company'},
28     { header: "Market Cap.", width: 100, sortable: true},
29     { header: "$ Sales", width: 100, sortable: true, renderer: money},
30     { header: "Employees", width: 100, sortable: true, resizable: false}
31  ]);
32  </code></pre>
33  * <p>The ColumnModel may be initialized with an Array of {@link Ext.grid.Column} column configuration
34  * objects to define the initial layout / display of the columns in the Grid. The order of each
35  * {@link Ext.grid.Column} column configuration object within the specified Array defines the initial
36  * order of the column display.  A Column's display may be initially hidden using the
37  * <tt>{@link Ext.grid.Column#hidden hidden}</tt></b> config property (and then shown using the column
38  * header menu).  Fields that are not included in the ColumnModel will not be displayable at all.</p>
39  * <p>How each column in the grid correlates (maps) to the {@link Ext.data.Record} field in the
40  * {@link Ext.data.Store Store} the column draws its data from is configured through the
41  * <b><tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt></b>.  If the
42  * <b><tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt></b> is not explicitly defined (as shown in the
43  * example above) it will use the column configuration's index in the Array as the index.</p>
44  * <p>See <b><tt>{@link Ext.grid.Column}</tt></b> for additional configuration options for each column.</p>
45  * <p><u>Initialization Method 2: an Object</u></p>
46  * <p>In order to use configuration options from <tt>Ext.grid.ColumnModel</tt>, an Object may be used to
47  * initialize the ColumnModel.  The column configuration Array will be specified in the <tt><b>{@link #columns}</b></tt>
48  * config property. The <tt><b>{@link #defaults}</b></tt> config property can be used to apply defaults
49  * for all columns, e.g.:</p><pre><code>
50  var colModel = new Ext.grid.ColumnModel({
51     columns: [
52         { header: "Ticker", width: 60, menuDisabled: false},
53         { header: "Company Name", width: 150, id: 'company'},
54         { header: "Market Cap."},
55         { header: "$ Sales", renderer: money},
56         { header: "Employees", resizable: false}
57     ],
58     defaults: {
59         sortable: true,
60         menuDisabled: true,
61         width: 100
62     },
63     listeners: {
64         {@link #hiddenchange}: function(cm, colIndex, hidden) {
65             saveConfig(colIndex, hidden);
66         }
67     }
68 });
69  </code></pre>
70  * <p>In both examples above, the ability to apply a CSS class to all cells in a column (including the
71  * header) is demonstrated through the use of the <b><tt>{@link Ext.grid.Column#id id}</tt></b> config
72  * option. This column could be styled by including the following css:</p><pre><code>
73  //add this css *after* the core css is loaded
74 .x-grid3-td-company {
75     color: red; // entire column will have red font
76 }
77 // modify the header row only, adding an icon to the column header
78 .x-grid3-hd-company {
79     background: transparent
80         url(../../resources/images/icons/silk/building.png)
81         no-repeat 3px 3px ! important;
82         padding-left:20px;
83 }
84  </code></pre>
85  * Note that the "Company Name" column could be specified as the
86  * <b><tt>{@link Ext.grid.GridPanel}.{@link Ext.grid.GridPanel#autoExpandColumn autoExpandColumn}</tt></b>.
87  * @constructor
88  * @param {Mixed} config Specify either an Array of {@link Ext.grid.Column} configuration objects or specify
89  * a configuration Object (see introductory section discussion utilizing Initialization Method 2 above).
90  */
91 Ext.grid.ColumnModel = Ext.extend(Ext.util.Observable, {
92     /**
93      * @cfg {Number} defaultWidth (optional) The width of columns which have no <tt>{@link #width}</tt>
94      * specified (defaults to <tt>100</tt>).  This property shall preferably be configured through the
95      * <tt><b>{@link #defaults}</b></tt> config property.
96      */
97     defaultWidth: 100,
98
99     /**
100      * @cfg {Boolean} defaultSortable (optional) Default sortable of columns which have no
101      * sortable specified (defaults to <tt>false</tt>).  This property shall preferably be configured
102      * through the <tt><b>{@link #defaults}</b></tt> config property.
103      */
104     defaultSortable: false,
105
106     /**
107      * @cfg {Array} columns An Array of object literals.  The config options defined by
108      * <b>{@link Ext.grid.Column}</b> are the options which may appear in the object literal for each
109      * individual column definition.
110      */
111
112     /**
113      * @cfg {Object} defaults Object literal which will be used to apply {@link Ext.grid.Column}
114      * configuration options to all <tt><b>{@link #columns}</b></tt>.  Configuration options specified with
115      * individual {@link Ext.grid.Column column} configs will supersede these <tt><b>{@link #defaults}</b></tt>.
116      */
117
118     constructor : function(config) {
119         /**
120              * An Array of {@link Ext.grid.Column Column definition} objects representing the configuration
121              * of this ColumnModel.  See {@link Ext.grid.Column} for the configuration properties that may
122              * be specified.
123              * @property config
124              * @type Array
125              */
126             if (config.columns) {
127                 Ext.apply(this, config);
128                 this.setConfig(config.columns, true);
129             } else {
130                 this.setConfig(config, true);
131             }
132             
133             this.addEvents(
134                 /**
135                  * @event widthchange
136                  * Fires when the width of a column is programmaticially changed using
137                  * <code>{@link #setColumnWidth}</code>.
138                  * Note internal resizing suppresses the event from firing. See also
139                  * {@link Ext.grid.GridPanel}.<code>{@link #columnresize}</code>.
140                  * @param {ColumnModel} this
141                  * @param {Number} columnIndex The column index
142                  * @param {Number} newWidth The new width
143                  */
144                 "widthchange",
145                 
146                 /**
147                  * @event headerchange
148                  * Fires when the text of a header changes.
149                  * @param {ColumnModel} this
150                  * @param {Number} columnIndex The column index
151                  * @param {String} newText The new header text
152                  */
153                 "headerchange",
154                 
155                 /**
156                  * @event hiddenchange
157                  * Fires when a column is hidden or "unhidden".
158                  * @param {ColumnModel} this
159                  * @param {Number} columnIndex The column index
160                  * @param {Boolean} hidden true if hidden, false otherwise
161                  */
162                 "hiddenchange",
163                 
164                 /**
165                  * @event columnmoved
166                  * Fires when a column is moved.
167                  * @param {ColumnModel} this
168                  * @param {Number} oldIndex
169                  * @param {Number} newIndex
170                  */
171                 "columnmoved",
172                 
173                 /**
174                  * @event configchange
175                  * Fires when the configuration is changed
176                  * @param {ColumnModel} this
177                  */
178                 "configchange"
179             );
180             
181             Ext.grid.ColumnModel.superclass.constructor.call(this);
182     },
183
184     /**
185      * Returns the id of the column at the specified index.
186      * @param {Number} index The column index
187      * @return {String} the id
188      */
189     getColumnId : function(index) {
190         return this.config[index].id;
191     },
192
193     getColumnAt : function(index) {
194         return this.config[index];
195     },
196
197     /**
198      * <p>Reconfigures this column model according to the passed Array of column definition objects.
199      * For a description of the individual properties of a column definition object, see the
200      * <a href="#Ext.grid.ColumnModel-configs">Config Options</a>.</p>
201      * <p>Causes the {@link #configchange} event to be fired. A {@link Ext.grid.GridPanel GridPanel}
202      * using this ColumnModel will listen for this event and refresh its UI automatically.</p>
203      * @param {Array} config Array of Column definition objects.
204      * @param {Boolean} initial Specify <tt>true</tt> to bypass cleanup which deletes the <tt>totalWidth</tt>
205      * and destroys existing editors.
206      */
207     setConfig : function(config, initial) {
208         var i, c, len;
209         
210         if (!initial) { // cleanup
211             delete this.totalWidth;
212             
213             for (i = 0, len = this.config.length; i < len; i++) {
214                 c = this.config[i];
215                 
216                 if (c.setEditor) {
217                     //check here, in case we have a special column like a CheckboxSelectionModel
218                     c.setEditor(null);
219                 }
220             }
221         }
222
223         // backward compatibility
224         this.defaults = Ext.apply({
225             width: this.defaultWidth,
226             sortable: this.defaultSortable
227         }, this.defaults);
228
229         this.config = config;
230         this.lookup = {};
231
232         for (i = 0, len = config.length; i < len; i++) {
233             c = Ext.applyIf(config[i], this.defaults);
234             
235             // if no id, create one using column's ordinal position
236             if (Ext.isEmpty(c.id)) {
237                 c.id = i;
238             }
239             
240             if (!c.isColumn) {
241                 var Cls = Ext.grid.Column.types[c.xtype || 'gridcolumn'];
242                 c = new Cls(c);
243                 config[i] = c;
244             }
245             
246             this.lookup[c.id] = c;
247         }
248         
249         if (!initial) {
250             this.fireEvent('configchange', this);
251         }
252     },
253
254     /**
255      * Returns the column for a specified id.
256      * @param {String} id The column id
257      * @return {Object} the column
258      */
259     getColumnById : function(id) {
260         return this.lookup[id];
261     },
262
263     /**
264      * Returns the index for a specified column id.
265      * @param {String} id The column id
266      * @return {Number} the index, or -1 if not found
267      */
268     getIndexById : function(id) {
269         for (var i = 0, len = this.config.length; i < len; i++) {
270             if (this.config[i].id == id) {
271                 return i;
272             }
273         }
274         return -1;
275     },
276
277     /**
278      * Moves a column from one position to another.
279      * @param {Number} oldIndex The index of the column to move.
280      * @param {Number} newIndex The position at which to reinsert the coolumn.
281      */
282     moveColumn : function(oldIndex, newIndex) {
283         var config = this.config,
284             c      = config[oldIndex];
285             
286         config.splice(oldIndex, 1);
287         config.splice(newIndex, 0, c);
288         this.dataMap = null;
289         this.fireEvent("columnmoved", this, oldIndex, newIndex);
290     },
291
292     /**
293      * Returns the number of columns.
294      * @param {Boolean} visibleOnly Optional. Pass as true to only include visible columns.
295      * @return {Number}
296      */
297     getColumnCount : function(visibleOnly) {
298         var length = this.config.length,
299             c = 0,
300             i;
301         
302         if (visibleOnly === true) {
303             for (i = 0; i < length; i++) {
304                 if (!this.isHidden(i)) {
305                     c++;
306                 }
307             }
308             
309             return c;
310         }
311         
312         return length;
313     },
314
315     /**
316      * Returns the column configs that return true by the passed function that is called
317      * with (columnConfig, index)
318 <pre><code>
319 // returns an array of column config objects for all hidden columns
320 var columns = grid.getColumnModel().getColumnsBy(function(c){
321   return c.hidden;
322 });
323 </code></pre>
324      * @param {Function} fn A function which, when passed a {@link Ext.grid.Column Column} object, must
325      * return <code>true</code> if the column is to be included in the returned Array.
326      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
327      * is executed. Defaults to this ColumnModel.
328      * @return {Array} result
329      */
330     getColumnsBy : function(fn, scope) {
331         var config = this.config,
332             length = config.length,
333             result = [],
334             i, c;
335             
336         for (i = 0; i < length; i++){
337             c = config[i];
338             
339             if (fn.call(scope || this, c, i) === true) {
340                 result[result.length] = c;
341             }
342         }
343         
344         return result;
345     },
346
347     /**
348      * Returns true if the specified column is sortable.
349      * @param {Number} col The column index
350      * @return {Boolean}
351      */
352     isSortable : function(col) {
353         return !!this.config[col].sortable;
354     },
355
356     /**
357      * Returns true if the specified column menu is disabled.
358      * @param {Number} col The column index
359      * @return {Boolean}
360      */
361     isMenuDisabled : function(col) {
362         return !!this.config[col].menuDisabled;
363     },
364
365     /**
366      * Returns the rendering (formatting) function defined for the column.
367      * @param {Number} col The column index.
368      * @return {Function} The function used to render the cell. See {@link #setRenderer}.
369      */
370     getRenderer : function(col) {
371         return this.config[col].renderer || Ext.grid.ColumnModel.defaultRenderer;
372     },
373
374     getRendererScope : function(col) {
375         return this.config[col].scope;
376     },
377
378     /**
379      * Sets the rendering (formatting) function for a column.  See {@link Ext.util.Format} for some
380      * default formatting functions.
381      * @param {Number} col The column index
382      * @param {Function} fn The function to use to process the cell's raw data
383      * to return HTML markup for the grid view. The render function is called with
384      * the following parameters:<ul>
385      * <li><b>value</b> : Object<p class="sub-desc">The data value for the cell.</p></li>
386      * <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
387      * <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
388      * <li><b>attr</b> : String<p class="sub-desc">An HTML attribute definition string to apply to the data container element <i>within</i> the table cell
389      * (e.g. 'style="color:red;"').</p></li></ul></p></li>
390      * <li><b>record</b> : Ext.data.record<p class="sub-desc">The {@link Ext.data.Record} from which the data was extracted.</p></li>
391      * <li><b>rowIndex</b> : Number<p class="sub-desc">Row index</p></li>
392      * <li><b>colIndex</b> : Number<p class="sub-desc">Column index</p></li>
393      * <li><b>store</b> : Ext.data.Store<p class="sub-desc">The {@link Ext.data.Store} object from which the Record was extracted.</p></li></ul>
394      */
395     setRenderer : function(col, fn) {
396         this.config[col].renderer = fn;
397     },
398
399     /**
400      * Returns the width for the specified column.
401      * @param {Number} col The column index
402      * @return {Number}
403      */
404     getColumnWidth : function(col) {
405         var width = this.config[col].width;
406         if(typeof width != 'number'){
407             width = this.defaultWidth;
408         }
409         return width;
410     },
411
412     /**
413      * Sets the width for a column.
414      * @param {Number} col The column index
415      * @param {Number} width The new width
416      * @param {Boolean} suppressEvent True to suppress firing the <code>{@link #widthchange}</code>
417      * event. Defaults to false.
418      */
419     setColumnWidth : function(col, width, suppressEvent) {
420         this.config[col].width = width;
421         this.totalWidth = null;
422         
423         if (!suppressEvent) {
424              this.fireEvent("widthchange", this, col, width);
425         }
426     },
427
428     /**
429      * Returns the total width of all columns.
430      * @param {Boolean} includeHidden True to include hidden column widths
431      * @return {Number}
432      */
433     getTotalWidth : function(includeHidden) {
434         if (!this.totalWidth) {
435             this.totalWidth = 0;
436             for (var i = 0, len = this.config.length; i < len; i++) {
437                 if (includeHidden || !this.isHidden(i)) {
438                     this.totalWidth += this.getColumnWidth(i);
439                 }
440             }
441         }
442         return this.totalWidth;
443     },
444
445     /**
446      * Returns the header for the specified column.
447      * @param {Number} col The column index
448      * @return {String}
449      */
450     getColumnHeader : function(col) {
451         return this.config[col].header;
452     },
453
454     /**
455      * Sets the header for a column.
456      * @param {Number} col The column index
457      * @param {String} header The new header
458      */
459     setColumnHeader : function(col, header) {
460         this.config[col].header = header;
461         this.fireEvent("headerchange", this, col, header);
462     },
463
464     /**
465      * Returns the tooltip for the specified column.
466      * @param {Number} col The column index
467      * @return {String}
468      */
469     getColumnTooltip : function(col) {
470             return this.config[col].tooltip;
471     },
472     /**
473      * Sets the tooltip for a column.
474      * @param {Number} col The column index
475      * @param {String} tooltip The new tooltip
476      */
477     setColumnTooltip : function(col, tooltip) {
478             this.config[col].tooltip = tooltip;
479     },
480
481     /**
482      * Returns the dataIndex for the specified column.
483 <pre><code>
484 // Get field name for the column
485 var fieldName = grid.getColumnModel().getDataIndex(columnIndex);
486 </code></pre>
487      * @param {Number} col The column index
488      * @return {String} The column's dataIndex
489      */
490     getDataIndex : function(col) {
491         return this.config[col].dataIndex;
492     },
493
494     /**
495      * Sets the dataIndex for a column.
496      * @param {Number} col The column index
497      * @param {String} dataIndex The new dataIndex
498      */
499     setDataIndex : function(col, dataIndex) {
500         this.config[col].dataIndex = dataIndex;
501     },
502
503     /**
504      * Finds the index of the first matching column for the given dataIndex.
505      * @param {String} col The dataIndex to find
506      * @return {Number} The column index, or -1 if no match was found
507      */
508     findColumnIndex : function(dataIndex) {
509         var c = this.config;
510         for(var i = 0, len = c.length; i < len; i++){
511             if(c[i].dataIndex == dataIndex){
512                 return i;
513             }
514         }
515         return -1;
516     },
517
518     /**
519      * Returns true if the cell is editable.
520 <pre><code>
521 var store = new Ext.data.Store({...});
522 var colModel = new Ext.grid.ColumnModel({
523   columns: [...],
524   isCellEditable: function(col, row) {
525     var record = store.getAt(row);
526     if (record.get('readonly')) { // replace with your condition
527       return false;
528     }
529     return Ext.grid.ColumnModel.prototype.isCellEditable.call(this, col, row);
530   }
531 });
532 var grid = new Ext.grid.GridPanel({
533   store: store,
534   colModel: colModel,
535   ...
536 });
537 </code></pre>
538      * @param {Number} colIndex The column index
539      * @param {Number} rowIndex The row index
540      * @return {Boolean}
541      */
542     isCellEditable : function(colIndex, rowIndex) {
543         var c = this.config[colIndex],
544             ed = c.editable;
545
546         //force boolean
547         return !!(ed || (!Ext.isDefined(ed) && c.editor));
548     },
549
550     /**
551      * Returns the editor defined for the cell/column.
552      * @param {Number} colIndex The column index
553      * @param {Number} rowIndex The row index
554      * @return {Ext.Editor} The {@link Ext.Editor Editor} that was created to wrap
555      * the {@link Ext.form.Field Field} used to edit the cell.
556      */
557     getCellEditor : function(colIndex, rowIndex) {
558         return this.config[colIndex].getCellEditor(rowIndex);
559     },
560
561     /**
562      * Sets if a column is editable.
563      * @param {Number} col The column index
564      * @param {Boolean} editable True if the column is editable
565      */
566     setEditable : function(col, editable) {
567         this.config[col].editable = editable;
568     },
569
570     /**
571      * Returns <tt>true</tt> if the column is <code>{@link Ext.grid.Column#hidden hidden}</code>,
572      * <tt>false</tt> otherwise.
573      * @param {Number} colIndex The column index
574      * @return {Boolean}
575      */
576     isHidden : function(colIndex) {
577         return !!this.config[colIndex].hidden; // ensure returns boolean
578     },
579
580     /**
581      * Returns <tt>true</tt> if the column is <code>{@link Ext.grid.Column#fixed fixed}</code>,
582      * <tt>false</tt> otherwise.
583      * @param {Number} colIndex The column index
584      * @return {Boolean}
585      */
586     isFixed : function(colIndex) {
587         return !!this.config[colIndex].fixed;
588     },
589
590     /**
591      * Returns true if the column can be resized
592      * @return {Boolean}
593      */
594     isResizable : function(colIndex) {
595         return colIndex >= 0 && this.config[colIndex].resizable !== false && this.config[colIndex].fixed !== true;
596     },
597     
598     /**
599      * Sets if a column is hidden.
600 <pre><code>
601 myGrid.getColumnModel().setHidden(0, true); // hide column 0 (0 = the first column).
602 </code></pre>
603      * @param {Number} colIndex The column index
604      * @param {Boolean} hidden True if the column is hidden
605      */
606     setHidden : function(colIndex, hidden) {
607         var c = this.config[colIndex];
608         if(c.hidden !== hidden){
609             c.hidden = hidden;
610             this.totalWidth = null;
611             this.fireEvent("hiddenchange", this, colIndex, hidden);
612         }
613     },
614
615     /**
616      * Sets the editor for a column and destroys the prior editor.
617      * @param {Number} col The column index
618      * @param {Object} editor The editor object
619      */
620     setEditor : function(col, editor) {
621         this.config[col].setEditor(editor);
622     },
623
624     /**
625      * Destroys this column model by purging any event listeners. Destroys and dereferences all Columns.
626      */
627     destroy : function() {
628         var length = this.config.length,
629             i = 0;
630
631         for (; i < length; i++){
632             this.config[i].destroy(); // Column's destroy encapsulates all cleanup.
633         }
634         delete this.config;
635         delete this.lookup;
636         this.purgeListeners();
637     },
638
639     /**
640      * @private
641      * Setup any saved state for the column, ensures that defaults are applied.
642      */
643     setState : function(col, state) {
644         state = Ext.applyIf(state, this.defaults);
645         Ext.apply(this.config[col], state);
646     }
647 });
648
649 // private
650 Ext.grid.ColumnModel.defaultRenderer = function(value) {
651     if (typeof value == "string" && value.length < 1) {
652         return "&#160;";
653     }
654     return value;
655 };