1. #1
    Sencha User willf1976's Avatar
    Join Date
    May 2009
    Location
    carpinteria, ca
    Posts
    84
    Vote Rating
    0
    willf1976 is on a distinguished road

      0  

    Default Ext.ux.grid.CellExpander2

    Ext.ux.grid.CellExpander2


    Hi All

    I have been going through the classes we have developed and sharing back with the community some of them that I think will be useful to others. This one I have received requests for before in this thread:
    http://www.sencha.com/forum/showthread.php?10311 I no longer have access to this thread so I am posting it here.

    Here is: Ext.ux.grid.CellExpander2

    Extended version of Ext.ux.grid.RowExpander. This version looks for changes in the grid and saves the html of the expanded rows, it then restores the html back into the grid when the view refreshes. Allows expanders to be attached to cells instead of just rows.

    Code:
    /**
     * @author Will Ferrer, Ethan Brooks
     * @copyright (c) 2012, Intellectual Property Private Equity Group
     * @licensee 2012 developed under license for Switchsoft LLC http://www.switchsoft.com a "Direct response telephony company" as part of it's "VOIP Call distribution, ROI analysis platform, call recording, and IVR for inbound and outbound sales" and Run the Business Systems LLC a "Technology development investment group" as part of it's "PHP, Javascript rapid application development framework and MySQL analysis tools"
     * @license licensed under the terms of
     * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
     * that the code/component(s) do NOT become part of another Open
    Source or Commercially
     * licensed development library or toolkit without explicit permission.
     * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
     * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
    * We are pretty nice just ask. We want to meet our licensees
    */
    /*
    history:
        03/06/10 -- took cell expander code and changed it to have the expander be inside the cell instead of next to it
    */
    Ext.ns('Ext.ux.grid');
    
    /**
     * @class Ext.ux.grid.CellExpander2
     * @extends Ext.ux.grid.RowExpander
     * Plugin (ptype = 'ux-grid-cellexpander2') extended version of Ext.ux.grid.RowExpander. This version looks for changes in the grid and saves the html of the expanded rows, it then restores the html back into the grid when the view refreshes. Allows expanders to be attached to cells instead of just rows.
     * @ptype ux-grid-cellexpander2
     */
    Ext.ux.grid.CellExpander2 = Ext.extend(Ext.ux.grid.RowExpander, {
        /**
         * @cfg {String|Number|Null} storeHtmlBy
         * This is the attribute of the store record that is used to save and restore the html in the grid. If left null then the id of the store record will be used, if you are going to be using remote sorting you may need to change this value. The property of the record you use will need to be a unique identifier. If changed to a number then this is the index of the column to use the value associated with. Defaults to null
         */
        storeHtmlBy : null,
        /**
         * @cfg {String|Null} allowMultiExpansions
         * Whether or not multiple row expanders can be triggered. Defaults to false
         */
        allowMultiExpansions : false,
        /**
         * @cfg {String|Null} dontRegenContent
         * If a row has content expanded into it already, whether or not to regenerate that content based on the tpl. Defaults to true
         */
        dontRegenContent : true,
        /**
         * @cfg {String|Null} clearContentOnExpand
         * Whether or not to clear existant content when a cell is expanded. Defaults to false
         */
        clearContentOnExpand : false,
        /**
         * @cfg {String|Null} clearContentOnCollapse
         * Whether or not to clear existant content when a cell is collapsed. Defaults to false
         */
        clearContentOnCollapse : false,
        /**
         * @cfg {String|Null} clearContentOnExpand
         * Whether or not to clean up content when a cell is expanded. Defaults to false
         */
        cleanUpOnExpand : false,
        /**
         * @cfg {String|Null} clearContentOnCollapse
         * Whether or not to clean up when a cell is collapsed. Defaults to false
         */
        cleanUptOnCollapse : false,
        /**
        * @cfg{Object} expandable
        * Whether or not the column should display an exapnder. Defaults to true.
        */
        expandable : true,
        /**
        * @cfg{Object} baseRenderer
        * The render to use for this column -- result given by this render will have an expander box added to it before it is displayed. Defaults to Ext.grid.ColumnModel.defaultRenderer.
        */
        baseRenderer : Ext.grid.ColumnModel.defaultRenderer,
        /**
        * @cfg{Object} displayTemplate
        * The template to use when rendering the contents of each cell. The template should contain a placeholder for: {value}. This placeholder is where the results of the baseRenderer will be populated to.
        */
        displayTemplate : '<div class="x-grid3-row-expander x-grid3-row-collapsed">&#160;</div><div style="float:left">{value}</div>',
        /**
        * @cfg {Boolean} cleanUpOnRefresh
        * trigger clean up when refresh happens. Defaults to false.
        */
        cleanUpOnRefresh : false,
        /**
        * @cfg {Boolean} fixRolloverHighlight
        * Tries to prevent multiple row rollovers visual bugs. Defaults to true.
        */
        fixRolloverHighlight : true,
        /**
        * @private internal config {Object} storedHtml
        * Used to store the html that will be restored to the grid.
        */
        storedHtml : {},
        /**
        * @private internal config {Number} randKey
        * There is an odd bug where the storedHtml of all expanders is being shared by parent grids and child grids. It makes no sence at all so I am storing html in a sub objects based on a random key.
        */
        randKey : null,
        id : null,
        width : undefined,
        menuDisabled : false,
        fixed : false,
        getRowClass : function(record, rowIndex, p, ds){
            return 'x-grid3-row-collapsed';
        },
        hideable : true,
        /** Public Function: clearStoredHtml
        * clears the stored html. May also be used to clean up the component manager.
        * @param {Boolean} cleanUp (Optional) whether to clean the componentManager or not. Defaults to false
        */
        clearStoredHtml : function (cleanUp) {
            var cleanUp = (Ext.isEmpty(cleanUp))?false:cleanUp,
                randKey = this.randKey;
            this.storedHtml = {};
            this.storedHtml[this.randKey] = {};
            if (cleanUp) {
                this.cleanUp();
            }
        },
        /** Public Function: cleanUp
        * Used to clean up the component manager destroying any orphaned components
        */
        cleanUp : function () {
            var items = Ext.ComponentMgr.all.items,
                curItem, n;
            for(n=0;n<items.length;n++){
                curItem = items[n];
                if (!Ext.isEmpty(curItem.rendered) && (Ext.isEmpty(curItem.el) || !Ext.fly(curItem.el)) && !Ext.isEmpty(curItem.cleanUpAfterExpand) && curItem.cleanUpAfterExpand) {
                    if (!Ext.isEmpty(curItem.remove)) {
                        curItem.remove(true);
                    }
                    if (!Ext.isEmpty(curItem.destroy)) {
                        curItem.destroy(true);
                    }
                    //curItem = undefined;
                    //delete curItem;
                }
            }
            
        },
        // @private
        constructor : function (config) {
            Ext.ux.grid.CellExpander2.superclass.constructor.call(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.
                 * @param {Number} cellIndex The current cell index.
                 * @param {Object} column The column object of the column after the row expander column.
                 * @param {Object} value The value  of the column after the row expander column.
                 * @param {Boolean} hasContent Whether or not there is html in the body of the effected row -- if there is html you may not wish to regenerate it as this can cause a memory leak.
                 */
                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.
                 * @param {Number} cellIndex The current cell index.
                 * @param {Object} column The column object of the column after the row expander column.
                 * @param {Object} value The value  of the column after the row expander column.
                 * @param {Boolean} hasContent Whether or not there is html in the body of the effected row -- if there is html you may not wish to regenerate it as this can cause a memory leak.
                 */
                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.
                 * @param {Number} cellIndex The current cell index.
                 * @param {Object} column The column object of the column after the row expander column.
                 * @param {Object} value The value  of the column after the row expander column.
                 * @param {Boolean} hasContent Whether or not there is html in the body of the effected row -- if there is html you may not wish to regenerate it as this can cause a memory leak.
                 */
                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.
                 * @param {Number} cellIndex The current cell index.
                 * @param {Object} column The column object of the column after the row expander column.
                 * @param {Object} value The value  of the column after the row expander column.
                 * @param {Boolean} hasContent Whether or not there is html in the body of the effected row -- if there is html you may not wish to regenerate it as this can cause a memory leak.
                 */
                collapse: true
            });
        },
        // @private
        init : function (grid) {
            Ext.ux.grid.CellExpander2.superclass.init.call(this, grid);
            this.grid.view.on('beforerefresh', this.storeHtml, this);
            this.grid.store.on('beforeload', this.storeHtml, this);
            this.grid.view.on('refresh', this.onRefresh, this);
            this.grid.on('viewready', this.markRows, this);
            this.grid.on('rowsinserted', this.markRows, this);
            
            this.grid.on('rowmouseover', this.onRowMouseOver, this);
            grid.cellExpander = this;
            
            this.randKey = Math.floor(Math.random() * 9999999999999);
            
            this.storedHtml = {};
            this.storedHtml[this.randKey] = {};
        },
        // @private
        onRowMouseOver : function (grid, row, e) {
            var store = this.grid.getStore(),
                view = this.grid.getView(),
                highlightedColumns = [],
                n, row;
    
            for(n=0;n<store.data.items.length;n++) {
                row = view.getRow(n);
                if (row) {
                    if (Ext.fly(row).hasClass('x-grid3-row-over')) {
                        highlightedColumns.push(row);
                    }
                }
            }
            for(n=0; n<highlightedColumns.length-1;n++){
                row = highlightedColumns[n];
                Ext.fly(row).removeClass('x-grid3-row-over');
            }
        },
        // @private
        onRender: function() {
            var grid = this.grid;
            var mainBody = grid.getView().mainBody;
            if (typeof(this.storeHtmlBy) == 'number') {
                this.storeHtmlBy = this.grid.colModel.config[this.storeHtmlBy].dataIndex;
            }
            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
        onMouseDown : function(e, t){
            e.stopEvent();
            e.stopPropagation();
            var row = e.getTarget('.x-grid3-row'),
                cell = e.getTarget('.x-grid3-cell-inner');
            this.toggleRow(row, cell);
        },
        // @private
        renderer : function(value, cell, record, row, col, store){
            var value,
                template = new Ext.Template(this.displayTemplate),
                populateData;
            if (!Ext.isEmpty(this.originalRenderer)) {
                value = (typeof(this.originalRenderer)=='string')?Ext.util.Format[this.originalRenderer](value, cell, record, row, col, store):this.originalRenderer(value, cell, record, row, col, store);
            }
            value = (typeof(this.baseRenderer)=='string')?Ext.util.Format[this.baseRenderer](value, cell, record, row, col, store):this.baseRenderer(value, cell, record, row, col, store);
            populateData = {
                value : value
            };
            parsedString = template.apply(populateData);
            return parsedString;
        },
        // @private
        beforeExpand : function(record, body, rowIndex, cellIndex, column, value, hasContent){
            if(this.fireEvent('beforeexpand', this, record, body, rowIndex, cellIndex, column, value, hasContent) !== false){
                if(this.tpl && this.lazyRender && !hasContent){
                    body.innerHTML = this.getBodyContent(record, rowIndex);
                }
                return true;
            }else{
                return false;
            }
        },
        // @private
        storeHtml : function () {
            var store = this.grid.getStore(),
                view = this.grid.getView(),
                storeHtmlBy = this.storeHtmlBy,
                randKey = this.randKey,
                n, n2, record, recordIndex, row, rowKey, expanded, body, storedBody, el, key, expanders;
    
            for(n=0;n<store.data.items.length;n++) {
                row = view.getRow(n);
                if (row) {
                    expanded = !Ext.fly(row).hasClass('x-grid3-row-collapsed');
    
                    if (expanded) {
                        rowKey = Ext.fly(row).getAttribute('rowexpanderkey');
                        body =  this.findRowBody(row);
                        //body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
                        storedBody = Ext.fly(body).child('*');
                        
                        if (Ext.isEmpty(this.storedHtml[randKey][rowKey])) {
                            this.storedHtml[randKey][rowKey] = {};
                        }
                        
                        this.storedHtml[randKey][rowKey]['body'] = storedBody;
                        this.storedHtml[randKey][rowKey]['expanded'] = expanded;
                    }
                }
            }
        },
        // @private
        onRefresh : function (view) {
            if (this.cleanUpOnRefresh) {
                this.clearStoredHtml();
                this.cleanUp();
            }
            var store = this.grid.getStore(),
                view = this.grid.getView(),
                storeHtmlBy = this.storeHtmlBy,
                storedHtml = this.storedHtml,
                randKey = this.randKey,
                obj, record, recordIndex, row,  body, expanded, expandedColumn, storedBody, curColumn, rowKey, cell, cellIndex, columnId, column, content,
                columnCount = this.grid.colModel.getColumnCount(true);
    
            for(obj in storedHtml[randKey]) {
                storedBody = storedHtml[randKey][obj]['body'];
                expanded = storedHtml[randKey][obj]['expanded'];
                expandedColumn = storedHtml[randKey][obj]['expandedColumn'];
                if (!Ext.isEmpty(obj) && !Ext.isEmpty(storedBody)) {
                    if (!Ext.isEmpty(storeHtmlBy)) {
                        recordIndex = store.findBy(function (record, id) {
                            return (record.get(storeHtmlBy)==obj);
                        });
                        record = store.getAt(recordIndex);
                    } else {
                        record = store.getById(obj);
                    }
    
                    if (!Ext.isEmpty(record) && record) {
                        if (!Ext.isEmpty(expanded) && expanded) {
                            row = view.getRow(recordIndex);
                            Ext.fly(row).removeClass('x-grid3-row-collapsed');
                            body =  this.findRowBody(row);
                            //body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
                            Ext.fly(body).appendChild(storedBody);
                            cells = Ext.fly(row).select('.x-grid3-cell-inner');
                            for(n2=0;n2<columnCount;n2++){
                                cell = cells.elements[n2];
                                cellIndex = this.grid.view.findCellIndex(cell);
                                columnId = this.grid.colModel.getColumnId(cellIndex);
                                column = this.grid.colModel.getColumnById(columnId);
                                if (column.dataIndex == expandedColumn) {
                                    Ext.fly(cell).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');
                                } else {
                                    Ext.fly(cell).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');
                                }
                            }
                        }
                    }
                }
            }
            
            this.markRows();
            this.storeHtml();
        
        },
        // @private
        markRows : function () {
            var view = store = this.grid.getView(),
                store = this.grid.getStore(),
                storeHtmlBy = this.storeHtmlBy;
            for(n=0;n<store.data.items.length;n++) {
                record = store.data.items[n];
                recordIndex = store.indexOf(record);
                row = view.getRow(recordIndex);
                if (row) {
                    if (!Ext.isEmpty(storeHtmlBy)) {
                        key = record.get(storeHtmlBy);
                    } else {
                        key = record.id;
                    }
                    
                    Ext.fly(row).set({rowexpanderkey:key});
                }
            }
        },
        // @private
        toggleRow : function(row, cell){
            var row = (typeof row == 'number')?this.grid.view.getRow(row):row;
                cell = (typeof cell == 'number')?this.grid.view.getCell(cell):cell,
                isExpanded = Ext.fly(cell).hasClass('x-grid3-row-expanded');
            this.storeHtml();
            if (!this.allowMultiExpansions) {
                Ext.fly(row).select('.x-grid3-row-expanded').replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');
                Ext.fly(row).replaceClass('x-grid3-row-collapsed', '');
            }
            this[isExpanded ? 'collapseRow' : 'expandRow'](row, cell);
        },
        // @private
        findRowBody : function (row) {
            bodyTr =Ext.DomQuery.selectNode('tr.x-grid3-row-body-tr', row),
            body = Ext.DomQuery.selectNode('div.x-grid3-row-body', bodyTr);
            return body;
        },
        // @private
        expandRow : function(row, cell){
            var row = (typeof row == 'number')?this.grid.view.getRow(row):row;
                cell = (typeof cell == 'number')?this.grid.view.getCell(cell):cell;
                cellIndex = this.grid.view.findCellIndex(cell),
                rowIndex = row.rowIndex,
                columnId = this.grid.colModel.getColumnId(cellIndex),
                column = this.grid.colModel.getColumnById(columnId),
                record = this.grid.store.getAt(rowIndex),
                value = record.get(column.dataIndex),
                body = this.findRowBody(row);
                //body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
            //Chrome breaks if you don't put these as with a seperate var -- chrome is apparently retarded.
            var hasContent = !Ext.isEmpty(body.innerHTML),
                content, dataIndex = column.dataIndex,
                rowKey = Ext.fly(row).getAttribute('rowexpanderkey'),
                randKey = this.randKey;
            if(this.beforeExpand(record, body, rowIndex, cellIndex, column, value, hasContent)){
                this.state[record.id] = true;
                Ext.fly(cell).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');
                Ext.fly(row).removeClass('x-grid3-row-collapsed');
                
                if (Ext.isEmpty(this.storedHtml[randKey][rowKey])) {
                    this.storedHtml[randKey][rowKey] = {};
                }
                
                this.storedHtml[randKey][rowKey]['expandedColumn'] = dataIndex;
                //this.storedHtml[randKey][rowKey]['rand'] = this.rand;
                
                if(this.clearContentOnExpand && hasContent){
                    content = Ext.fly(body).child('*');
                    if (!Ext.isEmpty(content.remove)) {
                        content.remove();
                    } else {
                        body.innerHTML = null;
                    }
                    hasContent = false;
                }
                if(this.cleanUpOnExpand){
                    this.cleanUp();
                }
                this.fireEvent('expand', this, record, body, rowIndex, cellIndex, column, value, hasContent);
            }
        },
        // @private
        collapseRow : function(row, cell){
            var row = (typeof row == 'number')?this.grid.view.getRow(row):row;
                cell = (typeof cell == 'number')?this.grid.view.getCell(cell):cell;
                cellIndex = this.grid.view.findCellIndex(cell),
                rowIndex = row.rowIndex,
                columnId = this.grid.colModel.getColumnId(cellIndex),
                column = this.grid.colModel.getColumnById(columnId),
                record = this.grid.store.getAt(rowIndex),
                value = record.get(column.dataIndex),
                //body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row),
                bodyTr =Ext.DomQuery.selectNode('tr.x-grid3-row-body-tr', row),
                body =  this.findRowBody(row),
                hasContent = !Ext.isEmpty(body.innerHTML),
                randKey = this.randKey,
                rowKey = Ext.fly(row).getAttribute('rowexpanderkey');
                
            if(this.fireEvent('beforecollapse', this, record, body, rowIndex, cellIndex, column, value, hasContent) !== false){
                this.state[record.id] = false;
                Ext.fly(cell).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');
                Ext.fly(row).addClass('x-grid3-row-collapsed');
                
                if (Ext.isEmpty(this.storedHtml[rowKey])) {
                    this.storedHtml[randKey][rowKey] = {};
                }
                
                this.storedHtml[randKey][rowKey]['expanded'] = false;
                this.storedHtml[randKey][rowKey]['expandedColumn'] = null;
                
                if(this.clearContentOnCollapse && hasContent){
                    content = Ext.fly(body).child('*');
                    if (!Ext.isEmpty(content.remove)) {
                        content.remove();
                    } else {
                        body.innerHTML = null;
                    }
                    hasContent = false;
                }
                if(this.cleanUptOnCollapse){
                    this.cleanUp();
                }
                this.fireEvent('collapse', this, record, body, rowIndex, cellIndex, column, value, hasContent);
            }
        }
    });
    
    Ext.preg('ux-grid-cellexpander2', Ext.ux.grid.CellExpander2);
    //backwards compat
    Ext.grid.CellExpander2 = Ext.ux.grid.CellExpander2;
    Best regards

    Will Ferrer (Run the Business)

  2. #2
    Sencha - Senior Forum Manager mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    Gainesville, FL
    Posts
    37,642
    Vote Rating
    899
    mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute

      0  

    Default


    Thanks for the contribution!
    Mitchell Simoens @SenchaMitch
    Sencha Inc, Senior Forum Manager
    ________________
    Check out my GitHub, lots of nice things for Ext JS 4 and Sencha Touch 2
    https://github.com/mitchellsimoens

    Think my support is good? Get more personalized support via a support subscription. https://www.sencha.com/store/

    Need more help with your app? Hire Sencha Services services@sencha.com

    Want to learn Sencha Touch 2? Check out Sencha Touch in Action that is in print!

    When posting code, please use BBCode's CODE tags.

Thread Participants: 1

Tags for this Thread