RowExpander.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  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. this.grid = grid;
  97. // Columns have to be added in init (after columns has been used to create the
  98. // headerCt). Otherwise, shared column configs get corrupted, e.g., if put in the
  99. // prototype.
  100. this.addExpander();
  101. grid.on('render', this.bindView, this, {single: true});
  102. grid.on('reconfigure', this.onReconfigure, this);
  103. },
  104. onReconfigure: function(){
  105. this.addExpander();
  106. },
  107. addExpander: function(){
  108. this.grid.headerCt.insert(0, this.getHeaderConfig());
  109. },
  110. getHeaderId: function() {
  111. if (!this.headerId) {
  112. this.headerId = Ext.id();
  113. }
  114. return this.headerId;
  115. },
  116. getRowBodyFeatureData: function(data, idx, record, orig) {
  117. var o = Ext.grid.feature.RowBody.prototype.getAdditionalData.apply(this, arguments),
  118. id = this.columnId;
  119. o.rowBodyColspan = o.rowBodyColspan - 1;
  120. o.rowBody = this.getRowBodyContents(data);
  121. o.rowCls = this.recordsExpanded[record.internalId] ? '' : this.rowCollapsedCls;
  122. o.rowBodyCls = this.recordsExpanded[record.internalId] ? '' : this.rowBodyHiddenCls;
  123. o[id + '-tdAttr'] = ' valign="top" rowspan="2" ';
  124. if (orig[id+'-tdAttr']) {
  125. o[id+'-tdAttr'] += orig[id+'-tdAttr'];
  126. }
  127. return o;
  128. },
  129. bindView: function() {
  130. var view = this.getCmp().getView(),
  131. viewEl;
  132. if (!view.rendered) {
  133. view.on('render', this.bindView, this, {single: true});
  134. } else {
  135. viewEl = view.getEl();
  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. onEnter: function(e) {
  149. var view = this.view,
  150. ds = view.store,
  151. sm = view.getSelectionModel(),
  152. sels = sm.getSelection(),
  153. ln = sels.length,
  154. i = 0,
  155. rowIdx;
  156. for (; i < ln; i++) {
  157. rowIdx = ds.indexOf(sels[i]);
  158. this.toggleRow(rowIdx);
  159. }
  160. },
  161. toggleRow: function(rowIdx) {
  162. var view = this.view,
  163. rowNode = view.getNode(rowIdx),
  164. row = Ext.get(rowNode),
  165. nextBd = Ext.get(row).down(this.rowBodyTrSelector),
  166. record = view.getRecord(rowNode),
  167. grid = this.getCmp();
  168. if (row.hasCls(this.rowCollapsedCls)) {
  169. row.removeCls(this.rowCollapsedCls);
  170. nextBd.removeCls(this.rowBodyHiddenCls);
  171. this.recordsExpanded[record.internalId] = true;
  172. view.refreshSize();
  173. view.fireEvent('expandbody', rowNode, record, nextBd.dom);
  174. } else {
  175. row.addCls(this.rowCollapsedCls);
  176. nextBd.addCls(this.rowBodyHiddenCls);
  177. this.recordsExpanded[record.internalId] = false;
  178. view.refreshSize();
  179. view.fireEvent('collapsebody', rowNode, record, nextBd.dom);
  180. }
  181. },
  182. onDblClick: function(view, cell, rowIdx, cellIndex, e) {
  183. this.toggleRow(rowIdx);
  184. },
  185. getHeaderConfig: function() {
  186. var me = this,
  187. toggleRow = Ext.Function.bind(me.toggleRow, me),
  188. selectRowOnExpand = me.selectRowOnExpand;
  189. return {
  190. id: this.getHeaderId(),
  191. width: 24,
  192. sortable: false,
  193. resizable: false,
  194. draggable: false,
  195. hideable: false,
  196. menuDisabled: true,
  197. cls: Ext.baseCSSPrefix + 'grid-header-special',
  198. renderer: function(value, metadata) {
  199. metadata.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
  200. return '<div class="' + Ext.baseCSSPrefix + 'grid-row-expander">&#160;</div>';
  201. },
  202. processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
  203. if (type == "mousedown" && e.getTarget('.x-grid-row-expander')) {
  204. var row = e.getTarget('.x-grid-row');
  205. toggleRow(row);
  206. return selectRowOnExpand;
  207. }
  208. }
  209. };
  210. }
  211. });