Upgrade to ExtJS 3.2.0 - Released 03/30/2010
[extjs.git] / examples / ux / RowExpander.js
1 /*!
2  * Ext JS Library 3.2.0
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 Ext.ns('Ext.ux.grid');
8
9 /**
10  * @class Ext.ux.grid.RowExpander
11  * @extends Ext.util.Observable
12  * Plugin (ptype = 'rowexpander') that adds the ability to have a Column in a grid which enables
13  * a second row body which expands/contracts.  The expand/contract behavior is configurable to react
14  * on clicking of the column, double click of the row, and/or hitting enter while a row is selected.
15  *
16  * @ptype rowexpander
17  */
18 Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, {
19     /**
20      * @cfg {Boolean} expandOnEnter
21      * <tt>true</tt> to toggle selected row(s) between expanded/collapsed when the enter
22      * key is pressed (defaults to <tt>true</tt>).
23      */
24     expandOnEnter : true,
25     /**
26      * @cfg {Boolean} expandOnDblClick
27      * <tt>true</tt> to toggle a row between expanded/collapsed when double clicked
28      * (defaults to <tt>true</tt>).
29      */
30     expandOnDblClick : true,
31
32     header : '',
33     width : 20,
34     sortable : false,
35     fixed : true,
36     hideable: false,
37     menuDisabled : true,
38     dataIndex : '',
39     id : 'expander',
40     lazyRender : true,
41     enableCaching : true,
42
43     constructor: function(config){
44         Ext.apply(this, config);
45
46         this.addEvents({
47             /**
48              * @event beforeexpand
49              * Fires before the row expands. Have the listener return false to prevent the row from expanding.
50              * @param {Object} this RowExpander object.
51              * @param {Object} Ext.data.Record Record for the selected row.
52              * @param {Object} body body element for the secondary row.
53              * @param {Number} rowIndex The current row index.
54              */
55             beforeexpand: true,
56             /**
57              * @event expand
58              * Fires after the row expands.
59              * @param {Object} this RowExpander object.
60              * @param {Object} Ext.data.Record Record for the selected row.
61              * @param {Object} body body element for the secondary row.
62              * @param {Number} rowIndex The current row index.
63              */
64             expand: true,
65             /**
66              * @event beforecollapse
67              * Fires before the row collapses. Have the listener return false to prevent the row from collapsing.
68              * @param {Object} this RowExpander object.
69              * @param {Object} Ext.data.Record Record for the selected row.
70              * @param {Object} body body element for the secondary row.
71              * @param {Number} rowIndex The current row index.
72              */
73             beforecollapse: true,
74             /**
75              * @event collapse
76              * Fires after the row collapses.
77              * @param {Object} this RowExpander object.
78              * @param {Object} Ext.data.Record Record for the selected row.
79              * @param {Object} body body element for the secondary row.
80              * @param {Number} rowIndex The current row index.
81              */
82             collapse: true
83         });
84
85         Ext.ux.grid.RowExpander.superclass.constructor.call(this);
86
87         if(this.tpl){
88             if(typeof this.tpl == 'string'){
89                 this.tpl = new Ext.Template(this.tpl);
90             }
91             this.tpl.compile();
92         }
93
94         this.state = {};
95         this.bodyContent = {};
96     },
97
98     getRowClass : function(record, rowIndex, p, ds){
99         p.cols = p.cols-1;
100         var content = this.bodyContent[record.id];
101         if(!content && !this.lazyRender){
102             content = this.getBodyContent(record, rowIndex);
103         }
104         if(content){
105             p.body = content;
106         }
107         return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed';
108     },
109
110     init : function(grid){
111         this.grid = grid;
112
113         var view = grid.getView();
114         view.getRowClass = this.getRowClass.createDelegate(this);
115
116         view.enableRowBody = true;
117
118
119         grid.on('render', this.onRender, this);
120         grid.on('destroy', this.onDestroy, this);
121     },
122
123     // @private
124     onRender: function() {
125         var grid = this.grid;
126         var mainBody = grid.getView().mainBody;
127         mainBody.on('mousedown', this.onMouseDown, this, {delegate: '.x-grid3-row-expander'});
128         if (this.expandOnEnter) {
129             this.keyNav = new Ext.KeyNav(this.grid.getGridEl(), {
130                 'enter' : this.onEnter,
131                 scope: this
132             });
133         }
134         if (this.expandOnDblClick) {
135             grid.on('rowdblclick', this.onRowDblClick, this);
136         }
137     },
138     
139     // @private    
140     onDestroy: function() {
141         if(this.keyNav){
142             this.keyNav.disable();
143             delete this.keyNav;
144         }
145         /*
146          * A majority of the time, the plugin will be destroyed along with the grid,
147          * which means the mainBody won't be available. On the off chance that the plugin
148          * isn't destroyed with the grid, take care of removing the listener.
149          */
150         var mainBody = this.grid.getView().mainBody;
151         if(mainBody){
152             mainBody.un('mousedown', this.onMouseDown, this);
153         }
154     },
155     // @private
156     onRowDblClick: function(grid, rowIdx, e) {
157         this.toggleRow(rowIdx);
158     },
159
160     onEnter: function(e) {
161         var g = this.grid;
162         var sm = g.getSelectionModel();
163         var sels = sm.getSelections();
164         for (var i = 0, len = sels.length; i < len; i++) {
165             var rowIdx = g.getStore().indexOf(sels[i]);
166             this.toggleRow(rowIdx);
167         }
168     },
169
170     getBodyContent : function(record, index){
171         if(!this.enableCaching){
172             return this.tpl.apply(record.data);
173         }
174         var content = this.bodyContent[record.id];
175         if(!content){
176             content = this.tpl.apply(record.data);
177             this.bodyContent[record.id] = content;
178         }
179         return content;
180     },
181
182     onMouseDown : function(e, t){
183         e.stopEvent();
184         var row = e.getTarget('.x-grid3-row');
185         this.toggleRow(row);
186     },
187
188     renderer : function(v, p, record){
189         p.cellAttr = 'rowspan="2"';
190         return '<div class="x-grid3-row-expander">&#160;</div>';
191     },
192
193     beforeExpand : function(record, body, rowIndex){
194         if(this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false){
195             if(this.tpl && this.lazyRender){
196                 body.innerHTML = this.getBodyContent(record, rowIndex);
197             }
198             return true;
199         }else{
200             return false;
201         }
202     },
203
204     toggleRow : function(row){
205         if(typeof row == 'number'){
206             row = this.grid.view.getRow(row);
207         }
208         this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row);
209     },
210
211     expandRow : function(row){
212         if(typeof row == 'number'){
213             row = this.grid.view.getRow(row);
214         }
215         var record = this.grid.store.getAt(row.rowIndex);
216         var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
217         if(this.beforeExpand(record, body, row.rowIndex)){
218             this.state[record.id] = true;
219             Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');
220             this.fireEvent('expand', this, record, body, row.rowIndex);
221         }
222     },
223
224     collapseRow : function(row){
225         if(typeof row == 'number'){
226             row = this.grid.view.getRow(row);
227         }
228         var record = this.grid.store.getAt(row.rowIndex);
229         var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);
230         if(this.fireEvent('beforecollapse', this, record, body, row.rowIndex) !== false){
231             this.state[record.id] = false;
232             Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');
233             this.fireEvent('collapse', this, record, body, row.rowIndex);
234         }
235     }
236 });
237
238 Ext.preg('rowexpander', Ext.ux.grid.RowExpander);
239
240 //backwards compat
241 Ext.grid.RowExpander = Ext.ux.grid.RowExpander;