<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>The source code</title> <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" /> <script type="text/javascript" src="../resources/prettify/prettify.js"></script> <style type="text/css"> .highlight { display: block; background-color: #ddd; } </style> <script type="text/javascript"> function highlight() { document.getElementById(location.hash.replace(/#/, "")).className = "highlight"; } </script> </head> <body onload="prettyPrint(); highlight();"> <pre class="prettyprint lang-js">// feature idea to enable Ajax loading and then the content // cache would actually make sense. Should we dictate that they use // data or support raw html as well? <span id='Ext-ux-RowExpander'>/** </span> * @class Ext.ux.RowExpander * @extends Ext.AbstractPlugin * Plugin (ptype = 'rowexpander') that adds the ability to have a Column in a grid which enables * a second row body which expands/contracts. The expand/contract behavior is configurable to react * on clicking of the column, double click of the row, and/or hitting enter while a row is selected. * * @ptype rowexpander */ Ext.define('Ext.ux.RowExpander', { extend: 'Ext.AbstractPlugin', requires: [ 'Ext.grid.feature.RowBody', 'Ext.grid.feature.RowWrap' ], alias: 'plugin.rowexpander', rowBodyTpl: null, <span id='Ext-ux-RowExpander-cfg-expandOnEnter'> /** </span> * @cfg {Boolean} expandOnEnter * <tt>true</tt> to toggle selected row(s) between expanded/collapsed when the enter * key is pressed (defaults to <tt>true</tt>). */ expandOnEnter: true, <span id='Ext-ux-RowExpander-cfg-expandOnDblClick'> /** </span> * @cfg {Boolean} expandOnDblClick * <tt>true</tt> to toggle a row between expanded/collapsed when double clicked * (defaults to <tt>true</tt>). */ expandOnDblClick: true, <span id='Ext-ux-RowExpander-cfg-selectRowOnExpand'> /** </span> * @cfg {Boolean} selectRowOnExpand * <tt>true</tt> to select a row when clicking on the expander icon * (defaults to <tt>false</tt>). */ selectRowOnExpand: false, rowBodyTrSelector: '.x-grid-rowbody-tr', rowBodyHiddenCls: 'x-grid-row-body-hidden', rowCollapsedCls: 'x-grid-row-collapsed', renderer: function(value, metadata, record, rowIdx, colIdx) { if (colIdx === 0) { metadata.tdCls = 'x-grid-td-expander'; } return '<div class="x-grid-row-expander">&#160;</div>'; }, <span id='Ext-ux-RowExpander-event-expandbody'> /** </span> * @event expandbody * <b<Fired through the grid's View</b> * @param {HTMLElement} rowNode The &lt;tr> element which owns the expanded row. * @param {Ext.data.Model} record The record providing the data. * @param {HTMLElement} expandRow The &lt;tr> element containing the expanded data. */ <span id='Ext-ux-RowExpander-event-collapsebody'> /** </span> * @event collapsebody * <b<Fired through the grid's View.</b> * @param {HTMLElement} rowNode The &lt;tr> element which owns the expanded row. * @param {Ext.data.Model} record The record providing the data. * @param {HTMLElement} expandRow The &lt;tr> element containing the expanded data. */ constructor: function() { this.callParent(arguments); var grid = this.getCmp(); this.recordsExpanded = {}; // <debug> if (!this.rowBodyTpl) { Ext.Error.raise("The 'rowBodyTpl' config is required and is not defined."); } // </debug> // TODO: if XTemplate/Template receives a template as an arg, should // just return it back! var rowBodyTpl = Ext.create('Ext.XTemplate', this.rowBodyTpl), features = [{ ftype: 'rowbody', columnId: this.getHeaderId(), recordsExpanded: this.recordsExpanded, rowBodyHiddenCls: this.rowBodyHiddenCls, rowCollapsedCls: this.rowCollapsedCls, getAdditionalData: this.getRowBodyFeatureData, getRowBodyContents: function(data) { return rowBodyTpl.applyTemplate(data); } },{ ftype: 'rowwrap' }]; if (grid.features) { grid.features = features.concat(grid.features); } else { grid.features = features; } // NOTE: features have to be added before init (before Table.initComponent) }, init: function(grid) { this.callParent(arguments); this.grid = grid; // Columns have to be added in init (after columns has been used to create the // headerCt). Otherwise, shared column configs get corrupted, e.g., if put in the // prototype. this.addExpander(); grid.on('render', this.bindView, this, {single: true}); grid.on('reconfigure', this.onReconfigure, this); }, onReconfigure: function(){ this.addExpander(); }, addExpander: function(){ this.grid.headerCt.insert(0, this.getHeaderConfig()); }, getHeaderId: function() { if (!this.headerId) { this.headerId = Ext.id(); } return this.headerId; }, getRowBodyFeatureData: function(data, idx, record, orig) { var o = Ext.grid.feature.RowBody.prototype.getAdditionalData.apply(this, arguments), id = this.columnId; o.rowBodyColspan = o.rowBodyColspan - 1; o.rowBody = this.getRowBodyContents(data); o.rowCls = this.recordsExpanded[record.internalId] ? '' : this.rowCollapsedCls; o.rowBodyCls = this.recordsExpanded[record.internalId] ? '' : this.rowBodyHiddenCls; o[id + '-tdAttr'] = ' valign="top" rowspan="2" '; if (orig[id+'-tdAttr']) { o[id+'-tdAttr'] += orig[id+'-tdAttr']; } return o; }, bindView: function() { var view = this.getCmp().getView(), viewEl; if (!view.rendered) { view.on('render', this.bindView, this, {single: true}); } else { viewEl = view.getEl(); if (this.expandOnEnter) { this.keyNav = Ext.create('Ext.KeyNav', viewEl, { 'enter' : this.onEnter, scope: this }); } if (this.expandOnDblClick) { view.on('itemdblclick', this.onDblClick, this); } this.view = view; } }, onEnter: function(e) { var view = this.view, ds = view.store, sm = view.getSelectionModel(), sels = sm.getSelection(), ln = sels.length, i = 0, rowIdx; for (; i < ln; i++) { rowIdx = ds.indexOf(sels[i]); this.toggleRow(rowIdx); } }, toggleRow: function(rowIdx) { var view = this.view, rowNode = view.getNode(rowIdx), row = Ext.get(rowNode), nextBd = Ext.get(row).down(this.rowBodyTrSelector), record = view.getRecord(rowNode), grid = this.getCmp(); if (row.hasCls(this.rowCollapsedCls)) { row.removeCls(this.rowCollapsedCls); nextBd.removeCls(this.rowBodyHiddenCls); this.recordsExpanded[record.internalId] = true; view.refreshSize(); view.fireEvent('expandbody', rowNode, record, nextBd.dom); } else { row.addCls(this.rowCollapsedCls); nextBd.addCls(this.rowBodyHiddenCls); this.recordsExpanded[record.internalId] = false; view.refreshSize(); view.fireEvent('collapsebody', rowNode, record, nextBd.dom); } }, onDblClick: function(view, cell, rowIdx, cellIndex, e) { this.toggleRow(rowIdx); }, getHeaderConfig: function() { var me = this, toggleRow = Ext.Function.bind(me.toggleRow, me), selectRowOnExpand = me.selectRowOnExpand; return { id: this.getHeaderId(), width: 24, sortable: false, resizable: false, draggable: false, hideable: false, menuDisabled: true, cls: Ext.baseCSSPrefix + 'grid-header-special', renderer: function(value, metadata) { metadata.tdCls = Ext.baseCSSPrefix + 'grid-cell-special'; return '<div class="' + Ext.baseCSSPrefix + 'grid-row-expander">&#160;</div>'; }, processEvent: function(type, view, cell, recordIndex, cellIndex, e) { if (type == "mousedown" && e.getTarget('.x-grid-row-expander')) { var row = e.getTarget('.x-grid-row'); toggleRow(row); return selectRowOnExpand; } } }; } }); </pre> </body> </html>