RowExpander.js 7.3 KB

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