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?
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.
14 Ext.define('Ext.ux.RowExpander', {
15 extend: 'Ext.AbstractPlugin',
16 alias: 'plugin.rowexpander',
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>).
28 * @cfg {Boolean} expandOnDblClick
29 * <tt>true</tt> to toggle a row between expanded/collapsed when double clicked
30 * (defaults to <tt>true</tt>).
32 expandOnDblClick: true,
35 * @cfg {Boolean} selectRowOnExpand
36 * <tt>true</tt> to select a row when clicking on the expander icon
37 * (defaults to <tt>false</tt>).
39 selectRowOnExpand: false,
41 rowBodyTrSelector: '.x-grid-rowbody-tr',
42 rowBodyHiddenCls: 'x-grid-row-body-hidden',
43 rowCollapsedCls: 'x-grid-row-collapsed',
47 renderer: function(value, metadata, record, rowIdx, colIdx) {
49 metadata.tdCls = 'x-grid-td-expander';
51 return '<div class="x-grid-row-expander"> </div>';
56 * <b<Fired through the grid's View</b>
57 * @param {HtmlElement} rowNode The <tr> element which owns the expanded row.
58 * @param {Ext.data.Model} record The record providing the data.
59 * @param {HtmlElement} expandRow The <tr> element containing the expanded data.
63 * <b<Fired through the grid's View.</b>
64 * @param {HtmlElement} rowNode The <tr> element which owns the expanded row.
65 * @param {Ext.data.Model} record The record providing the data.
66 * @param {HtmlElement} expandRow The <tr> element containing the expanded data.
69 constructor: function() {
70 this.callParent(arguments);
71 var grid = this.getCmp();
72 this.recordsExpanded = {};
74 if (!this.rowBodyTpl) {
75 Ext.Error.raise("The 'rowBodyTpl' config is required and is not defined.");
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),
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);
96 grid.features = features.concat(grid.features);
98 grid.features = features;
101 grid.columns.unshift(this.getHeaderConfig());
102 grid.on('afterlayout', this.onGridAfterLayout, this, {single: true});
105 getHeaderId: function() {
106 if (!this.headerId) {
107 this.headerId = Ext.id();
109 return this.headerId;
112 getRowBodyFeatureData: function(data, idx, record, orig) {
113 var o = Ext.grid.feature.RowBody.prototype.getAdditionalData.apply(this, arguments),
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'];
126 onGridAfterLayout: function() {
127 var grid = this.getCmp(),
131 this.getCmp().on('afterlayout', this.onGridAfterLayout, this, {single: true});
133 view = grid.down('gridview');
134 viewEl = view.getEl();
136 if (this.expandOnEnter) {
137 this.keyNav = Ext.create('Ext.KeyNav', viewEl, {
138 'enter' : this.onEnter,
142 if (this.expandOnDblClick) {
143 view.on('itemdblclick', this.onDblClick, this);
149 onEnter: function(e) {
150 var view = this.view,
152 sm = view.getSelectionModel(),
153 sels = sm.getSelection(),
158 for (; i < ln; i++) {
159 rowIdx = ds.indexOf(sels[i]);
160 this.toggleRow(rowIdx);
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);
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);
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);
181 this.view.up('gridpanel').invalidateScroller();
184 onDblClick: function(view, cell, rowIdx, cellIndex, e) {
186 this.toggleRow(rowIdx);
189 getHeaderConfig: function() {
191 toggleRow = Ext.Function.bind(me.toggleRow, me),
192 selectRowOnExpand = me.selectRowOnExpand;
195 id: this.getHeaderId(),
202 cls: Ext.baseCSSPrefix + 'grid-header-special',
203 renderer: function(value, metadata) {
204 metadata.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
206 return '<div class="' + Ext.baseCSSPrefix + 'grid-row-expander"> </div>';
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');
212 return selectRowOnExpand;