1. #1
    Sencha User
    Join Date
    Feb 2012
    Posts
    13
    Vote Rating
    0
    dmfr is on a distinguished road

      0  

    Default Ext.ux.ComponentRowExpander, component in grid row expander

    Ext.ux.ComponentRowExpander, component in grid row expander


    Hi,

    I am sharing a plugin extending Ext.ux.RowExpander to insert any component into the row expander.
    Do not hesitate to give feedback or share improvements if you find it useful.

    So far it has been (not so extensively) tested with ExtJS 4.1.x only.

    It features :
    - Rendering component inside RowExpander's rowbody
    - Monitoring view refresh to re-append existing component to new rowbodies
    - Monitoring column resize to trigger component layout
    - Garbage collection

    It lacks (for now):
    - smart monitoring of store / records to refresh components if needed (ie. when records are edited locally)


    How to use :
    Just override the createComponent method to configure and create the desired component.


    Example :

    Code:
    Ext.create('Ext.grid.Panel',{
      ....
      plugins:[{
        ptype:'cmprowexpander',
        expandOnDblClick: true,
        createComponent: function(view,record,htmlnode,index) {
          return Ext.create('Any.Component',{...}) ;
        }
      }]
    }) ;

    Source :
    Code:
    /*
     * Inspired by : http://www.rahulsingla.com/blog/2010/04/extjs-preserving-rowexpander-markup-across-view-refreshes
     * Reappend element to DOM : http://stackoverflow.com/questions/20143082/does-extjs-automatically-garbage-collect-components
     */
    
    Ext.define('Ext.ux.ComponentRowExpander', {
        extend: 'Ext.ux.RowExpander',
    
        alias: 'plugin.cmprowexpander',
    
        rowBodyTpl : ['<div></div>'],
        
        obj_recordId_componentId: {},
         
        init: function(grid) {
            this.callParent(arguments) ;
            
            var view = grid.getView() ;
            view.on('refresh', this.onRefresh, this);
            view.on('expandbody', this.onExpand, this);
            
            grid.on('destroy', this.onDestroyGrid, this) ;
            grid.headerCt.on('columnresize', this.onColumnResize, this) ;
            
            this.obj_recordId_componentId = {} ;
        },
        
        getRecordKey: function(record) {
            return (record.internalId);
        },
        
        createComponent: function(view, record, rowNode, rowIndex) {
            return Ext.create('Ext.Component') ;
        },
        
        onExpand: function(rowNode, record, expandRow) {
            var recordId = this.getRecordKey(record) ;
            if( Ext.isEmpty( this.obj_recordId_componentId[recordId] ) ) {
                var view = this.grid.getView(),
                    newComponent = this.createComponent(view, record, rowNode, view.indexOf(rowNode)),
                    targetRowbody = Ext.DomQuery.selectNode('div.x-grid-rowbody', expandRow) ;
                
                while (targetRowbody.hasChildNodes()) {
                    targetRowbody.removeChild(targetRowbody.lastChild);
                }
                newComponent.render( targetRowbody ) ;
                
                this.obj_recordId_componentId[recordId] = newComponent.getId() ;
            }
        },
        
        onRefresh: function(view) {
            var reusedCmpIds = [] ;
            Ext.Array.each( view.getNodes(), function(node) {
                var record = view.getRecord(node),
                    recordId = this.getRecordKey(record) ;
                    
                if( !Ext.isEmpty(this.obj_recordId_componentId[recordId]) ) {
                    var cmpId = this.obj_recordId_componentId[recordId] ;
                    
                    reusedCmpIds.push(cmpId) ;
                    var reusedComponent = Ext.getCmp(this.obj_recordId_componentId[recordId]),
                        targetRowbody = Ext.DomQuery.selectNode('div.x-grid-rowbody', node);
                    while (targetRowbody.hasChildNodes()) {
                        targetRowbody.removeChild(targetRowbody.lastChild);
                    }
                    
                    targetRowbody.appendChild( reusedComponent.getEl().dom );
                    reusedComponent.doComponentLayout() ;
                }
            },this) ;
            
            
            // Do Garbage collection
            // Method 1 ( http://skirtlesden.com/static/ux/download/component-column/1.1/Component.js )
            var keysToDelete = [] ;
            Ext.Object.each( this.obj_recordId_componentId, function( recordId, testCmpId ) {
                comp = Ext.getCmp(testCmpId);
                el = comp && comp.getEl();
    
                if (!el || (true && (!el.dom || Ext.getDom(Ext.id(el)) !== el.dom))) {
                    // The component is no longer in the DOM
                    if (comp && !comp.isDestroyed) {
                        comp.destroy();
                        keysToDelete.push(recordId) ;
                    }
                }
            }) ;
            
            // Method 2
            /*
            Ext.Object.each( this.obj_recordId_componentId, function( recordId, testCmpId ) {
                if( !Ext.Array.contains( reusedCmpIds, testCmpId ) ) {
                    comp = Ext.getCmp(testCmpId);
                    comp.destroy();
                    keysToDelete.push(recordId) ;
                }
            }) ;
            */
            
            // Clean map
            Ext.Array.each( keysToDelete, function(mkey) {
                delete this.obj_recordId_componentId[mkey] ;
            },this);
        },
        
        onColumnResize: function() {
            Ext.Object.each( this.obj_recordId_componentId, function( recordId, cmpId ) {
                Ext.getCmp(cmpId).doComponentLayout();
            }) ;
        },
         
        onDestroyGrid: function() {
            Ext.Object.each( this.obj_recordId_componentId, function(recordId, cmpId) {
                Ext.getCmp(cmpId).destroy() ;
            }) ;
         }
    });

  2. #2
    Sencha - Support Team
    Join Date
    Feb 2013
    Location
    California
    Posts
    3,625
    Vote Rating
    68
    Gary Schlosberg is a jewel in the rough Gary Schlosberg is a jewel in the rough Gary Schlosberg is a jewel in the rough

      0  

    Default


    Very cool. Thanks for sharing this with the rest of us!
    Get on the Fast Track with Sencha Training http://sencha.com/training

    Are you a Sencha products veteran who has wondered what it might be like to work at Sencha? If so, please reach out to our recruiting manager: sheryl@sencha.com

  3. #3
    Sencha User
    Join Date
    Oct 2010
    Posts
    28
    Vote Rating
    0
    undeclared is an unknown quantity at this point

      0  

    Default


    Actually works on Ext 4.2.1. Doesn't remember components though, working on that, and I will most likely post the solution here once I figure it out.

  4. #4
    Sencha User
    Join Date
    Oct 2010
    Posts
    28
    Vote Rating
    0
    undeclared is an unknown quantity at this point

      1  

    Default


    Code:
        
    onExpand: function(rowNode, record, expandRow) {        var recordId = this.getRecordKey(record) ;
            var targetRowbody = Ext.DomQuery.selectNode('div.x-grid-rowbody', expandRow);
            if( Ext.isEmpty( this.obj_recordId_componentId[recordId] ) ) {
                var view = this.grid.getView(),
                newComponent = this.createComponent(view, record, rowNode, view.indexOf(rowNode));
    
    
                while (targetRowbody.hasChildNodes()) {
                    targetRowbody.removeChild(targetRowbody.lastChild);
                }
                newComponent.render( targetRowbody ) ;
    
    
                this.obj_recordId_componentId[recordId] = newComponent.getId() ;
            }
            else
            {
                var cmpId = this.obj_recordId_componentId[recordId] ;
    
    
                var reusedComponent = Ext.getCmp(this.obj_recordId_componentId[recordId]);
    
    
                while (targetRowbody.hasChildNodes()) {
                    targetRowbody.removeChild(targetRowbody.lastChild);
                }
    
    
                targetRowbody.appendChild( reusedComponent.getEl().dom );
                reusedComponent.doComponentLayout() ;
            }
        },
    This fixed it. Now re-uses components.

  5. #5
    Sencha User
    Join Date
    Aug 2014
    Posts
    27
    Vote Rating
    0
    Richardmansfield is on a distinguished road

      0  

    Default


    Thanks for sharing the code undeclared as i have been searching for this these days.

  6. #6
    Sencha User
    Join Date
    Feb 2012
    Posts
    13
    Vote Rating
    0
    dmfr is on a distinguished road

      0  

    Default


    Hi,
    Can you explain what you mean by "remember the component" ?

    Collapsing a row body (with a component's dom inside) just hides the node, it doesn't destroy anything.
    On re-expand, previously rendered component shows and remains fully functional.

    May be I'm missing something ? Do you have a test case ?


    Btw, i've been adding a view resize listener (to sync components layout).
    + extending Ext.grid.plugin.RowExpander instead of obsolete Ext.ux.RowExpander

    Code:
    /*
     * Inspired by : http://www.rahulsingla.com/blog/2010/04/extjs-preserving-rowexpander-markup-across-view-refreshes
     * Reappend element to DOM : http://stackoverflow.com/questions/20143082/does-extjs-automatically-garbage-collect-components
     */
    
    Ext.define('Ext.ux.ComponentRowExpander', {
        extend: 'Ext.grid.plugin.RowExpander',
    
        alias: 'plugin.cmprowexpander',
    
        rowBodyTpl : ['<div></div>'],
        
        obj_recordId_componentId: {},
         
        init: function(grid) {
            this.callParent(arguments) ;
            
            var view = grid.getView() ;
            view.on('resize', this.onResize, this) ;
            view.on('refresh', this.onRefresh, this);
            view.on('expandbody', this.onExpand, this);
            
            grid.on('destroy', this.onDestroyGrid, this) ;
            grid.headerCt.on('columnresize', this.onResize, this) ;
            
            this.obj_recordId_componentId = {} ;
        },
        
        getRecordKey: function(record) {
            return (record.internalId);
        },
        
        createComponent: function(view, record, rowNode, rowIndex) {
            return Ext.create('Ext.Component') ;
        },
        
        onExpand: function(rowNode, record, expandRow) {
            var recordId = this.getRecordKey(record) ;
            if( Ext.isEmpty( this.obj_recordId_componentId[recordId] ) ) {
                var view = this.grid.getView(),
                    newComponent = this.createComponent(view, record, rowNode, view.indexOf(rowNode)),
                    targetRowbody = Ext.DomQuery.selectNode('div.x-grid-rowbody', expandRow) ;
                
                while (targetRowbody.hasChildNodes()) {
                    targetRowbody.removeChild(targetRowbody.lastChild);
                }
                newComponent.render( targetRowbody ) ;
                
                this.obj_recordId_componentId[recordId] = newComponent.getId() ;
            }
            this.onResize() ;
        },
        
        onRefresh: function(view) {
            var reusedCmpIds = [] ;
            Ext.Array.each( view.getNodes(), function(node) {
                var record = view.getRecord(node),
                    recordId = this.getRecordKey(record) ;
                    
                if( !Ext.isEmpty(this.obj_recordId_componentId[recordId]) ) {
                    var cmpId = this.obj_recordId_componentId[recordId] ;
                    
                    reusedCmpIds.push(cmpId) ;
                    var reusedComponent = Ext.getCmp(this.obj_recordId_componentId[recordId]),
                        targetRowbody = Ext.DomQuery.selectNode('div.x-grid-rowbody', node);
                    while (targetRowbody.hasChildNodes()) {
                        targetRowbody.removeChild(targetRowbody.lastChild);
                    }
                    
                    // http://stackoverflow.com/questions/20143082/does-extjs-automatically-garbage-collect-components
                    targetRowbody.appendChild( reusedComponent.getEl().dom );
                    reusedComponent.doComponentLayout() ;
                }
            },this) ;
            
            
            // Do Garbage collection
            // Method 1 ( http://skirtlesden.com/static/ux/download/component-column/1.1/Component.js )
            var keysToDelete = [] ;
            Ext.Object.each( this.obj_recordId_componentId, function( recordId, testCmpId ) {
                comp = Ext.getCmp(testCmpId);
                el = comp && comp.getEl();
    
                if (!el || (true && (!el.dom || Ext.getDom(Ext.id(el)) !== el.dom))) {
                    // The component is no longer in the DOM
                    if (comp && !comp.isDestroyed) {
                        comp.destroy();
                        keysToDelete.push(recordId) ;
                    }
                }
            }) ;
            
            // Method 2
            /*
            Ext.Object.each( this.obj_recordId_componentId, function( recordId, testCmpId ) {
                if( !Ext.Array.contains( reusedCmpIds, testCmpId ) ) {
                    comp = Ext.getCmp(testCmpId);
                    comp.destroy();
                    keysToDelete.push(recordId) ;
                }
            }) ;
            */
            
            // Clean map
            Ext.Array.each( keysToDelete, function(mkey) {
                delete this.obj_recordId_componentId[mkey] ;
            },this);
        },
        
        onResize: function() {
            Ext.Object.each( this.obj_recordId_componentId, function( recordId, cmpId ) {
                Ext.getCmp(cmpId).doComponentLayout();
            }) ;
        },
         
        onDestroyGrid: function() {
            Ext.Object.each( this.obj_recordId_componentId, function(recordId, cmpId) {
                Ext.getCmp(cmpId).destroy() ;
            }) ;
         }
    });

  7. #7
    Sencha User
    Join Date
    Oct 2010
    Posts
    28
    Vote Rating
    0
    undeclared is an unknown quantity at this point

      0  

    Default


    At least in my version (ExtJS 4.2.1) it would seemingly re-create every time, at least that's what it seems like