Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / examples / ux / RowExpander.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 // feature idea to enable Ajax loading and then the content
16 // cache would actually make sense. Should we dictate that they use
17 // data or support raw html as well?
18
19 /**
20  * @class Ext.ux.RowExpander
21  * @extends Ext.AbstractPlugin
22  * Plugin (ptype = 'rowexpander') that adds the ability to have a Column in a grid which enables
23  * a second row body which expands/contracts.  The expand/contract behavior is configurable to react
24  * on clicking of the column, double click of the row, and/or hitting enter while a row is selected.
25  *
26  * @ptype rowexpander
27  */
28 Ext.define('Ext.ux.RowExpander', {
29     extend: 'Ext.AbstractPlugin',
30     alias: 'plugin.rowexpander',
31
32     rowBodyTpl: null,
33
34     /**
35      * @cfg {Boolean} expandOnEnter
36      * <tt>true</tt> to toggle selected row(s) between expanded/collapsed when the enter
37      * key is pressed (defaults to <tt>true</tt>).
38      */
39     expandOnEnter: true,
40
41     /**
42      * @cfg {Boolean} expandOnDblClick
43      * <tt>true</tt> to toggle a row between expanded/collapsed when double clicked
44      * (defaults to <tt>true</tt>).
45      */
46     expandOnDblClick: true,
47
48     /**
49      * @cfg {Boolean} selectRowOnExpand
50      * <tt>true</tt> to select a row when clicking on the expander icon
51      * (defaults to <tt>false</tt>).
52      */
53     selectRowOnExpand: false,
54
55     rowBodyTrSelector: '.x-grid-rowbody-tr',
56     rowBodyHiddenCls: 'x-grid-row-body-hidden',
57     rowCollapsedCls: 'x-grid-row-collapsed',
58
59
60
61     renderer: function(value, metadata, record, rowIdx, colIdx) {
62         if (colIdx === 0) {
63             metadata.tdCls = 'x-grid-td-expander';
64         }
65         return '<div class="x-grid-row-expander">&#160;</div>';
66     },
67
68     /**
69      * @event expandbody
70      * <b<Fired through the grid's View</b>
71      * @param {HtmlElement} rowNode The &lt;tr> element which owns the expanded row.
72      * @param {Ext.data.Model} record The record providing the data.
73      * @param {HtmlElement} expandRow The &lt;tr> element containing the expanded data.
74      */
75     /**
76      * @event collapsebody
77      * <b<Fired through the grid's View.</b>
78      * @param {HtmlElement} rowNode The &lt;tr> element which owns the expanded row.
79      * @param {Ext.data.Model} record The record providing the data.
80      * @param {HtmlElement} expandRow The &lt;tr> element containing the expanded data.
81      */
82
83     constructor: function() {
84         this.callParent(arguments);
85         var grid = this.getCmp();
86         this.recordsExpanded = {};
87         // <debug>
88         if (!this.rowBodyTpl) {
89             Ext.Error.raise("The 'rowBodyTpl' config is required and is not defined.");
90         }
91         // </debug>
92         // TODO: if XTemplate/Template receives a template as an arg, should
93         // just return it back!
94         var rowBodyTpl = Ext.create('Ext.XTemplate', this.rowBodyTpl),
95             features = [{
96                 ftype: 'rowbody',
97                 columnId: this.getHeaderId(),
98                 recordsExpanded: this.recordsExpanded,
99                 rowBodyHiddenCls: this.rowBodyHiddenCls,
100                 rowCollapsedCls: this.rowCollapsedCls,
101                 getAdditionalData: this.getRowBodyFeatureData,
102                 getRowBodyContents: function(data) {
103                     return rowBodyTpl.applyTemplate(data);
104                 }
105             },{
106                 ftype: 'rowwrap'
107             }];
108
109         if (grid.features) {
110             grid.features = features.concat(grid.features);
111         } else {
112             grid.features = features;
113         }
114
115         grid.columns.unshift(this.getHeaderConfig());
116         grid.on('afterlayout', this.onGridAfterLayout, this, {single: true});
117     },
118
119     getHeaderId: function() {
120         if (!this.headerId) {
121             this.headerId = Ext.id();
122         }
123         return this.headerId;
124     },
125
126     getRowBodyFeatureData: function(data, idx, record, orig) {
127         var o = Ext.grid.feature.RowBody.prototype.getAdditionalData.apply(this, arguments),
128             id = this.columnId;
129         o.rowBodyColspan = o.rowBodyColspan - 1;
130         o.rowBody = this.getRowBodyContents(data);
131         o.rowCls = this.recordsExpanded[record.internalId] ? '' : this.rowCollapsedCls;
132         o.rowBodyCls = this.recordsExpanded[record.internalId] ? '' : this.rowBodyHiddenCls;
133         o[id + '-tdAttr'] = ' valign="top" rowspan="2" ';
134         if (orig[id+'-tdAttr']) {
135             o[id+'-tdAttr'] += orig[id+'-tdAttr'];
136         }
137         return o;
138     },
139
140     onGridAfterLayout: function() {
141         var grid = this.getCmp(),
142             view, viewEl;
143
144         if (!grid.hasView) {
145             this.getCmp().on('afterlayout', this.onGridAfterLayout, this, {single: true});
146         } else {
147             view = grid.down('gridview');
148             viewEl = view.getEl();
149
150             if (this.expandOnEnter) {
151                 this.keyNav = Ext.create('Ext.KeyNav', viewEl, {
152                     'enter' : this.onEnter,
153                     scope: this
154                 });
155             }
156             if (this.expandOnDblClick) {
157                 view.on('itemdblclick', this.onDblClick, this);
158             }
159             this.view = view;
160         }
161     },
162
163     onEnter: function(e) {
164         var view = this.view,
165             ds   = view.store,
166             sm   = view.getSelectionModel(),
167             sels = sm.getSelection(),
168             ln   = sels.length,
169             i = 0,
170             rowIdx;
171
172         for (; i < ln; i++) {
173             rowIdx = ds.indexOf(sels[i]);
174             this.toggleRow(rowIdx);
175         }
176     },
177
178     toggleRow: function(rowIdx) {
179         var rowNode = this.view.getNode(rowIdx),
180             row = Ext.get(rowNode),
181             nextBd = Ext.get(row).down(this.rowBodyTrSelector),
182             record = this.view.getRecord(rowNode);
183
184         if (row.hasCls(this.rowCollapsedCls)) {
185             row.removeCls(this.rowCollapsedCls);
186             nextBd.removeCls(this.rowBodyHiddenCls);
187             this.recordsExpanded[record.internalId] = true;
188             this.view.fireEvent('expandbody', rowNode, record, nextBd.dom);
189         } else {
190             row.addCls(this.rowCollapsedCls);
191             nextBd.addCls(this.rowBodyHiddenCls);
192             this.recordsExpanded[record.internalId] = false;
193             this.view.fireEvent('collapsebody', rowNode, record, nextBd.dom);
194         }
195         this.view.up('gridpanel').invalidateScroller();
196     },
197
198     onDblClick: function(view, cell, rowIdx, cellIndex, e) {
199
200         this.toggleRow(rowIdx);
201     },
202
203     getHeaderConfig: function() {
204         var me                = this,
205             toggleRow         = Ext.Function.bind(me.toggleRow, me),
206             selectRowOnExpand = me.selectRowOnExpand;
207
208         return {
209             id: this.getHeaderId(),
210             width: 24,
211             sortable: false,
212             fixed: true,
213             draggable: false,
214             hideable: false,
215             menuDisabled: true,
216             cls: Ext.baseCSSPrefix + 'grid-header-special',
217             renderer: function(value, metadata) {
218                 metadata.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
219
220                 return '<div class="' + Ext.baseCSSPrefix + 'grid-row-expander">&#160;</div>';
221             },
222             processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
223                 if (type == "mousedown" && e.getTarget('.x-grid-row-expander')) {
224                     var row = e.getTarget('.x-grid-row');
225                     toggleRow(row);
226                     return selectRowOnExpand;
227                 }
228             }
229         };
230     }
231 });
232