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