X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/6e39d509471fe9b4e2660e0d1631b350d0c66f40..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/examples/ux/RowExpander.js?ds=sidebyside diff --git a/examples/ux/RowExpander.js b/examples/ux/RowExpander.js index 7d22ae3d..77d2a6ff 100644 --- a/examples/ux/RowExpander.js +++ b/examples/ux/RowExpander.js @@ -1,240 +1,217 @@ -/*! - * Ext JS Library 3.1.0 - * Copyright(c) 2006-2009 Ext JS, LLC - * licensing@extjs.com - * http://www.extjs.com/license +// 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? + +/** + * @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.ns('Ext.ux.grid'); - -/** - * @class Ext.ux.grid.RowExpander - * @extends Ext.util.Observable - * 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.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, { - /** - * @cfg {Boolean} expandOnEnter - * true to toggle selected row(s) between expanded/collapsed when the enter - * key is pressed (defaults to true). - */ - expandOnEnter : true, - /** - * @cfg {Boolean} expandOnDblClick - * true to toggle a row between expanded/collapsed when double clicked - * (defaults to true). - */ - expandOnDblClick : true, - - header : '', - width : 20, - sortable : false, - fixed : true, - menuDisabled : true, - dataIndex : '', - id : 'expander', - lazyRender : true, - enableCaching : true, - - constructor: function(config){ - Ext.apply(this, config); - - this.addEvents({ - /** - * @event beforeexpand - * Fires before the row expands. Have the listener return false to prevent the row from expanding. - * @param {Object} this RowExpander object. - * @param {Object} Ext.data.Record Record for the selected row. - * @param {Object} body body element for the secondary row. - * @param {Number} rowIndex The current row index. - */ - beforeexpand: true, - /** - * @event expand - * Fires after the row expands. - * @param {Object} this RowExpander object. - * @param {Object} Ext.data.Record Record for the selected row. - * @param {Object} body body element for the secondary row. - * @param {Number} rowIndex The current row index. - */ - expand: true, - /** - * @event beforecollapse - * Fires before the row collapses. Have the listener return false to prevent the row from collapsing. - * @param {Object} this RowExpander object. - * @param {Object} Ext.data.Record Record for the selected row. - * @param {Object} body body element for the secondary row. - * @param {Number} rowIndex The current row index. - */ - beforecollapse: true, - /** - * @event collapse - * Fires after the row collapses. - * @param {Object} this RowExpander object. - * @param {Object} Ext.data.Record Record for the selected row. - * @param {Object} body body element for the secondary row. - * @param {Number} rowIndex The current row index. - */ - collapse: true - }); - - Ext.ux.grid.RowExpander.superclass.constructor.call(this); - - if(this.tpl){ - if(typeof this.tpl == 'string'){ - this.tpl = new Ext.Template(this.tpl); - } - this.tpl.compile(); - } - - this.state = {}; - this.bodyContent = {}; - }, - - getRowClass : function(record, rowIndex, p, ds){ - p.cols = p.cols-1; - var content = this.bodyContent[record.id]; - if(!content && !this.lazyRender){ - content = this.getBodyContent(record, rowIndex); - } - if(content){ - p.body = content; - } - return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed'; - }, - - init : function(grid){ - this.grid = grid; - - var view = grid.getView(); - view.getRowClass = this.getRowClass.createDelegate(this); - - view.enableRowBody = true; - - - grid.on('render', this.onRender, this); - grid.on('destroy', this.onDestroy, this); - }, - - // @private - onRender: function() { - var grid = this.grid; - var mainBody = grid.getView().mainBody; - mainBody.on('mousedown', this.onMouseDown, this, {delegate: '.x-grid3-row-expander'}); - if (this.expandOnEnter) { - this.keyNav = new Ext.KeyNav(this.grid.getGridEl(), { - 'enter' : this.onEnter, - scope: this - }); - } - if (this.expandOnDblClick) { - grid.on('rowdblclick', this.onRowDblClick, this); - } - }, - - // @private - onDestroy: function() { - if(this.keyNav){ - this.keyNav.disable(); - delete this.keyNav; - } - /* - * A majority of the time, the plugin will be destroyed along with the grid, - * which means the mainBody won't be available. On the off chance that the plugin - * isn't destroyed with the grid, take care of removing the listener. - */ - var mainBody = this.grid.getView().mainBody; - if(mainBody){ - mainBody.un('mousedown', this.onMouseDown, this); - } - }, - // @private - onRowDblClick: function(grid, rowIdx, e) { - this.toggleRow(rowIdx); - }, - - onEnter: function(e) { - var g = this.grid; - var sm = g.getSelectionModel(); - var sels = sm.getSelections(); - for (var i = 0, len = sels.length; i < len; i++) { - var rowIdx = g.getStore().indexOf(sels[i]); - this.toggleRow(rowIdx); - } - }, - - getBodyContent : function(record, index){ - if(!this.enableCaching){ - return this.tpl.apply(record.data); - } - var content = this.bodyContent[record.id]; - if(!content){ - content = this.tpl.apply(record.data); - this.bodyContent[record.id] = content; - } - return content; - }, - - onMouseDown : function(e, t){ - e.stopEvent(); - var row = e.getTarget('.x-grid3-row'); - this.toggleRow(row); - }, - - renderer : function(v, p, record){ - p.cellAttr = 'rowspan="2"'; - return '
 
'; - }, - - beforeExpand : function(record, body, rowIndex){ - if(this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false){ - if(this.tpl && this.lazyRender){ - body.innerHTML = this.getBodyContent(record, rowIndex); - } - return true; - }else{ - return false; - } - }, - - toggleRow : function(row){ - if(typeof row == 'number'){ - row = this.grid.view.getRow(row); - } - this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row); - }, - - expandRow : function(row){ - if(typeof row == 'number'){ - row = this.grid.view.getRow(row); - } - var record = this.grid.store.getAt(row.rowIndex); - var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row); - if(this.beforeExpand(record, body, row.rowIndex)){ - this.state[record.id] = true; - Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded'); - this.fireEvent('expand', this, record, body, row.rowIndex); - } - }, - - collapseRow : function(row){ - if(typeof row == 'number'){ - row = this.grid.view.getRow(row); - } - var record = this.grid.store.getAt(row.rowIndex); - var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true); - if(this.fireEvent('beforecollapse', this, record, body, row.rowIndex) !== false){ - this.state[record.id] = false; - Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed'); - this.fireEvent('collapse', this, record, body, row.rowIndex); - } - } -}); - -Ext.preg('rowexpander', Ext.ux.grid.RowExpander); - -//backwards compat -Ext.grid.RowExpander = Ext.ux.grid.RowExpander; \ No newline at end of file +Ext.define('Ext.ux.RowExpander', { + extend: 'Ext.AbstractPlugin', + alias: 'plugin.rowexpander', + + rowBodyTpl: null, + + /** + * @cfg {Boolean} expandOnEnter + * true to toggle selected row(s) between expanded/collapsed when the enter + * key is pressed (defaults to true). + */ + expandOnEnter: true, + + /** + * @cfg {Boolean} expandOnDblClick + * true to toggle a row between expanded/collapsed when double clicked + * (defaults to true). + */ + expandOnDblClick: true, + + /** + * @cfg {Boolean} selectRowOnExpand + * true to select a row when clicking on the expander icon + * (defaults to false). + */ + 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 '
 
'; + }, + + /** + * @event expandbody + * + * @param {HtmlElement} rowNode The <tr> element which owns the expanded row. + * @param {Ext.data.Model} record The record providing the data. + * @param {HtmlElement} expandRow The <tr> element containing the expanded data. + */ + /** + * @event collapsebody + * + * @param {HtmlElement} rowNode The <tr> element which owns the expanded row. + * @param {Ext.data.Model} record The record providing the data. + * @param {HtmlElement} expandRow The <tr> element containing the expanded data. + */ + + constructor: function() { + this.callParent(arguments); + var grid = this.getCmp(); + this.recordsExpanded = {}; + // + if (!this.rowBodyTpl) { + Ext.Error.raise("The 'rowBodyTpl' config is required and is not defined."); + } + // + // 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; + } + + grid.columns.unshift(this.getHeaderConfig()); + grid.on('afterlayout', this.onGridAfterLayout, this, {single: true}); + }, + + 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; + }, + + onGridAfterLayout: function() { + var grid = this.getCmp(), + view, viewEl; + + if (!grid.hasView) { + this.getCmp().on('afterlayout', this.onGridAfterLayout, this, {single: true}); + } else { + view = grid.down('gridview'); + 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 rowNode = this.view.getNode(rowIdx), + row = Ext.get(rowNode), + nextBd = Ext.get(row).down(this.rowBodyTrSelector), + record = this.view.getRecord(rowNode); + + if (row.hasCls(this.rowCollapsedCls)) { + row.removeCls(this.rowCollapsedCls); + nextBd.removeCls(this.rowBodyHiddenCls); + this.recordsExpanded[record.internalId] = true; + this.view.fireEvent('expandbody', rowNode, record, nextBd.dom); + } else { + row.addCls(this.rowCollapsedCls); + nextBd.addCls(this.rowBodyHiddenCls); + this.recordsExpanded[record.internalId] = false; + this.view.fireEvent('collapsebody', rowNode, record, nextBd.dom); + } + this.view.up('gridpanel').invalidateScroller(); + }, + + 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, + fixed: true, + draggable: false, + hideable: false, + menuDisabled: true, + cls: Ext.baseCSSPrefix + 'grid-header-special', + renderer: function(value, metadata) { + metadata.tdCls = Ext.baseCSSPrefix + 'grid-cell-special'; + + return '
 
'; + }, + 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; + } + } + }; + } +});