RowExpander.js 7.7 KB

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