<!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-grid-plugin-RowExpander'>/**
|
</span> * 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.
|
*/
|
Ext.define('Ext.grid.plugin.RowExpander', {
|
extend: 'Ext.AbstractPlugin',
|
<span id='Ext-grid-plugin-RowExpander-property-lockableScope'> lockableScope: 'normal',
|
</span>
|
requires: [
|
'Ext.grid.feature.RowBody',
|
'Ext.grid.feature.RowWrap'
|
],
|
|
alias: 'plugin.rowexpander',
|
|
<span id='Ext-grid-plugin-RowExpander-property-rowBodyTpl'> rowBodyTpl: null,
|
</span>
|
<span id='Ext-grid-plugin-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-grid-plugin-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-grid-plugin-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,
|
|
<span id='Ext-grid-plugin-RowExpander-property-rowBodyTrSelector'> rowBodyTrSelector: '.x-grid-rowbody-tr',
|
</span><span id='Ext-grid-plugin-RowExpander-property-rowBodyHiddenCls'> rowBodyHiddenCls: 'x-grid-row-body-hidden',
|
</span><span id='Ext-grid-plugin-RowExpander-property-rowCollapsedCls'> rowCollapsedCls: 'x-grid-row-collapsed',
|
</span>
|
<span id='Ext-grid-plugin-RowExpander-property-addCollapsedCls'> addCollapsedCls: {
|
</span> before: function(values, out) {
|
var me = this.rowExpander;
|
if (!me.recordsExpanded[values.record.internalId]) {
|
values.itemClasses.push(me.rowCollapsedCls);
|
}
|
},
|
priority: 500
|
},
|
|
<span id='Ext-grid-plugin-RowExpander-event-expandbody'> /**
|
</span> * @event expandbody
|
* **Fired through the grid's View**
|
* @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-grid-plugin-RowExpander-event-collapsebody'> /**
|
</span> * @event collapsebody
|
* **Fired through the grid's View.**
|
* @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.
|
*/
|
|
setCmp: function(grid) {
|
var me = this,
|
rowBodyTpl,
|
features;
|
|
me.callParent(arguments);
|
|
me.recordsExpanded = {};
|
// <debug>
|
if (!me.rowBodyTpl) {
|
Ext.Error.raise("The 'rowBodyTpl' config is required and is not defined.");
|
}
|
// </debug>
|
|
me.rowBodyTpl = Ext.XTemplate.getTpl(me, 'rowBodyTpl');
|
rowBodyTpl = this.rowBodyTpl;
|
features = [{
|
ftype: 'rowbody',
|
lockableScope: 'normal',
|
recordsExpanded: me.recordsExpanded,
|
rowBodyHiddenCls: me.rowBodyHiddenCls,
|
rowCollapsedCls: me.rowCollapsedCls,
|
setupRowData: me.getRowBodyFeatureData,
|
setup: me.setup,
|
getRowBodyContents: function(record) {
|
return rowBodyTpl.applyTemplate(record.getData());
|
}
|
},{
|
ftype: 'rowwrap',
|
lockableScope: 'normal'
|
}];
|
|
if (grid.features) {
|
grid.features = Ext.Array.push(features, grid.features);
|
} else {
|
grid.features = features;
|
}
|
// NOTE: features have to be added before init (before Table.initComponent)
|
},
|
|
<span id='Ext-grid-plugin-RowExpander-method-init'> init: function(grid) {
|
</span> var me = this,
|
reconfigurable = grid,
|
view, lockedView;
|
|
me.callParent(arguments);
|
me.grid = grid;
|
view = me.view = grid.getView();
|
// 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.
|
me.addExpander();
|
|
// Bind to view for key and mouse events
|
// Add row processor which adds collapsed class
|
me.bindView(view);
|
view.addRowTpl(me.addCollapsedCls).rowExpander = me;
|
|
// If the owning grid is lockable, then disable row height syncing - we do it here.
|
// Also ensure the collapsed class is applied to the locked side by adding a row processor.
|
if (grid.ownerLockable) {
|
// If our client grid is the normal side of a lockable grid, we listen to its lockable owner's beforereconfigure
|
reconfigurable = grid.ownerLockable;
|
reconfigurable.syncRowHeight = false;
|
lockedView = reconfigurable.lockedGrid.getView();
|
|
// Bind to locked view for key and mouse events
|
// Add row processor which adds collapsed class
|
me.bindView(lockedView);
|
lockedView.addRowTpl(me.addCollapsedCls).rowExpander = me;
|
|
// Refresh row heights of expended rows on the locked (non body containing) side upon lock & unlock.
|
// The locked side's expanded rows will collapse back because there's no body there
|
reconfigurable.mon(reconfigurable, 'columnschanged', me.refreshRowHeights, me);
|
reconfigurable.mon(reconfigurable.store, 'datachanged', me.refreshRowHeights, me);
|
}
|
reconfigurable.on('beforereconfigure', me.beforeReconfigure, me);
|
|
if (grid.ownerLockable && !grid.rowLines) {
|
// grids without row lines can gain a border when focused. When they do, the
|
// stylesheet adjusts the padding of the cells so that the height of the row
|
// does not change. It is necessary to refresh the row heights for lockable
|
// grids on focus to keep the height of the expander cells in sync.
|
view.on('rowfocus', me.refreshRowHeights, me);
|
}
|
},
|
|
<span id='Ext-grid-plugin-RowExpander-method-beforeReconfigure'> beforeReconfigure: function(grid, store, columns, oldStore, oldColumns) {
|
</span> var expander = this.getHeaderConfig();
|
expander.locked = true;
|
columns.unshift(expander);
|
},
|
|
<span id='Ext-grid-plugin-RowExpander-method-addExpander'> /**
|
</span> * @private
|
* Inject the expander column into the correct grid.
|
*
|
* If we are expanding the normal side of a lockable grid, poke the column into the locked side
|
*/
|
addExpander: function() {
|
var me = this,
|
expanderGrid = me.grid,
|
expanderHeader = me.getHeaderConfig();
|
|
// If this is the normal side of a lockable grid, find the other side.
|
if (expanderGrid.ownerLockable) {
|
expanderGrid = expanderGrid.ownerLockable.lockedGrid;
|
expanderGrid.width += expanderHeader.width;
|
}
|
expanderGrid.headerCt.insert(0, expanderHeader);
|
},
|
|
<span id='Ext-grid-plugin-RowExpander-method-getRowBodyFeatureData'> getRowBodyFeatureData: function(record, idx, rowValues) {
|
</span> var me = this
|
me.self.prototype.setupRowData.apply(me, arguments);
|
|
rowValues.rowBody = me.getRowBodyContents(record);
|
rowValues.rowBodyCls = me.recordsExpanded[record.internalId] ? '' : me.rowBodyHiddenCls;
|
},
|
|
<span id='Ext-grid-plugin-RowExpander-method-setup'> setup: function(rows, rowValues){
|
</span> var me = this;
|
me.self.prototype.setup.apply(me, arguments);
|
// If we are lockable, the expander column is moved into the locked side, so we don't have to span it
|
if (!me.grid.ownerLockable) {
|
rowValues.rowBodyColspan -= 1;
|
}
|
},
|
|
<span id='Ext-grid-plugin-RowExpander-method-bindView'> bindView: function(view) {
|
</span> if (this.expandOnEnter) {
|
view.on('itemkeydown', this.onKeyDown, this);
|
}
|
if (this.expandOnDblClick) {
|
view.on('itemdblclick', this.onDblClick, this);
|
}
|
},
|
|
<span id='Ext-grid-plugin-RowExpander-method-onKeyDown'> onKeyDown: function(view, record, row, rowIdx, e) {
|
</span> if (e.getKey() == e.ENTER) {
|
var ds = view.store,
|
sels = view.getSelectionModel().getSelection(),
|
ln = sels.length,
|
i = 0;
|
|
for (; i < ln; i++) {
|
rowIdx = ds.indexOf(sels[i]);
|
this.toggleRow(rowIdx, sels[i]);
|
}
|
}
|
},
|
|
<span id='Ext-grid-plugin-RowExpander-method-onDblClick'> onDblClick: function(view, record, row, rowIdx, e) {
|
</span> this.toggleRow(rowIdx, record);
|
},
|
|
<span id='Ext-grid-plugin-RowExpander-method-toggleRow'> toggleRow: function(rowIdx, record) {
|
</span> var me = this,
|
view = me.view,
|
rowNode = view.getNode(rowIdx),
|
row = Ext.fly(rowNode, '_rowExpander'),
|
nextBd = row.down(me.rowBodyTrSelector, true),
|
isCollapsed = row.hasCls(me.rowCollapsedCls),
|
addOrRemoveCls = isCollapsed ? 'removeCls' : 'addCls',
|
ownerLock, rowHeight, fireView;
|
|
// Suspend layouts because of possible TWO views having their height change
|
Ext.suspendLayouts();
|
row[addOrRemoveCls](me.rowCollapsedCls);
|
Ext.fly(nextBd)[addOrRemoveCls](me.rowBodyHiddenCls);
|
me.recordsExpanded[record.internalId] = isCollapsed;
|
view.refreshSize();
|
|
// Sync the height and class of the row on the locked side
|
if (me.grid.ownerLockable) {
|
ownerLock = me.grid.ownerLockable;
|
fireView = ownerLock.getView();
|
view = ownerLock.lockedGrid.view;
|
rowHeight = row.getHeight();
|
row = Ext.fly(view.getNode(rowIdx), '_rowExpander');
|
row.setHeight(rowHeight);
|
row[addOrRemoveCls](me.rowCollapsedCls);
|
view.refreshSize();
|
} else {
|
fireView = view;
|
}
|
fireView.fireEvent(isCollapsed ? 'expandbody' : 'collapsebody', row.dom, record, nextBd);
|
// Coalesce laying out due to view size changes
|
Ext.resumeLayouts(true);
|
},
|
|
<span id='Ext-grid-plugin-RowExpander-method-refreshRowHeights'> // refreshRowHeights often gets called in the middle of some complex processing.
|
</span> // For example, it's called on the store's datachanged event, but it must execute
|
// *after* other objects interested in datachanged have done their job.
|
// Or it's called on column lock/unlock, but that could be just the start of a cross-container
|
// drag/drop of column headers which then moves the column into its final place.
|
// So this throws execution forwards until the idle event.
|
refreshRowHeights: function() {
|
Ext.globalEvents.on({
|
idle: this.doRefreshRowHeights,
|
scope: this,
|
single: true
|
});
|
},
|
|
<span id='Ext-grid-plugin-RowExpander-method-doRefreshRowHeights'> doRefreshRowHeights: function() {
|
</span> var me = this,
|
recordsExpanded = me.recordsExpanded,
|
key, record,
|
lockedView = me.grid.ownerLockable.lockedGrid.view,
|
normalView = me.grid.ownerLockable.normalGrid.view,
|
normalRow,
|
lockedRow,
|
lockedHeight,
|
normalHeight;
|
|
for (key in recordsExpanded) {
|
if (recordsExpanded.hasOwnProperty(key)) {
|
record = this.view.store.data.get(key);
|
lockedRow = lockedView.getNode(record, false);
|
normalRow = normalView.getNode(record, false);
|
lockedRow.style.height = normalRow.style.height = '';
|
lockedHeight = lockedRow.offsetHeight;
|
normalHeight = normalRow.offsetHeight;
|
if (normalHeight > lockedHeight) {
|
lockedRow.style.height = normalHeight + 'px';
|
} else if (lockedHeight > normalHeight) {
|
normalRow.style.height = lockedHeight + 'px';
|
}
|
}
|
}
|
},
|
|
<span id='Ext-grid-plugin-RowExpander-method-getHeaderConfig'> getHeaderConfig: function() {
|
</span> var me = this;
|
|
return {
|
width: 24,
|
lockable: false,
|
sortable: false,
|
resizable: false,
|
draggable: false,
|
hideable: false,
|
menuDisabled: true,
|
tdCls: Ext.baseCSSPrefix + 'grid-cell-special',
|
innerCls: Ext.baseCSSPrefix + 'grid-cell-inner-row-expander',
|
renderer: function(value, metadata) {
|
// Only has to span 2 rows if it is not in a lockable grid.
|
if (!me.grid.ownerLockable) {
|
metadata.tdAttr += ' rowspan="2"';
|
}
|
return '<div class="' + Ext.baseCSSPrefix + 'grid-row-expander"></div>';
|
},
|
processEvent: function(type, view, cell, rowIndex, cellIndex, e, record) {
|
if (type == "mousedown" && e.getTarget('.x-grid-row-expander')) {
|
me.toggleRow(rowIndex, record);
|
return me.selectRowOnExpand;
|
}
|
}
|
};
|
}
|
});
|
</pre>
|
</body>
|
</html>
|