Hybrid View

  1. #1
    Sencha User steffenk's Avatar
    Join Date
    Jul 2007
    Location
    Haan, Germany
    Posts
    2,657
    Vote Rating
    5
    steffenk has a spectacular aura about steffenk has a spectacular aura about steffenk has a spectacular aura about

      0  

    Default RowPanelExpander for grids

    RowPanelExpander for grids


    This allows to use normal panels in RowExpander. I found the idea somewhere in the net and integrated it in RowExpander.

    Online-Demo

    Updated 23/12/2010: make it stateful, corrected other mentioned issues, added expandAll and collapsAll methods



    Here is the plugin:
    Code:
    Ext.ns('Ext.ux.grid');
    
    /**
     * @class Ext.ux.grid.RowPanelExpander
     * @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.RowPanelExpander = Ext.extend(Ext.util.Observable, {
    	/**
    	 * @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,
    	/**
    	 * @cfg {Boolean} expandOnDblClick
    	 * <tt>true</tt> to toggle a row between expanded/collapsed when double clicked
    	 * (defaults to <tt>true</tt>).
    	 */
    	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.RowPanelExpander.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.store.on('load', this.onStoreLoaded, this);
    		grid.on('destroy', this.onDestroy, this);
    		grid.on("beforestaterestore", this.applyState, this);
    		grid.on("beforestatesave", this.saveState, this);
    	},
    
    	// @private
    	onRender : function() {
    		var grid = this.grid;
    
    
    		var mainBody = grid.getView().mainBody;
    		mainBody.on('mousedown', this.onMouseDown, this, {
    			delegate : '.x-grid3-row-expander'
    		});
    
    		grid.getView().on('rowremoved', this.onRowRemoved, this);
    		grid.getView().on('rowupdated', this.onRowUpdated, this);
    
    		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);
    		}
    	},
    
    	onStoreLoaded: function(store, records,options) {
    		var index = -1;
    		for(var key in this.state){
    			if (this.state[key] === true) {
    				index = store.indexOfId(key);
    				if (index > -1) {
    					this.expandRow(index);
    				}
    			}
    		}
    	},
    
    	/** @private */
    	applyState: function(grid, state){
    		this.suspendStateStore = true;
    		if(state.expander) {
    			this.state = state.expander;
    		}
    		this.suspendStateStore = false;
    	},
    
    	/** @private */
    	saveState: function(grid, state){
    		return state.expander = this.state;
    	},
    
    	/** @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);
    	},
    
    
    		// This will not get fired for an update
    	onRowRemoved: function(view, row, rec) {
    		var panelItemIndex = rec.id;
    
    		if (this.expandingRowPanel && this.expandingRowPanel[panelItemIndex]) {
    			this.expandingRowPanel[panelItemIndex].destroy();
    			this.expandingRowPanel[panelItemIndex] = null;
    		}
    	},
    
    	onRowUpdated: function(view, row, rec) {
    		if (typeof row == 'number') {
    			row = this.grid.view.getRow(row);
    		}
    
    		this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'collapseRow' : 'expandRow'](row);
    	},
    
    	getBodyContent : function(record, index) {
    		// extend here
    		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 '<div class="x-grid3-row-expander">&#160;</div>';
    	},
    
    	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);
    			}
    			if (body.innerHTML == '' || !this.enableCaching) {
    				this.createExpandingRowPanel(record, body, 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.grid.saveState();
    			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.grid.saveState();
    			this.fireEvent('collapse', this, record, body, row.rowIndex);
    		}
    	},
    
    		// Expand all rows
    	expandAll : function() {
    		var aRows = this.grid.getView().getRows();
    		for (var i = 0; i < aRows.length; i++) {
    			this.expandRow(aRows[i]);
    		}
    	},
    
    		// Collapse all rows
    	collapseAll : function() {
    		var aRows = this.grid.getView().getRows();
    		for (var i = 0; i < aRows.length; i++) {
    			this.collapseRow(aRows[i]);
    		}
    	},
    
    	createExpandingRowPanel : function(record, rowBody, rowIndex) {
    		// record.id is more stable than rowIndex for panel item's key; rows can be deleted.
    		var panelItemIndex = record.id;
    		// var panelItemIndex = rowIndex;
    
    		// init array of expanding row panels if not already inited
    		if (!this.expandingRowPanel) {
    			this.expandingRowPanel = [];
    		}
    
    		// Destroy the existing panel if present
    		if (this.expandingRowPanel[panelItemIndex]) {
    			this.expandingRowPanel[panelItemIndex].destroy();
    		}
    		this.expandingRowPanel[panelItemIndex] = new Ext.Panel({
    			border : false,
    			bodyBorder : false,
    			layout : 'form',
    			renderTo : rowBody,
    			items : this.createExpandingRowPanelItems(record, rowIndex)
    		});
    
    	},
    
    	/**
    	 * Override this method to put Ext form items into the expanding row panel.
    	 * @return Array of panel items.
    	 */
    	createExpandingRowPanelItems : function(record, rowIndex) {
    		var panelItems = [];
    
    		return panelItems;
    	}
    });
    
    Ext.preg('rowexpander', Ext.ux.grid.RowPanelExpander);
    Here is a possible usage of the plugin:
    Code:
    this.expander = new Ext.ux.grid.RowPanelExpander({
    	createExpandingRowPanelItems: function(record, rowIndex){
    		var panelItems = [
    			new Ext.TabPanel({
    				plain: true,
    				activeTab: 0,
    				defaults: {
    					autoHeight: true
    				},
    				record: record,
    				items:[
    					{
    						title:'Info',
    						listeners: {
    							activate: function(panel) {
    								TYPO3.EM.ExtDetails.showExtInfo(panel, panel.ownerCt.record.data);
    							}
    						}
    					},
    					{
    						title:'Update',
    						html: '<div class="loading-indicator">Loading...</div>',
    						disabled: record.data.installed == 0,
    						listeners: {
    							activate: function(panel) {
    								TYPO3.EM.ExtDirect.getExtensionUpdate(record.data.extkey, function(response) {
    									panel.update(response);
    								});
    							}
    						}
    					},
    					{
    						title:'Configuration',
    						disabled: record.data.installed == 0,
    					},
    					{
    						title:'Files',
    						xtype: 'extfilelist'
    					},
    					{
    						title:'Upload to TER'
    					},
    					{
    						title:'Backup/Delete',
    						disabled: record.data.installed == 0,
    					}
    				]
    			})
    		];
    		return panelItems;
    	}
    });
    and it looks like on screenshot.

    When using it with stateful grid, don't forget to add expand and collapse to the stateEvents.

    have fun!
    Attached Images
    vg Steffen
    --------------------------------------
    Release Manager of TYPO3 4.5

  2. #2
    Sencha User
    Join Date
    Feb 2009
    Posts
    20
    Vote Rating
    0
    gregoire is on a distinguished road

      0  

    Default nice

    nice


    Very nice. I can see where this could be very useful.

  3. #3
    Sencha User
    Join Date
    Nov 2007
    Posts
    50
    Vote Rating
    0
    gurpal2000 is on a distinguished road

      0  

    Default


    How do you adapt this to the RowExpander example on the ExtJS 3 examples page?
    im seeing:

    this.addEvents is not a function
    [Break on this error] collapse : true

    thanks

  4. #4
    Sencha User steffenk's Avatar
    Join Date
    Jul 2007
    Location
    Haan, Germany
    Posts
    2,657
    Vote Rating
    5
    steffenk has a spectacular aura about steffenk has a spectacular aura about steffenk has a spectacular aura about

      0  

    Default


    Hi,

    maybe you forgot to use it in grids plugins?
    this.plugins[this.expander]

    I will make a online demo soon.
    vg Steffen
    --------------------------------------
    Release Manager of TYPO3 4.5

  5. #5
    Sencha User
    Join Date
    Nov 2007
    Posts
    50
    Vote Rating
    0
    gurpal2000 is on a distinguished road

      0  

    Default


    yes i forgot the 'new' keyword! but when i click + (expand) nothing happens.

    heres the code

    Code:
    Ext.onReady(function() {
    	
    	Ext.QuickTips.init();
    
    	var dummyData = [
    	    ['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
    	    ['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
    	    ['The Coca-Cola Company',45.07,0.26,0.58,'9/1 12:00am'],
    	    ['The Home Depot, Inc.',34.64,0.35,1.02,'9/1 12:00am'],
    	    ['The Procter & Gamble Company',61.91,0.01,0.02,'9/1 12:00am'],
    	    ['United Technologies Corporation',63.26,0.55,0.88,'9/1 12:00am'],
    	    ['Verizon Communications',35.57,0.39,1.11,'9/1 12:00am'],
    	    ['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am'],
    	    ['Walt Disney Company (The) (Holding Company)',29.89,0.24,0.81,'9/1 12:00am']
    	];
    	
    	// add in some dummy descriptions
    	for(var i = 0; i < dummyData.length; i++){
    	    dummyData[i].push('Lorem ipsum dolor sit amet, consectetuer adipiscing elit. ');
    	}
    
    	var reader = new Ext.data.ArrayReader({}, [
           {name: 'company'},
           {name: 'price', type: 'float'},
           {name: 'change', type: 'float'},
           {name: 'pctChange', type: 'float'},
           {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'},
           {name: 'desc'}
        ]);
        
        var expander = new Ext.ux.grid.RowPanelExpander({
    			createExpandingRowPanelItems: function(record, rowIndex) {
    				var panelItems = [
    							new Ext.TabPanel({
    								plain: true,
    								activeTab: 0,
    								defaults: {
    									autoHeight: true
    								},
    								record: record,
    								items:[
    									{
    										title:'Info'
    									},
    									{
    										title:'Info2'
    									}
    								]
    							})
    						];
    						return panelItems;
    			}
    		});
    
        var grid = new Ext.grid.GridPanel({
            store: new Ext.data.Store({
                reader: reader,
                data: dummyData
            }),
            cm: new Ext.grid.ColumnModel([
                expander,
                {id:'company',header: "Company", width: 40, sortable: true, dataIndex: 'company'},
                {header: "Price", width: 20, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
                {header: "Change", width: 20, sortable: true, dataIndex: 'change'},
                {header: "% Change", width: 20, sortable: true, dataIndex: 'pctChange'},
                {header: "Last Updated", width: 20, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
            ]),
            viewConfig: {
                forceFit:true
            },
    
            width: 600,
            height: 300,
            frame: true,
            expander: expander,
            title: 'Test row panel expander',
            renderTo: document.body
        });
    });

  6. #6
    Ext User tonedeaf's Avatar
    Join Date
    Dec 2007
    Posts
    137
    Vote Rating
    1
    tonedeaf is on a distinguished road

      0  

    Default


    @gurpal2000
    Your code still does not contain a plugins: config for the grid.

  7. #7
    Ext User
    Join Date
    Jan 2010
    Posts
    4
    Vote Rating
    0
    383257357@qq.com is on a distinguished road

      0  

    Default 非常感谢你的分享。

    非常感谢你的分享。


    请原谅我不会说英文。我之前也向里面嵌套过一个表格,但是我却无法给每个表格显示不同的数据,这是让我最头痛的一件事。不过还是感谢你的分享。

  8. #8
    Sencha Premium Member dawesi's Avatar
    Join Date
    Mar 2007
    Location
    Melbourne, Australia (aka GMT+10)
    Posts
    1,082
    Vote Rating
    41
    dawesi has a spectacular aura about dawesi has a spectacular aura about

      0  

    Default


    great extension... :-)
    Teahouse Training Company
    Official Certified Sencha Trainer

    Australia / New Zealand / Singapore / Hong Kong & APAC



    SenchaWorld.com - Sencha webinars, videos, etc
    SenchaForge.org - (coming soon)
    TeahouseHQ.com - Sencha ecosystem training portal

    Code Validation : JSLint | JSONLint | JSONPLint

  9. #9
    Ext JS Premium Member
    Join Date
    Mar 2010
    Posts
    32
    Vote Rating
    0
    mrusinak is on a distinguished road

      0  

    Default


    Quote Originally Posted by vector3d View Post
    I have the same problem.
    My case is: a GridPanel in RowExpander of EditorGridPanel if store of "main" EditorGridPanel makes update (after edit or after set data in store) the content of RowExpander disappears...

    How to fix this issue? Please Help!!!
    After some investigating, the problem appears to be (in 3.2.1 anyway) that when rows are removed or re-rendered (such as during an update), the existing rowbody's panel is not removed.

    My fix for when a row is updated changes the createExpandingRowPanel function to delete the existing panel and render a new one if an entry for that ID already exists, instead of ignoring creation:
    Code:
    createExpandingRowPanel : function(record, rowBody, rowIndex) {
    
        // record.id is more stable than rowIndex for panel item's key; rows can be deleted.
        var panelItemIndex = record.id;
        // var panelItemIndex = rowIndex;
    
        // init array of expanding row panels if not already done
        if (!this.expandingRowPanel) {
            this.expandingRowPanel = [];
        }
    
        // Destroy the existing panel if present
        if (this.expandingRowPanel[panelItemIndex]) {
            this.expandingRowPanel[panelItemIndex].destroy();
        }
        this.expandingRowPanel[panelItemIndex] = new Ext.Panel({
            border : false,
            bodyBorder : false,
            layout : 'fit',
            renderTo : rowBody,
            items : this.createExpandingRowPanelItems(record, rowIndex)
        });
    },
    The fix for row deletion I used was this, also shown with a fix to have the correct expanded/collapsed state after an update:
    Code:
    onRender : function() {
        var grid = this.grid;
        var mainBody = grid.getView().mainBody;
        mainBody.on('mousedown', this.onMouseDown, this, {
            delegate : '.x-grid3-row-expander'
        });
        grid.getView().on('rowremoved', this.onRowRemoved, this);
        grid.getView().on('rowupdated', this.onRowUpdated, this);
        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);
        }
    },
    
    // This will not get fired for an update
    onRowRemoved: function(view, row, rec) {
        var panelItemIndex = rec.id;
        
        if (this.expandingRowPanel && this.expandingRowPanel[panelItemIndex]) {
            this.expandingRowPanel[panelItemIndex].destroy();
            this.expandingRowPanel[panelItemIndex] = null;
        }
    },
    
    onRowUpdated: function(view, row, rec) {
        if (typeof row == 'number') {
            row = this.grid.view.getRow(row);
        }
    
        this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'collapseRow' : 'expandRow'](row);
    },
    Last edited by mrusinak; 8 Jun 2010 at 11:23 AM. Reason: Further investigating and tweaking

  10. #10
    Ext JS Premium Member
    Join Date
    Mar 2010
    Location
    Moldova, Republic of / Shelton, CT, US
    Posts
    136
    Vote Rating
    3
    amsoft is on a distinguished road

      0  

    Default


    Thanks a looooot=)